mini-enigma 0.3.0

#[no-std] (and no alloc) zero dependency implementation of the M3 Enigma
Documentation
use core::mem::MaybeUninit;

use super::utils::{Letter, DEFAULT_ROTOR_NUM};
use super::Rotor;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
/// Represents the configuration (i.e. ordering) of the `Rotor` set in an Enigma machine
pub struct Wheel<const ROTOR_NUM: usize = DEFAULT_ROTOR_NUM> {
    walzenlage: [Rotor; ROTOR_NUM],
    double_step: bool,
}

impl<const ROTOR_NUM: usize> Default for Wheel<ROTOR_NUM> {
    fn default() -> Self {
        Wheel {
            walzenlage: [Rotor::default(); ROTOR_NUM],
            double_step: false,
        }
    }
}

impl<const ROTOR_NUM: usize> Wheel<ROTOR_NUM> {
    /// Creates a new `Rotor` configuration in left-to-right ordering
    #[must_use]
    pub fn new(rotors: [Rotor; ROTOR_NUM]) -> Self {
        Wheel {
            walzenlage: rotors,
            double_step: false,
        }
    }

    /// Send a signal forward through the rotors
    pub fn forward(&mut self, letter: Letter) -> Letter {
        // If arbritrary wheels A, B, and C are arranged in the enigma left-to-right this means the output
        // from the Eintrittswalze (or entry stator wheel) enters C first, then B, then A before hitting the
        // reflector and flowing back through A, B and finally C, since walzenlage represents the left-to-right
        // ordering we must traverse this in the reverse order.
        self.walzenlage
            .iter()
            .rev()
            .fold(letter, |a, e| e.wiring_map(a))
    }

    fn simple_step(&mut self, from: usize) {
        self.walzenlage.iter_mut().take(from).rev().all(Rotor::step);
    }

    /// Step the rotors
    pub fn step(&mut self) {
        self.simple_step(ROTOR_NUM);
        if self.double_step {
            self.double_step = false;
            self.simple_step(ROTOR_NUM - 1);
        } else {
            let mut idx = 1;
            while idx < ROTOR_NUM - 1 && !self.walzenlage[idx].will_turn() {
                idx += 1;
            }
            if idx < ROTOR_NUM - 1 {
                self.double_step = true;
            }
        }
    }

    /// Send a signal backward through the rotors
    #[must_use]
    pub fn backward(&self, letter: Letter) -> Letter {
        self.walzenlage
            .iter()
            .fold(letter, |a, e| e.rev_wiring_map(a))
    }

    /// Set the positions of each `Rotor` in left-to-right ordering
    pub fn set_positions(&mut self, positions: [Letter; ROTOR_NUM]) {
        positions
            .iter()
            .zip(self.walzenlage.iter_mut())
            .for_each(|(pos, rotor)| rotor.set_grundstellung(*pos));
    }

    /// Set the wiring offset (aka ring setting) of each `Rotor` in left-to-right ordering
    pub fn set_ring_wiring_offset(&mut self, positions: [u8; ROTOR_NUM]) {
        positions
            .iter()
            .zip(self.walzenlage.iter_mut())
            .for_each(|(pos, rotor)| rotor.set_ringstellung(*pos));
    }

    /// Get current `Rotor` positions
    #[must_use]
    pub fn get_positions(&self) -> [Letter; ROTOR_NUM] {
        let mut ret: [MaybeUninit<Letter>; ROTOR_NUM] =
            unsafe { MaybeUninit::uninit().assume_init() };
        self.walzenlage
            .iter()
            .enumerate()
            .for_each(|(i, x)| ret[i] = MaybeUninit::new(x.get_grundstellung()));
        unsafe { core::mem::transmute_copy::<_, [Letter; ROTOR_NUM]>(&ret) }
    }
}

#[cfg(test)]
mod tests {
    use crate::components::{ROTOR_I, ROTOR_II, ROTOR_III};

    use super::*;

    #[test]
    fn test_single_step() {
        let mut wheel = Wheel::new([ROTOR_I, ROTOR_II, ROTOR_III]);
        wheel.set_positions([Letter::A, Letter::A, Letter::U]);
        assert_eq!([Letter::A, Letter::A, Letter::U], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::A, Letter::A, Letter::V], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::A, Letter::B, Letter::W], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::A, Letter::B, Letter::X], wheel.get_positions());
    }

    #[test]
    fn test_double_step() {
        let mut wheel = Wheel::new([ROTOR_I, ROTOR_II, ROTOR_III]);
        wheel.set_positions([Letter::A, Letter::D, Letter::U]);
        assert_eq!([Letter::A, Letter::D, Letter::U], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::A, Letter::D, Letter::V], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::A, Letter::E, Letter::W], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::B, Letter::F, Letter::X], wheel.get_positions());
        wheel.step();
        assert_eq!([Letter::B, Letter::F, Letter::Y], wheel.get_positions());
    }
}