encodex 0.2.0

Implementation of and cryptanalysis tool for legacy and modern codes, ciphers and hashes.
Documentation
// Copyright (C) 2023  Fabian Moos
// This file is part of encodex.
//
// encodex is free software: you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// encodex is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with encodex. If not,
// see <https://www.gnu.org/licenses/>.
//

//! Functionality for handling code algorithms.

use std::collections::linked_list::LinkedList;

use crate::{
    action::{
        CryptographicAction,
        Direction,
    },
    code::Code,
    ui::application::{
        ApplicationError,
        OPTION_CODE,
    },
};
#[cfg(any(feature = "base16", feature = "hex"))]
use crate::code::base_encoding::Base16Context;
#[cfg(feature = "base32")]
use crate::code::base_encoding::Base32Context;
#[cfg(feature = "base32hex")]
use crate::code::base_encoding::Base32HexContext;
#[cfg(feature = "base64")]
use crate::code::base_encoding::Base64Context;
#[cfg(feature = "base64url")]
use crate::code::base_encoding::Base64UrlContext;



/// The command line argument for the `Base16` code.
#[cfg(any(
feature = "base16",
feature = "hex"
))]
const ALGORITHM_BASE16: &str = "BASE16";
/// The command line argument for the `Base32` code.
#[cfg(feature = "base32")]
const ALGORITHM_BASE32: &str = "BASE32";
/// The command line argument for the `Base32Hex` code.
#[cfg(feature = "base32hex")]
const ALGORITHM_BASE32HEX: &str = "BASE32HEX";
/// The command line argument for the `Base64` code.
#[cfg(feature = "base64")]
const ALGORITHM_BASE64: &str = "BASE64";
/// The command line argument for the `Base64Url` code.
#[cfg(feature = "base64url")]
const ALGORITHM_BASE64URL: &str = "BASE64URL";
/// An additional command line argument for the `Base16` code.
#[cfg(any(
feature = "base16",
feature = "hex"
))]
const ALGORITHM_HEX: &str = "HEX";
/// The command line argument for enabling decoding. Can not be used together with the
/// [encode](OPTION_ENCODE) argument for the same code argument.
const OPTION_DECODE: &str = "decode";
/// The command line argument for enabling encoding. Can not be used together with the
/// [decode](OPTION_DECODE) argument for the same code argument.
const OPTION_ENCODE: &str = "encode";
/// The command line argument for enabling lowercase output. Can not be used together with the
/// [uppercase](OPTION_UPPERCASE) argument for the same code argument.
const OPTION_LOWERCASE: &str = "lowercase";
/// The command line argument for enabling uppercase output. Can not be used together with the
/// [lowercase](OPTION_LOWERCASE) argument for the same coder argument.
const OPTION_UPPERCASE: &str = "uppercase";



/// Helper function for creating new [`code`](crate::code) contexts.
///
/// Every new code that gets added to the crate must be added here to be accessible via the command
/// line argument.
#[inline]
fn create_new_code_context(
    arguments: Option<&String>
) -> Result<(Box<dyn Code>, String), ApplicationError> {
    let arguments = if let Some(arg) = arguments {
        arg
    } else {
        let mut description = "Missing argument for ".to_string();
        description.push_str(OPTION_CODE);
        description.push_str("!");
        return Err(ApplicationError::MissingArgument(
            description, "create_new_code_context()".to_string(),
        ));
    };

    let arguments = arguments
        .split(":")
        .collect::<Vec<&str>>();

    if arguments.len() == 1 {
        let mut description = "For algorithm class ".to_string();
        description.push_str(OPTION_CODE);
        description.push_str("!");
        Err(ApplicationError::MissingArgument(
            description, "create_new_code_context()".to_string(),
        ))
    } else if arguments.len() > 2 {
        let mut description = "For algorithm class ".to_string();
        description.push_str(OPTION_CODE);
        description.push_str("!");
        Err(ApplicationError::TooManyArguments(
            description, "create_new_code_context()".to_string(),
        ))
    } else {
        let ctx: Box<dyn Code> = match arguments[0] {
            #[cfg(any(feature = "base16", feature = "hex"))]
            algorithm if algorithm == ALGORITHM_BASE16 || algorithm == ALGORITHM_HEX => {
                Box::from(Base16Context::new())
            }
            #[cfg(feature = "base32")]
            algorithm if algorithm == ALGORITHM_BASE32 => {
                Box::from(Base32Context::new())
            }
            #[cfg(feature = "base32hex")]
            algorithm if algorithm == ALGORITHM_BASE32HEX => {
                Box::from(Base32HexContext::new())
            }
            #[cfg(feature = "base64")]
            algorithm if algorithm == ALGORITHM_BASE64 => {
                Box::from(Base64Context::new())
            }
            #[cfg(feature = "base64url")]
            algorithm if algorithm == ALGORITHM_BASE64URL => {
                Box::from(Base64UrlContext::new())
            }
            algorithm => {
                let mut description = "Algorithm ".to_string();
                description.push_str(algorithm);
                description.push_str(" unknown!");
                return Err(ApplicationError::WrongCommandLineArgument(
                    description,
                    "create_new_code_context()".to_string(),
                ));
            }
        };
        Ok((ctx, arguments[1].to_string()))
    }
}

