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