enigma_simulator/
unsafe_enigma.rs

1use crate::{
2    alphabet::{Alphabet, ALPHABET},
3    enigma::caeser_shift,
4    reflector::Reflector,
5    rotor::{IntoRotors as _, Rotor},
6};
7
8pub struct UncheckedEnigmaMachine {
9    pub(crate) rotors: (Rotor, Rotor, Rotor),
10    pub(crate) ring_positions: (u8, u8, u8),
11    pub(crate) ring_settings: (u8, u8, u8),
12    pub(crate) reflector: &'static std::collections::HashMap<char, char>,
13    pub(crate) plugboard: std::collections::HashMap<char, char>,
14}
15
16impl UncheckedEnigmaMachine {
17    /// Decrypts the given ciphertext while skipping all safety checks for maxmimum speed.
18    ///
19    /// This is exactly the same as calling `machine.encrypt_unchecked(text)`, since the enigma cipher is
20    /// symmetric; The only difference is semantic meaning and intent, i.e.,
21    ///
22    /// ```rust
23    ///	assert_eq!(text, machine.decrypt_unchecked(machine.decrypt_unchecked(text)));
24    ///	assert_eq!(text, machine.encrypt_unchecked(machine.encrypt_unchecked(text)));
25    ///	assert_eq!(text, machine.decrypt_unchecked(machine.encrypt_unchecked(text)));
26    ///	assert_eq!(text, machine.encrypt_unchecked(machine.decrypt_unchecked(text)));
27    /// ```
28    ///
29    /// # Parameters
30    /// - `text` - The ciphertext to decrypt
31    ///
32    /// # Returns
33    /// The decrypted plaintext.
34    ///
35    /// # Safety
36    /// This function may panic if the Enigma machine was constructed with invalid settings. If the machine was constructed
37    /// correctly, this is guarnateed to not panic, and will function identically to `EnigmaMachine::decrypt()`, with
38    /// marginally better performance. See the `Performance` section of the `README` for more information.
39    pub unsafe fn decrypt_unchecked(&self, text: &str) -> String {
40        let text = text.to_uppercase();
41        let rotor_a = self.rotors.0.alphabet();
42        let rotor_b = self.rotors.1.alphabet();
43        let rotor_c = self.rotors.2.alphabet();
44
45        let mut rotor_a_letter = self.ring_positions.0;
46        let mut rotor_b_letter = self.ring_positions.1;
47        let mut rotor_c_letter = self.ring_positions.2;
48
49        let rotor_a_setting = self.ring_settings.0;
50        let offset_a_setting = rotor_a_setting;
51        let rotor_b_setting = self.ring_settings.1;
52        let offset_b_setting = rotor_b_setting;
53        let rotor_c_setting = self.ring_settings.2;
54        let offset_c_setting = rotor_c_setting;
55
56        let rotor_a = caeser_shift(&rotor_a.letters(), offset_a_setting);
57        let rotor_b = caeser_shift(&rotor_b.letters(), offset_b_setting);
58        let rotor_c = caeser_shift(&rotor_c.letters(), offset_c_setting);
59
60        let rotor_a_first_half = unsafe { rotor_a.get_unchecked((26 - offset_a_setting as usize)..rotor_a.len()) }.to_owned();
61        let rotor_a_second_half = unsafe { rotor_a.get_unchecked(0..(26 - offset_a_setting as usize)) }.to_owned();
62        let rotor_a = rotor_a_first_half + &rotor_a_second_half;
63        let rotor_a = Alphabet::new_unchecked(&rotor_a);
64
65        let rotor_b_first_half = unsafe { rotor_b.get_unchecked((26 - offset_b_setting as usize)..rotor_b.len()) }.to_owned();
66        let rotor_b_second_half = unsafe { rotor_b.get_unchecked(0..(26 - offset_b_setting as usize)) }.to_owned();
67        let rotor_b = rotor_b_first_half + &rotor_b_second_half;
68        let rotor_b = Alphabet::new_unchecked(&rotor_b);
69
70        let rotor_c_first_half = unsafe { rotor_c.get_unchecked((26 - offset_c_setting as usize)..rotor_c.len()) }.to_owned();
71        let rotor_c_second_half = unsafe { rotor_c.get_unchecked(0..(26 - offset_c_setting as usize)) }.to_owned();
72        let rotor_c = rotor_c_first_half + &rotor_c_second_half;
73        let rotor_c = Alphabet::new_unchecked(&rotor_c);
74
75        text.chars()
76            .map(|mut letter| {
77                // Non-alphabetic characters stay the same
78                if !letter.is_alphabetic() {
79                    return letter.to_string();
80                }
81
82                // Rotate rotor 3
83                let mut rotor_trigger = self
84                    .rotors
85                    .2
86                    .notches()
87                    .iter()
88                    .map(|notch| ALPHABET.unchecked_index_of(*notch))
89                    .collect::<Vec<_>>()
90                    .contains(&rotor_c_letter);
91                rotor_c_letter = (rotor_c_letter + 1) % 26;
92
93                // Rotate rotor 2
94                if rotor_trigger {
95                    rotor_trigger = self
96                        .rotors
97                        .1
98                        .notches()
99                        .iter()
100                        .map(|notch| ALPHABET.unchecked_index_of(*notch))
101                        .collect::<Vec<_>>()
102                        .contains(&rotor_b_letter);
103                    rotor_b_letter = (rotor_b_letter + 1) % 26;
104
105                    // Rotate rotor 1
106                    if rotor_trigger {
107                        rotor_a_letter = (rotor_a_letter + 1) % 26;
108                    }
109                }
110                // Double step sequence
111                else if self
112                    .rotors
113                    .1
114                    .notches()
115                    .iter()
116                    .map(|notch| ALPHABET.unchecked_index_of(*notch))
117                    .collect::<Vec<_>>()
118                    .contains(&rotor_b_letter)
119                {
120                    rotor_b_letter = (rotor_b_letter + 1) % 26;
121                    rotor_a_letter = (rotor_a_letter + 1) % 26;
122                }
123
124                // Plugboard decryption
125                if let Some(plugboarded_letter) = self.plugboard.get(&letter) {
126                    letter = *plugboarded_letter;
127                }
128
129                let offset_a = rotor_a_letter;
130                let offset_b = rotor_b_letter;
131                let offset_c = rotor_c_letter;
132
133                // Rotor 3 Encryption
134                let pos = ALPHABET.unchecked_index_of(letter);
135                let let_ = rotor_c.unchecked_letter_at((pos + offset_c) % 26);
136                let pos = ALPHABET.unchecked_index_of(let_);
137                letter = ALPHABET.unchecked_letter_at((pos + 26 - offset_c) % 26);
138
139                // Rotor 2 Encryption
140                let pos = ALPHABET.unchecked_index_of(letter);
141                let let_ = rotor_b.unchecked_letter_at((pos + offset_b) % 26);
142                let pos = ALPHABET.unchecked_index_of(let_);
143                letter = ALPHABET.unchecked_letter_at((pos + 26 - offset_b) % 26);
144
145                // Rotor 1 Encryption
146                let pos = ALPHABET.unchecked_index_of(letter);
147                let let_ = rotor_a.unchecked_letter_at((pos + offset_a) % 26);
148                let pos = ALPHABET.unchecked_index_of(let_);
149                letter = ALPHABET.unchecked_letter_at((pos + 26 - offset_a) % 26);
150
151                // Reflector Encryption
152                letter = *self.reflector.get(&letter).unwrap();
153
154                // Rotor 1 Encryption
155                let pos = ALPHABET.unchecked_index_of(letter);
156                let let_ = ALPHABET.unchecked_letter_at((pos + offset_a) % 26);
157                let pos = rotor_a.unchecked_index_of(let_);
158                letter = ALPHABET.unchecked_letter_at((pos + 26 - offset_a) % 26);
159
160                // Rotor 2 Encryption
161                let pos = ALPHABET.unchecked_index_of(letter);
162                let let_ = ALPHABET.unchecked_letter_at((pos + offset_b) % 26);
163                let pos = rotor_b.unchecked_index_of(let_);
164                letter = ALPHABET.unchecked_letter_at((pos + 26 - offset_b) % 26);
165
166                // Rotor 3 Encryption
167                let pos = ALPHABET.unchecked_index_of(letter);
168                let let_ = ALPHABET.unchecked_letter_at((pos + offset_c) % 26);
169                let pos = rotor_c.unchecked_index_of(let_);
170                letter = ALPHABET.unchecked_letter_at((pos + 26 - offset_c) % 26);
171
172                // Plugboard Second Pass
173                if let Some(plugboarded_letter) = self.plugboard.get(&letter) {
174                    letter = *plugboarded_letter;
175                }
176
177                letter.to_string()
178            })
179            .collect()
180    }
181
182    /// Encrypts the given plaintext while skipping all safety checks for maxmimum speed.
183    ///
184    /// This is exactly the same as calling `machine.decrypt_unchecked(text)`, since the enigma cipher is
185    /// symmetric; The only difference is semantic meaning and intent, i.e.,
186    ///
187    /// ```rust
188    ///	assert_eq!(text, machine.decrypt_unchecked(machine.decrypt_unchecked(text)));
189    ///	assert_eq!(text, machine.encrypt_unchecked(machine.encrypt_unchecked(text)));
190    ///	assert_eq!(text, machine.decrypt_unchecked(machine.encrypt_unchecked(text)));
191    ///	assert_eq!(text, machine.encrypt_unchecked(machine.decrypt_unchecked(text)));
192    /// ```
193    ///
194    /// # Parameters
195    /// - `text` - The ciphertext to decrypt
196    ///
197    /// # Returns
198    /// The decrypted plaintext.
199    ///
200    /// # Safety
201    /// This function may panic if the Enigma machine was constructed with invalid settings. If the machine was constructed
202    /// correctly, this is guarnateed to not panic, and will function identically to `EnigmaMachine::decrypt()`, with
203    /// marginally better performance. See the `Performance` section of the `README` for more information.
204    pub unsafe fn encrypt_unchecked(&self, text: &str) -> String {
205        self.decrypt_unchecked(text)
206    }
207}
208
209pub trait UncheckedEnigmaBuilder {
210    /// Sets the rotors for the machine.
211    ///
212    /// # Parameters
213    /// - `first` - The first rotor to use
214    /// - `second` - The second rotor to use
215    /// - `third` - The third rotor to use
216    ///
217    /// # Returns
218    /// The machine builder with the given ring settings applied.
219    ///
220    /// # Panics
221    /// If the given numbers are not all in `[1, 26]`, an error is returned.
222    fn rotors(self, first: u8, second: u8, third: u8) -> impl UncheckedEnigmaBuilder;
223
224    /// Sets the plugboard for the machine. The given plugboard should be a space-separated string of letter pairs. This is automatically
225    /// bidirectional, meaning the pair `AY` will map `A` to `Y` and also `Y` to `A`.
226    ///
227    /// # Parameters
228    /// - `plugboard` - A space-separated string of letter pairs, i.e., `AY BF QR UX GZ`.
229    ///
230    /// # Returns
231    /// The machine builder with the given plugboard applied.
232    ///
233    /// # Panics
234    /// If the given plugboard is not formatted correctly, this function may, but is not guarnateed
235    /// to, panic. If the given plugboard contains duplicates, this function will not panic, and
236    /// instead the later duplicate will overwrite the earlier one.
237    fn plugboard(self, plugboard: &str) -> impl UncheckedEnigmaBuilder;
238
239    /// Sets the reflector of the machine.
240    ///
241    /// # Parameters
242    /// - `reflector` - The reflector to give the machine.
243    ///
244    /// # Returns
245    /// The machine builder with the given ring settings applied.
246    ///
247    /// # Panics
248    /// If the given reflector string does not represent an existing reflector. **Note that for
249    /// unchecked machines such as this, the reflector given is case-sensitive.** This makes the
250    /// function more performant by not converting casing to allow case insensitivity. The given
251    /// reflector thus *must* be one of the following *exactly*:
252    ///
253    /// - `A`
254    /// - `B`
255    /// - `C`
256    /// - `BThin`
257    /// - `CThin`
258    /// - `UKWK`
259    /// - `UKWR`
260    fn reflector(self, reflector: &str) -> impl UncheckedEnigmaBuilder;
261
262    /// Sets the ring settings of the machine, unchecked.
263    ///
264    /// # Parameters
265    /// - `first` - The first ring setting, in `[1, 26]`.
266    /// - `second` - The second ring setting, in `[1, 26]`.
267    /// - `third` - The third ring setting, in `[1, 26]`.
268    ///
269    /// # Returns
270    /// The machine builder with the given ring settings applied.
271    ///
272    /// # Panics
273    /// Even if the given numbers are not all in `[1, 26]`, this function will not panic; The
274    /// machine will simply be created with invalid settings. The machine will most likely panic
275    /// during encryption/decryption, or possibly will just produce an incorrect output.
276    fn ring_settings(self, first: u8, second: u8, third: u8) -> impl UncheckedEnigmaBuilder;
277
278    /// Sets the "ring positions" or "rotor positions" of the machine, unchecked.
279    ///
280    /// # Parameters
281    /// - `first` - The offset of the first rotor, in `[1, 26]`.
282    /// - `second` - The offset of the second rotor, in `[1, 26]`.
283    /// - `third` - The offset of the third rotor, in `[1, 26]`.
284    ///
285    /// # Returns
286    /// The machine builder with the given rotor positions applied.
287    ///
288    /// # Errors
289    /// Even if the given numbers are not all in `[1, 26]`, this function will not panic; The
290    /// machine will simply be created with invalid settings. The machine will most likely panic
291    /// during encryption/decryption, or possibly will just produce an incorrect output.
292    fn ring_positions(self, first: u8, second: u8, third: u8) -> impl UncheckedEnigmaBuilder;
293
294    /// Finalizes building the Enigma machine. This is an absolutely zero-cost method that simply
295    /// converts the `impl UncheckedEnigmaBuilder` opaque type into the concrete `EnigmaMachine`
296    /// by just returning `self`.
297    fn build(self) -> UncheckedEnigmaMachine;
298}
299
300impl UncheckedEnigmaBuilder for UncheckedEnigmaMachine {
301    fn rotors(mut self, first: u8, second: u8, third: u8) -> impl UncheckedEnigmaBuilder {
302        self.rotors = (first, second, third).unchecked_into_rotors();
303        self
304    }
305
306    fn ring_positions(mut self, first: u8, second: u8, third: u8) -> impl UncheckedEnigmaBuilder {
307        self.ring_positions = (first - 1, second - 1, third - 1);
308        self
309    }
310
311    fn ring_settings(mut self, first: u8, second: u8, third: u8) -> impl UncheckedEnigmaBuilder {
312        self.ring_settings = (first - 1, second - 1, third - 1);
313        self
314    }
315
316    fn plugboard(mut self, plugboard: &str) -> impl UncheckedEnigmaBuilder {
317        let mappings = plugboard.split_whitespace();
318        let mut plugboard = std::collections::HashMap::new();
319        for pair in mappings {
320            let mut chars = pair.chars();
321            let first = chars.next().unwrap();
322            let second = chars.next().unwrap();
323            plugboard.insert(first, second);
324            plugboard.insert(second, first);
325        }
326
327        self.plugboard = plugboard;
328        self
329    }
330
331    fn reflector(mut self, reflector: &str) -> impl UncheckedEnigmaBuilder {
332        self.reflector = Reflector::unchecked_from(reflector).alphabet();
333        self
334    }
335
336    fn build(self) -> UncheckedEnigmaMachine {
337        self
338    }
339}