ferric_crypto_lib 0.2.7

A library for Ferric Crypto
Documentation
//! # Utils
//! This module contains utility functions for the library, such as functions for converting strings to vectors of indices and vice versa.

pub mod matrix_functions;

use num_bigint::BigUint;
use std::fmt::Display;
// use num_bigint::{BigInt, BigUint};
use crate::error::CharacterParseError;
use crate::prelude::{ALPHABET, ALPHABET_LEN};
use num_integer::gcd;
// use num_traits::{One, Zero};
use rayon::prelude::*;

#[cfg(feature = "python-integration")]
use pyo3::prelude::*;
#[cfg(feature = "python-integration")]
use pyo3_helper_macros::py3_bind;
use rug::Integer;

const THRESHOLD: usize = 1000;

// users should not need to use this as its only used inside the EncodedString to know what decoding to use
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "python-integration", pyclass)]
pub enum StringType {
    Standard,
    Assymetric,
    Unknown,
}

#[cfg_attr(feature = "python-integration", py3_bind)]
impl Default for StringType {
    fn default() -> Self {
        Self::Unknown
    }
}

/// String wrapper struct for impl of encoding and decoding
#[derive(Default, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "python-integration", pyclass)]
pub struct BaseString {
    pub data: String,
}

#[derive(Default, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "python-integration", pyclass)]
pub struct EncodedString {
    pub data: Vec<usize>,
    str_type: StringType,
}

impl From<String> for BaseString {
    fn from(data: String) -> Self {
        Self { data }
    }
}

impl From<&str> for BaseString {
    fn from(data: &str) -> Self {
        Self {
            data: data.to_string(),
        }
    }
}

impl From<Vec<usize>> for EncodedString {
    fn from(data: Vec<usize>) -> Self {
        Self {
            data,
            str_type: StringType::Unknown,
        }
    }
}

impl From<BaseString> for String {
    fn from(data: BaseString) -> Self {
        data.data
    }
}

impl From<String> for EncodedString {
    fn from(data: String) -> Self {
        let mut numbers = Vec::new();
        let mut chars = data.chars().peekable();
        let mut string_type = StringType::Unknown; // Default to Unknown

        // Check the first character to determine the StringType
        if let Some(first_char) = chars.next() {
            string_type = match first_char {
                'S' => StringType::Standard,
                'A' => StringType::Assymetric,
                _ => StringType::Unknown,
            };
        }

        // Try to parse the first number which might be 1 or 2 digits
        if let Some(first_char) = chars.next() {
            let mut first_number_str = first_char.to_string();

            // Look ahead to see if the next character is part of the first number
            if first_char != '0' && chars.peek().map_or(false, |&c| c != '0') {
                if let Some(second_char) = chars.next() {
                    first_number_str.push(second_char);
                }
            }

            if let Ok(first_number) = first_number_str.parse::<usize>() {
                numbers.push(first_number);
            } else {
                numbers.push(*ALPHABET_LEN + 1); // For now we just push a number thats out of range to indicate an error in this position
            }
        }

        // Parse the rest of the numbers, which should be 2 digits long
        while chars.peek().is_some() {
            let mut number_str = String::new();
            number_str.push(chars.next().unwrap()); // safe due to peek above
            number_str.push(chars.next().unwrap_or('0')); // pad with zero if single digit

            if let Ok(number) = number_str.parse::<usize>() {
                numbers.push(number);
            } else {
                // Handle parsing error. For now, just push 0 or handle it as you see fit.
                numbers.push(0);
            }
        }

        // Create EncodedString with the parsed numbers
        EncodedString::new(numbers, string_type) // Assuming the StringType is Unknown here
    }
}

impl Display for BaseString {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.data)
    }
}

impl Display for EncodedString {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?} {:?}", self.data, self.str_type)
    }
}

/// Encodes a string to a vector of indices based on the alphabet.
///
/// # Arguments
///
/// * `t` - A `&str` that holds the text to be converted.
///
/// # Returns
///
/// * A `Vec<usize>` representing the indices of each character in the input string.
///
/// # Example
///
/// ```
/// # use ferric_crypto_lib::utils::encode_string;
///
/// let t = "abc";
/// println!("{:?}", encode_string(t));
/// ```
///
/// This will print `[0, 1, 2]`.
#[cfg_attr(feature = "python-integration", pyfunction)]
pub fn encode_string(t: &str) -> Result<Vec<usize>, CharacterParseError> {
    if t.len() > THRESHOLD {
        // Use parallel processing for larger inputs
        t.to_lowercase()
            .par_chars()
            .map(|x| {
                ALPHABET
                    .chars()
                    .position(|y| y == x)
                    .ok_or(CharacterParseError::InvalidCharacter(x))
            })
            .collect()
    } else {
        // Use sequential processing for smaller inputs
        t.to_lowercase()
            .chars()
            .map(|x| {
                ALPHABET
                    .chars()
                    .position(|y| y == x)
                    .ok_or(CharacterParseError::InvalidCharacter(x))
            })
            .collect()
    }
}

