mini-enigma 0.3.0

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

use mini_enigma::components::{
    Plugboard, Reflector, Rotor, REFLECTOR_A, REFLECTOR_B, REFLECTOR_B_THIN, REFLECTOR_C,
    REFLECTOR_C_THIN, ROTOR_BETA, ROTOR_GAMMA, ROTOR_I, ROTOR_II, ROTOR_III, ROTOR_IV, ROTOR_V,
    ROTOR_VI, ROTOR_VII, ROTOR_VIII,
};
use mini_enigma::state_machine::EnigmaSettings;
use mini_enigma::utils::Letter;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RingstellungJSON {
    Int(Vec<u8>),
    Str(Vec<char>),
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EnigmaSettingsJSON {
    pub name: String,
    pub date: Option<u32>,
    pub rotors: Vec<String>,
    pub reflector: String,
    pub ringstellung: RingstellungJSON,
    pub grundstellung: Vec<char>,
    pub plugboard: Vec<String>,
    pub ciphertext: String,
    pub plaintext: String,
}

fn get_rotor(name: String) -> Option<Rotor> {
    match name.as_str() {
        "I" => Some(ROTOR_I),
        "II" => Some(ROTOR_II),
        "III" => Some(ROTOR_III),
        "IV" => Some(ROTOR_IV),
        "V" => Some(ROTOR_V),
        "VI" => Some(ROTOR_VI),
        "VII" => Some(ROTOR_VII),
        "VIII" => Some(ROTOR_VIII),
        "beta" => Some(ROTOR_BETA),
        "gamma" => Some(ROTOR_GAMMA),
        _ => None,
    }
}

fn get_reflector(name: String) -> Option<Reflector> {
    match name.as_str() {
        "A" => Some(REFLECTOR_A),
        "B" => Some(REFLECTOR_B),
        "C" => Some(REFLECTOR_C),
        "B Thin" => Some(REFLECTOR_B_THIN),
        "C Thin" => Some(REFLECTOR_C_THIN),
        _ => None,
    }
}

pub fn load_enigma_settings<P>(file_path: P) -> EnigmaSettingsJSON
where
    P: AsRef<Path> + std::fmt::Display + Copy,
{
    serde_json::from_str(
        &std::fs::read_to_string(file_path).expect(&format!("File at {file_path} not found")),
    )
    .expect(&format!("Could not parse file {file_path}"))
}

impl<const ROTOR_NUM: usize> From<EnigmaSettingsJSON> for EnigmaSettings<ROTOR_NUM> {
    fn from(value: EnigmaSettingsJSON) -> Self {
        let mut rotors = [ROTOR_I; ROTOR_NUM];
        for (i, name) in value.rotors.iter().enumerate() {
            rotors[i] =
                get_rotor(name.clone()).expect(format!("Invalid rotor name: {name}").as_str());
        }
        let reflector = get_reflector(value.reflector.clone())
            .expect(format!("Invalid reflector name: {}", value.reflector).as_str());
        let ringstellung: [u8; ROTOR_NUM] = {
            let mut rs = [0u8; ROTOR_NUM];
            match value.ringstellung {
                RingstellungJSON::Int(x) => {
                    for (i, &ring) in x.iter().enumerate() {
                        rs[i] = ring
                    }
                }
                RingstellungJSON::Str(x) => {
                    for (i, &ring) in x.iter().enumerate() {
                        rs[i] = ring as u8 - 64
                    }
                }
            };
            rs
        };
        let grundstellung = {
            let mut gs = [Letter::A; ROTOR_NUM];
            for (i, &ring) in value.grundstellung.iter().enumerate() {
                gs[i] = Letter::try_from(ring).unwrap();
            }
            gs
        };
        let plugboard = {
            let pairs = value
                .plugboard
                .iter()
                .map(|x| {
                    (
                        Letter::try_from(x.as_bytes()[0] as char).unwrap(),
                        Letter::try_from(x.as_bytes()[1] as char).unwrap(),
                    )
                })
                .collect::<Vec<(Letter, Letter)>>();
            Plugboard::new(&pairs)
        };
        EnigmaSettings {
            rotors,
            plugboard,
            reflector,
            ringstellung,
            grundstellung,
        }
    }
}