1use crate::util::md5::*;
6use crate::util::thread::*;
7use std::collections::{BTreeMap, BTreeSet};
8use std::sync::Mutex;
9
10struct Shared<'a> {
12 input: &'a str,
13 part_two: bool,
14 iter: AtomicIter,
15 mutex: Mutex<Exclusive>,
16}
17
18struct Exclusive {
20 threes: BTreeMap<i32, u32>,
21 fives: BTreeMap<i32, u32>,
22 found: BTreeSet<i32>,
23}
24
25pub fn parse(input: &str) -> &str {
26 input.trim()
27}
28
29pub fn part1(input: &str) -> i32 {
31 generate_pad(input, false)
32}
33
34pub fn part2(input: &str) -> i32 {
36 generate_pad(input, true)
37}
38
39fn generate_pad(input: &str, part_two: bool) -> i32 {
41 let step = if cfg!(feature = "simd") { 32 } else { 1 };
42
43 let exclusive =
44 Exclusive { threes: BTreeMap::new(), fives: BTreeMap::new(), found: BTreeSet::new() };
45 let shared =
46 Shared { input, part_two, iter: AtomicIter::new(0, step), mutex: Mutex::new(exclusive) };
47
48 #[cfg(not(feature = "simd"))]
50 spawn(|| scalar::worker(&shared));
51 #[cfg(feature = "simd")]
52 spawn(|| simd::worker(&shared));
53
54 let exclusive = shared.mutex.into_inner().unwrap();
55 *exclusive.found.iter().nth(63).unwrap()
56}
57
58fn format_string(prefix: &str, n: i32) -> ([u8; 64], usize) {
60 let string = format!("{prefix}{n}");
61 let size = string.len();
62
63 let mut buffer = [0; 64];
64 buffer[0..size].copy_from_slice(string.as_bytes());
65
66 (buffer, size)
67}
68
69#[inline]
71fn to_ascii(n: u32) -> [u8; 8] {
72 let mut n = n as u64;
74 n = ((n << 16) & 0x0000ffff00000000) | (n & 0x000000000000ffff);
75 n = ((n << 8) & 0x00ff000000ff0000) | (n & 0x000000ff000000ff);
76 n = ((n << 4) & 0x0f000f000f000f00) | (n & 0x000f000f000f000f);
77
78 let mask = ((n + 0x0606060606060606) >> 4) & 0x0101010101010101;
88 n = n + 0x3030303030303030 + 0x27 * mask;
89 n.to_be_bytes()
90}
91
92#[cfg(not(feature = "simd"))]
93mod scalar {
94 use super::*;
95
96 pub(super) fn worker(shared: &Shared<'_>) {
97 while let Some(n) = shared.iter.next() {
98 let n = n as i32;
100
101 let (mut buffer, size) = format_string(shared.input, n);
103 let mut result = hash(&mut buffer, size);
104
105 if shared.part_two {
106 for _ in 0..2016 {
107 buffer[0..8].copy_from_slice(&to_ascii(result[0]));
108 buffer[8..16].copy_from_slice(&to_ascii(result[1]));
109 buffer[16..24].copy_from_slice(&to_ascii(result[2]));
110 buffer[24..32].copy_from_slice(&to_ascii(result[3]));
111 result = hash(&mut buffer, 32);
112 }
113 }
114
115 check(shared, n, result);
116 }
117 }
118
119 fn check(shared: &Shared<'_>, n: i32, hash: [u32; 4]) {
121 let [a, b, c, d] = hash;
122
123 let mut prev = u32::MAX;
124 let mut same = 1;
125 let mut three = 0;
126 let mut five = 0;
127
128 for mut word in [d, c, b, a] {
129 for _ in 0..8 {
130 let next = word & 0xf;
131
132 if next == prev {
133 same += 1;
134 } else {
135 same = 1;
136 }
137
138 if same == 3 {
139 three = 1 << next;
140 }
141 if same == 5 {
142 five |= 1 << next;
143 }
144
145 word >>= 4;
146 prev = next;
147 }
148 }
149
150 if three != 0 || five != 0 {
151 let mut exclusive = shared.mutex.lock().unwrap();
152 let mut candidates = Vec::new();
153
154 if three != 0 {
156 exclusive.threes.insert(n, three);
157
158 for (_, mask) in exclusive.fives.range(n + 1..n + 1001) {
159 if three & mask != 0 {
160 candidates.push(n);
161 }
162 }
163 }
164
165 if five != 0 {
167 exclusive.fives.insert(n, five);
168
169 for (&index, &mask) in exclusive.threes.range(n - 1000..n) {
170 if five & mask != 0 {
171 candidates.push(index);
172 }
173 }
174 }
175
176 exclusive.found.extend(candidates);
178
179 if exclusive.found.len() >= 64 {
180 shared.iter.stop();
181 }
182 }
183 }
184}
185
186#[cfg(feature = "simd")]
187mod simd {
188 use super::*;
189 use crate::util::bitset::*;
190 use crate::util::md5::simd::hash_fixed;
191 use std::simd::cmp::SimdPartialEq as _;
192 use std::simd::*;
193
194 #[cfg(feature = "simd")]
196 #[expect(clippy::needless_range_loop)]
197 pub(super) fn worker(shared: &Shared<'_>) {
198 let mut result = [Simd::splat(0); 4];
199 let mut buffers = [[0; 64]; 32];
200
201 while let Some(start) = shared.iter.next() {
202 let start = start as i32;
204
205 for i in 0..32 {
207 let (mut buffer, size) = format_string(shared.input, start + i as i32);
208 let [a, b, c, d] = hash(&mut buffer, size);
209
210 result[0][i] = a;
211 result[1][i] = b;
212 result[2][i] = c;
213 result[3][i] = d;
214 }
215
216 if shared.part_two {
217 for _ in 0..2016 {
218 for i in 0..32 {
219 buffers[i][0..8].copy_from_slice(&to_ascii(result[0][i]));
220 buffers[i][8..16].copy_from_slice(&to_ascii(result[1][i]));
221 buffers[i][16..24].copy_from_slice(&to_ascii(result[2][i]));
222 buffers[i][24..32].copy_from_slice(&to_ascii(result[3][i]));
223 }
224 result = hash_fixed(&mut buffers, 32);
225 }
226 }
227
228 check(shared, start, &result);
229 }
230 }
231
232 #[inline]
234 fn check(shared: &Shared<'_>, start: i32, hash: &[Simd<u32, 32>; 4]) {
235 let &[a, b, c, d] = hash;
236
237 let mut prev: Simd<u32, 32> = Simd::splat(u32::MAX);
238 let mut same: Simd<u32, 32> = Simd::splat(1);
239 let mut three: Simd<u32, 32> = Simd::splat(0);
240 let mut five: Simd<u32, 32> = Simd::splat(0);
241
242 for mut word in [d, c, b, a] {
243 for _ in 0..8 {
244 let next = word & Simd::splat(0xf);
245 same = next.simd_eq(prev).select(same + Simd::splat(1), Simd::splat(1));
246
247 three = same.simd_eq(Simd::splat(3)).select(Simd::splat(1) << next, three);
248 five |= same.simd_eq(Simd::splat(5)).select(Simd::splat(1) << next, Simd::splat(0));
249
250 word >>= 4;
251 prev = next;
252 }
253 }
254
255 let three_mask = three.simd_ne(Simd::splat(0)).to_bitmask();
256 let five_mask = five.simd_ne(Simd::splat(0)).to_bitmask();
257
258 if three_mask != 0 || five_mask != 0 {
259 let mut exclusive = shared.mutex.lock().unwrap();
260 let mut candidates = Vec::new();
261
262 for i in three_mask.biterator() {
263 let three = three[i];
264 let n = start + i as i32;
265
266 if three != 0 {
268 exclusive.threes.insert(n, three);
269
270 for (_, mask) in exclusive.fives.range(n + 1..n + 1001) {
271 if three & mask != 0 {
272 candidates.push(n);
273 }
274 }
275 }
276 }
277
278 for i in five_mask.biterator() {
279 let five = five[i];
280 let n = start + i as i32;
281
282 if five != 0 {
284 exclusive.fives.insert(n, five);
285
286 for (&index, &mask) in exclusive.threes.range(n - 1000..n) {
287 if five & mask != 0 {
288 candidates.push(index);
289 }
290 }
291 }
292 }
293
294 exclusive.found.extend(candidates);
296
297 if exclusive.found.len() >= 64 {
298 shared.iter.stop();
299 }
300 }
301 }
302}