ares/decoders/
caesar_decoder.rs

1///! Decode a caesar cipher string
2///! Performs error handling and returns a string
3///! Call caesar_decoder.crack to use. It returns option<String> and check with
4///! `result.is_some()` to see if it returned okay.
5///
6use crate::checkers::CheckerTypes;
7use crate::decoders::interface::check_string_success;
8
9use super::crack_results::CrackResult;
10use super::interface::Crack;
11use super::interface::Decoder;
12
13use log::{info, trace};
14
15/// The caesar decoder, call:
16/// `let caesar_decoder = Decoder::<caesarDecoder>::new()` to create a new instance
17/// And then call:
18/// `result = caesar_decoder.crack(input)` to decode a caesar string
19/// The struct generated by new() comes from interface.rs
20/// ```
21/// use ares::decoders::caesar_decoder::CaesarDecoder;
22/// use ares::decoders::interface::{Crack, Decoder};
23/// use ares::checkers::{athena::Athena, CheckerTypes, checker_type::{Check, Checker}};
24///
25/// let decode_caesar = Decoder::<CaesarDecoder>::new();
26/// let athena_checker = Checker::<Athena>::new();
27/// let checker = CheckerTypes::CheckAthena(athena_checker);
28///
29/// let result = decode_caesar.crack("uryyb guvf vf ybat grkg", &checker).unencrypted_text;
30/// assert!(result.is_some());
31/// // If it succeeds, the 0th element is the plaintext else it'll contain 25 elements
32/// // of unsuccessfully decoded text
33/// assert_eq!(result.unwrap()[0], "hello this is long text");
34/// ```
35pub struct CaesarDecoder;
36
37impl Crack for Decoder<CaesarDecoder> {
38    fn new() -> Decoder<CaesarDecoder> {
39        Decoder {
40            name: "Caesar Cipher",
41            description: "Caesar cipher, also known as Caesar's cipher, the shift cipher, Caesar's code or Caesar shift, is one of the simplest and most widely known encryption techniques. It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet.",
42            link: "https://en.wikipedia.org/wiki/Caesar_cipher",
43            tags: vec!["caesar", "decryption", "classic", "reciprocal"],
44            popularity: 1.0,
45            phantom: std::marker::PhantomData,
46        }
47    }
48
49    /// This function does the actual decoding
50    /// It returns an Option<string> if it was successful
51    /// Else the Option returns nothing and the error is logged in Trace
52    fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
53        trace!("Trying Caesar Cipher with text {:?}", text);
54        let mut results = CrackResult::new(self, text.to_string());
55        let mut decoded_strings = Vec::new();
56        for shift in 1..25 {
57            let decoded_text = caesar(text, shift);
58            decoded_strings.push(decoded_text);
59            let borrowed_decoded_text = &decoded_strings[decoded_strings.len() - 1];
60            if !check_string_success(borrowed_decoded_text, text) {
61                info!(
62                    "Failed to decode caesar because check_string_success returned false on string {}. This means the string is 'funny' as it wasn't modified.",
63                    borrowed_decoded_text
64                );
65                return results;
66            }
67            let checker_result = checker.check(borrowed_decoded_text);
68            // If checkers return true, exit early with the correct result
69            if checker_result.is_identified {
70                trace!("Found a match with caesar shift {}", shift);
71                results.unencrypted_text = Some(vec![borrowed_decoded_text.to_string()]);
72                results.update_checker(&checker_result);
73                return results;
74            }
75        }
76        results.unencrypted_text = Some(decoded_strings);
77        results
78    }
79    /// Gets all tags for this decoder
80    fn get_tags(&self) -> &Vec<&str> {
81        &self.tags
82    }
83    /// Gets the name for the current decoder
84    fn get_name(&self) -> &str {
85        self.name
86    }
87}
88
89///! Caesar cipher to rotate cipher text by shift and return an owned String.
90fn caesar(cipher: &str, shift: u8) -> String {
91    cipher
92        .chars()
93        .map(|c| {
94            if c.is_ascii_alphabetic() {
95                let first = if c.is_ascii_lowercase() { b'a' } else { b'A' };
96                // modulo the distance to keep character range
97                (first + (c as u8 + shift - first) % 26) as char
98            } else {
99                c
100            }
101        })
102        .collect()
103}
104
105#[cfg(test)]
106mod tests {
107    use super::CaesarDecoder;
108    use super::*;
109    use crate::{
110        checkers::{
111            athena::Athena,
112            checker_type::{Check, Checker},
113            CheckerTypes,
114        },
115        decoders::interface::{Crack, Decoder},
116    };
117
118    // helper for tests
119    fn get_athena_checker() -> CheckerTypes {
120        let athena_checker = Checker::<Athena>::new();
121        CheckerTypes::CheckAthena(athena_checker)
122    }
123
124    #[test]
125    fn empty() {
126        assert_eq!(caesar("", 13), "");
127    }
128
129    #[test]
130    fn caesar_rot_13() {
131        assert_eq!(caesar("rust", 13), "ehfg");
132    }
133
134    #[test]
135    fn caesar_unicode() {
136        assert_eq!(caesar("attack at dawn 攻", 5), "fyyfhp fy ifbs 攻");
137    }
138
139    #[test]
140    fn successful_decoding() {
141        let caesar_decoder = Decoder::<CaesarDecoder>::new();
142
143        let result = caesar_decoder.crack("fyyfhp", &get_athena_checker());
144        let decoded_str = &result
145            .unencrypted_text
146            .expect("No unencrypted text for caesar");
147        assert_eq!(decoded_str[0], "attack");
148    }
149
150    #[test]
151    fn successful_decoding_longer_text() {
152        let caesar_decoder = Decoder::<CaesarDecoder>::new();
153
154        let result = caesar_decoder.crack("uryyb guvf vf ybat grkg", &get_athena_checker());
155        let decoded_str = &result
156            .unencrypted_text
157            .expect("No unencrypted text for caesar");
158        assert_eq!(decoded_str[0], "hello this is long text");
159    }
160
161    #[test]
162    fn successful_decoding_longer_text_with_puncuation() {
163        let caesar_decoder = Decoder::<CaesarDecoder>::new();
164
165        let result = caesar_decoder.crack("Uryyb! guvf vf ybat grkg?", &get_athena_checker());
166        let decoded_str = &result
167            .unencrypted_text
168            .expect("No unencrypted text for caesar");
169        assert_eq!(decoded_str[0], "Hello! this is long text?");
170    }
171
172    #[test]
173    fn caesar_decode_empty_string() {
174        // caesar returns an empty string, this is a valid caesar string
175        // but returns False on check_string_success
176        let caesar_decoder = Decoder::<CaesarDecoder>::new();
177        let result = caesar_decoder
178            .crack("", &get_athena_checker())
179            .unencrypted_text;
180        assert!(result.is_none());
181    }
182
183    #[test]
184    fn caesar_decode_fails() {
185        let caesar_decoder = Decoder::<CaesarDecoder>::new();
186        let result = caesar_decoder
187            .crack("#", &get_athena_checker())
188            .unencrypted_text;
189        if result.is_some() {
190            panic!("Decode_caesar did not return an option with Some<t>.")
191        } else {
192            // If we get here, the test passed
193            // Because the caesar_decoder.crack function returned None
194            // as it should do for the input
195            assert!(true);
196        }
197    }
198}