ferric_crypto_lib 0.2.7

A library for Ferric Crypto
Documentation
use crate::prelude::ALPHABET;
use crate::Traits::{BruteForce, Decrypt, Encrypt};
#[cfg(feature = "python-integration")]
use pyo3::pyclass;
use rand::prelude::SliceRandom;
use std::collections::HashMap;

/// Represents errors that can occur during the operation of the Monosubstitution cipher.
///
/// # Variants
///
/// * `InvalidChar` - Represents an error that occurs when an invalid character is encountered.
#[derive(Debug, PartialEq)]
pub enum MonoError {
    InvalidCharacter(char),
}

/// Represents a Monosubstitution cipher.
///
/// A Monosubstitution cipher is a type of substitution cipher where each character in the plaintext is replaced by a different character in the ciphertext.
///
/// # Fields
///
/// * `dict` - A HashMap where each key-value pair represents a character mapping for the substitution cipher.
#[derive(Default)]
#[cfg_attr(feature = "python-integration", pyclass(get_all))]
pub struct Monosubstitution {
    pub dict: HashMap<char, char>,
}

impl From<HashMap<char, char>> for Monosubstitution {
    fn from(dict: HashMap<char, char>) -> Self {
        Self { dict }
    }
}

#[cfg(not(feature = "python-integration"))]
impl Monosubstitution {
    /// Creates a new instance of Monosubstitution with a a randomly generated dictionary.
    ///
    /// The default dictionary is generated by the `generate_dictionary` function.
    ///
    /// # Returns
    ///
    /// * An instance of Monosubstitution with a default dictionary.
    pub fn new() -> Self {
        Self {
            dict: Self::generate_dictionary(),
        }
    }

    /// Generates a dictionary for encryption.
    ///
    /// The function generates a dictionary where each character in the alphabet is mapped to another character in the alphabet.
    /// The mapping is random and is generated each time the function is called.
    ///
    /// # Returns
    ///
    /// * A HashMap that represents the dictionary for encryption.
    pub fn generate_dictionary() -> HashMap<char, char> {
        let plaintext_chars = ALPHABET.chars().collect::<Vec<_>>();
        let mut encrypted_chars = plaintext_chars.clone();
        encrypted_chars.shuffle(&mut rand::thread_rng());

        let mut dictionary = HashMap::new();
        for (plain, encrypted) in plaintext_chars.iter().zip(encrypted_chars.iter()) {
            dictionary.insert(*plain, *encrypted);
        }

        dictionary
    }
}

#[cfg(feature = "python-integration")]
mod python_integration {
    use super::*;
    use pyo3::{prelude::*, pyclass, pymethods, PyResult};
    use std::collections::HashMap;

    #[pymethods]
    impl Monosubstitution {
        /// Creates a new instance of Monosubstitution from a given dictionary.
        ///
        /// # Arguments
        ///
        /// * `dict` - A HashMap where each key-value pair represents a character mapping for the substitution cipher.
        ///
        /// # Returns
        ///
        /// * An instance of Monosubstitution with the provided dictionary.
        //#[pyo3(signature=(dict: HashMap<char, char>))]
        #[new]
        pub fn new_from(dict: HashMap<char, char>) -> Self {
            Self { dict }
        }

        /// Creates a new instance of Monosubstitution with a a randomly generated dictionary.
        ///
        /// The default dictionary is generated by the `generate_dictionary` function.
        ///
        /// # Returns
        ///
        /// * An instance of Monosubstitution with a default dictionary.
        //#[new]
        #[staticmethod]
        pub fn new() -> Self {
            Self {
                dict: Self::generate_dictionary(),
            }
        }

        /// Generates a dictionary for encryption.
        ///
        /// The function generates a dictionary where each character in the alphabet is mapped to another character in the alphabet.
        /// The mapping is random and is generated each time the function is called.
        ///
        /// # Returns
        ///
        /// * A HashMap that represents the dictionary for encryption.
        #[staticmethod]
        pub fn generate_dictionary() -> HashMap<char, char> {
            let plaintext_chars = ALPHABET.chars().collect::<Vec<_>>();
            let mut encrypted_chars = plaintext_chars.clone();
            encrypted_chars.shuffle(&mut rand::thread_rng());

            let mut dictionary = HashMap::new();
            for (plain, encrypted) in plaintext_chars.iter().zip(encrypted_chars.iter()) {
                dictionary.insert(*plain, *encrypted);
            }

            dictionary
        }

        pub fn encrypt(&self, input: String) -> PyResult<String> {
            match Encrypt::encrypt(self, input) {
                Ok(s) => Ok(s),
                Err(e) => Err(pyo3::exceptions::PyException::new_err(format!("{:?}", e))),
            }
        }

        pub fn decrypt(&self, input: String) -> PyResult<String> {
            match Decrypt::decrypt(self, input) {
                Ok(s) => Ok(s),
                Err(e) => Err(pyo3::exceptions::PyException::new_err(format!("{:?}", e))),
            }
        }

        // Define Python specific methods here, static methods require the pyo3 decorator `#[staticmethod]`
    }
}

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

    #[test]
    fn generates_dictionary_with_correct_size() {
        let dict = Monosubstitution::generate_dictionary();
        let alphabet_chars = ALPHABET.chars().count();
        assert_eq!(dict.len(), alphabet_chars);
    }

    #[test]
    fn generates_dictionary_with_unique_values() {
        let dict = Monosubstitution::generate_dictionary();
        let unique_values: std::collections::HashSet<_> = dict.values().collect();
        let alphabet_chars = ALPHABET.chars().count();
        assert_eq!(unique_values.len(), alphabet_chars);
    }

    #[test]
    fn generates_dictionary_with_alphabet_keys() {
        let dict = Monosubstitution::generate_dictionary();
        let keys: std::collections::HashSet<_> = dict.keys().cloned().collect();
        let alphabet_set: std::collections::HashSet<_> = ALPHABET.chars().collect();
        assert_eq!(keys, alphabet_set);
    }

    #[test]
    fn generates_dictionary_with_alphabet_values() {
        let dict = Monosubstitution::generate_dictionary();
        let values: std::collections::HashSet<_> = dict.values().cloned().collect();
        let alphabet_set: std::collections::HashSet<_> = ALPHABET.chars().collect();
        assert_eq!(values, alphabet_set);
    }
}