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
use crate::filtration_system::MyResults;
use crate::{cli_pretty_printing::decoded_how_many_times, config::get_config};
use crossbeam::{channel::bounded, select};
use log::{debug, trace};
use std::collections::HashSet;
use crate::{timer, DecoderResult};
/// Breadth first search is our search algorithm
/// https://en.wikipedia.org/wiki/Breadth-first_search
pub fn bfs(input: &str) -> Option<DecoderResult> {
let config = get_config();
let initial = DecoderResult {
text: vec![input.to_string()],
path: vec![],
};
let mut seen_strings = HashSet::new();
// all strings to search through
let mut current_strings = vec![initial];
let mut curr_depth: u32 = 1; // as we have input string, so we start from 1
let (result_send, result_recv) = bounded(1);
let timer = timer::start(config.timeout);
// loop through all of the strings in the vec
while !current_strings.is_empty() {
trace!("Number of potential decodings: {}", current_strings.len());
trace!("Current depth is {:?}", curr_depth);
let mut new_strings: Vec<DecoderResult> = vec![];
current_strings.into_iter().try_for_each(|current_string| {
let res = super::perform_decoding(¤t_string);
match res {
// if it's Break variant, we have cracked the text successfully
// so just stop processing further.
MyResults::Break(res) => {
let mut decoders_used = current_string.path;
let text = res.unencrypted_text.clone().unwrap_or_default();
decoders_used.push(res);
let result_text = DecoderResult {
text,
path: decoders_used,
};
result_send
.send(result_text)
.expect("Succesfully send the result");
None // short-circuits the iterator
}
MyResults::Continue(results_vec) => {
new_strings.extend(
results_vec
.into_iter()
.map(|r| {
let mut decoders_used = current_string.path.clone();
// text is a vector of strings
let text = r.unencrypted_text.clone().unwrap_or_default();
decoders_used.push(r);
DecoderResult {
// and this is a vector of strings
// TODO we should probably loop through all `text` and create Text structs for each one
// and append those structs
// I think we should keep text as a single string
// and just create more of them....
text,
path: decoders_used.to_vec(),
}
})
.filter(|s| seen_strings.insert(s.text.clone())),
);
Some(()) // indicate we want to continue processing
}
}
});
let mut new_strings_to_be_added = Vec::new();
for text_struct in new_strings {
for decoded_text in text_struct.text {
if check_if_string_cant_be_decoded(&decoded_text) {
continue;
}
new_strings_to_be_added.push(DecoderResult {
text: vec![decoded_text],
// quick hack
path: text_struct.path.clone(),
})
}
}
current_strings = new_strings_to_be_added;
curr_depth += 1;
select! {
recv(result_recv) -> exit_result => {
// if we find an element that matches our exit condition, return it!
// technically this won't check if the initial string matches our exit condition
// but this is a demo and i'll be lazy :P
let exit_result = exit_result.ok(); // convert Result to Some
if exit_result.is_some() {
decoded_how_many_times(curr_depth);
debug!("Found exit result: {:?}", exit_result);
return exit_result;
}
},
recv(timer) -> _ => {
decoded_how_many_times(curr_depth);
debug!("Ares has failed to decode");
return None;
},
default => continue,
};
trace!("Refreshed the vector, {:?}", current_strings);
}
None
}
/// If this returns False it will not attempt to decode that string
fn check_if_string_cant_be_decoded(text: &str) -> bool {
text.len() <= 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bfs_succeeds() {
// this will work after english checker can identify "CANARY: hello"
let result = bfs("b2xsZWg=");
assert!(result.is_some());
let txt = result.unwrap().text;
assert!(txt[0] == "hello");
}
// Vector storing the strings to perform decoding in next iteraion
// had strings only from result of last decoding it performed.
// This was due to reassignment in try_for_each block
// which lead to unintended behaviour.
// We want strings from all results, so to fix it,
// we call .extend() to extend the vector.
// Link to forum https://discord.com/channels/754001738184392704/1002135076034859068
// This also tests the bug whereby each iteration of caesar was not passed to the next decoder
// So in Ciphey only Rot1(X) was passed to base64, not Rot13(X)
#[test]
fn non_deterministic_like_behaviour_regression_test() {
// Caesar Cipher (Rot13) -> Base64
let result = bfs("MTkyLjE2OC4wLjE=");
assert!(result.is_some());
assert_eq!(result.unwrap().text[0], "192.168.0.1");
}
#[test]
fn string_size_checker_returns_bad_if_string_cant_be_decoded() {
// Should return true because it cant decode it
let text = "12";
assert!(check_if_string_cant_be_decoded(text));
}
#[test]
fn string_size_checker_returns_ok_if_string_can_be_decoded() {
// Should return true because it cant decode it
let text = "123";
assert!(!check_if_string_cant_be_decoded(text));
}
}