/// Helper function for handling all command line arguments for codes.
///
/// If new arguments are added for codes they must be added here to be accessible for all codes
/// that have been attached to the command line interface.
pub(crate) fn handle_code_option_arguments(
    arguments: Option<&String>,
    current_action: &mut Option<CryptographicAction>,
) -> Result<(), ApplicationError> {
    let (mut ctx, arguments) = match create_new_code_context(
        arguments
    ) {
        Ok(ctx) => { ctx }
        Err(error) => { return Err(error); }
    };

    let mut arguments = split_code_algorithm_arguments(&arguments);
    let mut direction = None;
    let mut maybe_arg = arguments.pop_front();
    let mut case_argument_encountered = false;
    while maybe_arg != None {
        let code_arg = maybe_arg.unwrap();
        match code_arg {
            arg if arg == OPTION_DECODE || arg == OPTION_ENCODE => {
                if direction.is_none() {
                    direction = Some(
                        if arg == OPTION_DECODE {
                            Direction::ToPlain
                        } else {
                            Direction::ToCipher
                        }
                    );
                } else {
                    return Err(ApplicationError::TooManyArguments(
                        "Only exactly one of \"encode\" and \"decode\" may be \
                               supplied!".to_string(),
                        "handle_code_option_arguments()".to_string(),
                    ));
                }
            }
            arg if arg == OPTION_LOWERCASE || arg == OPTION_UPPERCASE => {
                if case_argument_encountered {
                    return Err(ApplicationError::TooManyArguments(
                        "Only exactly one of \"uppercase\" and \"lowercase\" may be \
                               supplied!".to_string(),
                        "handle_code_option_arguments()".to_string(),
                    ));
                } else {
                    case_argument_encountered = true;
                }
                ctx.set_ciphertext_capitalization(arg == OPTION_UPPERCASE);
            }
            arg => {
                let mut description = "For algorithm ".to_string();
                description.push_str(ctx.get_atom_type().to_string().as_str());
                description.push_str("! \"");
                description.push_str(arg);
                description.push_str("\" is unknown!");
                return Err(ApplicationError::WrongCommandLineArgument(
                    description,
                    "handle_code_option_arguments()".to_string(),
                ));
            }
        }
        maybe_arg = arguments.pop_front();
    }

    if let Some(direction) = direction {
        *current_action = Some(CryptographicAction::new_code_action(
            ctx, direction,
        ));
        Ok(())
    } else {
        Err(ApplicationError::MissingArgument(
            "Neither \"encode\" nor \"decode\" argument has been supplied!".to_string(),
            "handle_code_option_arguments()".to_string(),
        ))
    }
}

/// Splits up the code algorithm arguments.
#[inline]
fn split_code_algorithm_arguments(
    arguments: &String,
) -> LinkedList<&str> {
    arguments
        .as_str()
        .split(",")
        .collect::<LinkedList<&str>>()
}