1#[cfg(test)]
16use polydat::ast::{PolydatNode, Value};
17
18#[polydat::polydat_node(category = ByteBuffers)]
24fn u64_to_bytes(input: u64) -> Vec<u8> {
25 input.to_le_bytes().to_vec()
26}
27
28#[polydat::polydat_node(category = ByteBuffers)]
32fn bytes_from_hash(
33 input: u64,
34 #[poly_default(8u64)] size: polydat::derive_support::Const<u64>,
35) -> Vec<u8> {
36 let sz = *size as usize;
37 let mut result = Vec::with_capacity(sz);
38 let chunks = sz.div_ceil(8);
39 for i in 0..chunks {
40 let h = crate::hash::splitmix64_u64(input.wrapping_add(i as u64));
41 let take = (sz - result.len()).min(8);
42 result.extend_from_slice(&h.to_le_bytes()[..take]);
43 }
44 result
45}
46
47pub struct ByteImage {
57 image: Vec<u8>,
58}
59
60impl ByteImage {
61 pub fn new(image_size: usize, seed: u64) -> Self {
63 let mut image = Vec::with_capacity(image_size);
64 let chunks = image_size.div_ceil(8);
65 for i in 0..chunks {
66 let h = crate::hash::splitmix64_u64(seed.wrapping_add(i as u64));
67 let take = (image_size - image.len()).min(8);
68 image.extend_from_slice(&h.to_le_bytes()[..take]);
69 }
70 Self { image }
71 }
72
73 pub fn extract(&self, hash_val: u64, slice_size: usize) -> &[u8] {
75 let max_offset = self.image.len().saturating_sub(slice_size);
76 let offset = if max_offset > 0 {
77 (hash_val as usize) % (max_offset + 1)
78 } else {
79 0
80 };
81 let end = (offset + slice_size).min(self.image.len());
82 &self.image[offset..end]
83 }
84}
85
86fn build_byte_image(image_size: u64, seed: u64) -> ByteImage {
89 ByteImage::new(image_size as usize, seed)
90}
91
92#[polydat::polydat_node(category = ByteBuffers)]
105fn byte_image_extract(
106 input: u64,
107 image_size: polydat::derive_support::Const<u64>,
108 slice_size: polydat::derive_support::Const<u64>,
109 seed: polydat::derive_support::Const<u64>,
110 #[poly_const(build_byte_image, from = (image_size, seed))] image: &ByteImage,
111) -> Vec<u8> {
112 let _ = image_size; let _ = seed;
114 image.extract(input, *slice_size as usize).to_vec()
115}
116
117pub struct CharImage {
123 image: String,
124}
125
126impl CharImage {
127 pub fn new(charset: &str, size: usize) -> Self {
129 let chars: Vec<char> = parse_charset(charset);
130 assert!(!chars.is_empty(), "charset must not be empty");
131 let mut image = String::with_capacity(size);
132 for idx in 0..size {
133 image.push(chars[idx % chars.len()]);
134 }
135 Self { image }
136 }
137
138 pub fn hashed(charset: &str, size: usize, seed: u64) -> Self {
140 let chars: Vec<char> = parse_charset(charset);
141 assert!(!chars.is_empty(), "charset must not be empty");
142 let mut image = String::with_capacity(size);
143 for i in 0..size {
144 let h = crate::hash::splitmix64_u64(seed.wrapping_add(i as u64));
145 image.push(chars[(h as usize) % chars.len()]);
146 }
147 Self { image }
148 }
149
150 fn extract(&self, hash_val: u64, slice_len: usize) -> &str {
151 let chars: Vec<(usize, char)> = self.image.char_indices().collect();
152 let max_start = chars.len().saturating_sub(slice_len);
153 let start_idx = if max_start > 0 {
154 (hash_val as usize) % (max_start + 1)
155 } else {
156 0
157 };
158 let end_idx = (start_idx + slice_len).min(chars.len());
159 let byte_start = chars[start_idx].0;
160 let byte_end = if end_idx < chars.len() {
161 chars[end_idx].0
162 } else {
163 self.image.len()
164 };
165 &self.image[byte_start..byte_end]
166 }
167}
168
169fn build_char_image(charset: &str, image_size: u64, seed: u64) -> CharImage {
172 CharImage::hashed(charset, image_size as usize, seed)
173}
174
175#[polydat::polydat_node(category = ByteBuffers)]
184fn char_image_extract(
185 input: u64,
186 charset: polydat::derive_support::Const<&str>,
187 image_size: polydat::derive_support::Const<u64>,
188 slice_size: polydat::derive_support::Const<u64>,
189 #[poly_default(0u64)] seed: polydat::derive_support::Const<u64>,
190 #[poly_const(build_char_image, from = (charset, image_size, seed))] image: &CharImage,
191) -> String {
192 let _ = charset;
193 let _ = image_size;
194 let _ = seed;
195 image.extract(input, *slice_size as usize).to_string()
196}
197
198#[polydat::polydat_node(category = ByteBuffers)]
204fn byte_slice(
205 input: &[u8],
206 #[poly_default(0u64)] offset: polydat::derive_support::Const<u64>,
207 #[poly_default(8u64)] length: polydat::derive_support::Const<u64>,
208) -> Vec<u8> {
209 let off = *offset as usize;
210 let len = *length as usize;
211 let end = (off + len).min(input.len());
212 let start = off.min(end);
213 input[start..end].to_vec()
214}
215
216const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
217
218#[polydat::polydat_node(category = ByteBuffers)]
220fn to_hex(input: &[u8]) -> String {
221 let mut out = String::with_capacity(input.len() * 2);
222 for &b in input {
223 out.push(HEX_CHARS[(b >> 4) as usize] as char);
224 out.push(HEX_CHARS[(b & 0x0f) as usize] as char);
225 }
226 out
227}
228
229#[inline(always)]
230fn hex_val(c: u8) -> Option<u8> {
231 match c {
232 b'0'..=b'9' => Some(c - b'0'),
233 b'a'..=b'f' => Some(c - b'a' + 10),
234 b'A'..=b'F' => Some(c - b'A' + 10),
235 _ => None,
236 }
237}
238
239#[polydat::polydat_node(category = ByteBuffers)]
241fn from_hex(input: &str) -> Vec<u8> {
242 let bytes = input.as_bytes();
243 let mut out = Vec::with_capacity(bytes.len() / 2);
244 let mut i = 0;
245 while i + 1 < bytes.len() {
246 if let (Some(h), Some(l)) = (hex_val(bytes[i]), hex_val(bytes[i + 1])) {
247 out.push((h << 4) | l);
248 }
249 i += 2;
250 }
251 out
252}
253
254fn parse_charset(spec: &str) -> Vec<char> {
257 let mut chars = Vec::new();
258 let spec_chars: Vec<char> = spec.chars().collect();
259 let mut i = 0;
260 while i < spec_chars.len() {
261 if i + 2 < spec_chars.len() && spec_chars[i + 1] == '-' {
262 for c in spec_chars[i]..=spec_chars[i + 2] {
263 chars.push(c);
264 }
265 i += 3;
266 } else {
267 chars.push(spec_chars[i]);
268 i += 1;
269 }
270 }
271 chars
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn u64_to_bytes_roundtrip() {
280 let node = U64ToBytes::new();
281 let mut out = [Value::None];
282 node.eval(&[Value::U64(0xDEADBEEF)], &mut out);
283 let bytes = out[0].as_bytes();
284 assert_eq!(bytes.len(), 8);
285 assert_eq!(u64::from_le_bytes(bytes.try_into().unwrap()), 0xDEADBEEF);
286 }
287
288 #[test]
289 fn bytes_from_hash_size() {
290 let node = BytesFromHash::new(32);
291 let mut out = [Value::None];
292 node.eval(&[Value::U64(42)], &mut out);
293 assert_eq!(out[0].as_bytes().len(), 32);
294 }
295
296 #[test]
297 fn bytes_from_hash_deterministic() {
298 let node = BytesFromHash::new(16);
299 let mut out1 = [Value::None];
300 let mut out2 = [Value::None];
301 node.eval(&[Value::U64(42)], &mut out1);
302 node.eval(&[Value::U64(42)], &mut out2);
303 assert_eq!(out1[0].as_bytes(), out2[0].as_bytes());
304 }
305
306 #[test]
307 fn byte_image_extract_consistent_size() {
308 let node = ByteImageExtract::new(10000, 100, 0);
309 let mut out = [Value::None];
310 for i in 0..100u64 {
311 node.eval(&[Value::U64(i)], &mut out);
312 assert_eq!(out[0].as_bytes().len(), 100);
313 }
314 }
315
316 #[test]
317 fn byte_image_extract_deterministic() {
318 let node = ByteImageExtract::new(10000, 50, 0);
319 let mut out1 = [Value::None];
320 let mut out2 = [Value::None];
321 node.eval(&[Value::U64(42)], &mut out1);
322 node.eval(&[Value::U64(42)], &mut out2);
323 assert_eq!(out1[0].as_bytes(), out2[0].as_bytes());
324 }
325
326 #[test]
327 fn char_image_extract_size() {
328 let node = CharImageExtract::new("A-Za-z0-9".to_string(), 10000, 50, 0);
329 let mut out = [Value::None];
330 node.eval(&[Value::U64(42)], &mut out);
331 assert_eq!(out[0].as_str().len(), 50);
332 }
333
334 #[test]
335 fn char_image_extract_charset() {
336 let node = CharImageExtract::new("A-Z".to_string(), 1000, 20, 0);
337 let mut out = [Value::None];
338 node.eval(&[Value::U64(42)], &mut out);
339 assert!(out[0].as_str().chars().all(|c| c.is_ascii_uppercase()));
340 }
341
342 #[test]
343 fn char_image_extract_varied() {
344 let node = CharImageExtract::new("A-Za-z0-9".to_string(), 10000, 30, 0);
345 let mut out1 = [Value::None];
346 let mut out2 = [Value::None];
347 node.eval(&[Value::U64(0)], &mut out1);
348 node.eval(&[Value::U64(999)], &mut out2);
349 assert_ne!(out1[0].as_str(), out2[0].as_str());
350 }
351
352 #[test]
353 fn byte_slice_basic() {
354 let node = ByteSlice::new(2, 3);
355 let mut out = [Value::None];
356 node.eval(
357 &[Value::Bytes(vec![10u8, 20, 30, 40, 50].into())],
358 &mut out[..],
359 );
360 assert_eq!(out[0].as_bytes(), &[30, 40, 50]);
361 }
362
363 #[test]
364 fn hex_roundtrip() {
365 let to = ToHex::new();
366 let from = FromHex::new();
367 let mut mid = [Value::None];
368 let mut out = [Value::None];
369 let input = vec![0xDE, 0xAD, 0xBE, 0xEF];
370 to.eval(&[Value::Bytes(input.clone().into())], &mut mid[..]);
371 assert_eq!(mid[0].as_str(), "deadbeef");
372 from.eval(&[mid[0].clone()], &mut out);
373 assert_eq!(out[0].as_bytes(), &input);
374 }
375}