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}