mini_enigma/components/
rotor.rs

1use crate::utils::UNIT_LETTER_MAP;
2
3use super::utils::{string_to_inv_letter_map, string_to_letter_map, Letter, LetterMapping};
4
5const MAX_NOTCHES: usize = 2;
6
7#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
8/// The Engima Rotor or Walzen
9pub struct Rotor {
10    ringstellung: u8,
11    step: Letter,
12    wiring: LetterMapping,
13    reverse_wiring: LetterMapping,
14    notches: [Letter; MAX_NOTCHES],
15    fixed: bool,
16}
17
18impl Default for Rotor {
19    fn default() -> Self {
20        Rotor {
21            ringstellung: u8::default(),
22            step: Letter::default(),
23            wiring: UNIT_LETTER_MAP,
24            reverse_wiring: UNIT_LETTER_MAP,
25            notches: [Letter::default(); MAX_NOTCHES],
26            fixed: false,
27        }
28    }
29}
30
31impl Rotor {
32    /// Create a `Rotor` from a string
33    ///
34    /// If the `Rotor` has a single notch then the `notches` array should be set to that value for all elements
35    #[must_use]
36    pub const fn from_static_str(
37        string: &'static str,
38        notches: [Letter; MAX_NOTCHES],
39        fixed: bool,
40    ) -> Rotor {
41        Rotor {
42            wiring: string_to_letter_map(string),
43            reverse_wiring: string_to_inv_letter_map(string),
44            step: Letter::A,
45            ringstellung: 0,
46            notches,
47            fixed,
48        }
49    }
50
51    /// Set the rotors initial positions
52    pub fn set_grundstellung(&mut self, letter: Letter) {
53        self.step = letter;
54    }
55
56    /// Set the rotors ring settings
57    ///
58    /// # Panics
59    ///
60    /// ringstellung is 0 or lower
61    pub fn set_ringstellung(&mut self, ringstellung: u8) {
62        assert!(ringstellung > 0);
63        self.ringstellung = ringstellung - 1;
64    }
65
66    /// Send a signal to the right side of the rotor at position `index`
67    /// and return the position the signal exits the rotor
68    #[must_use]
69    pub fn wiring_map(&self, index: Letter) -> Letter {
70        self.wiring[index - self.ringstellung + self.step] + self.ringstellung - self.step
71    }
72
73    /// Send a signal to the left side of the rotor at position `index`
74    /// and return the position the signal exits the rotor
75    #[must_use]
76    pub fn rev_wiring_map(&self, index: Letter) -> Letter {
77        self.reverse_wiring[index - self.ringstellung + self.step] + self.ringstellung - self.step
78    }
79
80    /// Step the rotor and return whether to turnover the next rotor
81    pub fn step(&mut self) -> bool {
82        if self.fixed {
83            false
84        } else {
85            self.step += 1;
86            self.notches.iter().any(|&x| x == self.step)
87        }
88    }
89
90    /// Get current position
91    #[must_use]
92    pub fn get_grundstellung(&self) -> Letter {
93        self.step
94    }
95
96    /// Returns whether the rotor will cause a turnover on the next step
97    ///
98    /// Required for doublestepping
99    #[must_use]
100    pub fn will_turn(&self) -> bool {
101        self.notches.iter().any(|&x| x == self.step + 1)
102    }
103}
104
105#[allow(missing_docs)]
106pub const ROTOR_I: Rotor =
107    Rotor::from_static_str("EKMFLGDQVZNTOWYHXUSPAIBRCJ", [Letter::R, Letter::R], false);
108
109#[allow(missing_docs)]
110pub const ROTOR_II: Rotor =
111    Rotor::from_static_str("AJDKSIRUXBLHWTMCQGZNPYFVOE", [Letter::F, Letter::F], false);
112
113#[allow(missing_docs)]
114pub const ROTOR_III: Rotor =
115    Rotor::from_static_str("BDFHJLCPRTXVZNYEIWGAKMUSQO", [Letter::W, Letter::W], false);
116
117#[allow(missing_docs)]
118pub const ROTOR_IV: Rotor =
119    Rotor::from_static_str("ESOVPZJAYQUIRHXLNFTGKDCMWB", [Letter::K, Letter::K], false);
120
121#[allow(missing_docs)]
122pub const ROTOR_V: Rotor =
123    Rotor::from_static_str("VZBRGITYUPSDNHLXAWMJQOFECK", [Letter::A, Letter::A], false);
124
125#[allow(missing_docs)]
126pub const ROTOR_VI: Rotor =
127    Rotor::from_static_str("JPGVOUMFYQBENHZRDKASXLICTW", [Letter::A, Letter::N], false);
128
129#[allow(missing_docs)]
130pub const ROTOR_VII: Rotor =
131    Rotor::from_static_str("NZJHGRCXMYSWBOUFAIVLPEKQDT", [Letter::A, Letter::N], false);
132
133#[allow(missing_docs)]
134pub const ROTOR_VIII: Rotor =
135    Rotor::from_static_str("FKQHTLXOCBJSPDZRAMEWNIUYGV", [Letter::A, Letter::N], false);
136
137#[allow(missing_docs)]
138pub const ROTOR_BETA: Rotor =
139    Rotor::from_static_str("LEYJVCNIXWPBQMDRTAKZGFUHOS", [Letter::A, Letter::A], true);
140
141#[allow(missing_docs)]
142pub const ROTOR_GAMMA: Rotor =
143    Rotor::from_static_str("FSOKANUERHMBTIYCWLQPZXVGJD", [Letter::A, Letter::A], true);
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_get_at() {
151        let rotor =
152            Rotor::from_static_str("ZYXWVUTSRQPONMLKJIHGFEDCBA", [Letter::A, Letter::A], false);
153        assert_eq!(rotor.wiring_map(Letter::A), Letter::Z);
154        assert_eq!(rotor.wiring_map(Letter::C), Letter::X);
155        assert_eq!(rotor.wiring_map(Letter::Z), Letter::A);
156    }
157
158    #[test]
159    fn test_offset() {
160        let mut rotor = ROTOR_I;
161        rotor.set_grundstellung(Letter::B);
162        assert_eq!(rotor.wiring_map(Letter::A), Letter::J)
163    }
164
165    #[test]
166    fn test_ringstellung() {
167        let mut rotor = ROTOR_I;
168        rotor.set_grundstellung(Letter::A);
169        rotor.set_ringstellung(2);
170        assert_eq!(rotor.wiring_map(Letter::A), Letter::K)
171    }
172
173    #[test]
174    fn test_reverse() {
175        let rotor =
176            Rotor::from_static_str("NMLKJIHGFEDCBAZYXWVUTSRQPO", [Letter::A, Letter::A], false);
177        let forward = rotor.wiring_map(Letter::C);
178        assert_eq!(rotor.rev_wiring_map(forward), Letter::C);
179    }
180
181    #[test]
182    fn test_step() {
183        let mut rotor =
184            Rotor::from_static_str("NMLKJIHGFEDCBAZYXWVUTSRQPO", [Letter::C, Letter::C], false);
185        assert!(!rotor.step());
186        assert!(rotor.step());
187        assert!(!rotor.step());
188    }
189
190    #[test]
191    fn test_get_pos() {
192        let mut rotor =
193            Rotor::from_static_str("NMLKJIHGFEDCBAZYXWVUTSRQPO", [Letter::C, Letter::C], false);
194        rotor.set_grundstellung(Letter::G);
195        assert_eq!(Letter::G, rotor.get_grundstellung());
196    }
197}