1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//! The Fractionated Morse cipher builds upon Morse code, a well-known method for encoding text which
//! can then be sent across simple visual or audio channels.
//! 
//! The Fractionated Morse cipher does not produce a one-to-one mapping of plaintext characters to
//! ciphertext characters and is therefore slightly more secure than a simple substitution cipher.
//! In addition to this, it allows many non-alphabetic symbols to be encoded.
use common::cipher::Cipher;
use common::alphabet;
use common::keygen;
use common::morse_alphabet;

// The Fractionated Morse alphabet. Decodings depend on the keyed alphabet
const FRAC_MORSE_ALPHABET: [&str; 26] = ["...", "..-", "..|", ".-.", ".--", ".-|", ".|.", ".|-",
".||", "-..", "-.-", "-.|", "--.", "---", "--|", "-|.", "-|-", "-||", "|..", "|.-", "|.|", "|-.",
"|--", "|-|", "||.", "||-"];


/// A Fractionated Morse cipher.
///
/// This struct is created by the `new()` method. See its documentation for more.
pub struct FractionatedMorse {
    keyed_alphabet: String,
}

impl Cipher for FractionatedMorse {
    type Key = String;
    type Algorithm = FractionatedMorse;

    /// Initialise a Fractionated Morse cipher given a specific key.
    ///
    /// Will return `Err` if the key contains non-alphabetic symbols.
    fn new(key: String) -> Result<FractionatedMorse, &'static str> {
        for c in key.chars() {
            // The key can only contain alphabetic characters.
            if alphabet::find_position(c).is_none() {
                return Err("Invalid key. Fractionated Morse keys cannot contain non-alphabetic symbols.");
            }
        }

        let keyed_alphabet = keygen::keyed_alphabet(&key, false)?;
        Ok(FractionatedMorse { keyed_alphabet: keyed_alphabet })
    }

    /// Encrypt a message using a Fractionated Morse cipher.
    ///
    /// Morse code supports the characters `a-z`, `A-Z`, `0-9` and the special characters
    /// `@ ( ) . , : ' " ! ? - ; =`. This function will return `Err` if the message contains any
    /// symbol not in this list. 
    ///
    /// # Examples
    /// Basic usage:
    ///
    /// ```
    /// use cipher_crypt::{Cipher, FractionatedMorse};
    ///
    /// let fm = FractionatedMorse::new(String::from("key")).unwrap();
    /// assert_eq!("cpsujiswhsspfanr", fm.encrypt("AttackAtDawn!").unwrap());
    /// ```
    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
        // Encryption process
        //   (1) The message is encoded in Morse using `|` as a character separator and finishing
        //       with the sequence `||`.
        //   (2) Dots are added to the end of the Morse string until the length is a multiple of 3.
        //   (3) The message is split into groups of 3 and the substitution 0 for '.', 1 for '-'
        //       and 2 for '|' is made to produce a series of ternary numbers between 0 and 26.
        //   (4) The keyed alphabet is obtained from the key.
        //   (5) The numbers obtained in step 3 are converted to letters using the keyed alphabet.
        //   (6) The letters are then concatenated to form the ciphertext.
        // 
        // Example: Key: `alphabet`, Plaintext: `hello`
        //   (1) The Morse message `....|.|.-..|.-..|---||` is produced.
        //   (2) Two dots are added to give `....|.|.-..|.-..|---||..`
        //   (3) ...  -> 000 ->  0
        //       .|.  -> 020 ->  6
        //       |.-  -> 201 -> 19
        //       ..|  -> 002 ->  2
        //       and so on.
        //   (4) The alphabet `alphbetcdfgijkmnoqrsuvwxyz` is produced.
        //   (5) 0(a), 6(t), 19(s), 2(p)
        //   (6) The ciphertext `atsphcmr` is produced.
        let morse = FractionatedMorse::to_morse(message.to_string())?;
        let ciphertext = FractionatedMorse::to_ciphertext(&self.keyed_alphabet, morse)?;
        Ok(ciphertext)
    }

    /// Decrypt a message using a Fractionated Morse cipher.
    ///
    /// The Fractionated Morse alphabet only contains the normal alphabetic characters `a-z`,
    /// therefore this function will return `Err` if the message contains any non-alphabetic
    /// characters. Furthermore, it is possible that a purely alphabetic message will not produce
    /// valid Morse code, in which case an `Err` will again be returned.
    ///
    /// # Examples
    /// Basic usage:
    ///
    /// ```
    /// use cipher_crypt::{Cipher, FractionatedMorse};
    ///
    /// let fm = FractionatedMorse::new(String::from("key")).unwrap();
    /// assert_eq!("attackatdawn!", fm.decrypt("cpsujiswhsspfanr").unwrap());
    /// ```
    fn decrypt(&self, cipher_text: &str) -> Result<String, &'static str> {
        // Decryption process:
        //   (1) The keyed alphabet is obtained from the key.
        //   (2) Each ciphertext char is located by index in the keyed alphabet.
        //   (3) The indices are convert to 3 digit ternary and the substitution '.' for 0,
        //       '-' for 1 and '|' for 2 is made to produce a trigraph for each letter.
        //   (4) These trigraphs are substituted for each letter in the message and concatenated
        //       to produce a Morse string.
        //   (5) The Morse message is decoded.
        //
        // Example: Key: `alphabet`, Ciphertext: `atsphcmr`
        //   (1) The alphabet `alphbetcdfgijkmnoqrsuvwxyz` is produced.
        //   (2) a(0), t(6), s(19), p(2), h(3), c(7), m(14), r(18)
        //   (3) 0  -> 000 ->  ...
        //       6  -> 020 ->  .|.
        //       19 -> 201 ->  |.-
        //       2  -> 002 ->  ..|
        //       and so on.
        //   (4) The Morse message `....|.|.-..|.-..|---||..` is produced.
        //   (5) The plaintext `hello i` is recovered.
        let frac_morse = FractionatedMorse::to_fractionated_morse(&self.keyed_alphabet, cipher_text.to_string())?;
        let plaintext = FractionatedMorse::to_plaintext(frac_morse)?;
        Ok(plaintext)
    }
}


