ares/decoders/
morse_code.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::{debug, info, trace};
9
10///! Morse Code Decoder
11///! Does not support decoding of morse code with / instead of a space
12///! or new lines for new words.
13pub struct MorseCodeDecoder;
14
15impl Crack for Decoder<MorseCodeDecoder> {
16    fn new() -> Decoder<MorseCodeDecoder> {
17        Decoder {
18            name: "Morse Code",
19            description: "Morse code is a method used in telecommunication to encode text characters as standardized sequences of two different signal durations, called dots and dashes, or dits and dahs.",
20            link: "https://en.wikipedia.org/wiki/Morse_code",
21            tags: vec!["morseCode", "decoder", "signals"],
22            popularity: 0.5,
23            phantom: std::marker::PhantomData,
24        }
25    }
26
27    /// This function does the actual decoding
28    /// It returns an Option<string> if it was successful
29    /// Else the Option returns nothing and the error is logged in Trace
30    fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
31        trace!("Trying Morse Code with text {:?}", text);
32        // TODO support new line and slash morse code
33        let text = normalise_morse_string(text);
34        let decoded_text: Option<String> = text.split(' ').map(morse_to_alphanumeric).collect();
35
36        trace!("Decoded text for morse code: {:?}", decoded_text);
37        let mut results = CrackResult::new(self, text.to_string());
38
39        if decoded_text.is_none() {
40            debug!("Failed to decode Morse Code because a character was not in the dictionary");
41            return results;
42        }
43
44        let decoded_text = decoded_text.unwrap();
45        if !check_string_success(&decoded_text, &text) {
46            info!(
47                "Failed to decode morse code because check_string_success returned false on string {}",
48                decoded_text
49            );
50            return results;
51        }
52
53        let checker_result = checker.check(&decoded_text);
54        results.unencrypted_text = Some(vec![decoded_text]);
55
56        results.update_checker(&checker_result);
57
58        results
59    }
60    /// Gets all tags for this decoder
61    fn get_tags(&self) -> &Vec<&str> {
62        &self.tags
63    }
64    /// Gets the name for the current decoder
65    fn get_name(&self) -> &str {
66        self.name
67    }
68}
69
70/// We want to remove new lines / line breaks so all the morse is on 1 line and we can parse it better
71fn normalise_morse_string(text: &str) -> String {
72    // The replace function supports patterns https://doc.rust-lang.org/std/str/pattern/trait.Pattern.html#impl-Pattern%3C%27a%3E-3
73    text.to_lowercase()
74        .replace(['\n', '\r'], "")
75        .replace("\\n", "")
76        .replace("\\r", "")
77        .replace('\\', "")
78}
79
80/// Maps morse code to its alphanumeric character, returns None for invalid morse-code
81fn morse_to_alphanumeric(text: &str) -> Option<&str> {
82    trace!("Starting to map morse code to alphanumeric");
83    let result = match text {
84        ".-" => "A",
85        "-..." => "B",
86        "-.-." => "C",
87        "-.." => "D",
88        "." => "E",
89        "..-." => "F",
90        "--." => "G",
91        "...." => "H",
92        ".." => "I",
93        ".---" => "J",
94        "-.-" => "K",
95        ".-.." => "L",
96        "--" => "M",
97        "-." => "N",
98        "---" => "O",
99        ".--." => "P",
100        "--.-" => "Q",
101        ".-." => "R",
102        "..." => "S",
103        "-" => "T",
104        "..-" => "U",
105        "...-" => "V",
106        ".--" => "W",
107        "-..-" => "X",
108        "-.--" => "Y",
109        "--.." => "Z",
110
111        ".----" => "1",
112        "..---" => "2",
113        "...--" => "3",
114        "....-" => "4",
115        "....." => "5",
116        "-...." => "6",
117        "--..." => "7",
118        "---.." => "8",
119        "----." => "9",
120        "-----" => "0",
121
122        ".-..." => "&",
123        ".--.-." => "@",
124        "---..." => ":",
125        "--..--" => ",",
126        ".-.-.-" => ".",
127        ".----." => "'",
128        ".-..-." => "\"",
129        "..--.." => "?",
130        "-..-." => "/",
131        "-...-" => "=",
132        ".-.-." => "+",
133        "-....-" => "-",
134        "-.--." => "(",
135        "-.--.-" => ")",
136        "/" => " ",
137        "-.-.--" => "!",
138        " " => " ",
139        "" => "",
140        // Turns line breaks and new lines into space. This may break what the plaintext is supposed to be
141        // But enables us to support them
142        "\n" => " ",
143        "\r" => " ",
144        _ => return None,
145    };
146
147    Some(result)
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::checkers::athena::Athena;
154    use crate::checkers::checker_type::{Check, Checker};
155    use crate::checkers::CheckerTypes;
156    use crate::decoders::interface::Crack;
157
158    // helper for tests
159    fn get_athena_checker() -> CheckerTypes {
160        let athena_checker = Checker::<Athena>::new();
161        CheckerTypes::CheckAthena(athena_checker)
162    }
163
164    #[test]
165    fn test_morse_code() {
166        let decoder = Decoder::<MorseCodeDecoder>::new();
167        let result = decoder.crack(
168            ".---- ----. ..--- .-.-.- .---- -.... ---.. .-.-.- ----- .-.-.- .----",
169            &get_athena_checker(),
170        );
171        assert_eq!(result.unencrypted_text.unwrap()[0], "192.168.0.1");
172    }
173    #[test]
174    fn test_morse_code_new_line() {
175        let decoder = Decoder::<MorseCodeDecoder>::new();
176        let result = decoder.crack(
177            ".---- ----. ..--- .-.-.- .---- -.... ---.. .-.-.- ----- .-.-.- .----\n",
178            &get_athena_checker(),
179        );
180        assert_eq!(result.unencrypted_text.unwrap()[0], "192.168.0.1");
181    }
182    #[test]
183    fn test_morse_code_new_line_with_space() {
184        let decoder = Decoder::<MorseCodeDecoder>::new();
185        let result = decoder.crack(
186            ".---- ----. ..--- .-.-.- .---- -.... ---.. .-.-.- ----- .-.-.- .---- \n",
187            &get_athena_checker(),
188        );
189        assert_eq!(result.unencrypted_text.unwrap()[0], "192.168.0.1");
190    }
191    #[test]
192    fn test_morse_code_carrage_arrage_return_with_space() {
193        let decoder = Decoder::<MorseCodeDecoder>::new();
194        let result = decoder.crack(
195            ".---- ----. ..--- .-.-.- .---- -.... ---.. .-.-.- ----- .-.-.- .---- \r",
196            &get_athena_checker(),
197        );
198        assert_eq!(result.unencrypted_text.unwrap()[0], "192.168.0.1");
199    }
200
201    #[test]
202    fn test_morse_code_both_new_line_and_carrage_return() {
203        let decoder = Decoder::<MorseCodeDecoder>::new();
204        let result = decoder.crack(
205            ".---- ----. \n..--- .-.-.- \r.---- -.... ---.. .-.-.- ----- .-.-.- .---- \r",
206            &get_athena_checker(),
207        );
208        assert_eq!(result.unencrypted_text.unwrap()[0], "192.168.0.1");
209    }
210
211    #[test]
212    // We cannot decode this because backslashes are not supported
213    #[ignore]
214    fn test_morse_code_backslash() {
215        let decoder = Decoder::<MorseCodeDecoder>::new();
216        let result = decoder.crack(
217            r".... . .-.. .-.. ---\.-- --- .-. .-.. -.. -.-.--",
218            &get_athena_checker(),
219        );
220        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world!");
221    }
222    #[test]
223    // We cannot decode this because linefeeds are not supported
224    // This is the default on CyberChef
225    // Maybe file input would be better here, does it detect the new line in the text?
226    #[ignore]
227    fn test_morse_code_line_feed() {
228        let decoder = Decoder::<MorseCodeDecoder>::new();
229        let result = decoder.crack(
230            r".... . .-.. .-.. ---
231            .-- --- .-. .-.. -.. -.-.--",
232            &get_athena_checker(),
233        );
234        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world!");
235    }
236    #[test]
237    // This is broken because we split on spaces in our code
238    // And because the comma is not a space it is not split
239    #[ignore]
240    fn test_morse_code_comma() {
241        let decoder = Decoder::<MorseCodeDecoder>::new();
242        let result = decoder.crack(
243            r".... . .-.. .-.. ---,.-- --- .-. .-.. -.. -.-.--",
244            &get_athena_checker(),
245        );
246        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world!");
247    }
248
249    #[test]
250    // This is broken because we split on spaces in our code
251    // And because the colon is not a space it is not split
252    #[ignore]
253    fn test_morse_code_colon() {
254        let decoder = Decoder::<MorseCodeDecoder>::new();
255        let result = decoder.crack(
256            r".... . .-.. .-.. ---:.-- --- .-. .-.. -.. -.-.--",
257            &get_athena_checker(),
258        );
259        assert_eq!(result.unencrypted_text.unwrap()[0], "hello world!");
260    }
261}