1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
use crate::utils::UNIT_LETTER_MAP;

use super::utils::{string_to_inv_letter_map, string_to_letter_map, Letter, LetterMapping};

const MAX_NOTCHES: usize = 2;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
/// The Engima Rotor or Walzen
pub struct Rotor {
    ringstellung: u8,
    step: Letter,
    wiring: LetterMapping,
    reverse_wiring: LetterMapping,
    notches: [Letter; MAX_NOTCHES],
    fixed: bool,
}

impl Default for Rotor {
    fn default() -> Self {
        Rotor {
            ringstellung: u8::default(),
            step: Letter::default(),
            wiring: UNIT_LETTER_MAP,
            reverse_wiring: UNIT_LETTER_MAP,
            notches: [Letter::default(); MAX_NOTCHES],
            fixed: false,
        }
    }
}

impl Rotor {
    /// Create a `Rotor` from a string
    ///
    /// If the `Rotor` has a single notch then the `notches` array should be set to that value for all elements
    #[must_use]
    pub const fn from_static_str(
        string: &'static str,
        notches: [Letter; MAX_NOTCHES],
        fixed: bool,
    ) -> Rotor {
        Rotor {
            wiring: string_to_letter_map(string),
            reverse_wiring: string_to_inv_letter_map(string),
            step: Letter::A,
            ringstellung: 0,
            notches,
            fixed,
        }
    }

    /// Set the rotors initial positions
    pub fn set_grundstellung(&mut self, letter: Letter) {
        self.step = letter;
    }

    /// Set the rotors ring settings
    ///
    /// # Panics
    ///
    /// ringstellung is 0 or lower
    pub fn set_ringstellung(&mut self, ringstellung: u8) {
        assert!(ringstellung > 0);
        self.ringstellung = ringstellung - 1;
    }

    /// Send a signal to the right side of the rotor at position `index`
    /// and return the position the signal exits the rotor
    #[must_use]
    pub fn wiring_map(&self, index: Letter) -> Letter {
        self.wiring[index - self.ringstellung + self.step] + self.ringstellung - self.step
    }

    /// Send a signal to the left side of the rotor at position `index`
    /// and return the position the signal exits the rotor
    #[must_use]
    pub fn rev_wiring_map(&self, index: Letter) -> Letter {
        self.reverse_wiring[index - self.ringstellung + self.step] + self.ringstellung - self.step
    }

    /// Step the rotor and return whether to turnover the next rotor
    pub fn step(&mut self) -> bool {
        if self.fixed {
            false
        } else {
            self.step += 1;
            self.notches.iter().any(|&x| x == self.step)
        }
    }

    /// Get current position
    #[must_use]
    pub fn get_grundstellung(&self) -> Letter {
        self.step
    }

    /// Returns whether the rotor will cause a turnover on the next step
    ///
    /// Required for doublestepping
    #[must_use]
    pub fn will_turn(&self) -> bool {
        self.notches.iter().any(|&x| x == self.step + 1)
    }
}

#[allow(missing_docs)]
pub const ROTOR_I: Rotor =
    Rotor::from_static_str("EKMFLGDQVZNTOWYHXUSPAIBRCJ", [Letter::R, Letter::R], false);

#[allow(missing_docs)]
pub const ROTOR_II: Rotor =
    Rotor::from_static_str("AJDKSIRUXBLHWTMCQGZNPYFVOE", [Letter::F, Letter::F], false);

#[allow(missing_docs)]
pub const ROTOR_III: Rotor =
    Rotor::from_static_str("BDFHJLCPRTXVZNYEIWGAKMUSQO", [Letter::W, Letter::W], false);

#[allow(missing_docs)]
pub const ROTOR_IV: Rotor =
    Rotor::from_static_str("ESOVPZJAYQUIRHXLNFTGKDCMWB", [Letter::K, Letter::K], false);

#[allow(missing_docs)]
pub const ROTOR_V: Rotor =
    Rotor::from_static_str("VZBRGITYUPSDNHLXAWMJQOFECK", [Letter::A, Letter::A], false);

#[allow(missing_docs)]
pub const ROTOR_VI: Rotor =
    Rotor::from_static_str("JPGVOUMFYQBENHZRDKASXLICTW", [Letter::A, Letter::N], false);

#[allow(missing_docs)]
pub const ROTOR_VII: Rotor =
    Rotor::from_static_str("NZJHGRCXMYSWBOUFAIVLPEKQDT", [Letter::A, Letter::N], false);

#[allow(missing_docs)]
pub const ROTOR_VIII: Rotor =
    Rotor::from_static_str("FKQHTLXOCBJSPDZRAMEWNIUYGV", [Letter::A, Letter::N], false);

#[allow(missing_docs)]
pub const ROTOR_BETA: Rotor =
    Rotor::from_static_str("LEYJVCNIXWPBQMDRTAKZGFUHOS", [Letter::A, Letter::A], true);

#[allow(missing_docs)]
pub const ROTOR_GAMMA: Rotor =
    Rotor::from_static_str("FSOKANUERHMBTIYCWLQPZXVGJD", [Letter::A, Letter::A], true);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_at() {
        let rotor =
            Rotor::from_static_str("ZYXWVUTSRQPONMLKJIHGFEDCBA", [Letter::A, Letter::A], false);
        assert_eq!(rotor.wiring_map(Letter::A), Letter::Z);
        assert_eq!(rotor.wiring_map(Letter::C), Letter::X);
        assert_eq!(rotor.wiring_map(Letter::Z), Letter::A);
    }

    #[test]
    fn test_offset() {
        let mut rotor = ROTOR_I;
        rotor.set_grundstellung(Letter::B);
        assert_eq!(rotor.wiring_map(Letter::A), Letter::J)
    }

    #[test]
    fn test_ringstellung() {
        let mut rotor = ROTOR_I;
        rotor.set_grundstellung(Letter::A);
        rotor.set_ringstellung(2);
        assert_eq!(rotor.wiring_map(Letter::A), Letter::K)
    }

    #[test]
    fn test_reverse() {
        let rotor =
            Rotor::from_static_str("NMLKJIHGFEDCBAZYXWVUTSRQPO", [Letter::A, Letter::A], false);
        let forward = rotor.wiring_map(Letter::C);
        assert_eq!(rotor.rev_wiring_map(forward), Letter::C);
    }

    #[test]
    fn test_step() {
        let mut rotor =
            Rotor::from_static_str("NMLKJIHGFEDCBAZYXWVUTSRQPO", [Letter::C, Letter::C], false);
        assert!(!rotor.step());
        assert!(rotor.step());
        assert!(!rotor.step());
    }

    #[test]
    fn test_get_pos() {
        let mut rotor =
            Rotor::from_static_str("NMLKJIHGFEDCBAZYXWVUTSRQPO", [Letter::C, Letter::C], false);
        rotor.set_grundstellung(Letter::G);
        assert_eq!(Letter::G, rotor.get_grundstellung());
    }
}