cryptan 0.4.2

A simple crypto playground (Caesar, Morse, ...).
Documentation
pub use crate::{normalize_shift, load_set, meaningful_ratio, ClassicalCipher, BruteForce, DecodedResult};
use log::{warn, info};

pub struct CaesarCipher {
    key: i8,
}

impl CaesarCipher {
    pub fn new() -> Self {
        Self {
            key: 0,
        }
    }

    pub fn from_key(key: i8) -> Self {
        Self {
            key,
        }
    }

    pub fn set_key(&mut self, key: i8) {
        self.key = key;
    }
}

impl ClassicalCipher for CaesarCipher {
    fn encrypt(&self, input: &str) -> String {
        if self.key == 0 {
            warn!("Key is 0, no encryption performed.");
            return input.to_string();
        }

        let shift = normalize_shift(self.key);
        let mut out = String::with_capacity(input.len());

        for &b in input.as_bytes() {
            let new_b = if b.is_ascii_uppercase() {
                ((b - b'A' + shift) % 26) + b'A'
            } else if b.is_ascii_lowercase() {
                ((b - b'a' + shift) % 26) + b'a'
            } else {
                b
            };
            out.push(new_b as char);
        }

        out
    }

    fn decrypt(&self, input: &str) -> String {
        if self.key == 0 {
            warn!("Key is 0, no decryption performed.");
            return input.to_string();
        }

        let shift = normalize_shift(-self.key);
        let mut out = String::with_capacity(input.len());

        for &b in input.as_bytes() {
            let new_b = if b.is_ascii_uppercase() {
                ((b - b'A' + shift) % 26) + b'A'
            } else if b.is_ascii_lowercase() {
                ((b - b'a' + shift) % 26) + b'a'
            } else {
                b
            };
            out.push(new_b as char);
        }

        out
    }
}

impl BruteForce for CaesarCipher {
    fn bruteforce(&mut self, input: &str, threshold: Option<f32>) -> Vec<DecodedResult> {
        let mut results: Vec<DecodedResult> = Vec::new();

        let mut threshold_value: f32;

        if threshold.is_none() {
            info!("No threshold provided, returning all candidates.");
            threshold_value = 0.0;
        }
        else {
            threshold_value = threshold.unwrap();
            if threshold_value < 0.0 || threshold_value > 1.0 {
                warn!("Threshold {} out of range [0.0, 1.0], defaulting to 0.0", threshold_value);
                threshold_value = 0.0;
            }
        }
        let wordslist = load_set("public/words.txt");
        for key in 0..26 {
            self.set_key(key as i8);
            let decrypted_text = self.decrypt(input);
            let ratio = match meaningful_ratio(&decrypted_text, &wordslist) {
                Ok(r) => Some(r),
                Err(_) => None,
            };
            if ratio.is_none() {
                warn!("Meaningful ratio computation failed, possibly due to missing word frequency data.");
            }
            else if ratio.unwrap_or(0.0) < threshold_value {
                continue;
            }
            let result = DecodedResult {
                text: decrypted_text,
                key: key as u8,
                meaningful_ratio: ratio,
            };
            results.push(result);
        }

        results
    }
}