impl FractionatedMorse {

    /// Takes a string and tries to convert it to Morse code, using the character `|` as a
    /// separator. The Morse code is ended with two separators `||`. This function returns `Err`
    /// if an unsupported symbol is present. The support characters are `a-z`, `A-Z`, `0-9` and
    /// the special characters `@ ( ) . , : ' " ! ? - ; =`.
    fn to_morse(message: String) -> Result<String, &'static str> {
        let mut morse = String::new();

        // Attempt to convert each letter in message to the corresponding Morse code.
        for c in message.chars() {
            if let Some(m) = morse_alphabet::to_morse(c) {
                morse.extend(m.chars());
                morse.push('|');
            } else {
                return Err("Invalid message. Please strip any whitespace or unsupported symbols.")
            }     
        }

        // Finish the Morse message with a double separator `||`.
        morse.push('|');

        Ok(morse)
    }

    /// Takes a Morse code string, with each Morse character separated by `|`, and converts it to
    /// plaintext. This function returns `Err` if an invalid Morse character is encountered.
    fn to_plaintext(mut morse: String) -> Result<String, &'static str> {
        let mut plaintext = String::new();

        // Remove character separators from the beginning of the message if present
        while morse.starts_with('|') {
            morse.remove(0);
        }

        // Loop over every Morse character
        for morse_chr in morse.split('|') {
            // If a double separator is present we have reached the end of the message therefore we
            // can break. A double separator will produce an empty string when split in the line above.
            if morse_chr == "" {
                break;
            }

            // Find the Morse character in the alphabet and decode it.
            if let Some(c) = morse_alphabet::to_plaintext(morse_chr) {
                plaintext.push(c);
            } else {
                return Err("Invalid Fractionated Morse message. Unknown Morse character found.")
            }
        }

        Ok(plaintext)
    }

    /// Takes a purely alphabetic ciphertext and converts it to Fractionated Morse. This function will
    /// return `Err` if a non-alphabetic symbol is present in the message.
    fn to_fractionated_morse(key: &String, message: String) -> Result<String, &'static str> {
        let mut frac_morse = String::new();

        // We are using a keyed alphabet which is lowercase, therefore loop over a lowercase version
        // of the message.
        for c in message.to_lowercase().chars() {
            if let Some(pos) = key.chars().position(|a| a == c) {
                frac_morse.extend(FRAC_MORSE_ALPHABET[pos].chars());
            } else {
                return Err("Invalid message. Please strip any whitespace or non-alphabetic symbols.")
            }
        }

        Ok(frac_morse)
    }

    /// Takes a Morse string and converts it to a purely alphabetic string using the Fractionated
    /// Morse alphabet. This function returns `Err` if an invalid Fractionated Morse trigraph is
    /// encountered.
    fn to_ciphertext(key: &str, morse: String) -> Result<String, &'static str> {
        let mut ciphertext = String::new();

        // Pad the string so its length is a multiple of 3. This is required to allow the Morse
        // message to be interpreted as a Fractionated Morse message.
        let fractionated_morse = FractionatedMorse::pad_morse_message(morse);

        // Loop over each trigraph and decode it to an alphabetic character
        for trigraph in fractionated_morse.as_bytes().chunks(3) {
            if let Some(pos) = FRAC_MORSE_ALPHABET.iter().position(|&t| t.as_bytes() == trigraph) {
                // FRAC_MORSE_ALPHABET and key both have length 26, therefore this unwrap is safe.
                ciphertext.push(key.chars().nth(pos).unwrap());
            } else {
                // This will only occur for the trigraph `|||` which should not occur in a valid
                // Fractionated Morse message.
                return Err("Unknown Fractionated Morse trigraph found.")
            }
        }

        Ok(ciphertext)
    }

    /// Takes a Morse string and pads it with dots to a length that is a multiple of 3. This allows
    /// it to be interpreted as a Fractionated Morse message.
    fn pad_morse_message(morse: String) -> String {
        let mut fractionated_morse = morse;

        while fractionated_morse.len() % 3 != 0 {
            fractionated_morse.push('.');
        }

        fractionated_morse
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encrypt_test() {
        let message = "attackatdawn";
        let f = FractionatedMorse::new(String::from("key")).unwrap();
        assert_eq!("cpsujiswhsspg", f.encrypt(message).unwrap());
    }

    #[test]
    fn decrypt_test() {
        let message = "cpsujiswhsspg";
        let f = FractionatedMorse::new(String::from("key")).unwrap();
        assert_eq!("attackatdawn", f.decrypt(message).unwrap());
    }

    #[test]
    fn encrypt_mixed_case() {
        let message = "AttackAtDawn";
        let f = FractionatedMorse::new(String::from("OranGE")).unwrap();
        assert_eq!("eptvihtxfttpd", f.encrypt(message).unwrap());
    }

    #[test]
    fn decrypt_mixed_case() {
        let message = "EPtvihtXFttPD";
        let f = FractionatedMorse::new(String::from("OranGE")).unwrap();
        assert_eq!("attackatdawn", f.decrypt(message).unwrap());
    }

    #[test]
    fn encrypt_punctuation() {
        let message = "Testingpunctuation!Willitwork?";
        let f = FractionatedMorse::new(String::from("Punctuation")).unwrap();
        assert_eq!("kqoqvwigqlocxurxnhvvekjncidqxtwkfeqgb", f.encrypt(message).unwrap());
    }

    #[test]
    fn encrypt_no_key() {
        let message = "defendtheeastwall";
        let f = FractionatedMorse::new(String::from("")).unwrap();
        assert_eq!("jubgvvhscgtshtppjtcs", f.encrypt(message).unwrap());
    }

    #[test]
    fn encrypt_long_key() {
        let message = "defendtheeastwall";
        let f = FractionatedMorse::new(String::from("nnhhyqzabguuxwdrvvctspefmjoklii")).unwrap();
        assert_eq!("xmhbjjgeybfegfttxfye", f.encrypt(message).unwrap());
    }

    #[test]
    fn exhaustive_encrypt() {
        let message = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.,:\'\"!?@-;()=";
        let encrypted = "sbiaqtndfnhhulsailijuicothksekjblurhsbiaqtndfnhhulsailijuicot\
                         hksekjblurhujxjejesehbhfhghgdgjacrxlfhufoxiajxbociescaqfqflem";
        let f = FractionatedMorse::new(String::from("exhaustive")).unwrap();
        assert_eq!(encrypted, f.encrypt(message).unwrap());
    }

    #[test]
    fn exhaustive_decrypt() {
        let encrypted = "sbiaqtndfnhhulsailijuicothksekjblurhsbiaqtndfnhhulsailijuicot\
                         hksekjblurhujxjejesehbhfhghgdgjacrxlfhufoxiajxbociescaqfqflem";
        let decrypted = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz1234567890.,:\'\"!?@-;()=";
        let f = FractionatedMorse::new(String::from("exhaustive")).unwrap();
        assert_eq!(decrypted, f.decrypt(encrypted).unwrap());
    }

    #[test]
    fn bad_key() {
        assert!(FractionatedMorse::new(String::from("bad key")).is_err());
    }

    #[test]
    fn encrypt_bad_message() {
        let message = "Spaces are not supported.";
        let f = FractionatedMorse::new(String::from("")).unwrap();
        assert!(f.encrypt(message).is_err());
    }

    #[test]
    fn decrypt_bad_message() {
        let message = "badmessagefordecryption";
        let f = FractionatedMorse::new(String::from("")).unwrap();
        assert!(f.decrypt(message).is_err());
    }
}