encodex 0.2.0

Implementation of and cryptanalysis tool for legacy and modern codes, ciphers and hashes.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// 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/>.
//

//! Interactive and non-interactive ui elements.

#[cfg(feature = "cipher")]
mod cipher;
#[cfg(feature = "code")]
mod code;



use core::fmt;

use std::{
    collections::vec_deque::VecDeque,
    env,
};

use crate::{
    action::{
        CryptographicAction
    },
    CryptoError,
};



/// Represents the option for enabling [cipher](Cipher) input. After this option the next argument
/// must be a cipher algorithm.
#[cfg(feature = "cipher")]
const OPTION_CIPHER: &str = "cipher";

/// Represents the option for enabling [code](Code) input. After this option the next argument must
/// be a code algorithm.
#[cfg(feature = "code")]
const OPTION_CODE: &str = "code";

/// Represents the help option. When this option is encountered, a help text is printed to `stdout`.
const OPTION_HELP: &str = "help";

/// Represents the interactive option. When this option is encountered, further arguments are
/// ignored and the [application](ApplicationContext) is [`run`](ApplicationContext::run) in
/// interactive mode.
const OPTION_INTERACTIVE: &str = "interactive";

/// Represents the version option. When this option is encountered, the current program version and
/// a short license notice is printed to `stdout`.
const OPTION_VERSION: &str = "version";



/// Represents all errors that can be returned by an [`ApplicationContext`].
pub enum ApplicationError {
    /// Is build when a key for a [`CryptographicAtom`] is malformed. A malformed key looks
    /// different for all `ciphers`. See the documentation of all ciphers for how their key must
    /// look like.
    ///
    /// The first argument is a description of the error. The second argument is the origin of the
    /// error. The third argument is the [`CryptoError`] that has been returned by the
    /// [`CryptographicAtom`] that has produced the error.
    MalformedKey(String, String, CryptoError),
    /// Is build when an argument defining a [`CryptographicAtom`] is missing.
    ///
    /// The first argument is a description of the error. The second argument is the origin of the
    /// error.
    MissingArgument(String, String),
    /// Is build when no command line arguments have been supplied.
    ///
    /// The first argument is a description of the error.
    NoCommandLineArguments(String),
    /// Is build when an argument that is only required once has been supplied more than once.
    ///
    /// The first argument is a description of the error. The second argument is the origin of the
    /// error.
    TooManyArguments(String, String),
    /// Is build when a command line argument is encountered during command line parsing, that is
    /// not a valid argument.
    ///
    /// The first argument is a description of the error. The second argument is the origin of the
    /// error.
    WrongCommandLineArgument(String, String),
}

impl fmt::Display for ApplicationError {
    /// Formats every error nicely for printing it to `stdout` and `stderr`.
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>,
    ) -> fmt::Result {
        match self {
            ApplicationError::MalformedKey(
                description, origin, crypto_error
            ) => {
                write![f, ">>> Application Runtime Error:\n\
                           ...  Class: Malformed Key\n\
                           ...  Origin: {}\n\
                           ...  Description: {}\n\
                           ...  Crypto Error {}\n",
                       origin, description, crypto_error]
            }
            ApplicationError::MissingArgument(description, origin) => {
                write![f, ">>> Application Runtime Error:\n\
                           ...  Class: Missing Argument\n\
                           ...  Origin: {}\n\
                           ...  Description: {}\n",
                       origin, description]
            }
            ApplicationError::NoCommandLineArguments(origin) => {
                write![f, ">>> Application Runtime Error:\n\
                           ...  Class: No Command Line Arguments\n\
                           ...  Origin: {}\n\
                           ...  Description: Use \"--help\" for usage information!",
                       origin]
            }
            ApplicationError::TooManyArguments(description, origin) => {
                write![f, ">>> Application Runtime Error:\n\
                           ...  Class: Too Many Arguments\n\
                           ...  Origin: {}\n\
                           ...  Description: {}",
                       origin, description]
            }
            ApplicationError::WrongCommandLineArgument(description, origin) => {
                write![f, ">>> Application Runtime Error:\n\
                           ...  Class: Wrong Command Line Argument\n\
                           ...  Origin: {}\n\
                           ...  Description: {}",
                       origin, description]
            }
        }
    }
}



/// The context of an application's execution.
///
/// This struct manages the application state during runtime for [`interactive`](OPTION_INTERACTIVE)
/// and ***non-interactive*** mode.
pub struct ApplicationContext {
    /// A queue with all actions for the non interactive mode.
    action_queue: VecDeque<(CryptographicAction, Vec<u8>)>,
    /// Determines if the application is run in interactive mode or not.
    run_interactively: bool,
}

