mini-enigma 0.3.0

#[no-std] (and no alloc) zero dependency implementation of the M3 Enigma
Documentation
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());
    }
}