<mosaic.cnfolio.com>
PROGRAM
  1. /*
  2.    (Note: The majority of this program deals with validating input. The code that applies the cipher only makes up ~8 lines)
  3.    
  4.    Functions ordered alphabetically for pseudocode
  5.    
  6.    ApplyCipher:
  7.       Loop through the Key, n times (where n is the number of letters in the key) starting with i = 1:
  8.          Set character A to Key[i]
  9.          Set character B to Key[i+1] (If that's out of range then set B to Key[0])
  10.          Run the ReplaceChars function, with 'PlainText' as the input, and feeding the output to the seperate char array 'Output'
  11.          Print "Step i substitute A with B"
  12.          Print the output as it is now
  13.  
  14.  
  15.    Error: - Accepts a string to display as an error message, and an error number
  16.       Simply prints the error message, and then calls the standard library function 'exit', passing in the negative
  17.          of the error code that has been passed in as an argument to be sent to the parent process.
  18.    
  19.    
  20.    GetInput:
  21.       Read in the key length, check it read properly, check it's between 2 and 16
  22.       Read in key, check it read properly, check the length matches with what was previously specified, check for
  23.          illegal or duplicate characters
  24.       Read in plaintext length, check it read properly, check it's between 4 and 256
  25.       Read in plaintext, check it read properly, check the length matches with what was previously specified, check for
  26.          illegal or duplicate characters
  27.       Copy the input PlainText to Output. This is done because ApplyCipher function doesn't modify the input and it
  28.          only writes changed characters. This way, the unchanged characters will (of course) persist, and the modified
  29.          characters will just overwrite the old ones. This double buffering is needed to correctly execute the cipher, as
  30.          without it, a 2 letter cipher key (Eg: 'hg') would simply change all h's to g's, and then change all g's back to
  31.          h's.
  32.                  
  33.                  
  34.    HasNoDuplicates: - Accepts a pointer to a string
  35.       Creates an array for every letter of the alphabet, initialized to FALSE.
  36.       Then, for each character of the string:
  37.          If Array[This letter] == TRUE then that letter must already have been encountered, so return FALSE
  38.          else set Array[This letter] to true, then continue looping
  39.       Finally, if the program reaches this point, then there can't be any duplicates so return TRUE
  40.    
  41.    
  42.    IsNonPrinting: - Accepts a character to check
  43.       Simply checks if the character is less than 33 (ASCII '!', previous character is SPACE, and before SPACE
  44.          are entirely non-printing characters such as \n). Returns the result of (ASCII value < 33).
  45.    
  46.    
  47.    PopBlanks:
  48.       Pops characters from the input stream until it reaches a printing character. Once it does, it puts that char
  49.          back on the string where it came from. If it encounters an EOF, it just returns, leaving the empty stream
  50.          to be dealt with by later validation.
  51.    
  52.    
  53.    ReplaceChars: - Accepts the source string, a destination string, a character to replace, and the character to replace it with
  54.       Loops through every character of Source:
  55.          If Source[i] is the char due to be replaced:
  56.             Write the replacement char into Output[i]
  57.    
  58.    
  59.    TrimBlanks: - Accepts a pointer to a string to trim the non-printing characters from
  60.       Starts at the end of the string, and works toward the start.
  61.       Replaces all non-printing characters with '\0', and returns once it hits a printing character
  62.    
  63.    
  64.    ValidateString: - Accepts a pointer to a string, and a boolean that determines if spaces are allowed
  65.       For every character of the string:
  66.          If the character isn't a lowercase letter:
  67.             If it's a space, and that's allowed than continue the loop
  68.             Else, return FALSE
  69.       If execution has reached the end of the function, then the string must be valid so return TRUE
  70.    
  71.    
  72.    ValueInRange: - Accepts a value, an inclusive minimum and an inclusive maximum
  73.       Simply returns the result of (min <= X <= max)
  74.    
  75.    
  76.    main:
  77.       Call GetInput
  78.       Call ApplyCipher
  79.       return 0
  80.    
  81. */
  82.  
  83. #include <stdlib.h>
  84. #include <stdio.h>
  85. #include <string.h>
  86.  
  87. #define TRUE 1
  88. #define FALSE 0
  89.  
  90. typedef unsigned char bool;
  91.  
  92. char PlainText[257] = {0}; /* Only lower case or spaces */
  93. char Output[257] = {0};
  94. char Key[17] = {0}; /* Only lower case. No duplicates */
  95.  
  96. int KeyLength;
  97. int PlainLength;
  98.  
  99.  
  100. /*
  101.    Replaces all instances of A with B. Reads from Source, writes to Out.
  102. */
  103. void ReplaceChars(char* Source, char* Out, char A, char B)
  104. {
  105.    int i = 0;
  106.    while (Source[i] != '\0')
  107.    {
  108.       if (Source[i] == A)
  109.          Out[i] = B;
  110.       i++;
  111.    }
  112. }
  113.  
  114. /*
  115.    Returns true if the character doesn't print
  116. */
  117. bool IsNonPrinting(char In)
  118. {
  119.    return (In < '!');
  120. }
  121.  
  122. /*
  123.    Removes non-printing characters after a string of printable characters
  124.       Starts at the end of the string, and works toward the start.
  125.       Replaces all non-printing characters with '\0', and returns once it hits a printing character
  126. */
  127. void TrimBlanks(char* In)
  128. {
  129.    for (int I = strlen(In) - 0; I >= 0; I--)
  130.    {
  131.       if (IsNonPrinting(In[I]))
  132.          In[I] = '\0';
  133.       else
  134.          return;
  135.    }
  136. }
  137.  
  138. /*
  139.    Pops non printing characters such as newlines and spaces off stdin until it reaches a printable character.
  140.    When it reaches a printable character it pushes it back to where it was on the stream and returns.
  141.    
  142.    Designed to prepare the input stream to be read by fgets (as functions such as scanf often leave trailing
  143.       newline characters that would cause fgets to read an empty line)
  144. */
  145. void PopBlanks()
  146. {
  147.    char Temp = 0;
  148.    while (IsNonPrinting(Temp = getc(stdin)) && Temp != EOF)
  149.       continue;
  150.    ungetc(Temp, stdin);
  151. }
  152.  
  153. /*
  154.    Returns true if the value lies within the inclusive range
  155. */
  156. bool ValueInRange(int Value, int MinInc, int MaxInc)
  157. {
  158.    return (Value >= MinInc && Value <= MaxInc);
  159. }
  160.  
  161. /*
  162.    Prints the error and error code, the exits (returning -ErrorNumber to parent)
  163. */
  164. void Error(char* Message, int ErrorNumber)
  165. {
  166.    fprintf(stderr,"Error(%d): %s", ErrorNumber, Message);
  167.    exit(-ErrorNumber);
  168. }
  169.  
  170. /*
  171.    Goes through all the characters of the string checking they're lowercase letters.
  172.    If it hits a space and they're allowed it continues looping through.
  173. */
  174. bool ValidateString(char* Input, char AllowSpaces)
  175. {
  176.    int Length = strlen(Input);
  177.    for (int I = 0; I < Length; I++)
  178.    {
  179.       if (Input[I] < 'a' || Input[I] > 'z')
  180.       {
  181.          if (Input[I] == ' ' && AllowSpaces)
  182.             continue;
  183.          return FALSE;
  184.       }
  185.    }
  186.    return TRUE;
  187. }
  188.  
  189. /*
  190.    Returns true if there are any duplicate characters (not including spaces).
  191.    Input must have already been validated to ensure that no non-lowercase letters are in the string.
  192.    
  193.    AlreadyGot is a 26 char array representing all the characters in the alphabet. All elements in the array are initialized to false.
  194.    For each letter the function reads the following occurs:
  195.       If the element is still FALSE, then it's changed to true and iteration continues.
  196.       If the element has already been changed to TRUE, then the function returns FALSE.
  197.    If the program reaches the end, there must be no duplicates, and so it returns TRUE.
  198. */
  199. bool HasNoDuplicates(char* Input)
  200. {
  201.    char AlreadyGot[26] = { FALSE };
  202.    
  203.    int Length = strlen(Input);
  204.    for (int I = 0; I < Length; I++)
  205.    {
  206.       if (Input[I] == ' ')
  207.          continue;
  208.    
  209.       int Index = Input[I] - 'a';
  210.      
  211.       if (AlreadyGot[Index])
  212.          return FALSE;
  213.       else
  214.          AlreadyGot[Index] = TRUE;
  215.    }
  216.    return TRUE;
  217. }
  218.  
  219. /*
  220.    Gets and checks input in these steps:
  221.    
  222.    Read KeyLength, check it read, check it's in the range [2;16]
  223.    Read Key, check it read, check its length is right, check no illegal characters, check no duplicates
  224.    
  225.    Read PlainLength, check it read, check it's in the range [4;256]
  226.    Read PlainText, check it read,
  227. */
  228. void GetInput()
  229. {
  230.    /* KeyLength
  231.    */
  232.    if (!scanf("%d", &KeyLength))
  233.       Error("Couldn't read key length", 0);
  234.      
  235.    if (!ValueInRange(KeyLength, 2, 16))
  236.       Error("Invalid key length [2;16]", 1);
  237.      
  238.    /* Key
  239.    */
  240.    PopBlanks();
  241.    /* Prepare the stream for fgets (otherwise it would only read the newline left by scanf) */
  242.    
  243.    if (fgets(Key, 17, stdin) == NULL)
  244.       Error("Couldn't read key", 2);
  245.  
  246.    TrimBlanks(Key);
  247.      
  248.    if (strlen(Key) != KeyLength)
  249.       Error("Conflicting key lengths", 3);
  250.      
  251.    if (!ValidateString(Key, FALSE))
  252.     Error("Illegal characters in key", 4);
  253.    
  254.    if (!HasNoDuplicates(Key))
  255.       Error("Duplicate characters in key", 5);
  256.      
  257.    /* PlainLength
  258.    */
  259.    if (!scanf("%d" , &PlainLength))
  260.       Error("Couldn't read plaintext length", 6);
  261.      
  262.    if (!ValueInRange(PlainLength, 4, 256))
  263.       Error("Invalid plaintext length [4;256]", 7);
  264.      
  265.    /* PlainText
  266.    */
  267.    PopBlanks();
  268.  
  269.    if (fgets(PlainText, 257, stdin) == NULL)
  270.       Error("Couldn't read plaintext", 8);
  271.  
  272.    TrimBlanks(PlainText);
  273.    
  274.    if (strlen(PlainText) != PlainLength)
  275.       Error("Conflicting plaintext lengths", 9);
  276.      
  277.    if (!ValidateString(PlainText, TRUE))
  278.       Error("Invalid characters in plaintext", 10);
  279.    
  280.    strcpy(Output, PlainText);
  281.    /* Letters are only added to the output if they have been changed by the cipher, so this
  282.          populates the output - any changes during the enciphering will just overwrite letters. */
  283. }
  284.  
  285. /*
  286.    Applies the cipher. Doesn't modify PlainText.
  287. */
  288. void ApplyCipher()
  289. {
  290.    for (int N = 1; N <= KeyLength; N++)
  291.    {
  292.       char A = Key[N-1];
  293.       char B = Key[N == KeyLength ? 0 : N];
  294.      
  295.       ReplaceChars(PlainText, Output, A, B);
  296.       printf("Step %i substitute %c with %c\n%s\n\n", N, A, B, Output);
  297.    }
  298. }
  299.  
  300. int main(void)
  301. {
  302.    GetInput();
  303.    ApplyCipher();
  304.  
  305.    return 0;
  306. }