impl ApplicationContext {
    /// Creates a new [`ApplicationContext`].
    ///
    /// After creating a new, empty context, this function parses all command line arguments to
    /// initialize the context.
    ///
    /// # Errors
    ///
    /// Returns a
    ///
    /// * [`NoCommandLineArguments`](ApplicationError::NoCommandLineArguments) if no arguments have
    ///   been supplied.
    /// * [`WrongCommandLineArgument`](ApplicationError::WrongCommandLineArgument) if a wrong
    ///   command line argument has been encountered or a valid command line argument on the wrong
    ///   position.
    /// * [`MissingArgument`](ApplicationError::MissingArgument) if no algorithm has been defined
    ///   before any other command line argument.
    pub fn new() -> Result<ApplicationContext, ApplicationError> {
        let mut app_ctx = ApplicationContext {
            action_queue: VecDeque::new(),
            run_interactively: false,
        };

        if let Err(error) = app_ctx.parse_cmd_args() {
            return Err(error);
        } else {
            Ok(app_ctx)
        }
    }

    /// Parses the command line arguments and fills out the [application](ApplicationContext).
    ///
    /// After this method returns successfully, the [application](ApplicationContext) is ready to
    /// [`run`](ApplicationContext::run).
    ///
    /// # Errors
    ///
    /// Returns a
    ///
    /// * [`NoCommandLineArguments`](ApplicationError::NoCommandLineArguments) if no arguments have
    ///   been supplied.
    /// * [`WrongCommandLineArgument`](ApplicationError::WrongCommandLineArgument) if a wrong
    ///   command line argument has been encountered or a valid command line argument on the wrong
    ///   position.
    /// * [`MissingArgument`](ApplicationError::MissingArgument) if no algorithm has been defined
    ///   before any other command line argument.
    fn parse_cmd_args(
        &mut self
    ) -> Result<(), ApplicationError> {
        let mut args = env::args().collect::<Vec<String>>();
        let mut args_iterator = args.iter().skip(1);
        let mut maybe_arg = args_iterator.next();

        if maybe_arg == None {
            return Err(ApplicationError::NoCommandLineArguments(
                "ApplicationContext::parse_cmd_args()".to_string()
            ));
        }

        let mut current_action = None;

        while maybe_arg != None {
            let arg = maybe_arg.unwrap();
            let current_value: &str;
            let cmd_line_option = if arg.len() >= 2 && arg.is_ascii() && "--" == &arg[0..2] {
                current_value = &arg[2..];
                true
            } else {
                current_value = &arg[..];
                false
            };

            match current_value {
                #[cfg(feature = "cipher")]
                OPTION_CIPHER if cmd_line_option => {
                    if let Err(error) = cipher::handle_cipher_option_arguments(
                        args_iterator.next(), &mut current_action,
                    ) {
                        return Err(error);
                    }
                }
                #[cfg(feature = "code")]
                OPTION_CODE if cmd_line_option => {
                    if let Err(error) = code::handle_code_option_arguments(
                        args_iterator.next(), &mut current_action,
                    ) {
                        return Err(error);
                    }
                }
                option
                if cmd_line_option && (option == OPTION_HELP || option == OPTION_VERSION) => {
                    if option == OPTION_HELP {
                        ApplicationContext::print_elp();
                    } else {
                        ApplicationContext::print_version();
                    }
                    self.action_queue.truncate(0);
                    self.run_interactively = false;
                    // Truncate, then rebuild the iterator from the truncated vector. That avoids
                    // the mutable borrow while borrowed immutable error during compilation and
                    // achieves the intended goal of leaving the while loop and exiting the
                    // program after a help or version option.
                    args.truncate(0);
                    args_iterator = args.iter().skip(0);
                }
                OPTION_INTERACTIVE if cmd_line_option => {
                    self.run_interactively = true;
                    self.action_queue.truncate(0);
                    args.truncate(0);
                    args_iterator = args.iter().skip(0);
                }
                ever_other_string if cmd_line_option => {
                    let mut description = "\"".to_string();
                    description.push_str(ever_other_string);
                    description.push_str("\" is not a command line option!");
                    return Err(ApplicationError::WrongCommandLineArgument(
                        description, "ApplicationContext::parse_cmd_args()".to_string(),
                    ));
                }
                every_other_string => {
                    if let Some(action) = &current_action {
                        self.queue_action(action.clone(), Vec::from(every_other_string));
                    } else {
                        return Err(ApplicationError::MissingArgument(
                            "Define an algorithm before an input!".to_string(),
                            "ApplicationContext::parse_cmd_args()".to_string(),
                        ));
                    }
                }
            }

            maybe_arg = args_iterator.next();
        }

        Ok(())
    }

