1use base64::{prelude::BASE64_STANDARD, Engine};
2use std::{
3 collections::{HashMap, HashSet},
4 fmt::Display,
5 sync::mpsc,
6};
7
8use crate::Word;
9
10fn intersect(a: HashSet<char>, b: HashSet<char>) -> HashSet<char> {
11 let mut result = HashSet::new();
12
13 for i in a {
14 if b.contains(&i) {
15 result.insert(i);
16 }
17 }
18
19 result
20}
21
22pub fn prune(cipher_words: &mut [Word]) {
24 let mut pruner: HashMap<char, HashSet<char>> = HashMap::new();
26 for i in 'a'..='z' {
27 for j in 'a'..='z' {
28 pruner.entry(i).or_default().insert(j);
29 }
30 }
31
32 for word in cipher_words.iter() {
34 for j in word.word.chars() {
35 pruner.entry(j).and_modify(|e| {
36 *e = intersect(e.clone(), word.letter_map.get(&j).unwrap().clone())
37 });
38 }
39 }
40
41 for word in cipher_words.iter_mut() {
43 for j in 0..word.word.len() {
44 word.candidates.retain(|k| {
45 pruner
46 .get(&word.word.chars().nth(j).unwrap())
47 .unwrap()
48 .contains(&k.chars().nth(j).unwrap())
49 });
50 }
51 }
52}
53
54pub fn order_by_possible_words(cipher_words: &mut [Word]) {
55 cipher_words.sort_by(|a, b| {
56 let a = a.word.len();
57 let b = b.word.len();
58
59 b.cmp(&a)
60 });
61}
62
63fn is_consistent(map: &HashMap<char, char>) -> bool {
64 let mut counter: HashMap<char, char> = HashMap::new();
65
66 for (&first, &second) in map.iter() {
67 counter.insert(second, first);
68 }
69
70 map.len() == counter.len()
71}
72
73fn apply_map(cipher: &str, plain: &str, map: &HashMap<char, char>) -> String {
74 let mut result = String::new();
75
76 for (i, c) in cipher.chars().enumerate() {
77 let c = *map.get(&c).unwrap_or(&plain.chars().nth(i).unwrap());
78 result.push(c);
79 }
80
81 result
82}
83fn update_map(cipher: &str, plain: &str, map: &HashMap<char, char>) -> HashMap<char, char> {
84 let mut map = map.to_owned();
85
86 for (i, c) in cipher.chars().enumerate() {
87 map.entry(c)
88 .or_insert_with(|| plain.chars().nth(i).unwrap());
89 }
90
91 map
92}
93
94pub struct Solver {
95 pub cipher_words: Vec<Word>,
96}
97impl Solver {
98 pub fn new(cipher_words: &Vec<Word>) -> Self {
99 Solver {
100 cipher_words: cipher_words.to_owned(),
101 }
102 }
103
104 pub fn solve(
105 &mut self,
106 mut starting_key: HashMap<char, char>,
107 tx: Option<&mpsc::Sender<Solution>>,
108 ) {
109 self.solve_recursive(0, &mut starting_key, tx);
110 }
111
112 fn solve_recursive(
113 &mut self,
114 depth: usize,
115 map: &mut HashMap<char, char>,
116 tx: Option<&mpsc::Sender<Solution>>,
117 ) {
118 if is_consistent(map) {
119 if depth >= self.cipher_words.len() {
120 let solution = Solution::new(map.to_owned());
122 if let Some(tx) = tx {
123 tx.send(solution).unwrap();
124 }
125 } else {
126 for i in self.cipher_words[depth].candidates.to_owned().iter() {
128 if &apply_map(&self.cipher_words[depth].word, i, map) == i {
129 self.solve_recursive(
130 depth + 1,
131 &mut update_map(&self.cipher_words[depth].word, i, map),
132 tx,
133 );
134 }
135 }
136 }
137 }
138 }
139}
140
141pub struct Solution {
142 pub key: HashMap<char, char>,
143}
144impl Solution {
145 pub fn new(key: HashMap<char, char>) -> Self {
146 Solution { key }
147 }
148
149 pub fn fill_key(&mut self) {
151 let mut unused: Vec<char> = ('a'..='z').rev().collect();
152 for value in self.key.values() {
154 unused.retain(|x| x != value);
155 }
156
157 for c in 'a'..='z' {
159 self.key.entry(c).or_insert_with(|| unused.pop().unwrap());
160 }
161 }
162
163 pub fn apply(&self, ciphertext: &str) -> String {
164 let mut result = String::new();
165
166 for c in ciphertext.chars() {
167 result.push(*self.key.get(&c).unwrap_or_else(|| {
169 if c.is_alphabetic() {
170 &'?'
171 } else {
172 &c
173 }
174 }));
175 }
176
177 result
178 }
179
180 pub fn format_hyperlink(&self, ciphertext: &str) -> String {
181 let ciphertext = BASE64_STANDARD.encode(ciphertext);
182 let url = format!("https://gchq.github.io/CyberChef/#recipe=Substitute('ABCDEFGHIJKLMNOPQRSTUVWXYZ','{self}',true)&input={ciphertext}");
183 format!("\x1b]8;;{url}\x1b\\{self}\x1b]8;;\x1b\\")
184 }
185}
186impl Display for Solution {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 ('a'..='z')
189 .map(|c| *self.key.get(&c).unwrap_or(&'?'))
190 .collect::<String>()
191 .fmt(f)
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use crate::{input::input_to_words, load_wordlist};
198
199 use super::*;
200
201 #[test]
202 fn can_solve() {
203 let ciphertext = "x cbt tloap";
204 let wordlist = [
205 "many", "words", "here", "to", "test", "the", "solver", "also", "few", "a", "ok",
206 "now", "all", "words", "should", "be", "good",
207 ]
208 .join("\n");
209 let dictionary = load_wordlist(&wordlist);
210
211 let cipher_words = input_to_words(ciphertext, &dictionary).unwrap();
212 let mut solver = Solver::new(&cipher_words);
213
214 let (tx, rx) = mpsc::channel();
215 solver.solve(HashMap::new(), Some(&tx));
216
217 let solution = rx.recv().unwrap();
218 let plaintext = solution.apply(ciphertext);
219 assert_eq!(plaintext, "a few words");
220 }
221}