/// Decodes a vector of indices to a string based on the alphabet.
///
/// # Arguments
///
/// * `t` - A `Vec<usize>` that holds the indices to be converted.
///
/// # Returns
///
/// * A `String` representing the characters for each index in the input vector.
///
/// # Example
///
/// ```
/// # use ferric_crypto_lib::utils::decode_list;
///
/// let t = vec![0, 1, 2];
/// println!("{}", decode_list(t).unwrap());
/// ```
///
/// This will print `abc`.
#[cfg_attr(feature = "python-integration", pyfunction)]
pub fn decode_list(t: Vec<usize>) -> Result<String, CharacterParseError> {
    if t.len() > THRESHOLD {
        t.par_iter()
            .map(|&x| {
                ALPHABET
                    .chars()
                    .nth(x)
                    .ok_or(CharacterParseError::InvalidIndex(x))
            })
            .collect::<Result<String, _>>()
    } else {
        t.iter()
            .map(|&x| {
                ALPHABET
                    .chars()
                    .nth(x)
                    .ok_or(CharacterParseError::InvalidIndex(x))
            })
            .collect::<Result<String, _>>()
    }
}

/// Encodes a character to its corresponding index based on the alphabet.
///
/// # Arguments
///
/// * `t` - A `char` that holds the character to be converted.
///
/// # Returns
///
/// * A `Result<usize, ParseError>` representing the index of the character in the alphabet.
///   Returns `ParseError::InvalidCharacter(t)` if the character is not in the alphabet.
///
/// # Example
///
/// ```
/// # use ferric_crypto_lib::utils::encode_char;
///
/// let t = 'a';
/// println!("{}", encode_char(t).unwrap());
/// ```
///
/// This will print `0`.
#[cfg_attr(feature = "python-integration", pyfunction)]
pub fn encode_char(t: char) -> Result<usize, CharacterParseError> {
    ALPHABET
        .find(t.to_lowercase().next().unwrap())
        .ok_or(CharacterParseError::InvalidCharacter(t))
}

/// Decodes a single digit to its corresponding character based on the alphabet.
///
/// # Arguments
///
/// * `i` - A `usize` that holds the index to be converted.
///
/// # Returns
///
/// * A `Result<char, CharacterParseError>` representing the character in the alphabet at the given index.
///   Returns `CharacterParseError::InvalidIndex(i)` if the index is not in the alphabet.
///
/// # Example
///
/// ```
/// # use ferric_crypto_lib::utils::decode_digit;
///
/// let i = 0;
/// println!("{}", decode_digit(i).unwrap());
/// ```
///
/// This will print `a`.
#[cfg_attr(feature = "python-integration", pyfunction)]
pub fn decode_digit(i: usize) -> Result<char, CharacterParseError> {
    ALPHABET
        .chars()
        .nth(i)
        .ok_or(CharacterParseError::InvalidIndex(i))
}

#[cfg_attr(feature = "python-integration", pyfunction)]
pub fn num_gcd(a: usize, b: usize) -> usize {
    gcd(a, b)
}

/// if there is a way to do this with simillar performance using rug, please let me know
pub fn factorize(n: BigUint) -> Vec<String> {
    let factors = num_prime::nt_funcs::factorize(n);
    factors.iter().map(|x| format!("{:?}", x.0)).collect()
}

pub fn mod_add(a: usize, b: usize) -> usize {
    ((a + b) % *ALPHABET_LEN + *ALPHABET_LEN) % *ALPHABET_LEN // Ensure positive result within Z28
}

pub fn cyclic_left_shift(vec: &Vec<usize>, shift: usize) -> Vec<usize> {
    let effective_shift = shift % vec.len(); // Adjust shift to prevent over-rotation
    let mut shifted = vec.clone();
    shifted.rotate_left(effective_shift);
    shifted
}

#[cfg(feature = "python-integration")]
/// This function prints information about the library.
#[pyfunction]
pub fn print_info(py: Python) -> PyResult<()> {
    let code = r#"
import ferric_crypto_lib

# Get a list of all attributes in the ferric_crypto_lib module
attributes = dir(ferric_crypto_lib)

# Filter the list to include only functions and classes
functions_and_classes = [attr for attr in attributes if callable(getattr(ferric_crypto_lib, attr))]

# Print each function and class
for item in functions_and_classes:
    print(item)
    actual_class = getattr(ferric_crypto_lib, item)

    if isinstance(actual_class, type):
        item_attributes = dir(actual_class)
        item_functions_and_classes = [attr for attr in item_attributes if callable(getattr(actual_class, attr)) and not attr.startswith('_')]
        if len(item_functions_and_classes) > 0:
            print(f'\t{item} member functions:')
            for class_item in item_functions_and_classes:
                print(f'\t\t{class_item}')
        else:
            print(f'\t\tNo member functions')
    "#;

    py.run(code, None, None)?;
    Ok(())
}

mod encoding;
#[cfg(test)]
mod test;

#[cfg(feature = "python-integration")]
pub mod python_integration;