    /// Prints the help for this program.
    fn print_elp() {
        let mut elp = String::new();
        elp.push_str("Usage: encodex [[--<class> <ALGORITHM>:<arguments>]\n");
        elp.push_str("                [<input> | --file <name>]* ]+\n\n");
        elp.push_str("<class> can be one of the following:\n\n");
        #[cfg(feature = "cipher")]
        elp.push_str("    cipher\n");
        #[cfg(feature = "code")]
        elp.push_str("    code\n");
        #[cfg(feature = "digest")]
        elp.push_str("    digest\n");
        elp.push_str("\nEvery <ALGORITHM> falls into one of the above classes and can be\n");
        elp.push_str("one of the following:\n\n");
        elp.push_str("    <ALGORITHM>  <class>\n");
        #[cfg(feature = "base64")]
        elp.push_str("    BASE64       code\n");
        #[cfg(feature = "base64url")]
        elp.push_str("    BASE64URL    code\n");
        #[cfg(feature = "base32")]
        elp.push_str("    BASE32       code\n");
        #[cfg(feature = "base32hex")]
        elp.push_str("    BASE32HEX    code\n");
        #[cfg(any(feature = "base16", feature = "hex"))]
        elp.push_str("    BASE16       code\n\n");
        #[cfg(feature = "sha256")]
        elp.push_str("    SHA256       digest  (TODO)\n\n");
        #[cfg(feature = "vigenere")]
        elp.push_str("    VIGENERE     cipher\n");
        #[cfg(feature = "caesar")]
        elp.push_str("    CAESAR       cipher \n");
        elp.push_str("\nOptions:\n\n");
        elp.push_str("  --version        Prints version information to the terminal.\n");
        elp.push_str("  --help           Prints this help to the terminal.\n\n");

        println!["{}", elp];
    }

    /// Prints the current version of the program and a short license notice to `stdout`.
    fn print_version() {
        let program_name = String::from(env!["CARGO_PKG_NAME"]);
        let mut version = "v".to_string();
        version.push_str(env!["CARGO_PKG_VERSION_MAJOR"]);
        version.push_str(".");
        version.push_str(env!["CARGO_PKG_VERSION_MINOR"]);
        version.push_str(".");
        version.push_str(env!["CARGO_PKG_VERSION_PATCH"]);
        let description = String::from(env!["CARGO_PKG_DESCRIPTION"]);
        println!["{} {}  {}\n\
                  {}\n\
                  Copyright (C) 2022,2023  Fabian Moos\n\n\
                  This program is free software: you can redistribute it and/or modify\n\
                  it under the terms of the GNU General Public License as published by\n\
                  the Free Software Foundation, either version 3 of the License, or\n\
                  (at your option) any later version.\n\n\
                  This program is distributed in the hope that it will be useful,\n\
                  but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
                  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
                  GNU General Public License for more details.\n\n\
                  You should have received a copy of the GNU General Public License\n\
                  along with this program.  If not, see <https://www.gnu.org/licenses/>.\n",
                 program_name, version, &description[..51], &description[51..]];
    }

    /// Adds another action to the `action_queue` together with its respective input.
    fn queue_action(
        &mut self,
        action: CryptographicAction,
        input: Vec<u8>,
    ) {
        self.action_queue.push_back((action, input));
    }

    /// Starts the actual application.
    ///
    /// There are two modes of operation:
    /// 1. ***Non-interactive mode***: This mode is the default and just executes all parsed
    ///    algorithms from the command line and then exits.
    /// 2. ***Interactive mode***: This mode ignores all other command line arguments and prints an
    ///    interactive prompt that accepts certain commands to execute [`CryptographicAction`]s
    ///    interactively. This mode is started with the command line option
    ///    [`--interactive`](OPTION_INTERACTIVE).
    pub fn run(
        &mut self
    ) -> Result<(), ApplicationError> {
        if self.run_interactively {
            todo!["ApplicationContext::run(&mut self) -> Result<(), ApplicationError>\n interactive execution!"];
        } else {
            let mut maybe_action = self.action_queue.pop_front();

            while maybe_action != None {
                let (mut action, input) = maybe_action.unwrap();

                match action.execute(&input) {
                    Ok(result) => {
                        println!["{}", std::str::from_utf8(&result).unwrap()];
                    }
                    Err(error) => {
                        eprintln!["{}", error];
                    }
                }

                maybe_action = self.action_queue.pop_front();
            }
        }
        Ok(())
    }
}