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
#![doc = include_str!("../README.md")]
#![deny(rust_2018_idioms)]
#![warn(missing_docs)]

use iterator_ext::IteratorExt;
use std::{
    fmt::{self},
    rc::Rc,
};

mod modules;
pub use modules::*;

mod tree;
mod utils;
use tree::{RefNode, Tree};

/// Encodes the given plaintext using the specified bases and return the result as a vector of steps.
/// E.g. (plaintext, step1, step2, ..., ciphertext)
pub fn encode(plaintext: &str, bases: &[Box<dyn Base>]) -> Vec<String> {
    bases
        .iter()
        .scan(plaintext.to_string(), |text, base| {
            let encoded = base.encode(text.as_bytes());
            *text = encoded.clone();
            Some(encoded)
        })
        .collect()
}

/// Decodes the given ciphertext using the specified bases and return the result as a vector of steps.
/// E.g. (ciphertext, step1, step2, ..., plaintext)
pub fn decode(ciphertext: &str, bases: &[Box<dyn Base>]) -> Result<Vec<Vec<u8>>, DecodeError> {
    bases
        .iter()
        .map(Ok)
        .try_scan(ciphertext.as_bytes().to_vec(), |acc, base| {
            let decoded =
                base.decode(&String::from_utf8(acc.to_vec()).map_err(DecodeError::InvalidUtf8)?)?;
            *acc = decoded.clone();
            Ok(Some(decoded))
        })
        .collect()
}

/// Crack data.
#[derive(Clone, PartialEq)]
pub struct CrackData {
    /// The base used to decode the ciphertext.
    pub base: Option<&'static BaseMetadata>,
    /// The decoded data.
    pub decoded: Vec<u8>,
    /// The percentage of printable characters in the decoded text.
    pub printable_percentage: f32,
}

impl fmt::Debug for CrackData {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let decoded = String::from_utf8(self.decoded.clone());
        f.debug_struct("CrackData")
            .field("base", &self.base.map(|b| b.name).unwrap_or("unknown"))
            .field(
                "decoded",
                if let Ok(decoded) = &decoded {
                    decoded
                } else {
                    &self.decoded
                },
            )
            .finish()
    }
}

/// Crack tree.
pub type CrackTree = Tree<CrackData>;
/// Crack node.
pub type CrackNode = RefNode<CrackData>;

/// Cracks the given ciphertext using the specified bases and return the result as a tree of steps.
pub fn crack(
    ciphertext: &str,
    bases: &[Box<dyn Base>],
    min_printable_percentage: f32,
) -> CrackTree {
    let mut tree = CrackTree::new(CrackData {
        base: None,
        decoded: ciphertext.as_bytes().to_vec(),
        printable_percentage: utils::printable_percentage(ciphertext.as_bytes()),
    });

    crack_round(ciphertext, bases, min_printable_percentage, tree.root());

    tree
}

/// Iterates over the given bases and generates a tree of all possible combinations of good bases.
pub fn crack_round(
    ciphertext: &str,
    bases: &[Box<dyn Base>],
    min_printable_percentage: f32,
    node: RefNode<CrackData>,
) {
    for base in bases {
        let decoded = match base.decode(ciphertext) {
            Ok(decoded) => decoded,
            Err(_) => continue,
        };

        let printable_percentage = utils::printable_percentage(&decoded);

        if printable_percentage >= min_printable_percentage {
            let data = CrackData {
                base: Some(base.get_metadata()),
                decoded: decoded.clone(),
                printable_percentage,
            };

            let child = tree::add_child(&node, data);

            // Recurse if the decoded data is valid UTF-8
            if let Ok(decoded) = String::from_utf8(decoded) {
                crack_round(&decoded, bases, min_printable_percentage, child);
            }
        }
    }
}

/// Returns the base sequence of the given node including itself, until the root node.
pub fn get_recipe(node: &CrackNode) -> Vec<Rc<CrackData>> {
    let mut bases = vec![node.borrow().data.clone()];
    let mut current = node.clone();

    while let Some(parent) = &current.clone().borrow().parent {
        // Ignore the first node.
        if parent.borrow().parent.is_none() {
            break;
        }
        bases.push(parent.borrow().data.clone());
        current = parent.clone();
    }
    // Reverse the bases to get the decoding sequence.
    bases.reverse();
    bases
}