commit 2dab896a09409cf89cfe1070d6c59aebeb3b6235 Author: vodofrede Date: Wed Apr 21 20:47:47 2021 +0200 1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6e00b06 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'enigma'", + "cargo": { + "args": [ + "build", + "--bin=enigma", + "--package=enigma" + ], + "filter": { + "name": "enigma", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'enigma'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=enigma", + "--package=enigma" + ], + "filter": { + "name": "enigma", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..02f458b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,224 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "enigma" +version = "0.1.0" +dependencies = [ + "log", + "pretty_env_logger", + "rand", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "libc" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..42b62eb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "enigma" +version = "0.1.0" +authors = ["vodofrede "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.14" +pretty_env_logger = "0.4.0" +rand = "0.8.3" diff --git a/src/enigma.rs b/src/enigma.rs new file mode 100644 index 0000000..8f76d86 --- /dev/null +++ b/src/enigma.rs @@ -0,0 +1,67 @@ +use std::str::FromStr; + +use crate::*; + +#[derive(Debug, Clone)] +pub struct Enigma { + rotors: Vec, + plugboard: Plugboard, + reflector: Reflector, +} + +impl Enigma { + pub fn new(rotors: Vec, plugboard: Plugboard, reflector: Reflector) -> Self { + Enigma { + rotors, + plugboard, + reflector, + } + } + pub fn encode(&mut self, input: &str) -> Result { + let output: Result = input + .chars() + .map(|c| { + self.rotate_rotors(); + + let mut letter = Letter::from_str(&c.to_string())?; + letter = self.plugboard.letter(letter); + + for rotor in self.rotors.iter() { + letter = rotor.forwards(letter); + } + + letter = self.reflector.letter(letter); + + for rotor in self.rotors.iter().rev() { + letter = rotor.backwards(letter); + } + + letter = self.plugboard.letter(letter); + + Ok(char::from(letter)) + }) + .collect(); + + output + } + + fn rotate_rotors(&mut self) { + let mut rotate = true; + + for rotor in self.rotors.iter_mut() { + let rotate_next = rotor.at_notch(); + + if rotate { + rotor.rotate(); + } + + rotate = rotate_next; + } + } +} + +impl Random for Enigma { + fn random() -> Self { + todo!() + } +} diff --git a/src/letter.rs b/src/letter.rs new file mode 100644 index 0000000..c244eb0 --- /dev/null +++ b/src/letter.rs @@ -0,0 +1,212 @@ +use crate::*; +use rand::prelude::*; +use std::{ + ops::{Add, Sub}, + str::FromStr, +}; + +pub const AMOUNT_OF_LETTERS: usize = 26; + +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)] +pub enum Letter { + A = 0, + B = 1, + C = 2, + D = 3, + E = 4, + F = 5, + G = 6, + H = 7, + I = 8, + J = 9, + K = 10, + L = 11, + M = 12, + N = 13, + O = 14, + P = 15, + Q = 16, + R = 17, + S = 18, + T = 19, + U = 20, + V = 21, + W = 22, + X = 23, + Y = 24, + Z = 25, +} + +impl Letter { + pub fn all() -> Vec { + use Letter::*; + + vec![ + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + ] + } +} + +impl Add for Letter { + type Output = Letter; + + fn add(self, rhs: Self) -> Self::Output { + Letter::from((self as usize + rhs as usize) % 26) + } +} + +impl Sub for Letter { + type Output = Letter; + + fn sub(self, rhs: Self) -> Self::Output { + let sum = (self as isize - rhs as isize).rem_euclid(26); + Letter::from(sum as usize) + } +} + +impl Random for Letter { + fn random() -> Self { + let mut rng = rand::thread_rng(); + let num = rng.gen_range(0..AMOUNT_OF_LETTERS); + Letter::from(num) + } +} + +impl From for Letter { + fn from(n: usize) -> Self { + let index = n % 26; + + match index { + 0 => Letter::A, + 1 => Letter::B, + 2 => Letter::C, + 3 => Letter::D, + 4 => Letter::E, + 5 => Letter::F, + 6 => Letter::G, + 7 => Letter::H, + 8 => Letter::I, + 9 => Letter::J, + 10 => Letter::K, + 11 => Letter::L, + 12 => Letter::M, + 13 => Letter::N, + 14 => Letter::O, + 15 => Letter::P, + 16 => Letter::Q, + 17 => Letter::R, + 18 => Letter::S, + 19 => Letter::T, + 20 => Letter::U, + 21 => Letter::V, + 22 => Letter::W, + 23 => Letter::X, + 24 => Letter::Y, + 25 => Letter::Z, + _ => unreachable!(), + } + } +} + +impl From for Letter { + fn from(c: char) -> Self { + match c { + 'A' => Letter::A, + 'B' => Letter::B, + 'C' => Letter::C, + 'D' => Letter::D, + 'E' => Letter::E, + 'F' => Letter::F, + 'G' => Letter::G, + 'H' => Letter::H, + 'I' => Letter::I, + 'J' => Letter::J, + 'K' => Letter::K, + 'L' => Letter::L, + 'M' => Letter::M, + 'N' => Letter::N, + 'O' => Letter::O, + 'P' => Letter::P, + 'Q' => Letter::Q, + 'R' => Letter::R, + 'S' => Letter::S, + 'T' => Letter::T, + 'U' => Letter::U, + 'V' => Letter::V, + 'W' => Letter::W, + 'X' => Letter::X, + 'Y' => Letter::Y, + 'Z' => Letter::Z, + _ => panic!("Tried to parse unknown letter: '{}'", c), + } + } +} + +impl FromStr for Letter { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "A" => Ok(Letter::A), + "B" => Ok(Letter::B), + "C" => Ok(Letter::C), + "D" => Ok(Letter::D), + "E" => Ok(Letter::E), + "F" => Ok(Letter::F), + "G" => Ok(Letter::G), + "H" => Ok(Letter::H), + "I" => Ok(Letter::I), + "J" => Ok(Letter::J), + "K" => Ok(Letter::K), + "L" => Ok(Letter::L), + "M" => Ok(Letter::M), + "N" => Ok(Letter::N), + "O" => Ok(Letter::O), + "P" => Ok(Letter::P), + "Q" => Ok(Letter::Q), + "R" => Ok(Letter::R), + "S" => Ok(Letter::S), + "T" => Ok(Letter::T), + "U" => Ok(Letter::U), + "V" => Ok(Letter::V), + "W" => Ok(Letter::W), + "X" => Ok(Letter::X), + "Y" => Ok(Letter::Y), + "Z" => Ok(Letter::Z), + _ => Err("Failed to parse string as letter"), + } + } +} + +impl From for char { + fn from(letter: Letter) -> Self { + match letter { + Letter::A => 'A', + Letter::B => 'B', + Letter::C => 'C', + Letter::D => 'D', + Letter::E => 'E', + Letter::F => 'F', + Letter::G => 'G', + Letter::H => 'H', + Letter::I => 'I', + Letter::J => 'J', + Letter::K => 'K', + Letter::L => 'L', + Letter::M => 'M', + Letter::N => 'N', + Letter::O => 'O', + Letter::P => 'P', + Letter::Q => 'Q', + Letter::R => 'R', + Letter::S => 'S', + Letter::T => 'T', + Letter::U => 'U', + Letter::V => 'V', + Letter::W => 'W', + Letter::X => 'X', + Letter::Y => 'Y', + Letter::Z => 'Z', + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bbc5f46 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,203 @@ +mod enigma; +mod letter; +mod plugboard; +mod random; +mod reflector; +mod rotor; + +pub use enigma::*; +pub use letter::*; +pub use plugboard::*; +pub use random::*; +pub use reflector::*; +pub use rotor::*; + +use std::io; + +fn main() { + #[cfg(debug_assertions)] + pretty_env_logger::init(); + + let rotors = choose_rotors(); + let plugboard = choose_plugboard(); + let reflector = choose_reflector(); + println!("Plugboard configuration: {}", plugboard); + let enigma_machine = Enigma::new(rotors, plugboard, reflector); + + loop { + let mut enigma_machine = enigma_machine.clone(); + + println!("Input some text to encrypt:"); + + let input = clean_input(get_input_from_stdin()); + let output = enigma_machine.encode(&input); + + match output { + Ok(output) => println!("Input: {}\nOutput: {}", input, output), + Err(e) => println!("Wrong input: {}", e), + } + } +} + +fn choose_rotors() -> Vec { + let mut rotors = vec![]; + + while rotors.is_empty() { + rotors.clear(); + println!("Available rotors:"); + for rotor in Rotor::all() { + println!("\t{}", rotor.variant); + } + println!("Choose rotors (comma-separated, right to left):"); + let input = clean_input(get_input_from_stdin()); + + for choice in input.split(',') { + let rotor = match choice { + "I" => Rotor::variant(RotorVariant::I), + "II" => Rotor::variant(RotorVariant::II), + "III" => Rotor::variant(RotorVariant::III), + "IV" => Rotor::variant(RotorVariant::IV), + "V" => Rotor::variant(RotorVariant::V), + "VI" => Rotor::variant(RotorVariant::VI), + "VII" => Rotor::variant(RotorVariant::VII), + "VIII" => Rotor::variant(RotorVariant::VIII), + "IDENTITY" => Rotor::variant(RotorVariant::Identity), + _ => { + println!("Invalid rotor choice: {}", choice); + break; + } + }; + + rotors.push(rotor); + } + } + + rotors +} + +fn choose_plugboard() -> Plugboard { + let mut plugboard = None; + + while plugboard.is_none() { + println!("Available plugboards:"); + println!("\tRandom"); + println!("\tIdentity"); + println!("Choose plugboard (only one):"); + let input = clean_input(get_input_from_stdin()); + + plugboard = match &input[..] { + "IDENTITY" => Some(Plugboard::identity()), + "RANDOM" => Some(Plugboard::random()), + _ => { + println!("Invalid plugboard choice: {}", input); + None + } + }; + } + + plugboard.unwrap() +} + +fn choose_reflector() -> Reflector { + let mut reflector = None; + + while reflector.is_none() { + println!("Available reflectors:"); + for reflector in Reflector::all() { + println!("\t{}", reflector.variant); + } + println!("Choose reflector (only one):"); + let input = clean_input(get_input_from_stdin()); + + reflector = match &input[..] { + "A" => Some(Reflector::variant(ReflectorVariant::A)), + "B" => Some(Reflector::variant(ReflectorVariant::B)), + "C" => Some(Reflector::variant(ReflectorVariant::C)), + "BETA" => Some(Reflector::variant(ReflectorVariant::Beta)), + "GAMMA" => Some(Reflector::variant(ReflectorVariant::Gamma)), + "BTHIN" => Some(Reflector::variant(ReflectorVariant::BThin)), + "CTHIN" => Some(Reflector::variant(ReflectorVariant::CThin)), + "IDENTITY" => Some(Reflector::variant(ReflectorVariant::Identity)), + _ => { + println!("Invalid reflector choice: {}", input); + None + } + }; + } + + reflector.unwrap() +} + +fn get_input_from_stdin() -> String { + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + input +} + +fn clean_input(input: String) -> String { + input + .trim() + .chars() + .filter(|c| c != &' ' && c != &'\r' && c != &'\n') + .collect::() + .to_uppercase() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn enigma_machine_encodes_correctly() { + let rotors = vec![ + Rotor::variant(RotorVariant::I), + Rotor::variant(RotorVariant::II), + Rotor::variant(RotorVariant::III), + ]; + let plugboard = Plugboard::identity(); + let reflector = Reflector::variant(ReflectorVariant::B); + let enigma_machine = Enigma::new(rotors, plugboard, reflector); + + let mut em1 = enigma_machine.clone(); + let mut em2 = enigma_machine.clone(); + + println!("Encoding string"); + let result = em1 + .encode("MAGNUSERPISSEDAARLIGTILLEAGUEOFLEGENDS") + .unwrap(); + assert_eq!(result, "HTMPRVHFYZUHSANJWQCCAOHUTDRICUYUJJTHBE"); + + println!("Decoding string"); + let result = em2 + .encode("HTMPRVHFYZUHSANJWQCCAOHUTDRICUYUJJTHBE") + .unwrap(); + assert_eq!(result, "MAGNUSERPISSEDAARLIGTILLEAGUEOFLEGENDS"); + } + + #[test] + fn random_plugboard_is_complete() { + let plugboard = Plugboard::random(); + + for letter in Letter::all() { + println!("Testing if '{:?}' is mapped in plugboard", letter); + plugboard.letter(letter); + } + } + + #[test] + fn rotor_rotates_as_expected() { + let mut rotor = Rotor::variant(RotorVariant::Identity); + + println!("{:?}", rotor.mappings); + assert_eq!(rotor.forwards(Letter::A), Letter::A); + rotor.rotate(); + assert_eq!(rotor.forwards(Letter::A), Letter::A); + + for _ in 0..26 { + rotor.rotate(); + } + + println!("{:?}", rotor.mappings); + assert_eq!(rotor.forwards(Letter::A), Letter::A); + } +} diff --git a/src/plugboard.rs b/src/plugboard.rs new file mode 100644 index 0000000..561b0eb --- /dev/null +++ b/src/plugboard.rs @@ -0,0 +1,122 @@ +use crate::*; +use rand::prelude::*; +use std::{collections::HashMap, fmt}; + +// MUST be less than total amount of letters divided by two +const CABLES: usize = 10; + +#[derive(Debug, Clone)] +pub struct Plugboard { + mappings: Vec, +} + +impl Plugboard { + pub fn new(mappings: Vec) -> Self { + Plugboard { mappings } + } + + pub fn identity() -> Self { + let mappings = vec![ + Letter::A, + Letter::B, + Letter::C, + Letter::D, + Letter::E, + Letter::F, + Letter::G, + Letter::H, + Letter::I, + Letter::J, + Letter::K, + Letter::L, + Letter::M, + Letter::N, + Letter::O, + Letter::P, + Letter::Q, + Letter::R, + Letter::S, + Letter::T, + Letter::U, + Letter::V, + Letter::W, + Letter::X, + Letter::Y, + Letter::Z, + ]; + + Plugboard::new(mappings) + } + + pub fn index(&self, index: usize) -> usize { + *self.mappings.get(index).expect( + &format!( + "Switchboard is missing mapping for a letter: {:?}", + Letter::from(index) + ) + .to_string(), + ) as usize + } + + pub fn letter(&self, letter: Letter) -> Letter { + Letter::from(self.index(letter as usize)) + } +} + +impl fmt::Display for Plugboard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut pairs = HashMap::new(); + + for (i, mapping) in self.mappings.iter().enumerate() { + let left_letter = Letter::from(i); + let right_letter = *mapping; + + if left_letter != right_letter + && !pairs.get(&left_letter).is_some() + && !pairs.get(&right_letter).is_some() + { + pairs.insert(left_letter, right_letter); + } + } + + let mut s = String::new(); + if pairs.is_empty() { + s = "Identity".to_string(); + } + + for (left_letter, right_letter) in pairs.iter() { + s = format!("{:?}{:?} {}", left_letter, right_letter, s); + } + + f.write_str(&s) + } +} + +impl Random for Plugboard { + fn random() -> Self { + let mut mappings = vec![Letter::A; AMOUNT_OF_LETTERS]; + let mut rng = rand::thread_rng(); + + let mut input = Letter::all(); + input.shuffle(&mut rng); + input.truncate(CABLES); + + let mut output: Vec = Letter::all() + .iter() + .filter(|l| !input.contains(l)) + .cloned() + .collect(); + let remaining = output.split_off(CABLES); + + for (input, output) in input.iter().zip(output.iter()) { + mappings.insert(*input as usize, *output); + mappings.insert(*output as usize, *input); + } + + for letter in remaining { + mappings.insert(letter as usize, letter); + } + + Plugboard::new(mappings) + } +} diff --git a/src/random.rs b/src/random.rs new file mode 100644 index 0000000..102ed5a --- /dev/null +++ b/src/random.rs @@ -0,0 +1,3 @@ +pub trait Random { + fn random() -> Self; +} diff --git a/src/reflector.rs b/src/reflector.rs new file mode 100644 index 0000000..2f4e714 --- /dev/null +++ b/src/reflector.rs @@ -0,0 +1,96 @@ +use crate::*; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct Reflector { + pub variant: ReflectorVariant, + mappings: Vec, +} + +impl Reflector { + pub fn variant(variant: ReflectorVariant) -> Self { + let mappings = match variant { + ReflectorVariant::Identity => mappings(VARIANT_IDENTITY), + ReflectorVariant::Beta => mappings(VARIANT_BETA), + ReflectorVariant::Gamma => mappings(VARIANT_GAMMA), + ReflectorVariant::A => mappings(VARIANT_A), + ReflectorVariant::B => mappings(VARIANT_B), + ReflectorVariant::C => mappings(VARIANT_C), + ReflectorVariant::BThin => mappings(VARIANT_B_THIN), + ReflectorVariant::CThin => mappings(VARIANT_C_THIN), + }; + + Reflector { variant, mappings } + } + + pub fn all() -> Vec { + vec![ + Reflector::variant(ReflectorVariant::A), + Reflector::variant(ReflectorVariant::B), + Reflector::variant(ReflectorVariant::C), + Reflector::variant(ReflectorVariant::Beta), + Reflector::variant(ReflectorVariant::Gamma), + Reflector::variant(ReflectorVariant::BThin), + Reflector::variant(ReflectorVariant::CThin), + Reflector::variant(ReflectorVariant::Identity), + ] + } + + pub fn index(&self, letter: usize) -> usize { + *self + .mappings + .get(letter) + .expect(&format!("Reflector is missing mapping for a letter: {:?}", letter).to_string()) + as usize + } + + pub fn letter(&self, letter: Letter) -> Letter { + Letter::from(self.index(letter as usize)) + } +} + +const VARIANT_IDENTITY: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const VARIANT_BETA: &'static str = "LEYJVCNIXWPBQMDRTAKZGFUHOS"; +const VARIANT_GAMMA: &'static str = "FSOKANUERHMBTIYCWLQPZXVGJD"; +const VARIANT_A: &'static str = "EJMZALYXVBWFCRQUONTSPIKHGD"; +const VARIANT_B: &'static str = "YRUHQSLDPXNGOKMIEBFZCWVJAT"; +const VARIANT_C: &'static str = "FVPJIAOYEDRZXWGCTKUQSBNMHL"; +const VARIANT_B_THIN: &'static str = "ENKQAUYWJICOPBLMDXZVFTHRGS"; +const VARIANT_C_THIN: &'static str = "RDOBJNTKVEHMLFCWZAXGYIPSUQ"; + +#[derive(Debug, Copy, Clone)] +pub enum ReflectorVariant { + Identity, + Beta, + Gamma, + A, + B, + C, + BThin, + CThin, +} + +impl fmt::Display for ReflectorVariant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ReflectorVariant::Identity => f.write_str("Identity"), + ReflectorVariant::Beta => f.write_str("Beta"), + ReflectorVariant::Gamma => f.write_str("Gamma"), + ReflectorVariant::A => f.write_str("A"), + ReflectorVariant::B => f.write_str("B"), + ReflectorVariant::C => f.write_str("C"), + ReflectorVariant::BThin => f.write_str("B Thin"), + ReflectorVariant::CThin => f.write_str("C Thin"), + } + } +} + +fn mappings(input: &'static str) -> Vec { + let mut mappings = vec![]; + + for c in input.chars() { + mappings.push(Letter::from(c)); + } + + mappings +} diff --git a/src/rotor.rs b/src/rotor.rs new file mode 100644 index 0000000..bbe6b7b --- /dev/null +++ b/src/rotor.rs @@ -0,0 +1,131 @@ +use crate::*; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct Rotor { + pub variant: RotorVariant, + pub mappings: Vec, + pub position: Letter, + pub notches: Vec, +} + +impl Rotor { + pub fn variant(variant: RotorVariant) -> Self { + let (mappings, notches) = match variant { + RotorVariant::Identity => (mappings(IDENTITY), vec![Letter::Z]), + RotorVariant::I => (mappings(VARIANT_I), vec![Letter::Q]), + RotorVariant::II => (mappings(VARIANT_II), vec![Letter::E]), + RotorVariant::III => (mappings(VARIANT_III), vec![Letter::V]), + RotorVariant::IV => (mappings(VARIANT_IV), vec![Letter::J]), + RotorVariant::V => (mappings(VARIANT_V), vec![Letter::Z]), + RotorVariant::VI => (mappings(VARIANT_VI), vec![Letter::Z, Letter::M]), + RotorVariant::VII => (mappings(VARIANT_VII), vec![Letter::Z, Letter::M]), + RotorVariant::VIII => (mappings(VARIANT_VIII), vec![Letter::Z, Letter::M]), + }; + + let position = Letter::A; + + Rotor { + variant, + mappings, + position, + notches, + } + } + + pub fn all() -> Vec { + vec![ + Rotor::variant(RotorVariant::I), + Rotor::variant(RotorVariant::II), + Rotor::variant(RotorVariant::III), + Rotor::variant(RotorVariant::IV), + Rotor::variant(RotorVariant::V), + Rotor::variant(RotorVariant::VI), + Rotor::variant(RotorVariant::VII), + Rotor::variant(RotorVariant::VIII), + Rotor::variant(RotorVariant::Identity), + ] + } + + pub fn set_position(&mut self, position: Letter) { + self.position = position; + } + + pub fn forwards(&self, letter: Letter) -> Letter { + *self + .mappings + .get((letter + self.position) as usize) + .expect(&format!("Rotor is missing mapping for a letter: {:?}", letter).to_string()) + - self.position + } + + pub fn backwards(&self, letter: Letter) -> Letter { + Letter::from( + self.mappings + .iter() + .enumerate() + .filter(|(_, l)| (**l - self.position) == letter) + .next() + .unwrap() + .0, + ) - self.position + } + + pub fn at_notch(&self) -> bool { + self.notches.contains(&self.position) + } + + pub fn rotate(&mut self) { + self.position = Letter::from((self.position as usize + 1) % AMOUNT_OF_LETTERS); + } +} + +const IDENTITY: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const VARIANT_I: &'static str = "EKMFLGDQVZNTOWYHXUSPAIBRCJ"; +const VARIANT_II: &'static str = "AJDKSIRUXBLHWTMCQGZNPYFVOE"; +const VARIANT_III: &'static str = "BDFHJLCPRTXVZNYEIWGAKMUSQO"; +const VARIANT_IV: &'static str = "ESOVPZJAYQUIRHXLNFTGKDCMWB"; +const VARIANT_V: &'static str = "VZBRGITYUPSDNHLXAWMJQOFECK"; +const VARIANT_VI: &'static str = "JPGVOUMFYQBENHZRDKASXLICTW"; +const VARIANT_VII: &'static str = "NZJHGRCXMYSWBOUFAIVLPEKQDT"; +const VARIANT_VIII: &'static str = "FKQHTLXOCBJSPDZRAMEWNIUYGV"; + +#[non_exhaustive] +#[derive(Debug, Copy, Clone)] +pub enum RotorVariant { + Identity, + I, + II, + III, + IV, + V, + VI, + VII, + VIII, +} + +impl fmt::Display for RotorVariant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RotorVariant::Identity => f.write_str("Identity"), + RotorVariant::I => f.write_str("I"), + RotorVariant::II => f.write_str("II"), + RotorVariant::III => f.write_str("III"), + RotorVariant::IV => f.write_str("IV"), + RotorVariant::V => f.write_str("V"), + RotorVariant::VI => f.write_str("VI"), + RotorVariant::VII => f.write_str("VII"), + RotorVariant::VIII => f.write_str("VIII"), + } + } +} + +fn mappings(input: &'static str) -> Vec { + let mut mappings = vec![]; + + for c in input.chars() { + mappings.push(Letter::from(c)); + } + + mappings +}