ares/decoders/
atbash_decoder.rs

1use crate::checkers::CheckerTypes;
2use crate::decoders::interface::check_string_success;
3
4use super::crack_results::CrackResult;
5use super::interface::Crack;
6use super::interface::Decoder;
7
8use log::{info, trace};
9
10///! Atbash Decoder
11pub struct AtbashDecoder;
12
13impl Crack for Decoder<AtbashDecoder> {
14    fn new() -> Decoder<AtbashDecoder> {
15        Decoder {
16            name: "Atbash",
17            description: "Atbash is a monoalphabetic substitution cipher originally used to encrypt the Hebrew alphabet. It can be modified for use with any known writing system with a standard collating order.",
18            link: "https://en.wikipedia.org/wiki/Atbash",
19            tags: vec!["atbash", "substitution", "decoder", "reciprocal"],
20            popularity: 1.0,
21            phantom: std::marker::PhantomData,
22        }
23    }
24
25    /// This function does the actual decoding
26    /// It returns an Option<string> if it was successful
27    /// Else the Option returns nothing and the error is logged in Trace
28    fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
29        trace!("Trying atbash with text {:?}", text);
30        let decoded_text = atbash_to_alphabet(text);
31
32        trace!("Decoded text for atbash: {:?}", decoded_text);
33        let mut results = CrackResult::new(self, text.to_string());
34
35        if !check_string_success(&decoded_text, text) {
36            info!(
37                "Failed to decode atbash because check_string_success returned false on string {}",
38                decoded_text
39            );
40            return results;
41        }
42
43        let checker_result = checker.check(&decoded_text);
44        results.unencrypted_text = Some(vec![decoded_text]);
45
46        results.update_checker(&checker_result);
47
48        results
49    }
50    /// Gets all tags for this decoder
51    fn get_tags(&self) -> &Vec<&str> {
52        &self.tags
53    }
54    /// Gets the name for the current decoder
55    fn get_name(&self) -> &str {
56        self.name
57    }
58}
59
60/// Maps atbash to the alphabet
61fn atbash_to_alphabet(text: &str) -> String {
62    text.chars()
63        .map(|char| match char {
64            letter @ 'a'..='z' => (b'a' + b'z' - letter as u8) as char,
65            letter @ 'A'..='Z' => (b'A' + b'Z' - letter as u8) as char,
66            other => other,
67        })
68        .collect()
69}
70
71#[cfg(test)]
72mod tests {
73    use super::AtbashDecoder;
74    use crate::{
75        checkers::{
76            athena::Athena,
77            checker_type::{Check, Checker},
78            CheckerTypes,
79        },
80        decoders::interface::{Crack, Decoder},
81    };
82
83    // helper for tests
84    fn get_athena_checker() -> CheckerTypes {
85        let athena_checker = Checker::<Athena>::new();
86        CheckerTypes::CheckAthena(athena_checker)
87    }
88
89    #[test]
90    fn test_atbash() {
91        let decoder = Decoder::<AtbashDecoder>::new();
92        let result = decoder.crack("svool dliow", &get_athena_checker());
93        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world");
94    }
95
96    #[test]
97    fn test_atbash_capitalization() {
98        let decoder = Decoder::<AtbashDecoder>::new();
99        let result = decoder.crack(
100            "Zgyzhs Hslfow Pvvk Xzkrgzorazgrlm orpv GSRH",
101            &get_athena_checker(),
102        );
103        assert_eq!(
104            result.unencrypted_text.unwrap()[0],
105            "Atbash Should Keep Capitalization like THIS"
106        );
107    }
108
109    #[test]
110    fn test_atbash_non_alphabetic_characters() {
111        let decoder = Decoder::<AtbashDecoder>::new();
112        let result = decoder.crack(
113            "Zgyzhs hslfow ovzev xszizxgvih orpv gsvhv: ',.39=_#%^ rmgzxg zugvi wvxlwrmt!",
114            &get_athena_checker(),
115        );
116        assert_eq!(
117            result.unencrypted_text.unwrap()[0],
118            "Atbash should leave characters like these: ',.39=_#%^ intact after decoding!"
119        );
120    }
121
122    #[test]
123    fn atbash_decode_empty_string() {
124        // Atbash returns an empty string, this is a valid atbash string
125        // but returns False on check_string_success
126        let atbash_decoder = Decoder::<AtbashDecoder>::new();
127        let result = atbash_decoder
128            .crack("", &get_athena_checker())
129            .unencrypted_text;
130        assert!(result.is_none());
131    }
132
133    #[test]
134    fn atbash_decode_handles_panics() {
135        let atbash_decoder = Decoder::<AtbashDecoder>::new();
136        let result = atbash_decoder
137            .crack("583920482058430191", &get_athena_checker())
138            .unencrypted_text;
139        if result.is_some() {
140            panic!("Decode_atbash did not return an option with Some<t>.")
141        } else {
142            // If we get here, the test passed
143            // Because the atbash_decoder.crack function returned None
144            // as it should do for the input
145            assert_eq!(true, true);
146        }
147    }
148
149    #[test]
150    fn atbash_handle_panic_if_empty_string() {
151        let atbash_decoder = Decoder::<AtbashDecoder>::new();
152        let result = atbash_decoder
153            .crack("", &get_athena_checker())
154            .unencrypted_text;
155        if result.is_some() {
156            assert_eq!(true, true);
157        }
158    }
159
160    #[test]
161    fn atbash_work_if_string_not_atbash() {
162        let atbash_decoder = Decoder::<AtbashDecoder>::new();
163        let result = atbash_decoder
164            .crack("hello good day!", &get_athena_checker())
165            .unencrypted_text;
166        if result.is_some() {
167            assert_eq!(true, true);
168        }
169    }
170
171    #[test]
172    fn atbash_handle_panic_if_emoji() {
173        let atbash_decoder = Decoder::<AtbashDecoder>::new();
174        let result = atbash_decoder
175            .crack("😂", &get_athena_checker())
176            .unencrypted_text;
177        if result.is_some() {
178            assert_eq!(true, true);
179        }
180    }
181}