diff --git a/.gitignore b/.gitignore index b18848f..7a7fda6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .idea/ -target/ machine.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 0fe26f7..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,299 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "clap" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "proc-macro2" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "toml" -version = "0.9.10+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_parser" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - -[[package]] -name = "ultra_cli" -version = "0.2.0" -dependencies = [ - "clap", - "serde", - "toml", - "ultra_core", -] - -[[package]] -name = "ultra_core" -version = "2.1.0" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 86cb53c..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[workspace] -members = ["ultra_cli", "ultra_core"] -resolver = "3" diff --git a/machine.toml b/machine.toml deleted file mode 100644 index f84973f..0000000 --- a/machine.toml +++ /dev/null @@ -1,22 +0,0 @@ -[rotors] - -[rotors.left] -configuration = "I" -starting_position = "A" -ring_setting = "A" - -[rotors.middle] -configuration = "II" -starting_position = "A" -ring_setting = "A" - -[rotors.right] -configuration = "III" -starting_position = "A" -ring_setting = "A" - -[reflector] -configuration = "B" - -[plugboard] -configuration = "" \ No newline at end of file diff --git a/ultra_core/Cargo.toml b/ultra_core/Cargo.toml deleted file mode 100644 index 8e32fc2..0000000 --- a/ultra_core/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "ultra_core" -version = "2.1.0" -edition = "2024" - -[dependencies] diff --git a/ultra_core/src/lib.rs b/ultra_core/src/lib.rs deleted file mode 100644 index a2c7169..0000000 --- a/ultra_core/src/lib.rs +++ /dev/null @@ -1,634 +0,0 @@ -/// Converts a character to a zero-based index related to its position in the alphabet, where 'A' -/// equals 0, 'B' equals 1, ..., 'Z' equals 25. -fn char_to_index(char: char) -> u8 { - char as u8 - 65 -} - -/// Converts a zero-based index into a character, where 0 equals 'A', 1 equals 'B', ..., 25 equals -/// 'Z'. -fn index_to_char(index: u8) -> char { - (index + 65) as char -} - -/// Converts a wiring string, e.g. `"EKMFLGDQVZNTOWYHXUSPAIBRCJ"` to an array of its character -/// indexes, like `[4, 10, 12, ...]`. -fn wiring_string_to_array(wiring_string: &str) -> [u8; 26] { - let mut wiring: [u8; 26] = [0; 26]; - - for (index, character) in wiring_string.chars().enumerate() { - wiring[index] = char_to_index(character); - } - - wiring -} - -/// Converts a wiring string, e.g. `"EKMFLGDQVZNTOWYHXUSPAIBRCJ"` to an array of its character -/// indexes, like `[4, 10, 12, ...]`. -fn inverse_wiring_array(wiring_array: [u8; 26]) -> [u8; 26] { - let mut inverted: [u8; 26] = [0; 26]; - - for (original_index, &original_value) in wiring_array.iter().enumerate() { - inverted[original_value as usize] = original_index as u8; - } - - inverted -} - -/// Represents an Enigma machine. -pub struct EnigmaMachine { - /// The rotor in the left-hand slot of the machine. - pub left_rotor: Rotor, - /// The rotor in the middle slot of the machine. - pub middle_rotor: Rotor, - /// The rotor in the right-hand slot of the machine. - pub right_rotor: Rotor, - /// The fourth rotor at the far left of the machine, between the reflector and the left rotor. - pub fourth_rotor: Option, - /// The machine's reflector. - pub reflector: Reflector, - /// The machine's plugboard configuration (if configured). - pub plugboard: Option -} - -impl EnigmaMachine { - /// Takes the key that was pressed by the user and returns the character that would light up. - fn press_key(&mut self, key: char) -> char { - let mut signal: u8 = char_to_index(key); - - signal = self.map_through_plugboard(signal); - - self.rotate_rotors(); - - signal = self.right_rotor.map_signal(signal, false); - signal = self.middle_rotor.map_signal(signal, false); - signal = self.left_rotor.map_signal(signal, false); - signal = self.map_through_fourth_rotor(signal, false); - - signal = self.reflector.map_signal(signal); - - signal = self.map_through_fourth_rotor(signal, true); - signal = self.left_rotor.map_signal(signal, true); - signal = self.middle_rotor.map_signal(signal, true); - signal = self.right_rotor.map_signal(signal, true); - - signal = self.map_through_plugboard(signal); - - index_to_char(signal) - } - - /// Rotates the rotors to the next state. - fn rotate_rotors(&mut self) { - let middle_in_notch = self.middle_rotor.notches.contains(&self.middle_rotor.position); - let right_in_notch = self.right_rotor.notches.contains(&self.right_rotor.position); - - self.right_rotor.rotate(); - - if middle_in_notch { - self.left_rotor.rotate(); - } - - if middle_in_notch || right_in_notch { - self.middle_rotor.rotate(); - } - } - - /// Map a single character through the plugboard to its matching character (if any). - fn map_through_plugboard(&self, signal: u8) -> u8 { - // If we have a plugboard, map the signal through it. - // If not, let the signal pass through as it is. - match &self.plugboard { - Some(plugboard) => plugboard.map_signal(signal), - None => signal - } - } - - /// Map a single character through the fourth rotor to its matching character (if any). - fn map_through_fourth_rotor(&self, signal: u8, inverse: bool) -> u8 { - // If we have a fourth rotor, map the signal through it. - // If not, let the signal pass through it as it is. - match &self.fourth_rotor { - Some(fourth_rotor) => fourth_rotor.map_signal(signal, inverse), - None => signal - } - } - - /// Set the rotors to the positions specified by parameters. - #[allow(dead_code)] // Used in tests but shows as dead code, so override compiler warnings. - fn update_rotor_positions(&mut self, left_rotor: char, middle_rotor: char, right_rotor: char) { - self.left_rotor.position = char_to_index(left_rotor); - self.middle_rotor.position = char_to_index(middle_rotor); - self.right_rotor.position = char_to_index(right_rotor); - } - - /// Take a message as a string, process each character individually using this Enigma machine - /// and return the output. - pub fn process(&mut self, message: &str) -> String { - // We know that the output won't be any longer/shorter than the input as letters are - // translated 1:1. - let mut output = String::with_capacity(message.len()); - - if message.is_empty() { - panic!("Plaintext cannot be empty."); - } - - // Check if all characters are uppercase letters. If not, panic. - if !message.chars().all(|x| x.is_ascii_uppercase()) { - panic!("Plaintext must consist only of uppercase letters."); - } - - for character in message.chars() { - output.push(self.press_key(character)); - } - - output - } -} - -/// Represents a single Rotor in an Enigma machine. -pub struct Rotor { - /// This rotor's mappings - i.e. if this wiring array was `[3, 7, 12, 8, 5]` then this would - /// indicate that the character `A` maps through this rotor to the character `D`, that the - /// character `B` maps to the character `H` and so on. - wiring: [u8; 26], - /// This rotor's inverse mappings, i.e. the wiring array with its indexes and values swapped. - inverse_wiring: [u8; 26], - /// This rotor's notch position, i.e. the position at which it will cause the next rotor to - /// rotate. - notches: Vec, - /// This rotor's current position. - position: u8, - /// This rotor's current ring setting, i.e. its wiring offset. - ring_setting: u8 -} - -impl Rotor { - /// Constructs a Rotor. Planning to add custom rotor configurations rather than having to use - /// the enum to use one of the five standard variants. - pub fn new(rotor_configuration: RotorConfiguration, starting_position: char, ring_setting: char) -> Self { - // Map the RotorConfiguration enum value to the actual wiring string and notch position. - let (wiring_string, notches_chars) = match rotor_configuration { - RotorConfiguration::I => ("EKMFLGDQVZNTOWYHXUSPAIBRCJ", vec!['Q']), - RotorConfiguration::II => ("AJDKSIRUXBLHWTMCQGZNPYFVOE", vec!['E']), - RotorConfiguration::III => ("BDFHJLCPRTXVZNYEIWGAKMUSQO", vec!['V']), - RotorConfiguration::IV => ("ESOVPZJAYQUIRHXLNFTGKDCMWB", vec!['J']), - RotorConfiguration::V => ("VZBRGITYUPSDNHLXAWMJQOFECK", vec!['Z']), - RotorConfiguration::VI => ("JPGVOUMFYQBENHZRDKASXLICTW", vec!['Z', 'M']), - RotorConfiguration::VII => ("NZJHGRCXMYSWBOUFAIVLPEKQDT", vec!['Z', 'M']), - RotorConfiguration::VIII => ("FKQHTLXOCBJSPDZRAMEWNIUYGV", vec!['Z', 'M']) - }; - - let wiring: [u8; 26] = wiring_string_to_array(wiring_string); - - let mut notches_indexes: Vec = Vec::new(); - for notch_char in notches_chars { - notches_indexes.push(char_to_index(notch_char)); - } - - Self { - wiring, - // Store the reversed variant of the wiring array so it can be used in processing - // without having to calculate on the fly each time. - inverse_wiring: inverse_wiring_array(wiring), - notches: notches_indexes, - position: char_to_index(starting_position), - ring_setting: char_to_index(ring_setting) - } - } - - /// Increment the rotor's position by 1. Use modulo to ensure that the position will wrap - /// around, i.e. if the rotor rotates on Z, it will wrap around to A rather than being stuck - /// in limbo at some non-existent 27th letter. - fn rotate(&mut self) { - self.position = (self.position + 1) % 26 - } - - /// Maps a signal through a single Rotor, taking into account the rotation of the rotor. - /// https://en.wikipedia.org/wiki/Enigma_rotor_details#Rotor_offset - /// If the `inverse` Boolean parameter is true, the inverse wiring array is used (for return - /// runs through the rotor set). If it is not, the standard wiring array is used. - fn map_signal(&self, signal: u8, inverse: bool) -> u8 { - let delta = ((self.position + 26) - self.ring_setting) % 26; - let contact_in = (signal + delta) % 26; - let contact_out = match inverse { - false => self.wiring[contact_in as usize], - true => self.inverse_wiring[contact_in as usize] - }; - let signal_out = ((contact_out + 26) - delta) % 26; - - signal_out - } -} - -/// Represents the five standard rotor configurations of an Enigma I. -pub enum RotorConfiguration { - I, - II, - III, - IV, - V, - VI, - VII, - VIII -} - -// TODO: Did fourth rotor have ring settings? Investigate... -pub struct FourthRotor { - /// This rotor's mappings - i.e. if this wiring array was `[3, 7, 12, 8, 5]` then this would - /// indicate that the character `A` maps through this rotor to the character `D`, that the - /// character `B` maps to the character `H` and so on. - wiring: [u8; 26], - /// This rotor's inverse mappings, i.e. the wiring array with its indexes and values swapped. - inverse_wiring: [u8; 26], - /// This rotor's current position. - position: u8 -} - -impl FourthRotor { - /// Constructs a Rotor. Planning to add custom rotor configurations rather than having to use - /// the enum to use one of the five standard variants. - pub fn new(rotor_configuration: FourthRotorConfiguration, starting_position: char) -> Option { - // Map the RotorConfiguration enum value to the actual wiring string and notch position. - let wiring_string = match rotor_configuration { - FourthRotorConfiguration::Beta => "LEYJVCNIXWPBQMDRTAKZGFUHOS", - FourthRotorConfiguration::Gamma => "FSOKANUERHMBTIYCWLQPZXVGJD" - }; - - let wiring: [u8; 26] = wiring_string_to_array(wiring_string); - - Some( - Self { - wiring, - // Store the reversed variant of the wiring array so it can be used in processing - // without having to calculate on the fly each time. - inverse_wiring: inverse_wiring_array(wiring), - position: char_to_index(starting_position), - } - ) - } - - /// Maps a signal through a single Rotor, taking into account the rotation of the rotor. - /// https://en.wikipedia.org/wiki/Enigma_rotor_details#Rotor_offset - /// If the `inverse` Boolean parameter is true, the inverse wiring array is used (for return - /// runs through the rotor set). If it is not, the standard wiring array is used. - fn map_signal(&self, signal: u8, inverse: bool) -> u8 { - let contact_in = (signal + self.position) % 26; - let contact_out = match inverse { - false => self.wiring[contact_in as usize], - true => self.inverse_wiring[contact_in as usize] - }; - let signal_out = ((contact_out + 26) - self.position) % 26; - - signal_out - } -} - -/// Represents the three standard reflector configurations of an Enigma I. -pub enum FourthRotorConfiguration { - Beta, - Gamma -} - -/// Represents a reflector in an Enigma machine. -pub struct Reflector { - /// The mapping of the reflector, representing what each character leaves the reflector as. - wiring: [u8; 26] -} - -/// Represents the three standard reflector configurations of an Enigma I. -pub enum ReflectorConfiguration { - A, - B, - C, - // Both of the below configurations are used in Enigma M4. - NarrowB, - NarrowC -} - -impl Reflector { - /// Constructs a Reflector. Planning to add custom reflector configurations rather than having - /// to use the enum to use one of the three standard variants. - pub fn new(reflector_configuration: ReflectorConfiguration) -> Self { - // Map the ReflectorConfiguration enum value to the actual wiring string. - let wiring_string = match reflector_configuration { - ReflectorConfiguration::A => "EJMZALYXVBWFCRQUONTSPIKHGD", - ReflectorConfiguration::B => "YRUHQSLDPXNGOKMIEBFZCWVJAT", - ReflectorConfiguration::C => "FVPJIAOYEDRZXWGCTKUQSBNMHL", - ReflectorConfiguration::NarrowB => "ENKQAUYWJICOPBLMDXZVFTHRGS", - ReflectorConfiguration::NarrowC => "RDOBJNTKVEHMLFCWZAXGYIPSUQ" - }; - - Self { - wiring: wiring_string_to_array(wiring_string) - } - } - - /// Maps a signal through the reflector, returning the index of the character as it leaves - /// the reflector. - fn map_signal(&self, signal: u8) -> u8 { - self.wiring[signal as usize] - } -} - -/// Represents the plugboard of an Enigma machine. Contains a `Vector` of `(u8, u8)` tuples. -/// The two `u8` values in the tuple correspond to the pairing of letters on a plugboard. -/// A tuple of `(3, 8)` means that an `D` would leave the plugboard as `I` and vice versa. -pub struct Plugboard { - configuration: Vec<(u8, u8)> -} - -impl Plugboard { - /// This constructor returns an `Option` because there is the potential that if the string - /// is empty, we will consider the machine to have no plugboard. - /// The plugboard configuration is passed as a string like "ABCDEFGH" where A will be paired - /// with B, C will be paired with D, E will be paired with F and G will be paired with H. - pub fn new(plugboard_string: &str) -> Option { - // If the plugboard string is empty, we can return None as we have no plugboard. - if plugboard_string.is_empty() { - return None - } - - // Make sure that plugboard string has an even number of characters, we don't want a plug - // that is disconnected on one end. - if plugboard_string.len() % 2 != 0 { - let invalid_connection = plugboard_string.as_bytes()[plugboard_string.len() - 1] as char; - panic!("Plugboard configuration has an invalid connection: {} is not connected to any other character.", invalid_connection); - } - - // Convert the plugboard string to a `Vec<(u8, u8)>` as explained in the documentation - // of the Plugboard struct. - let mut plugboard_pairs: Vec<(u8, u8)> = Vec::new(); - for i in (0..(plugboard_string.len() - 1)).step_by(2) { - let x = char_to_index(plugboard_string.as_bytes()[i] as char); - let y = char_to_index(plugboard_string.as_bytes()[i + 1] as char); - - plugboard_pairs.push((x, y)); - } - - Some( - Self { - configuration: plugboard_pairs - } - ) - } - - /// Maps a signal through the plugboard. - /// Iterate through all plugboard pairs. If there is a pair containing the signal, - /// return the value on the other end of the pair. - /// If there is no plugboard pair containing this signal, just return the signal itself - /// as it is not paired with any other signal. - fn map_signal(&self, signal: u8) -> u8 { - for i in &self.configuration { - if i.0 == signal { - return i.1 - } - - if i.1 == signal { - return i.0 - } - } - - signal - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_case_1_sanity_check() { - let mut machine: EnigmaMachine = EnigmaMachine { - left_rotor: Rotor::new(RotorConfiguration::I, 'A', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'A', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'A', 'A'), - fourth_rotor: None, - reflector: Reflector::new(ReflectorConfiguration::B), - plugboard: None - }; - - assert_eq!(machine.process("AAAAA"), "BDZGO"); - } - - #[test] - fn test_case_2_reciprocity() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'M', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'C', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'K', 'A'), - fourth_rotor: None, - plugboard: None - }; - - assert_eq!(machine.process("ENIGMA"), "QMJIDO"); - - machine.update_rotor_positions('M', 'C', 'K'); - - assert_eq!(machine.process("QMJIDO"), "ENIGMA"); - } - - #[test] - fn test_case_3_normal_turnover() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'K', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'D', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'O', 'A'), - fourth_rotor: None, - plugboard: None - }; - - assert_eq!(machine.process("AAAAA"), "JWZBJ"); - - machine.update_rotor_positions('K', 'D', 'U'); - - assert_eq!(machine.process("AAAAA"), "YWDVQ"); - } - - #[test] - fn test_case_4_double_step() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'A', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'D', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'U', 'A'), - fourth_rotor: None, - plugboard: None - }; - - assert_eq!(machine.process("AAAAA"), "EQIBM"); - } - - #[test] - fn test_case_5_plugboard() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'Z', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'Z', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'Z', 'A'), - fourth_rotor: None, - plugboard: Plugboard::new("ABCDEFGH") - }; - - assert_eq!(machine.process("AAAAA"), "UZYRQ"); - } - - #[test] - fn test_case_6_full_integration() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::II, 'A', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::IV, 'B', 'A'), - right_rotor: Rotor::new(RotorConfiguration::V, 'L', 'A'), - fourth_rotor: None, - plugboard: Plugboard::new("BQCRDIEJKWMTOSPXUZGH") - }; - - assert_eq!( - machine.process("EVERYTHINGISGOINGEXTREMELYWELL"), - "LLSDWFYUVEVDHBJVTWWECZNWYXLCNX" - ); - } - - #[test] - fn ring_test_case_1() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'A', 'B'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'A', 'B'), - right_rotor: Rotor::new(RotorConfiguration::III, 'A', 'B'), - fourth_rotor: None, - plugboard: None - }; - - assert_eq!(machine.process("AAAAA"), "EWTYX"); - } - - #[test] - fn ring_test_case_2() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'K', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'D', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'T', 'B'), - fourth_rotor: None, - plugboard: None - }; - - assert_eq!(machine.process("AAAA"), "JTIN"); - } - - #[test] - fn ring_test_case_3() { - let mut machine: EnigmaMachine = EnigmaMachine { - reflector: Reflector::new(ReflectorConfiguration::B), - left_rotor: Rotor::new(RotorConfiguration::I, 'G', 'R'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'U', 'T'), - right_rotor: Rotor::new(RotorConfiguration::III, 'M', 'M'), - fourth_rotor: None, - plugboard: Plugboard::new("AKSORILP") - }; - - assert_eq!(machine.process("HELLOWORLD"), "CDKSEVMKXJ"); - } - - #[test] - fn random_test_case_1() { - let mut machine = EnigmaMachine { - left_rotor: Rotor::new(RotorConfiguration::II, 'F', 'D'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'P', 'W'), - right_rotor: Rotor::new(RotorConfiguration::II, 'K', 'L'), - fourth_rotor: None, - reflector: Reflector::new(ReflectorConfiguration::B), - plugboard: Plugboard::new("JWYLFKREVPXTHOBCMQZG") - }; - - assert_eq!(machine.process("UISDNUUINSNASIAASNUUIDIADIADDDNNNS"), "LNLCJPIFILXIKZPROFOZATVGWZUWOFBFVB"); - } - - #[test] - fn random_test_case_2() { - let mut machine = EnigmaMachine { - left_rotor: Rotor::new(RotorConfiguration::III, 'K', 'B'), - middle_rotor: Rotor::new(RotorConfiguration::III, 'E', 'T'), - right_rotor: Rotor::new(RotorConfiguration::V, 'C', 'H'), - fourth_rotor: None, - reflector: Reflector::new(ReflectorConfiguration::B), - plugboard: Plugboard::new("ACPQUHYFWRMJOSKTDIVG") - }; - - assert_eq!(machine.process("EEBXZZEBZXNXLBLBBZNLZBLNLNBBNBLLNBXNEZLB"), "BPNAFCWSDBGAFDIQPKGHXNFMXIGIKLXPKTPORWOX"); - } - - #[test] - fn test_rotor_multiple_turnovers() { - let mut machine = EnigmaMachine { - left_rotor: Rotor::new(RotorConfiguration::II, 'K', 'B'), - middle_rotor: Rotor::new(RotorConfiguration::III, 'E', 'T'), - right_rotor: Rotor::new(RotorConfiguration::VI, 'Z', 'H'), - fourth_rotor: None, - reflector: Reflector::new(ReflectorConfiguration::B), - plugboard: None - }; - - assert_eq!(machine.right_rotor.position, 25); // Z, not yet turned over - assert_eq!(machine.middle_rotor.position, 4); // E, not yet turned over - - // Right rotor is currently in Z position so should turn over on next rotation. - machine.press_key('A'); - - assert_eq!(machine.right_rotor.position, 0); // A, has turned over - assert_eq!(machine.middle_rotor.position, 5); // F, has turned over because right rotor was at notch - - machine.update_rotor_positions('K', 'F', 'M'); - - assert_eq!(machine.right_rotor.position, 12); // M, not yet turned over - assert_eq!(machine.middle_rotor.position, 5); // F, not yet turned over - - machine.press_key('A'); - - assert_eq!(machine.right_rotor.position, 13); // N, has turned over - assert_eq!(machine.middle_rotor.position, 6); // F, has turned over because right rotor was at notch - } - - // I could probably find a better name for this test but it does the job for now... - #[test] - fn test_original_ukw_b_and_narrow_ukw_b_and_beta_equivalency() { - let mut enigma_i = EnigmaMachine { - left_rotor: Rotor::new(RotorConfiguration::I, 'A', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'A', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'A', 'A'), - fourth_rotor: None, - reflector: Reflector::new(ReflectorConfiguration::B), - plugboard: None - }; - - let mut enigma_m4 = EnigmaMachine { - left_rotor: Rotor::new(RotorConfiguration::I, 'A', 'A'), - middle_rotor: Rotor::new(RotorConfiguration::II, 'A', 'A'), - right_rotor: Rotor::new(RotorConfiguration::III, 'A', 'A'), - fourth_rotor: FourthRotor::new(FourthRotorConfiguration::Beta, 'A'), - reflector: Reflector::new(ReflectorConfiguration::NarrowB), - plugboard: None - }; - - // The Enigma I and Enigma M4 here should give the same output. - assert_eq!(enigma_i.process("ABCDEFG"), enigma_m4.process("ABCDEFG")); - } - - #[test] - fn test_char_to_index() { - assert_eq!(char_to_index('A'), 0); - assert_eq!(char_to_index('B'), 1); - assert_eq!(char_to_index('C'), 2); - assert_eq!(char_to_index('Z'), 25); - } - - #[test] - fn test_index_to_char() { - assert_eq!(index_to_char(0), 'A'); - assert_eq!(index_to_char(1), 'B'); - assert_eq!(index_to_char(2), 'C'); - assert_eq!(index_to_char(25), 'Z'); - } -} \ No newline at end of file