1pub fn find_chunk_boundaries(data: &[u8], chunk_size: usize, delimiter: u8) -> Vec<(usize, usize)> {
15 if data.is_empty() {
16 return Vec::new();
17 }
18
19 let len = data.len();
20 let step = chunk_size.max(1);
21 let estimate = (len / step) + 2;
22 let mut chunks = Vec::with_capacity(estimate);
23
24 let mut start = 0usize;
25
26 while start < len {
27 let mut end = start.saturating_add(step);
28
29 if end >= len {
30 end = len;
31 } else {
32 let remainder = &data[end..];
33 if let Some(rel_pos) = find_byte_swar(remainder, delimiter) {
34 end = end + rel_pos + 1;
35 if end > len {
36 end = len;
37 }
38 } else {
39 end = len;
40 }
41 }
42
43 chunks.push((start, end));
44 start = end;
45 }
46
47 chunks
48}
49
50#[cfg(test)]
51mod differential_tests {
52 use super::{
53 find_byte_swar, find_chunk_boundaries, find_chunk_boundaries_pattern,
54 find_partition_boundaries, ChunkCursor, PatternChunkCursor,
55 };
56
57 const SINGLE_SEED: u64 = 0x5349_4e47_4c45_0001;
58 const CURSOR_SEED: u64 = 0x4355_5253_4f52_0002;
59 const PATTERN_SEED: u64 = 0x5041_5454_4552_0003;
60 const PATTERN_CURSOR_SEED: u64 = 0x5043_5552_534f_0004;
61 const SWAR_SEED: u64 = 0x5357_4152_0000_0005;
62 const PARTITION_SEED: u64 = 0x5041_5254_0000_0006;
63
64 #[derive(Clone, Copy)]
65 struct Lcg {
66 state: u64,
67 }
68
69 impl Lcg {
70 fn new(seed: u64) -> Self {
71 Self { state: seed }
72 }
73
74 fn next_u64(&mut self) -> u64 {
75 self.state = self
76 .state
77 .wrapping_mul(6_364_136_223_846_793_005)
78 .wrapping_add(1_442_695_040_888_963_407);
79 self.state
80 }
81
82 fn next_u8(&mut self) -> u8 {
83 self.next_u64() as u8
84 }
85
86 fn next_usize(&mut self, upper_exclusive: usize) -> usize {
87 if upper_exclusive == 0 {
88 0
89 } else {
90 (self.next_u64() % upper_exclusive as u64) as usize
91 }
92 }
93 }
94
95 fn scalar_single_byte_boundaries(
96 data: &[u8],
97 chunk_size: usize,
98 delimiter: u8,
99 ) -> Vec<(usize, usize)> {
100 let mut boundaries = Vec::new();
101 let step = chunk_size.max(1);
102 let mut start = 0;
103
104 while start < data.len() {
105 let target = start.saturating_add(step);
106 if target >= data.len() {
107 boundaries.push((start, data.len()));
108 break;
109 }
110
111 let mut end = target;
112 while end < data.len() {
113 if data[end] == delimiter {
114 end += 1;
115 break;
116 }
117 end += 1;
118 }
119 boundaries.push((start, end.min(data.len())));
120 start = end;
121 }
122
123 boundaries
124 }
125
126 fn scalar_byte_position(haystack: &[u8], delimiter: u8) -> Option<usize> {
127 let mut position = 0;
128 while position < haystack.len() {
129 if haystack[position] == delimiter {
130 return Some(position);
131 }
132 position += 1;
133 }
134 None
135 }
136
137 fn scalar_pattern_boundaries(
138 data: &[u8],
139 chunk_size: usize,
140 pattern: &[u8],
141 ) -> Vec<(usize, usize)> {
142 assert!(!pattern.is_empty());
143
144 let mut boundaries = Vec::new();
145 let step = chunk_size.max(1);
146 let mut start = 0;
147
148 while start < data.len() {
149 let target = start.saturating_add(step);
150 if target >= data.len() {
151 boundaries.push((start, data.len()));
152 break;
153 }
154
155 let mut candidate = target;
156 let mut end = data.len();
157 while candidate + pattern.len() <= data.len() {
158 let mut matches = true;
159 for offset in 0..pattern.len() {
160 if data[candidate + offset] != pattern[offset] {
161 matches = false;
162 break;
163 }
164 }
165 if matches {
166 end = candidate + pattern.len();
167 break;
168 }
169 candidate += 1;
170 }
171
172 boundaries.push((start, end));
173 start = end;
174 }
175
176 boundaries
177 }
178
179 fn scalar_partition_boundaries(
180 data: &[u8],
181 num_partitions: usize,
182 delimiter: u8,
183 ) -> Vec<(usize, usize)> {
184 if data.is_empty() || num_partitions == 0 {
185 return Vec::new();
186 }
187 if num_partitions == 1 {
188 return vec![(0, data.len())];
189 }
190
191 let mut cut_points = Vec::new();
192 let mut last_cut = 0;
193
194 for partition in 1..num_partitions {
195 let target = data.len() * partition / num_partitions;
196 if target <= last_cut {
197 continue;
198 }
199
200 let mut position = target;
201 while position < data.len() && data[position] != delimiter {
202 position += 1;
203 }
204
205 let cut = if position < data.len() {
206 position + 1
207 } else {
208 data.len()
209 };
210 cut_points.push(cut);
211 last_cut = cut;
212
213 if cut == data.len() {
214 break;
215 }
216 }
217
218 let mut partitions = Vec::with_capacity(cut_points.len() + 1);
219 let mut start = 0;
220 for end in cut_points {
221 if end > start {
222 partitions.push((start, end));
223 }
224 start = end;
225 }
226 if start < data.len() {
227 partitions.push((start, data.len()));
228 }
229 partitions
230 }
231
232 fn generated_single_case(seed: u64, case: usize) -> (Vec<u8>, usize, u8) {
233 const LENGTHS: &[usize] = &[0, 1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 63, 64, 127, 255];
234 let mut rng = Lcg::new(seed ^ (case as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15));
235 let len = if case % 3 == 0 {
236 LENGTHS[(case / 3) % LENGTHS.len()]
237 } else {
238 rng.next_usize(256)
239 };
240 let mut data = vec![0; len];
241 for byte in &mut data {
242 *byte = rng.next_u8();
243 }
244
245 let delimiter = match case % 8 {
246 0 => 0x00,
247 1 => 0xff,
248 2 => b'\n',
249 3 => 0x80,
250 _ => rng.next_u8(),
251 };
252 if case % 11 == 0 {
253 data.fill(delimiter);
254 } else if !data.is_empty() {
255 let len = data.len();
256 data[case % len] = delimiter;
257 if case % 3 == 1 && len > 1 {
258 data[(case * 7 + 1) % len] = delimiter;
259 }
260 }
261
262 let chunk_size = match case % 10 {
263 0 => 0,
264 1 => 1,
265 2 => 2,
266 3 => data.len(),
267 4 => data.len().saturating_add(1),
268 5 => usize::MAX,
269 _ => rng.next_usize(128),
270 };
271 (data, chunk_size, delimiter)
272 }
273
274 fn generated_pattern_case(seed: u64, case: usize) -> (Vec<u8>, usize, Vec<u8>) {
275 const DATA_LENGTHS: &[usize] = &[0, 1, 2, 3, 7, 8, 15, 16, 31, 32, 63, 64, 127];
276 const PATTERN_LENGTHS: &[usize] = &[1, 2, 3, 4, 5, 8, 16, 32, 64];
277 let mut rng = Lcg::new(seed ^ (case as u64).wrapping_mul(0xd6e8_feb8_6659_fd93));
278 let data_len = if case % 3 == 0 {
279 DATA_LENGTHS[(case / 3) % DATA_LENGTHS.len()]
280 } else {
281 rng.next_usize(192)
282 };
283 let pattern_len = PATTERN_LENGTHS[case % PATTERN_LENGTHS.len()];
284 let mut data = vec![0; data_len];
285 for byte in &mut data {
286 *byte = rng.next_u8();
287 }
288 let mut pattern = vec![0; pattern_len];
289 for byte in &mut pattern {
290 *byte = rng.next_u8();
291 }
292
293 let chunk_size = match case % 9 {
294 0 => 0,
295 1 => 1,
296 2 => 2,
297 3 => data_len,
298 4 => data_len.saturating_add(1),
299 5 => usize::MAX,
300 _ => rng.next_usize(96),
301 };
302
303 if case % 5 == 0 {
304 data.fill(b'a');
305 pattern.fill(b'a');
306 if pattern.len() > 1 {
307 *pattern.last_mut().unwrap() = b'b';
308 }
309 }
310
311 if data.len() >= pattern.len() {
312 let max_start = data.len() - pattern.len();
313 let start = match case % 4 {
314 0 => 0,
315 1 => max_start,
316 2 => chunk_size.max(1).min(max_start),
317 _ => rng.next_usize(max_start + 1),
318 };
319 data[start..start + pattern.len()].copy_from_slice(&pattern);
320 }
321
322 (data, chunk_size, pattern)
323 }
324
325 fn generated_partition_case(seed: u64, case: usize) -> (Vec<u8>, usize, u8) {
326 const LENGTHS: &[usize] = &[0, 1, 2, 3, 7, 8, 15, 16, 31, 32, 63, 64, 127, 255];
327 let mut rng = Lcg::new(seed ^ (case as u64).wrapping_mul(0xa409_3822_299f_31d0));
328 let len = if case % 4 == 0 {
329 LENGTHS[(case / 4) % LENGTHS.len()]
330 } else {
331 rng.next_usize(256)
332 };
333 let mut data = vec![0; len];
334 for byte in &mut data {
335 *byte = rng.next_u8();
336 }
337 let delimiter = match case % 7 {
338 0 => 0x00,
339 1 => 0xff,
340 2 => b'\n',
341 _ => rng.next_u8(),
342 };
343
344 match case % 10 {
345 0 => data.fill(delimiter),
346 1 => {}
347 _ if !data.is_empty() => {
348 let injections = 1 + case % 5;
349 let len = data.len();
350 for offset in 0..injections {
351 data[(case * 13 + offset * 17) % len] = delimiter;
352 }
353 }
354 _ => {}
355 }
356
357 let num_partitions = match case % 9 {
358 0 => 0,
359 1 => 1,
360 2 => 2,
361 3 => 4,
362 4 => 8,
363 5 => 16,
364 6 => 64,
365 _ => 1 + rng.next_usize(128),
366 };
367 (data, num_partitions, delimiter)
368 }
369
370 fn cursor_ranges(data: &[u8], chunk_size: usize, delimiter: u8) -> Vec<(usize, usize)> {
371 let base = data.as_ptr() as usize;
372 let mut cursor = ChunkCursor::new(data, chunk_size, delimiter);
373 let mut ranges = Vec::new();
374 for chunk in cursor.by_ref() {
375 let start = chunk.as_ptr() as usize - base;
376 let end = start + chunk.len();
377 assert_eq!(&data[start..end], chunk);
378 ranges.push((start, end));
379 }
380 assert!(cursor.next().is_none());
381 assert!(cursor.is_empty());
382 assert_eq!(cursor.position(), data.len());
383 ranges
384 }
385
386 fn pattern_cursor_ranges(
387 data: &[u8],
388 chunk_size: usize,
389 pattern: &[u8],
390 ) -> Vec<(usize, usize)> {
391 let base = data.as_ptr() as usize;
392 let mut cursor = PatternChunkCursor::new(data, chunk_size, pattern);
393 let mut ranges = Vec::new();
394 for chunk in cursor.by_ref() {
395 let start = chunk.as_ptr() as usize - base;
396 let end = start + chunk.len();
397 assert_eq!(&data[start..end], chunk);
398 ranges.push((start, end));
399 }
400 assert!(cursor.next().is_none());
401 assert!(cursor.is_empty());
402 assert_eq!(cursor.position(), data.len());
403 ranges
404 }
405
406 fn assert_cover(data: &[u8], ranges: &[(usize, usize)]) {
407 if data.is_empty() {
408 assert!(ranges.is_empty());
409 return;
410 }
411 assert_eq!(ranges.first().unwrap().0, 0);
412 assert_eq!(ranges.last().unwrap().1, data.len());
413 let mut next_start = 0;
414 for &(start, end) in ranges {
415 assert_eq!(start, next_start);
416 assert!(end > start);
417 next_start = end;
418 }
419 assert_eq!(next_start, data.len());
420 }
421
422 fn assert_partition_invariants(
423 data: &[u8],
424 num_partitions: usize,
425 delimiter: u8,
426 partitions: &[(usize, usize)],
427 ) {
428 if data.is_empty() || num_partitions == 0 {
429 assert!(partitions.is_empty());
430 return;
431 }
432
433 assert!(!partitions.is_empty());
434 assert!(partitions.len() <= num_partitions);
435 assert_eq!(partitions.first().unwrap().0, 0);
436 assert_eq!(partitions.last().unwrap().1, data.len());
437
438 let mut previous_end = 0;
439 for (index, &(start, end)) in partitions.iter().enumerate() {
440 assert_eq!(start, previous_end, "gap or overlap at partition {index}");
441 assert!(end > start, "empty partition at index {index}");
442 if index + 1 < partitions.len() {
443 assert_eq!(data[end - 1], delimiter);
444 }
445 previous_end = end;
446 }
447 assert_eq!(previous_end, data.len());
448 }
449
450 #[test]
451 fn single_byte_oracle_matches_deterministic_corpus() {
452 let cases: &[(&[u8], usize, u8)] = &[
453 (b"", 4, b'\n'),
454 (b"x", 4, b'\n'),
455 (b"xxxx\n", 4, b'\n'),
456 (b"xx\nxx\n", 2, b'\n'),
457 (b"\n\n\n", 1, b'\n'),
458 (b"a\x00b\x00c", 2, 0),
459 (b"no delimiter", 3, b'\n'),
460 ];
461
462 for &(data, chunk_size, delimiter) in cases {
463 let expected = scalar_single_byte_boundaries(data, chunk_size, delimiter);
464 let actual = find_chunk_boundaries(data, chunk_size, delimiter);
465 assert_eq!(
466 actual, expected,
467 "mismatch for data={data:?}, chunk_size={chunk_size}, delimiter={delimiter:#04x}"
468 );
469 assert_cover(data, &expected);
470 }
471 }
472
473 #[test]
474 fn single_byte_oracle_matches_generated_cases() {
475 for case in 0..4096 {
476 let (data, chunk_size, delimiter) = generated_single_case(SINGLE_SEED, case);
477 let expected = scalar_single_byte_boundaries(&data, chunk_size, delimiter);
478 let actual = find_chunk_boundaries(&data, chunk_size, delimiter);
479 assert_eq!(
480 actual, expected,
481 "single-byte mismatch: seed={SINGLE_SEED:#018x}, case={case}, data={data:?}, chunk_size={chunk_size}, delimiter={delimiter:#04x}"
482 );
483 assert_cover(&data, &expected);
484 }
485 }
486
487 #[test]
488 fn cursor_ranges_match_single_byte_oracle() {
489 for case in 0..2048 {
490 let (data, chunk_size, delimiter) = generated_single_case(CURSOR_SEED, case);
491 let expected = scalar_single_byte_boundaries(&data, chunk_size, delimiter);
492 let eager = find_chunk_boundaries(&data, chunk_size, delimiter);
493 let cursor = cursor_ranges(&data, chunk_size, delimiter);
494 assert_eq!(
495 eager, expected,
496 "eager mismatch: seed={CURSOR_SEED:#018x}, case={case}"
497 );
498 assert_eq!(cursor, expected, "cursor mismatch: seed={CURSOR_SEED:#018x}, case={case}, data={data:?}, chunk_size={chunk_size}, delimiter={delimiter:#04x}");
499 assert_cover(&data, &cursor);
500 assert_eq!(cursor_ranges(&data, chunk_size, delimiter), cursor);
501 }
502 }
503
504 #[test]
505 fn pattern_oracle_matches_deterministic_fixtures() {
506 let cases: &[(&[u8], usize, &[u8])] = &[
507 (b"", 4, b"\r\n"),
508 (b"a\r\nb\r\nc", 4, b"\r\n"),
509 (b"a\x00\xff\x00b\x00\xff\x00c", 2, b"\x00\xff\x00"),
510 (b"aaaaaa", 1, b"aa"),
511 (b"prefixEND_RECORDsuffix", 3, b"END_RECORD"),
512 (b"no delimiter", 2, b"\r\n\r\n"),
513 (b"abc", 1, b"abcdef"),
514 (b"xx\r\n\r\nxx", 1, b"\r\n\r\n"),
515 ];
516
517 for &(data, chunk_size, pattern) in cases {
518 let expected = scalar_pattern_boundaries(data, chunk_size, pattern);
519 let actual = find_chunk_boundaries_pattern(data, chunk_size, pattern);
520 assert_eq!(
521 actual, expected,
522 "pattern mismatch for data={data:?}, chunk_size={chunk_size}, pattern={pattern:?}"
523 );
524 assert_cover(data, &expected);
525 }
526 }
527
528 #[test]
529 fn pattern_oracle_matches_generated_cases() {
530 for case in 0..4096 {
531 let (data, chunk_size, pattern) = generated_pattern_case(PATTERN_SEED, case);
532 let expected = scalar_pattern_boundaries(&data, chunk_size, &pattern);
533 let actual = find_chunk_boundaries_pattern(&data, chunk_size, &pattern);
534 assert_eq!(
535 actual, expected,
536 "pattern mismatch: seed={PATTERN_SEED:#018x}, case={case}, data={data:?}, chunk_size={chunk_size}, pattern={pattern:?}"
537 );
538 assert_cover(&data, &expected);
539 }
540 }
541
542 #[test]
543 fn pattern_cursor_ranges_match_pattern_oracle() {
544 for case in 0..2048 {
545 let (data, chunk_size, pattern) = generated_pattern_case(PATTERN_CURSOR_SEED, case);
546 let expected = scalar_pattern_boundaries(&data, chunk_size, &pattern);
547 let eager = find_chunk_boundaries_pattern(&data, chunk_size, &pattern);
548 let cursor = pattern_cursor_ranges(&data, chunk_size, &pattern);
549 assert_eq!(
550 eager, expected,
551 "eager pattern mismatch: seed={PATTERN_CURSOR_SEED:#018x}, case={case}"
552 );
553 assert_eq!(cursor, expected, "pattern cursor mismatch: seed={PATTERN_CURSOR_SEED:#018x}, case={case}, data={data:?}, chunk_size={chunk_size}, pattern={pattern:?}");
554 assert_cover(&data, &cursor);
555 assert_eq!(pattern_cursor_ranges(&data, chunk_size, &pattern), cursor);
556 }
557 }
558
559 #[test]
560 fn swar_matches_scalar_byte_search_across_offsets_and_lengths() {
561 const LENGTHS: &[usize] = &[
562 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127,
563 ];
564 let mut comparisons = 0usize;
565 for case in 0..16 {
566 let mut rng = Lcg::new(SWAR_SEED ^ case as u64);
567 for prefix in 0..8 {
568 for &len in LENGTHS {
569 let mut backing = vec![0; prefix + len];
570 for byte in &mut backing[prefix..] {
571 *byte = rng.next_u8();
572 }
573 let haystack = &backing[prefix..];
574 for delimiter in 0..=u8::MAX {
575 assert_eq!(
576 find_byte_swar(haystack, delimiter),
577 scalar_byte_position(haystack, delimiter),
578 "SWAR mismatch: seed={SWAR_SEED:#018x}, case={case}, prefix={prefix}, len={len}, delimiter={delimiter:#04x}, haystack={haystack:?}"
579 );
580 comparisons += 1;
581 }
582 }
583 }
584 }
585 assert_eq!(comparisons, 16 * 8 * LENGTHS.len() * 256);
586 }
587
588 #[test]
589 fn partition_oracle_matches_deterministic_fixtures() {
590 let cases: &[(&[u8], usize, u8)] = &[
591 (b"", 4, b'\n'),
592 (b"x", 1, b'\n'),
593 (b"no delimiter", 8, b'\n'),
594 (b"a\n\n\nb\n", 8, b'\n'),
595 (b"aa\nbbbb\ncccccccccccc\ndd\n", 4, b'\n'),
596 (b"aaaa\x00bbbb\x00cccc", 2, 0),
597 (b"123456789", 0, b'\n'),
598 (b"123456789", 1, b'\n'),
599 (b"123456789", 64, b'\n'),
600 ];
601
602 for &(data, num_partitions, delimiter) in cases {
603 let expected = scalar_partition_boundaries(data, num_partitions, delimiter);
604 let actual = find_partition_boundaries(data, num_partitions, delimiter);
605 assert_eq!(
606 actual, expected,
607 "partition mismatch for data={data:?}, n={num_partitions}, delimiter={delimiter:#04x}"
608 );
609 assert_partition_invariants(data, num_partitions, delimiter, &actual);
610 }
611
612 let mut giant = vec![b'x'; 10_000];
613 giant.extend_from_slice(b"\nsmall\nrecords\n");
614 let expected = scalar_partition_boundaries(&giant, 64, b'\n');
615 let actual = find_partition_boundaries(&giant, 64, b'\n');
616 assert_eq!(actual, expected);
617 assert_partition_invariants(&giant, 64, b'\n', &actual);
618 }
619
620 #[test]
621 fn partition_oracle_matches_generated_cases() {
622 for case in 0..4096 {
623 let (data, num_partitions, delimiter) = generated_partition_case(PARTITION_SEED, case);
624 let expected = scalar_partition_boundaries(&data, num_partitions, delimiter);
625 let actual = find_partition_boundaries(&data, num_partitions, delimiter);
626 assert_eq!(
627 actual, expected,
628 "partition mismatch: seed={PARTITION_SEED:#018x}, case={case}, data={data:?}, n={num_partitions}, delimiter={delimiter:#04x}"
629 );
630 assert_partition_invariants(&data, num_partitions, delimiter, &actual);
631 assert_eq!(
632 find_partition_boundaries(&data, num_partitions, delimiter),
633 actual
634 );
635 }
636 }
637
638 #[test]
639 fn partition_request_above_file_len_matches_file_len_oracle() {
640 for case in 0..4096 {
641 let (data, _, delimiter) = generated_partition_case(PARTITION_SEED, case);
642 if data.is_empty() {
643 continue;
644 }
645
646 let expected = find_partition_boundaries(&data, data.len(), delimiter);
647 for requested in [data.len() + 1, usize::MAX] {
648 assert_eq!(
649 find_partition_boundaries(&data, requested, delimiter),
650 expected,
651 "request above file length changed output: seed={PARTITION_SEED:#018x}, case={case}, data_len={}, requested={requested}, delimiter={delimiter:#04x}",
652 data.len()
653 );
654 }
655 }
656 }
657}
658
659#[derive(Debug, Clone)]
683pub struct ChunkCursor<'a> {
684 data: &'a [u8],
685 chunk_size: usize,
686 delimiter: u8,
687 position: usize,
688}
689
690impl<'a> ChunkCursor<'a> {
691 #[inline]
699 pub fn new(data: &'a [u8], chunk_size: usize, delimiter: u8) -> Self {
700 Self {
701 data,
702 chunk_size,
703 delimiter,
704 position: 0,
705 }
706 }
707
708 #[inline]
710 pub fn position(&self) -> usize {
711 self.position
712 }
713
714 #[inline]
716 pub fn len(&self) -> usize {
717 self.data.len()
718 }
719
720 #[inline]
722 pub fn is_empty(&self) -> bool {
723 self.position >= self.data.len()
724 }
725}
726
727impl<'a> Iterator for ChunkCursor<'a> {
728 type Item = &'a [u8];
729
730 fn next(&mut self) -> Option<&'a [u8]> {
731 let len = self.data.len();
732 if self.position >= len {
733 return None;
734 }
735
736 let step = self.chunk_size.max(1);
737 let target = self.position.saturating_add(step);
738 let end = if target >= len {
739 len
740 } else {
741 let remainder = &self.data[target..];
742 match find_byte_swar(remainder, self.delimiter) {
743 Some(rel_pos) => (target + rel_pos + 1).min(len),
744 None => len,
745 }
746 };
747
748 let chunk = &self.data[self.position..end];
749 self.position = end;
750 Some(chunk)
751 }
752
753 fn size_hint(&self) -> (usize, Option<usize>) {
754 let len = self.data.len();
755 if self.position >= len {
756 return (0, Some(0));
757 }
758 let remaining = len - self.position;
759 (1, Some(remaining))
760 }
761}
762
763fn find_pattern_in_slice(haystack: &[u8], pattern: &[u8]) -> Option<usize> {
773 let plen = pattern.len();
774 if plen == 0 || haystack.len() < plen {
775 return None;
776 }
777 if plen == 1 {
778 return find_byte_swar(haystack, pattern[0]);
779 }
780
781 let first_byte = pattern[0];
782 let hlen = haystack.len();
783 let max_search = hlen - plen;
784 let mut search_start = 0;
785
786 while search_start <= max_search {
787 let remainder = &haystack[search_start..];
788 let rel_pos = find_byte_swar(remainder, first_byte)?;
789 let pos = search_start + rel_pos;
790 if pos > max_search {
791 return None;
792 }
793 if haystack[pos..].starts_with(pattern) {
794 return Some(pos);
795 }
796 search_start = pos + 1;
797 }
798 None
799}
800
801pub fn find_chunk_boundaries_pattern(
814 data: &[u8],
815 chunk_size: usize,
816 delimiter: &[u8],
817) -> Vec<(usize, usize)> {
818 assert!(!delimiter.is_empty(), "delimiter must not be empty");
819 if data.is_empty() {
820 return Vec::new();
821 }
822
823 let dlen = delimiter.len();
824 let len = data.len();
825 let step = chunk_size.max(1);
826 let estimate = (len / step) + 2;
827 let mut chunks = Vec::with_capacity(estimate);
828
829 let mut start = 0usize;
830
831 while start < len {
832 let mut end = start.saturating_add(step);
833
834 if end >= len {
835 end = len;
836 } else {
837 let remainder = &data[end..];
838 if let Some(rel_pos) = find_pattern_in_slice(remainder, delimiter) {
839 end = end + rel_pos + dlen;
840 if end > len {
841 end = len;
842 }
843 } else {
844 end = len;
845 }
846 }
847
848 chunks.push((start, end));
849 start = end;
850 }
851
852 chunks
853}
854
855#[derive(Debug, Clone)]
879pub struct PatternChunkCursor<'a, 'p> {
880 data: &'a [u8],
881 chunk_size: usize,
882 delimiter: &'p [u8],
883 position: usize,
884}
885
886impl<'a, 'p> PatternChunkCursor<'a, 'p> {
887 #[inline]
893 pub fn new(data: &'a [u8], chunk_size: usize, delimiter: &'p [u8]) -> Self {
894 assert!(!delimiter.is_empty(), "delimiter must not be empty");
895 Self {
896 data,
897 chunk_size,
898 delimiter,
899 position: 0,
900 }
901 }
902
903 #[inline]
904 pub fn position(&self) -> usize {
905 self.position
906 }
907
908 #[inline]
909 pub fn len(&self) -> usize {
910 self.data.len()
911 }
912
913 #[inline]
915 pub fn is_empty(&self) -> bool {
916 self.position >= self.data.len()
917 }
918}
919
920impl<'a, 'p> Iterator for PatternChunkCursor<'a, 'p> {
921 type Item = &'a [u8];
922
923 fn next(&mut self) -> Option<&'a [u8]> {
924 let len = self.data.len();
925 if self.position >= len {
926 return None;
927 }
928
929 let dlen = self.delimiter.len();
930 let step = self.chunk_size.max(1);
931 let target = self.position.saturating_add(step);
932 let end = if target >= len {
933 len
934 } else {
935 let remainder = &self.data[target..];
936 match find_pattern_in_slice(remainder, self.delimiter) {
937 Some(rel_pos) => (target + rel_pos + dlen).min(len),
938 None => len,
939 }
940 };
941
942 let chunk = &self.data[self.position..end];
943 self.position = end;
944 Some(chunk)
945 }
946
947 fn size_hint(&self) -> (usize, Option<usize>) {
948 let len = self.data.len();
949 if self.position >= len {
950 return (0, Some(0));
951 }
952 let remaining = len - self.position;
953 (1, Some(remaining))
954 }
955}
956
957pub(crate) fn find_byte_swar(haystack: &[u8], delimiter: u8) -> Option<usize> {
965 let len = haystack.len();
966 if len == 0 {
967 return None;
968 }
969
970 let pattern = (delimiter as u64).wrapping_mul(0x0101010101010101u64);
971 let lo = 0x0101010101010101u64;
972 let hi = 0x8080808080808080u64;
973
974 let mut i = 0usize;
975
976 let ptr = haystack.as_ptr() as usize;
978 let align = ptr % 8;
979 if align != 0 {
980 let prefix_end = (8 - align).min(len);
981 while i < prefix_end {
982 if haystack[i] == delimiter {
983 return Some(i);
984 }
985 i += 1;
986 }
987 }
988
989 while i + 8 <= len {
991 let chunk: [u8; 8] = haystack[i..i + 8].try_into().unwrap();
992 let word = u64::from_ne_bytes(chunk);
993 let xored = word ^ pattern;
994
995 let has_zero = xored.wrapping_sub(lo) & !xored & hi;
996 if has_zero != 0 {
997 return Some(i + (has_zero.trailing_zeros() / 8) as usize);
998 }
999 i += 8;
1000 }
1001
1002 while i < len {
1004 if haystack[i] == delimiter {
1005 return Some(i);
1006 }
1007 i += 1;
1008 }
1009
1010 None
1011}
1012
1013#[inline]
1018pub fn fixed_chunk_count(file_len: usize, chunk_size: usize) -> usize {
1019 if file_len == 0 {
1020 return 0;
1021 }
1022 file_len.div_ceil(chunk_size.max(1))
1023}
1024
1025#[inline]
1032pub fn fixed_chunk_bounds(
1033 file_len: usize,
1034 chunk_size: usize,
1035 index: usize,
1036) -> Option<(usize, usize)> {
1037 let effective_size = chunk_size.max(1);
1038 if file_len == 0 {
1039 return None;
1040 }
1041 let count = file_len.div_ceil(effective_size);
1042 if index >= count {
1043 return None;
1044 }
1045 let start = index.saturating_mul(effective_size);
1046
1047 let raw_end = start.saturating_add(effective_size);
1048 let end = if raw_end > file_len {
1049 file_len
1050 } else {
1051 raw_end
1052 };
1053
1054 Some((start, end))
1055}
1056
1057pub fn find_partition_boundaries(
1094 data: &[u8],
1095 num_partitions: usize,
1096 delimiter: u8,
1097) -> Vec<(usize, usize)> {
1098 let file_len = data.len();
1099 if file_len == 0 || num_partitions == 0 {
1100 return Vec::new();
1101 }
1102 if num_partitions == 1 {
1103 return vec![(0, file_len)];
1104 }
1105
1106 let n = num_partitions.min(file_len);
1113
1114 let mut boundaries = Vec::new();
1115 let mut last_boundary: usize = 0;
1116
1117 for i in 1..n {
1118 let target = ((file_len as u128) * (i as u128) / (n as u128)) as usize;
1120 if target <= last_boundary {
1121 continue;
1122 }
1123
1124 let remainder = &data[target..];
1125 match find_byte_swar(remainder, delimiter) {
1126 Some(rel_pos) => {
1127 let boundary = target + rel_pos + 1;
1128 let boundary = boundary.min(file_len);
1129 boundaries.push(boundary);
1130 last_boundary = boundary;
1131 }
1132 None => {
1133 boundaries.push(file_len);
1134 break;
1135 }
1136 }
1137 }
1138
1139 let effective_n = boundaries.len() + 1;
1140 let mut partitions = Vec::with_capacity(effective_n);
1141
1142 let mut prev = 0usize;
1143 for &b in &boundaries {
1144 if b > prev {
1145 partitions.push((prev, b));
1146 }
1147 prev = b;
1148 }
1149
1150 if prev < file_len {
1151 partitions.push((prev, file_len));
1152 }
1153
1154 partitions
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159 use super::*;
1160
1161 #[test]
1162 fn test_empty_input() {
1163 assert_eq!(find_chunk_boundaries(b"", 1024, b'\n'), vec![]);
1164 }
1165
1166 #[test]
1167 fn test_smaller_than_chunk() {
1168 let data = b"hello\nworld\n";
1169 let chunks = find_chunk_boundaries(data, 1024, b'\n');
1170 assert_eq!(chunks, vec![(0, 12)]);
1171 }
1172
1173 #[test]
1174 fn test_fixed_lines() {
1175 let data = b"aaa\nbbb\nccc\nddd\neee\n";
1176 let chunks = find_chunk_boundaries(data, 6, b'\n');
1177 assert_eq!(chunks.len(), 3);
1178 assert_eq!(&data[chunks[0].0..chunks[0].1], b"aaa\nbbb\n");
1179 assert_eq!(&data[chunks[1].0..chunks[1].1], b"ccc\nddd\n");
1180 assert_eq!(&data[chunks[2].0..chunks[2].1], b"eee\n");
1181 }
1182
1183 #[test]
1184 fn test_no_delimiter_in_large_remainder() {
1185 let data = b"aaa\nbbb\ncccccccccccccccc";
1186 let chunks = find_chunk_boundaries(data, 4, b'\n');
1187 assert_eq!(chunks.len(), 2);
1188 assert_eq!(chunks[0], (0, 4 + 3 + 1)); assert_eq!(chunks[1], (8, data.len()));
1190 }
1191
1192 #[test]
1193 fn test_no_delimiter() {
1194 let data = b"no_newlines_here";
1195 let chunks = find_chunk_boundaries(data, 5, b'\n');
1196 assert_eq!(chunks, vec![(0, data.len())]);
1197 }
1198
1199 #[test]
1200 fn test_sequential_coverage() {
1201 let data = b"line1\nline2\nline3\nline4\nline5\n";
1202 let chunks = find_chunk_boundaries(data, 10, b'\n');
1203 let mut pos = 0;
1204 for (start, end) in &chunks {
1205 assert_eq!(*start, pos);
1206 pos = *end;
1207 }
1208 assert_eq!(pos, data.len());
1209 }
1210
1211 #[test]
1212 fn test_chunk_size_zero_clamps_to_one() {
1213 let data = b"abc\n";
1214 let chunks = find_chunk_boundaries(data, 0, b'\n');
1215 assert!(!chunks.is_empty());
1216 }
1217
1218 #[test]
1219 fn test_chunk_size_one() {
1220 let data = b"a\nb\nc\n";
1221 let chunks = find_chunk_boundaries(data, 1, b'\n');
1222 let mut pos = 0;
1223 for (start, end) in &chunks {
1224 assert_eq!(*start, pos);
1225 assert!(end > start);
1226 pos = *end;
1227 }
1228 assert_eq!(pos, data.len());
1229 }
1230
1231 #[test]
1232 fn test_no_trailing_newline() {
1233 let data = b"hello\nworld";
1234 let chunks = find_chunk_boundaries(data, 100, b'\n');
1235 assert_eq!(chunks, vec![(0, 11)]);
1236 }
1237
1238 #[test]
1239 fn test_only_newlines() {
1240 let data = b"\n\n\n";
1241 let chunks = find_chunk_boundaries(data, 1, b'\n');
1242 let mut pos = 0;
1243 for (start, end) in &chunks {
1244 assert_eq!(*start, pos);
1245 pos = *end;
1246 }
1247 assert_eq!(pos, data.len());
1248 }
1249
1250 #[test]
1251 fn test_consecutive_delimiters() {
1252 let data = b"line1\n\n\nline2\n";
1253 let chunks = find_chunk_boundaries(data, 6, b'\n');
1254 let mut pos = 0;
1255 for (start, end) in &chunks {
1256 assert_eq!(*start, pos);
1257 pos = *end;
1258 }
1259 assert_eq!(pos, data.len());
1260 }
1261
1262 #[test]
1263 fn test_record_larger_than_chunk() {
1264 let data = b"short\nverylonglinewithnoddelimiteratall\nshort\n";
1265 let chunks = find_chunk_boundaries(data, 6, b'\n');
1266 let mut pos = 0;
1267 for (start, end) in &chunks {
1268 assert_eq!(*start, pos);
1269 pos = *end;
1270 }
1271 assert_eq!(pos, data.len());
1272 }
1273
1274 #[test]
1275 fn test_binary_with_nul() {
1276 let data = b"prefix\x00suffix\n";
1277 let chunks = find_chunk_boundaries(data, 100, b'\n');
1278 assert_eq!(chunks, vec![(0, 14)]);
1279 }
1280
1281 #[test]
1282 fn test_chunk_size_larger_than_data() {
1283 let data = b"tiny\n";
1284 let chunks = find_chunk_boundaries(data, 1_000_000, b'\n');
1285 assert_eq!(chunks, vec![(0, 5)]);
1286 }
1287
1288 #[test]
1289 fn test_chunk_size_exact_multiple() {
1290 let data = b"xxxx\n";
1291 let chunks = find_chunk_boundaries(data, 5, b'\n');
1292 assert_eq!(chunks, vec![(0, 5)]);
1293 }
1294
1295 #[test]
1296 fn test_repeated_scan_same_size() {
1297 let data = b"a\nb\nc\n";
1298 let chunks1 = find_chunk_boundaries(data, 2, b'\n');
1299 let chunks2 = find_chunk_boundaries(data, 2, b'\n');
1300 assert_eq!(chunks1, chunks2);
1301 }
1302
1303 #[test]
1304 fn test_repeated_scan_different_size() {
1305 let data = b"aaa\nbbb\nccc\nddd\n";
1306 let chunks1 = find_chunk_boundaries(data, 4, b'\n');
1307 let chunks2 = find_chunk_boundaries(data, 8, b'\n');
1308 assert_ne!(chunks1, chunks2);
1309 let sum1: usize = chunks1.iter().map(|(s, e)| e - s).sum();
1310 let sum2: usize = chunks2.iter().map(|(s, e)| e - s).sum();
1311 assert_eq!(sum1, data.len());
1312 assert_eq!(sum2, data.len());
1313 }
1314
1315 #[test]
1316 fn test_one_byte_file() {
1317 let data = b"x";
1318 let chunks = find_chunk_boundaries(data, 1024, b'\n');
1319 assert_eq!(chunks, vec![(0, 1)]);
1320 }
1321
1322 #[test]
1323 fn test_one_byte_newline() {
1324 let data = b"\n";
1325 let chunks = find_chunk_boundaries(data, 1024, b'\n');
1326 assert_eq!(chunks, vec![(0, 1)]);
1327 }
1328
1329 #[test]
1330 fn property_concatenation_equals_input() {
1331 let cases: &[(&[u8], usize, u8)] = &[
1332 (b"hello\nworld\n", 4, b'\n'),
1333 (b"a,b,c,d", 2, b','),
1334 (b"one\ttwo\tthree", 4, b'\t'),
1335 (b"a|b|c|d|e|f", 3, b'|'),
1336 (b"x\x00y\x00z", 2, b'\x00'),
1337 (b"single", 1024, b'\n'),
1338 (b"\n\n\n\n\n", 1, b'\n'),
1339 (b"", 1024, b'\n'),
1340 (b"\n", 1, b'\n'),
1341 (b"a", 1024, b'\n'),
1342 ];
1343 for &(data, chunk_size, delim) in cases {
1344 let chunks = find_chunk_boundaries(data, chunk_size, delim);
1345 let total: usize = chunks.iter().map(|(s, e)| e - s).sum();
1346 assert_eq!(total, data.len(), "concatenation property failed");
1347 }
1348 }
1349
1350 #[test]
1351 fn property_no_gaps() {
1352 let cases: &[(&[u8], usize, u8)] = &[
1353 (b"a\nb\nc\n", 2, b'\n'),
1354 (b"a,b,c,d,e", 1, b','),
1355 (b"a\tb\tc\t", 2, b'\t'),
1356 ];
1357 for &(data, chunk_size, delim) in cases {
1358 let chunks = find_chunk_boundaries(data, chunk_size, delim);
1359 if chunks.is_empty() {
1360 continue;
1361 }
1362 assert_eq!(chunks[0].0, 0, "first chunk must start at 0");
1363 for i in 1..chunks.len() {
1364 assert_eq!(
1365 chunks[i].0,
1366 chunks[i - 1].1,
1367 "gap at chunk {}->{}",
1368 i - 1,
1369 i
1370 );
1371 }
1372 assert_eq!(
1373 chunks.last().unwrap().1,
1374 data.len(),
1375 "last chunk must end at EOF"
1376 );
1377 }
1378 }
1379
1380 #[test]
1381 fn property_determinism() {
1382 let data = b"line1\nline2\nline3\nline4\nline5\n";
1383 let chunks1 = find_chunk_boundaries(data, 10, b'\n');
1384 let chunks2 = find_chunk_boundaries(data, 10, b'\n');
1385 assert_eq!(chunks1, chunks2);
1386 let chunks3 = find_chunk_boundaries(data, 10, b'\n');
1387 assert_eq!(chunks1, chunks3);
1388 }
1389
1390 #[test]
1391 fn property_monotonic_offsets() {
1392 let data = b"x\nxx\nxxx\nxxxx\nxxxxx\n";
1393 let chunks = find_chunk_boundaries(data, 1, b'\n');
1394 let mut last_end = 0usize;
1395 for (start, end) in &chunks {
1396 assert!(*start >= last_end, "offsets must be monotonic");
1397 assert!(*end > *start, "chunk must be non-empty");
1398 last_end = *end;
1399 }
1400 }
1401
1402 #[test]
1403 fn test_alternative_delimiters() {
1404 assert_eq!(
1405 find_chunk_boundaries(b"a,b,c,d,e", 2, b','),
1406 vec![(0, 4), (4, 8), (8, 9)]
1407 );
1408 assert_eq!(
1409 find_chunk_boundaries(b"one\ttwo\tthree", 4, b'\t'),
1410 vec![(0, 8), (8, 13)]
1411 );
1412 assert_eq!(
1413 find_chunk_boundaries(b"a|b|c|d|e|f", 3, b'|'),
1414 vec![(0, 4), (4, 8), (8, 11)]
1415 );
1416 assert_eq!(
1417 find_chunk_boundaries(b"x\x00y\x00z", 2, b'\x00'),
1418 vec![(0, 4), (4, 5)]
1419 );
1420 }
1421
1422 fn cursor_equals_eager(data: &[u8], chunk_size: usize, delimiter: u8) {
1426 let eager = find_chunk_boundaries(data, chunk_size, delimiter);
1427 let cursor: Vec<&[u8]> = ChunkCursor::new(data, chunk_size, delimiter).collect();
1428 let lazy_ranges: Vec<(usize, usize)> = cursor
1429 .iter()
1430 .scan(0usize, |pos, &chunk| {
1431 let start = *pos;
1432 *pos += chunk.len();
1433 Some((start, *pos))
1434 })
1435 .collect();
1436 assert_eq!(
1437 lazy_ranges, eager,
1438 "cursor mismatch: chunk_size={chunk_size}, delim={delimiter:#04x}"
1439 );
1440 }
1441
1442 #[test]
1443 fn cursor_empty_input() {
1444 assert_eq!(
1445 ChunkCursor::new(b"", 1024, b'\n').collect::<Vec<_>>(),
1446 Vec::<&[u8]>::new()
1447 );
1448 }
1449
1450 #[test]
1451 fn cursor_one_byte_file() {
1452 cursor_equals_eager(b"x", 1024, b'\n');
1453 }
1454
1455 #[test]
1456 fn cursor_one_byte_delimiter() {
1457 cursor_equals_eager(b"\n", 1024, b'\n');
1458 }
1459
1460 #[test]
1461 fn cursor_delimiter_only_file() {
1462 cursor_equals_eager(b"\n\n\n", 1, b'\n');
1463 }
1464
1465 #[test]
1466 fn cursor_no_delimiter() {
1467 cursor_equals_eager(b"no_newlines_here", 5, b'\n');
1468 }
1469
1470 #[test]
1471 fn cursor_delimiter_exactly_at_target() {
1472 cursor_equals_eager(b"xxxx\n", 5, b'\n');
1473 }
1474
1475 #[test]
1476 fn cursor_delimiter_after_target() {
1477 cursor_equals_eager(b"xxxyy\n", 3, b'\n');
1478 }
1479
1480 #[test]
1481 fn cursor_multiple_consecutive_delimiters() {
1482 cursor_equals_eager(b"line1\n\n\nline2\n", 6, b'\n');
1483 }
1484
1485 #[test]
1486 fn cursor_nul_delimiter() {
1487 cursor_equals_eager(b"prefix\x00suffix\n", 100, b'\n');
1488 }
1489
1490 #[test]
1491 fn cursor_giant_record() {
1492 cursor_equals_eager(b"tiny\nverylongrecordwithnobreaksanywhere\nend\n", 6, b'\n');
1493 }
1494
1495 #[test]
1496 fn cursor_no_trailing_delimiter() {
1497 cursor_equals_eager(b"hello\nworld", 100, b'\n');
1498 }
1499
1500 #[test]
1501 fn cursor_chunk_size_zero() {
1502 cursor_equals_eager(b"abc\n", 0, b'\n');
1503 }
1504
1505 #[test]
1506 fn cursor_chunk_size_one() {
1507 cursor_equals_eager(b"a\nb\nc\n", 1, b'\n');
1508 }
1509
1510 #[test]
1511 fn cursor_chunk_size_larger_than_data() {
1512 cursor_equals_eager(b"tiny\n", 1_000_000, b'\n');
1513 }
1514
1515 #[test]
1516 fn cursor_chunk_size_equals_len() {
1517 cursor_equals_eager(b"aaaa\n", 5, b'\n');
1518 }
1519
1520 #[test]
1521 fn cursor_different_delimiters() {
1522 cursor_equals_eager(b"a,b,c,d,e", 2, b',');
1523 cursor_equals_eager(b"one\ttwo\tthree", 4, b'\t');
1524 cursor_equals_eager(b"a|b|c|d|e|f", 3, b'|');
1525 cursor_equals_eager(b"x\x00y\x00z", 2, b'\x00');
1526 }
1527
1528 #[test]
1529 fn cursor_binary_input() {
1530 let data: Vec<u8> = (0u8..=255).collect();
1531 cursor_equals_eager(&data, 32, b'\n');
1532 cursor_equals_eager(&data, 32, 0x00);
1533 cursor_equals_eager(&data, 32, 0xff);
1534 }
1535
1536 #[test]
1537 fn cursor_repeated_iteration_new_cursor() {
1538 let data = b"a\nb\nc\n";
1539 let first: Vec<&[u8]> = ChunkCursor::new(data, 2, b'\n').collect();
1540 let second: Vec<&[u8]> = ChunkCursor::new(data, 2, b'\n').collect();
1541 assert_eq!(first, second);
1542 }
1543
1544 #[test]
1545 fn cursor_fixed_lines_equivalence() {
1546 let data = b"aaa\nbbb\nccc\nddd\neee\n";
1547 cursor_equals_eager(data, 6, b'\n');
1548 }
1549
1550 #[test]
1551 fn cursor_only_newlines() {
1552 cursor_equals_eager(b"\n\n\n", 1, b'\n');
1553 }
1554
1555 #[test]
1556 fn cursor_record_larger_than_chunk() {
1557 let data = b"short\nverylonglinewithnodelimiteratall\nshort\n";
1558 cursor_equals_eager(data, 6, b'\n');
1559 }
1560
1561 #[test]
1562 fn cursor_deterministic_random_corpus() {
1563 let cases: &[(&[u8], usize, u8)] = &[
1564 (b"hello\nworld\n", 4, b'\n'),
1565 (b"a,b,c,d", 2, b','),
1566 (b"one\ttwo\tthree", 4, b'\t'),
1567 (b"a|b|c|d|e|f", 3, b'|'),
1568 (b"x\x00y\x00z", 2, b'\x00'),
1569 (b"single", 1024, b'\n'),
1570 (b"\n\n\n\n\n", 1, b'\n'),
1571 (b"\n", 1, b'\n'),
1572 (b"a", 1024, b'\n'),
1573 (b"line1\nline2\nline3\nline4\nline5\n", 10, b'\n'),
1574 ];
1575 for &(data, chunk_size, delim) in cases {
1576 cursor_equals_eager(data, chunk_size, delim);
1577 }
1578 }
1579
1580 #[test]
1581 fn cursor_large_corpus_equivalence() {
1582 let mut data = Vec::new();
1583 for i in 0..1000u32 {
1584 data.extend_from_slice(format!("line_content_{i:04}\n").as_bytes());
1585 }
1586 cursor_equals_eager(&data, 64, b'\n');
1587 }
1588
1589 #[test]
1592 fn cursor_size_hint_before_iteration() {
1593 let data = b"aaa\nbbb\nccc\nddd\neee\n";
1594 let cur = ChunkCursor::new(data, 6, b'\n');
1595 let (lo, hi) = cur.size_hint();
1596 assert_eq!(lo, 1);
1597 assert_eq!(hi, Some(data.len()));
1598 }
1599
1600 #[test]
1601 fn cursor_size_hint_after_one_next() {
1602 let data = b"aaa\nbbb\nccc\nddd\neee\n";
1603 let mut cur = ChunkCursor::new(data, 6, b'\n');
1604 let _ = cur.next();
1605 let (lo, hi) = cur.size_hint();
1606 assert_eq!(lo, 1);
1607 assert!(hi.unwrap() < data.len());
1608 }
1609
1610 #[test]
1611 fn cursor_size_hint_after_exhaustion() {
1612 let data = b"hello\n";
1613 let mut cur = ChunkCursor::new(data, 1024, b'\n');
1614 let _ = cur.next();
1615 let (lo, hi) = cur.size_hint();
1616 assert_eq!(lo, 0);
1617 assert_eq!(hi, Some(0));
1618 }
1619
1620 #[test]
1621 fn cursor_size_hint_empty_input() {
1622 let cur = ChunkCursor::new(b"", 1024, b'\n');
1623 let (lo, hi) = cur.size_hint();
1624 assert_eq!(lo, 0);
1625 assert_eq!(hi, Some(0));
1626 }
1627
1628 #[test]
1629 fn cursor_is_empty_before_iteration() {
1630 let data = b"hello\nworld";
1631 let cur = ChunkCursor::new(data, 100, b'\n');
1632 assert!(!cur.is_empty());
1633 }
1634
1635 #[test]
1636 fn cursor_is_empty_after_exhaustion() {
1637 let data = b"hello\n";
1638 let mut cur = ChunkCursor::new(data, 1024, b'\n');
1639 let _ = cur.next(); assert!(cur.is_empty());
1641 }
1642
1643 #[test]
1644 fn cursor_is_empty_empty_input() {
1645 let cur = ChunkCursor::new(b"", 1024, b'\n');
1646 assert!(cur.is_empty());
1647 }
1648
1649 #[test]
1650 fn cursor_size_hint_no_delimiter() {
1651 let data = b"no_newlines_at_all";
1652 let cur = ChunkCursor::new(data, 5, b'\n');
1653 let (lo, hi) = cur.size_hint();
1654 assert_eq!(lo, 1, "at least one chunk even with no delimiter");
1655 assert_eq!(hi, Some(data.len()));
1656 }
1657
1658 #[test]
1659 fn cursor_size_hint_chunk_size_zero() {
1660 let data = b"abcd\n";
1661 let cur = ChunkCursor::new(data, 0, b'\n');
1662 let (lo, _) = cur.size_hint();
1663 assert_eq!(lo, 1);
1664 }
1665
1666 #[test]
1667 fn cursor_size_hint_chunk_size_one() {
1668 let data = b"a\nb\nc\n";
1669 let cur = ChunkCursor::new(data, 1, b'\n');
1670 let (lo, _) = cur.size_hint();
1671 assert_eq!(lo, 1);
1672 }
1673
1674 #[test]
1675 fn cursor_size_hint_chunk_size_larger_than_data() {
1676 let data = b"tiny\n";
1677 let cur = ChunkCursor::new(data, 1_000_000, b'\n');
1678 let (lo, hi) = cur.size_hint();
1679 assert_eq!(lo, 1);
1680 assert_eq!(hi, Some(data.len()));
1681 }
1682
1683 #[test]
1684 fn property_size_hint_lower_bound_accurate() {
1685 let cases: &[(&[u8], usize, u8)] = &[
1686 (b"hello\nworld\n", 4, b'\n'),
1687 (b"a,b,c,d", 2, b','),
1688 (b"one\ttwo\tthree", 4, b'\t'),
1689 (b"a|b|c|d|e|f", 3, b'|'),
1690 (b"x\x00y\x00z", 2, b'\x00'),
1691 (b"single", 1024, b'\n'),
1692 (b"\n\n\n\n\n", 1, b'\n'),
1693 (b"", 1024, b'\n'),
1694 (b"\n", 1, b'\n'),
1695 (b"a", 1024, b'\n'),
1696 (b"a\nb\nc\n", 1, b'\n'),
1697 (b"aaaa\n", 5, b'\n'),
1698 ];
1699 for &(data, cs, delim) in cases {
1700 let mut cur = ChunkCursor::new(data, cs, delim);
1701 let mut total_yielded = 0usize;
1702 loop {
1703 let (lo, hi) = cur.size_hint();
1704 let remaining = cur.len() - cur.position();
1705 if remaining == 0 {
1706 assert_eq!(lo, 0);
1707 assert_eq!(hi, Some(0));
1708 break;
1709 }
1710 assert!(
1711 lo <= remaining,
1712 "lower bound {lo} exceeds remaining {remaining}: data={data:?} cs={cs}"
1713 );
1714 if let Some(next) = cur.next() {
1715 total_yielded += next.len();
1716 }
1717 }
1718 assert_eq!(total_yielded, data.len());
1719 }
1720 }
1721
1722 #[test]
1723 fn property_size_hint_upper_bound_accurate() {
1724 let cases: &[(&[u8], usize, u8)] = &[
1725 (b"hello\nworld\n", 4, b'\n'),
1726 (b"a,b,c,d", 2, b','),
1727 (b"a|b|c|d|e|f", 3, b'|'),
1728 (b"\n\n\n", 1, b'\n'),
1729 ];
1730 for &(data, cs, delim) in cases {
1731 let mut cur = ChunkCursor::new(data, cs, delim);
1732 loop {
1733 let (_, hi) = cur.size_hint();
1734 let remaining = cur.len() - cur.position();
1735 if remaining == 0 {
1736 assert_eq!(hi, Some(0));
1737 break;
1738 }
1739 assert!(
1740 hi.unwrap() >= remaining,
1741 "upper bound {} < remaining {}: data={data:?} cs={cs}",
1742 hi.unwrap(),
1743 remaining
1744 );
1745 let _ = cur.next();
1746 }
1747 }
1748 }
1749
1750 #[test]
1753 fn pattern_cursor_size_hint_before_iteration() {
1754 let data = b"a\r\nb\r\nc\r\n";
1755 let cur = PatternChunkCursor::new(data, 4, b"\r\n");
1756 let (lo, hi) = cur.size_hint();
1757 assert_eq!(lo, 1);
1758 assert_eq!(hi, Some(data.len()));
1759 }
1760
1761 #[test]
1762 fn pattern_cursor_size_hint_after_one_next() {
1763 let data = b"a\r\nb\r\nc\r\n";
1764 let mut cur = PatternChunkCursor::new(data, 4, b"\r\n");
1765 let _ = cur.next();
1766 let (lo, hi) = cur.size_hint();
1767 assert_eq!(lo, 1);
1768 assert!(hi.unwrap() < data.len());
1769 }
1770
1771 #[test]
1772 fn pattern_cursor_size_hint_after_exhaustion() {
1773 let data = b"a\r\n";
1774 let mut cur = PatternChunkCursor::new(data, 1024, b"\r\n");
1775 let _ = cur.next();
1776 let (lo, hi) = cur.size_hint();
1777 assert_eq!(lo, 0);
1778 assert_eq!(hi, Some(0));
1779 }
1780
1781 #[test]
1782 fn pattern_cursor_size_hint_empty_input() {
1783 let cur = PatternChunkCursor::new(b"", 1024, b"\r\n");
1784 let (lo, hi) = cur.size_hint();
1785 assert_eq!(lo, 0);
1786 assert_eq!(hi, Some(0));
1787 }
1788
1789 #[test]
1790 fn pattern_cursor_is_empty_before_iteration() {
1791 let data = b"hello\r\nworld";
1792 let cur = PatternChunkCursor::new(data, 100, b"\r\n");
1793 assert!(!cur.is_empty());
1794 }
1795
1796 #[test]
1797 fn pattern_cursor_is_empty_after_exhaustion() {
1798 let data = b"hello\r\n";
1799 let mut cur = PatternChunkCursor::new(data, 1024, b"\r\n");
1800 let _ = cur.next();
1801 assert!(cur.is_empty());
1802 }
1803
1804 #[test]
1805 fn pattern_cursor_is_empty_empty_input() {
1806 let cur = PatternChunkCursor::new(b"", 1024, b"\r\n");
1807 assert!(cur.is_empty());
1808 }
1809
1810 #[test]
1811 fn pattern_cursor_size_hint_no_delimiter() {
1812 let data = b"no_crlf_here";
1813 let cur = PatternChunkCursor::new(data, 5, b"\r\n");
1814 let (lo, _) = cur.size_hint();
1815 assert_eq!(lo, 1);
1816 }
1817
1818 #[test]
1819 fn pattern_property_size_hint_lower_bound_accurate() {
1820 let cases: &[(&[u8], usize, &[u8])] = &[
1821 (b"a\r\nb\r\nc\r\n", 4, b"\r\n"),
1822 (b"ab||cd||ef", 4, b"||"),
1823 (b"single", 1024, b"\r\n"),
1824 (b"AB\xff\x00CD\xff\x00EF", 4, b"\xff\x00"),
1825 (b"\r\n\r\n\r\n", 1, b"\r\n"),
1826 ];
1827 for &(data, cs, delim) in cases {
1828 let mut cur = PatternChunkCursor::new(data, cs, delim);
1829 let mut total_yielded = 0usize;
1830 loop {
1831 let (lo, hi) = cur.size_hint();
1832 let remaining = cur.len() - cur.position();
1833 if remaining == 0 {
1834 assert_eq!(lo, 0);
1835 assert_eq!(hi, Some(0));
1836 break;
1837 }
1838 assert!(
1839 lo <= remaining,
1840 "lower bound {lo} exceeds remaining {remaining}"
1841 );
1842 if let Some(next) = cur.next() {
1843 total_yielded += next.len();
1844 }
1845 }
1846 assert_eq!(total_yielded, data.len());
1847 }
1848 }
1849
1850 #[test]
1853 #[ignore = "performance experiment — run with --ignored --nocapture"]
1854 fn bench_cursor_vs_eager() {
1855 use std::hint::black_box;
1856 use std::time::Instant;
1857
1858 const SAMPLES: usize = 7;
1859
1860 fn gen_log_data(target_size: usize) -> Vec<u8> {
1861 let mut data = Vec::with_capacity(target_size);
1862 let mut n = 0u64;
1863 while data.len() < target_size {
1864 let line = format!(
1865 "[2026-08-08T12:00:00Z] INFO request_id={} status=200 latency_ms={}\n",
1866 n,
1867 n % 100
1868 );
1869 data.extend_from_slice(line.as_bytes());
1870 n += 1;
1871 }
1872 data.truncate(target_size);
1873 data
1874 }
1875
1876 fn elapsed_per_iter(iters: u64, f: impl Fn()) -> f64 {
1877 let start = Instant::now();
1878 for _ in 0..iters {
1879 f();
1880 }
1881 start.elapsed().as_nanos() as f64 / iters as f64
1882 }
1883
1884 fn samples_median(mut ns: Vec<f64>) -> f64 {
1885 ns.sort_by(|a, b| a.partial_cmp(b).unwrap());
1886 ns[ns.len() / 2]
1887 }
1888
1889 fn samples_p10(mut ns: Vec<f64>) -> f64 {
1890 ns.sort_by(|a, b| a.partial_cmp(b).unwrap());
1891 let idx = ((ns.len() - 1) as f64 * 0.10) as usize;
1892 ns[idx]
1893 }
1894
1895 fn samples_p90(mut ns: Vec<f64>) -> f64 {
1896 ns.sort_by(|a, b| a.partial_cmp(b).unwrap());
1897 let idx = ((ns.len() - 1) as f64 * 0.90) as usize;
1898 ns[idx]
1899 }
1900
1901 fn bench_one(
1902 data: &[u8],
1903 chunk_size: usize,
1904 delim: u8,
1905 label: &str,
1906 iters: u64,
1907 eager_chunks: usize,
1908 lazy_chunks: usize,
1909 ) {
1910 let total_bytes = data.len();
1911
1912 let mut tfc_eager_ns: Vec<f64> = Vec::with_capacity(SAMPLES);
1913 let mut tfc_lazy_ns: Vec<f64> = Vec::with_capacity(SAMPLES);
1914 let mut full_eager_ns: Vec<f64> = Vec::with_capacity(SAMPLES);
1915 let mut full_lazy_ns: Vec<f64> = Vec::with_capacity(SAMPLES);
1916
1917 for _ in 0..SAMPLES {
1918 tfc_eager_ns.push(elapsed_per_iter(iters, || {
1919 let d = black_box(data);
1920 let cs = black_box(chunk_size);
1921 let dl = black_box(delim);
1922 let chunks = find_chunk_boundaries(d, cs, dl);
1923 black_box(chunks.first().copied());
1924 }));
1925
1926 tfc_lazy_ns.push(elapsed_per_iter(iters, || {
1927 let d = black_box(data);
1928 let cs = black_box(chunk_size);
1929 let dl = black_box(delim);
1930 let mut cursor = ChunkCursor::new(d, cs, dl);
1931 black_box(cursor.next());
1932 }));
1933
1934 full_eager_ns.push(elapsed_per_iter(iters, || {
1935 let d = black_box(data);
1936 let cs = black_box(chunk_size);
1937 let dl = black_box(delim);
1938 let chunks = find_chunk_boundaries(d, cs, dl);
1939 let mut total = 0usize;
1940 for &(s, e) in &chunks {
1941 total = total.wrapping_add(e - s);
1942 }
1943 black_box(total);
1944 black_box(&chunks);
1945 }));
1946
1947 full_lazy_ns.push(elapsed_per_iter(iters, || {
1948 let d = black_box(data);
1949 let cs = black_box(chunk_size);
1950 let dl = black_box(delim);
1951 let mut total = 0usize;
1952 let cursor = ChunkCursor::new(d, cs, dl);
1953 for chunk in cursor {
1954 total = total.wrapping_add(chunk.len());
1955 }
1956 black_box(total);
1957 }));
1958 }
1959
1960 let tfc_e_p50 = samples_median(tfc_eager_ns.clone());
1961 let tfc_l_p50 = samples_median(tfc_lazy_ns.clone());
1962 let full_e_p50 = samples_median(full_eager_ns.clone());
1963 let full_l_p50 = samples_median(full_lazy_ns.clone());
1964
1965 let tfc_e_p10 = samples_p10(tfc_eager_ns.clone());
1966 let tfc_l_p10 = samples_p10(tfc_lazy_ns.clone());
1967 let full_e_p10 = samples_p10(full_eager_ns.clone());
1968 let full_l_p10 = samples_p10(full_lazy_ns.clone());
1969
1970 let tfc_e_p90 = samples_p90(tfc_eager_ns.clone());
1971 let tfc_l_p90 = samples_p90(tfc_lazy_ns.clone());
1972 let full_e_p90 = samples_p90(full_eager_ns.clone());
1973 let full_l_p90 = samples_p90(full_lazy_ns.clone());
1974
1975 println!();
1976 println!(" === {label} ===");
1977 println!(" file={total_bytes}B chunk={chunk_size}B delim=0x{delim:02x}",);
1978 println!(
1979 " build={} samples={SAMPLES} iters/sample={iters}",
1980 if cfg!(debug_assertions) {
1981 "debug"
1982 } else {
1983 "release"
1984 },
1985 );
1986 println!(" eager_chunks={eager_chunks} lazy_chunks={lazy_chunks}");
1987 println!(
1988 " TFC eager p50={:>10.1}ns p10={:>10.1} p90={:>10.1}",
1989 tfc_e_p50, tfc_e_p10, tfc_e_p90,
1990 );
1991 println!(
1992 " TFC lazy p50={:>10.1}ns p10={:>10.1} p90={:>10.1} ratio={:.2}x",
1993 tfc_l_p50,
1994 tfc_l_p10,
1995 tfc_l_p90,
1996 tfc_e_p50 / tfc_l_p50,
1997 );
1998 println!(
1999 " Full eager p50={:>10.1}ns p10={:>10.1} p90={:>10.1}",
2000 full_e_p50, full_e_p10, full_e_p90,
2001 );
2002 println!(
2003 " Full lazy p50={:>10.1}ns p10={:>10.1} p90={:>10.1} ratio={:.2}x",
2004 full_l_p50,
2005 full_l_p10,
2006 full_l_p90,
2007 full_e_p50 / full_l_p50,
2008 );
2009 }
2010
2011 println!("=== Time-to-First-Chunk + Full Traversal ===");
2012 println!(
2013 " CPU: {} cores",
2014 std::thread::available_parallelism()
2015 .map(|n| n.get())
2016 .unwrap_or(1)
2017 );
2018 println!(" OS: {}", std::env::consts::OS);
2019
2020 let jsonl = gen_log_data(100_000);
2021 let logs = gen_log_data(1_000_000);
2022 let sparse = gen_log_data(10_000_000);
2023
2024 let jsonl_ec = find_chunk_boundaries(&jsonl, 64 * 1024, b'\n').len();
2025 let jsonl_lc = ChunkCursor::new(&jsonl, 64 * 1024, b'\n').count();
2026 bench_one(
2027 &jsonl,
2028 64 * 1024,
2029 b'\n',
2030 "JSONL-like ~100B rec 100KB 64KiB",
2031 200,
2032 jsonl_ec,
2033 jsonl_lc,
2034 );
2035
2036 let logs_ec = find_chunk_boundaries(&logs, 64 * 1024, b'\n').len();
2037 let logs_lc = ChunkCursor::new(&logs, 64 * 1024, b'\n').count();
2038 bench_one(
2039 &logs,
2040 64 * 1024,
2041 b'\n',
2042 "Log-like ~100B rec 1MB 64KiB",
2043 50,
2044 logs_ec,
2045 logs_lc,
2046 );
2047
2048 let sparse_ec = find_chunk_boundaries(&sparse, 64 * 1024, b'\n').len();
2049 let sparse_lc = ChunkCursor::new(&sparse, 64 * 1024, b'\n').count();
2050 bench_one(
2051 &sparse,
2052 64 * 1024,
2053 b'\n',
2054 "Sparse ~100B rec 10MB 64KiB",
2055 10,
2056 sparse_ec,
2057 sparse_lc,
2058 );
2059
2060 let jsonl_ec_1m = find_chunk_boundaries(&jsonl, 1024 * 1024, b'\n').len();
2061 let jsonl_lc_1m = ChunkCursor::new(&jsonl, 1024 * 1024, b'\n').count();
2062 bench_one(
2063 &jsonl,
2064 1024 * 1024,
2065 b'\n',
2066 "JSONL-like ~100B rec 100KB 1MiB",
2067 200,
2068 jsonl_ec_1m,
2069 jsonl_lc_1m,
2070 );
2071
2072 let logs_ec_1m = find_chunk_boundaries(&logs, 1024 * 1024, b'\n').len();
2073 let logs_lc_1m = ChunkCursor::new(&logs, 1024 * 1024, b'\n').count();
2074 bench_one(
2075 &logs,
2076 1024 * 1024,
2077 b'\n',
2078 "Log-like ~100B rec 1MB 1MiB",
2079 50,
2080 logs_ec_1m,
2081 logs_lc_1m,
2082 );
2083 }
2084
2085 #[test]
2088 fn test_fixed_chunk_count_empty() {
2089 assert_eq!(fixed_chunk_count(0, 1024), 0);
2090 }
2091
2092 #[test]
2093 fn test_fixed_chunk_count_exact_multiple() {
2094 assert_eq!(fixed_chunk_count(1024, 256), 4);
2095 }
2096
2097 #[test]
2098 fn test_fixed_chunk_count_with_remainder() {
2099 assert_eq!(fixed_chunk_count(1000, 256), 4);
2100 }
2101
2102 #[test]
2103 fn test_fixed_chunk_count_single() {
2104 assert_eq!(fixed_chunk_count(10, 1024), 1);
2105 }
2106
2107 #[test]
2108 fn test_fixed_chunk_count_zero_clamps() {
2109 assert_eq!(fixed_chunk_count(5, 0), 5);
2110 }
2111
2112 #[test]
2113 fn test_fixed_chunk_bounds_empty() {
2114 assert_eq!(fixed_chunk_bounds(0, 256, 0), None);
2115 }
2116
2117 #[test]
2118 fn test_fixed_chunk_bounds_exact() {
2119 assert_eq!(fixed_chunk_bounds(1024, 256, 0), Some((0, 256)));
2120 assert_eq!(fixed_chunk_bounds(1024, 256, 1), Some((256, 512)));
2121 assert_eq!(fixed_chunk_bounds(1024, 256, 2), Some((512, 768)));
2122 assert_eq!(fixed_chunk_bounds(1024, 256, 3), Some((768, 1024)));
2123 assert_eq!(fixed_chunk_bounds(1024, 256, 4), None);
2124 }
2125
2126 #[test]
2127 fn test_fixed_chunk_bounds_remainder() {
2128 assert_eq!(fixed_chunk_bounds(1000, 256, 3), Some((768, 1000)));
2129 }
2130
2131 #[test]
2132 fn test_fixed_chunk_bounds_size_larger_than_file() {
2133 assert_eq!(fixed_chunk_bounds(10, 1024, 0), Some((0, 10)));
2134 assert_eq!(fixed_chunk_bounds(10, 1024, 1), None);
2135 }
2136
2137 #[test]
2138 fn test_fixed_chunk_bounds_oob() {
2139 assert_eq!(fixed_chunk_bounds(1024, 256, 4), None);
2140 assert_eq!(fixed_chunk_bounds(1024, 256, 100), None);
2141 }
2142
2143 #[test]
2144 fn test_fixed_chunk_bounds_zero_clamps() {
2145 assert_eq!(fixed_chunk_bounds(5, 0, 0), Some((0, 1)));
2146 assert_eq!(fixed_chunk_bounds(5, 0, 1), Some((1, 2)));
2147 assert_eq!(fixed_chunk_bounds(5, 0, 4), Some((4, 5)));
2148 assert_eq!(fixed_chunk_bounds(5, 0, 5), None);
2149 }
2150
2151 #[test]
2152 fn test_fixed_chunk_bounds_single() {
2153 assert_eq!(fixed_chunk_bounds(1, 1024, 0), Some((0, 1)));
2154 }
2155
2156 #[test]
2157 fn test_fixed_property_concat_equals_len() {
2158 let cases: &[(usize, usize)] = &[
2159 (1024, 256),
2160 (1000, 256),
2161 (1, 1024),
2162 (0, 1024),
2163 (5, 1),
2164 (1024, 1024),
2165 (1025, 1024),
2166 ];
2167 for &(len, cs) in cases {
2168 let count = fixed_chunk_count(len, cs);
2169 let mut total = 0usize;
2170 for i in 0..count {
2171 let (s, e) = fixed_chunk_bounds(len, cs, i).unwrap();
2172 total += e - s;
2173 assert!(s <= e);
2174 }
2175 assert_eq!(total, len, "len={len} cs={cs}");
2176 }
2177 }
2178
2179 #[test]
2180 fn test_fixed_property_no_gaps() {
2181 let cases: &[(usize, usize)] = &[(1024, 256), (1000, 256), (5, 1)];
2182 for &(len, cs) in cases {
2183 let count = fixed_chunk_count(len, cs);
2184 let mut prev_end = 0usize;
2185 for i in 0..count {
2186 let (s, e) = fixed_chunk_bounds(len, cs, i).unwrap();
2187 assert_eq!(s, prev_end, "gap at i={i}");
2188 prev_end = e;
2189 }
2190 assert_eq!(prev_end, len);
2191 }
2192 }
2193
2194 #[test]
2195 fn test_fixed_property_non_final_full() {
2196 let cases: &[(usize, usize)] = &[(1024, 256), (1000, 256), (5, 1)];
2197 for &(len, cs) in cases {
2198 let count = fixed_chunk_count(len, cs);
2199 let eff = cs.max(1);
2200 for i in 0..count.saturating_sub(1) {
2201 let (s, e) = fixed_chunk_bounds(len, cs, i).unwrap();
2202 assert_eq!(e - s, eff, "non-final chunk {i} not full");
2203 }
2204 }
2205 }
2206
2207 #[test]
2208 fn test_fixed_property_final_not_larger_than_chunk_size() {
2209 let cases: &[(usize, usize)] = &[(1024, 256), (1000, 256), (5, 1)];
2210 for &(len, cs) in cases {
2211 let count = fixed_chunk_count(len, cs);
2212 if count > 0 {
2213 let (s, e) = fixed_chunk_bounds(len, cs, count - 1).unwrap();
2214 assert!(e - s <= cs.max(1));
2215 }
2216 }
2217 }
2218
2219 #[test]
2220 fn test_fixed_property_deterministic() {
2221 for _ in 0..10 {
2222 assert_eq!(fixed_chunk_count(1000, 256), 4);
2223 assert_eq!(fixed_chunk_bounds(1000, 256, 1), Some((256, 512)));
2224 }
2225 }
2226
2227 #[test]
2228 fn test_fixed_overflow_safety() {
2229 let len: usize = 1024;
2231 let huge: usize = usize::MAX;
2232 assert_eq!(fixed_chunk_count(len, huge), 1);
2233 assert_eq!(fixed_chunk_bounds(len, huge, 0), Some((0, len)));
2234
2235 assert_eq!(fixed_chunk_count(len, 1), len);
2237 assert_eq!(fixed_chunk_bounds(len, 1, 0), Some((0, 1)));
2238 assert_eq!(fixed_chunk_bounds(len, 1, len - 1), Some((len - 1, len)));
2239
2240 assert_eq!(fixed_chunk_count(0, usize::MAX), 0);
2242 }
2243
2244 mod swar_bench {
2247 use super::find_byte_swar;
2248 use std::hint::black_box;
2249 use std::time::Instant;
2250
2251 #[inline(always)]
2252 fn find_byte_scalar(haystack: &[u8], delimiter: u8) -> Option<usize> {
2253 haystack.iter().position(|&b| b == delimiter)
2254 }
2255
2256 const DELIMITERS: &[u8] = &[0x00, 0x01, b'\n', b',', b'|', 0x7f, 0x80, 0xfe, 0xff];
2259 const LENGTHS: &[usize] = &[
2260 0, 1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 200, 256,
2261 ];
2262
2263 fn assert_swar_eq_scalar(data: &[u8], delim: u8, label: &str) {
2264 let s = find_byte_scalar(data, delim);
2265 let w = find_byte_swar(data, delim);
2266 assert_eq!(
2267 s,
2268 w,
2269 "SWAR != scalar: {label} data[0]={:02x} len={} delim={:02x}",
2270 data.first().copied().unwrap_or(0),
2271 data.len(),
2272 delim
2273 );
2274 }
2275
2276 fn deterministic_bytes(seed: u64, len: usize) -> Vec<u8> {
2277 let mut state = seed;
2278 (0..len)
2279 .map(|_| {
2280 state = state
2281 .wrapping_mul(6364136223846793005)
2282 .wrapping_add(1442695040888963407);
2283 (state >> 32) as u8
2284 })
2285 .collect()
2286 }
2287
2288 #[test]
2289 fn correctness_empty() {
2290 for &d in DELIMITERS {
2291 assert_swar_eq_scalar(&[], d, "empty");
2292 }
2293 }
2294
2295 #[test]
2296 fn correctness_all_lengths_no_match() {
2297 for &len in LENGTHS {
2298 let delim = b'\n';
2299 let data = vec![b'x'; len];
2300 assert_swar_eq_scalar(&data, delim, &format!("len={len} nomatch"));
2301 }
2302 }
2303
2304 #[test]
2305 fn correctness_all_lengths_all_match() {
2306 for &len in LENGTHS {
2307 if len == 0 {
2308 continue;
2309 }
2310 let delim = b'\n';
2311 let data = vec![delim; len];
2312 assert_swar_eq_scalar(&data, delim, &format!("len={len} allmatch"));
2313 }
2314 }
2315
2316 #[test]
2317 fn correctness_match_at_every_position() {
2318 for &len in &[1, 8, 16, 32, 64, 128, 200] {
2319 for pos in 0..len {
2320 let delim = b'\n';
2321 let mut data = vec![b'x'; len];
2322 data[pos] = delim;
2323 assert_swar_eq_scalar(&data, delim, &format!("len={len} pos={pos}"));
2324 }
2325 }
2326 }
2327
2328 #[test]
2329 fn correctness_all_delimiters() {
2330 let len = 256usize;
2331 for &delim in DELIMITERS {
2332 let other: u8 = if delim == 0x00 { 0x01 } else { 0x00 };
2333 let data = vec![other; len];
2334 assert_swar_eq_scalar(&data, delim, &format!("delim={delim:02x} nomatch"));
2335 let mut data = vec![other; len];
2336 data[len / 2] = delim;
2337 assert_swar_eq_scalar(&data, delim, &format!("delim={delim:02x} middle"));
2338 }
2339 }
2340
2341 #[test]
2342 fn correctness_unaligned_starts() {
2343 let base = deterministic_bytes(42, 256);
2344 for start in 0..16 {
2345 for &delim in &[b'\n', b',', 0x00, 0xff] {
2346 let slice = &base[start..];
2347 assert_swar_eq_scalar(
2348 slice,
2349 delim,
2350 &format!("unaligned start={start} delim={delim:02x}"),
2351 );
2352 }
2353 }
2354 }
2355
2356 #[test]
2357 fn correctness_match_at_boundaries() {
2358 let delim = b'\n';
2359 for &len in &[8, 9, 15, 16, 17, 24, 25, 31, 32, 33] {
2360 let mut data = vec![b'x'; len];
2361 for pos in [0, 7, 8, 15, 16, 23, 24, 31, 32].iter().copied() {
2362 if pos < len {
2363 data[pos] = delim;
2364 assert_swar_eq_scalar(
2365 &data,
2366 delim,
2367 &format!("len={len} word-boundary pos={pos}"),
2368 );
2369 data[pos] = b'x';
2370 }
2371 }
2372 data[len - 1] = delim;
2373 assert_swar_eq_scalar(&data, delim, &format!("len={len} last-byte"));
2374 }
2375 }
2376
2377 #[test]
2378 fn correctness_deterministic_random() {
2379 for seed in 0..20u64 {
2380 let data = deterministic_bytes(seed, 1024);
2381 for delim in [b'\n', b',', b'|', 0x00, 0xff] {
2382 assert_swar_eq_scalar(
2383 &data,
2384 delim,
2385 &format!("random seed={seed} delim={delim:02x}"),
2386 );
2387 }
2388 }
2389 }
2390
2391 #[test]
2392 fn correctness_many_matches() {
2393 let delim = b'\n';
2394 let mut data = Vec::with_capacity(10000);
2395 for i in 0..1000 {
2396 data.extend_from_slice(b"line_content_");
2397 data.extend_from_slice(&(i as u32).to_le_bytes());
2398 data.push(delim);
2399 }
2400 for &delim_test in &[b'\n', b'l', b'_', 0x00, 0xff] {
2401 assert_swar_eq_scalar(
2402 &data,
2403 delim_test,
2404 &format!("many_matches delim={delim_test:02x}"),
2405 );
2406 }
2407 }
2408
2409 fn bench_find_byte<F>(haystack: &[u8], delim: u8, f: F, iters: u64) -> f64
2412 where
2413 F: Fn(&[u8], u8) -> Option<usize>,
2414 {
2415 let start = Instant::now();
2416 for _ in 0..iters {
2417 let result = f(black_box(haystack), delim);
2418 black_box(result);
2419 }
2420 start.elapsed().as_nanos() as f64 / iters as f64
2421 }
2422
2423 fn bench_find_byte_auto<F>(haystack: &[u8], delim: u8, f: F, name: &str) -> (f64, usize)
2424 where
2425 F: Fn(&[u8], u8) -> Option<usize>,
2426 {
2427 let warmup_iters = 10;
2428 for _ in 0..warmup_iters {
2429 black_box(f(black_box(haystack), delim));
2430 }
2431
2432 let mut iters: u64 = 100;
2433 let max_iters: u64 = 100_000;
2434 let mut samples = Vec::with_capacity(10);
2435
2436 while iters <= max_iters {
2437 let avg_ns = bench_find_byte(haystack, delim, &f, iters);
2438 if avg_ns * iters as f64 > 500_000_000.0 {
2439 samples.push(avg_ns);
2440 if samples.len() >= 5 {
2441 break;
2442 }
2443 }
2444 iters = (iters * 2).min(max_iters);
2445 if iters == max_iters && samples.is_empty() {
2446 samples.push(avg_ns);
2447 break;
2448 }
2449 }
2450
2451 if samples.is_empty() {
2452 return (0.0, 0);
2453 }
2454
2455 let n = samples.len();
2456 let mut sorted = samples.clone();
2457 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
2458 let median_ns = sorted[n / 2];
2459
2460 let result = black_box(f(black_box(haystack), delim));
2461 let _ = black_box(result);
2462
2463 println!(
2464 " {name:<20} {median_ns:>10.1} ns/call (n={n})",
2465 name = name,
2466 n = n
2467 );
2468 (median_ns, n)
2469 }
2470
2471 #[test]
2472 #[ignore = "performance experiment — run with --ignored --nocapture"]
2473 fn bench_byte_search_primitive() {
2474 println!();
2475 println!("=== Byte-Search Microbenchmark (scalar vs SWAR) ===");
2476 println!(
2477 " Build: {}",
2478 if cfg!(debug_assertions) {
2479 "debug"
2480 } else {
2481 "release"
2482 }
2483 );
2484
2485 let delim = b'\n';
2486
2487 fn gen_data(len: usize, pattern: &str) -> Vec<u8> {
2488 match pattern {
2489 "no_match" => vec![b'x'; len],
2490 "match_mid" => {
2491 let mut d = vec![b'x'; len];
2492 d[len / 2] = b'\n';
2493 d
2494 }
2495 "match_end" => {
2496 let mut d = vec![b'x'; len];
2497 d[len - 1] = b'\n';
2498 d
2499 }
2500 "match_first" => {
2501 let mut d = vec![b'x'; len];
2502 d[0] = b'\n';
2503 d
2504 }
2505 _ => vec![b'x'; len],
2506 }
2507 }
2508
2509 let sizes: &[(usize, &str)] = &[
2510 (32, "Tiny"),
2511 (64, "1 cache line"),
2512 (128, "2 cache lines"),
2513 (256, "4 cache lines"),
2514 (4096, "4 KiB page"),
2515 (65536, "64 KiB"),
2516 (1048576, "1 MiB"),
2517 ];
2518
2519 println!(" Case Scalar (ns) SWAR (ns) Ratio");
2520
2521 for &(size, label) in sizes {
2522 for pattern in ["no_match", "match_mid"] {
2523 let data = gen_data(size, pattern);
2524 let case = format!("{label} {size}B {pattern}");
2525 let (s, _) = bench_find_byte_auto(
2526 &data,
2527 delim,
2528 find_byte_scalar,
2529 &format!("scalar {case}"),
2530 );
2531 let (w, _) = bench_find_byte_auto(
2532 &data,
2533 delim,
2534 find_byte_swar,
2535 &format!("SWAR {case}"),
2536 );
2537 let ratio = if s > 0.0 { w / s } else { f64::NAN };
2538 println!(
2539 " {case:<25} {s:>14.1} {w:>14.1} {ratio:>9.2}x",
2540 case = case,
2541 s = s,
2542 w = w,
2543 ratio = ratio
2544 );
2545 }
2546 }
2547 }
2548 }
2549
2550 #[test]
2553 fn partition_empty_data() {
2554 assert_eq!(find_partition_boundaries(b"", 4, b'\n'), vec![]);
2555 }
2556
2557 #[test]
2558 fn partition_zero_partitions() {
2559 let data = b"hello\nworld\n";
2560 assert_eq!(find_partition_boundaries(data, 0, b'\n'), vec![]);
2561 }
2562
2563 #[test]
2564 fn partition_request_far_above_file_len_is_bounded() {
2565 let data = b"a\nb\nc\n";
2566
2567 assert_eq!(
2568 find_partition_boundaries(data, usize::MAX, b'\n'),
2569 vec![(0, 2), (2, 4), (4, 6)]
2570 );
2571 }
2572
2573 #[test]
2574 fn partition_single_partition() {
2575 let data = b"hello\nworld\n";
2576 assert_eq!(
2577 find_partition_boundaries(data, 1, b'\n'),
2578 vec![(0, data.len())]
2579 );
2580 }
2581
2582 #[test]
2583 fn partition_no_delimiter() {
2584 let data = b"no_newlines_here";
2585 for &n in &[2, 4, 8, 16] {
2586 let partitions = find_partition_boundaries(data, n, b'\n');
2587 assert_eq!(partitions, vec![(0, data.len())]);
2588 }
2589 }
2590
2591 #[test]
2592 fn partition_block_count_semantics() {
2593 let data = b"a\nb\nc\nd\ne\n";
2594 let partitions = find_partition_boundaries(data, 2, b'\n');
2596 assert!(partitions.len() >= 2);
2597 assert_eq!(partitions[0].0, 0);
2599 assert_eq!(partitions.last().unwrap().1, data.len());
2600 }
2601
2602 #[test]
2603 fn partition_property_no_gaps() {
2604 let cases: &[(&[u8], usize, u8)] = &[
2605 (b"a\nb\nc\n", 2, b'\n'),
2606 (b"a\nb\nc\n", 3, b'\n'),
2607 (b"a\nb\nc\n", 4, b'\n'),
2608 (b"a,b,c,d,e,f", 3, b','),
2609 (b"a\tb\tc\t", 2, b'\t'),
2610 (b"a|b|c|d|e|f", 4, b'|'),
2611 ];
2612 for &(data, n, delim) in cases {
2613 let partitions = find_partition_boundaries(data, n, delim);
2614 if partitions.is_empty() {
2615 continue;
2616 }
2617 assert_eq!(partitions[0].0, 0, "first must start at 0");
2618 for i in 1..partitions.len() {
2619 assert_eq!(
2620 partitions[i].0,
2621 partitions[i - 1].1,
2622 "gap at partition {}->{}",
2623 i - 1,
2624 i
2625 );
2626 }
2627 assert_eq!(
2628 partitions.last().unwrap().1,
2629 data.len(),
2630 "last must end at EOF"
2631 );
2632 }
2633 }
2634
2635 #[test]
2636 fn partition_property_concatenation_equals_input() {
2637 let cases: &[(&[u8], usize, u8)] = &[
2638 (b"hello\nworld\n", 2, b'\n'),
2639 (b"a,b,c,d", 2, b','),
2640 (b"one\ttwo\tthree", 2, b'\t'),
2641 (b"a|b|c|d|e|f", 3, b'|'),
2642 (b"x\x00y\x00z", 2, b'\x00'),
2643 (b"single", 4, b'\n'),
2644 (b"\n\n\n\n\n", 2, b'\n'),
2645 (b"\n", 2, b'\n'),
2646 (b"a", 4, b'\n'),
2647 ];
2648 for &(data, n, delim) in cases {
2649 let partitions = find_partition_boundaries(data, n, delim);
2650 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
2651 assert_eq!(
2652 total,
2653 data.len(),
2654 "concat property failed: n={n} delim={delim:02x}"
2655 );
2656 }
2657 }
2658
2659 #[test]
2660 fn partition_property_determinism() {
2661 let data = b"line1\nline2\nline3\nline4\nline5\n";
2662 let p1 = find_partition_boundaries(data, 3, b'\n');
2663 let p2 = find_partition_boundaries(data, 3, b'\n');
2664 assert_eq!(p1, p2);
2665 let p3 = find_partition_boundaries(data, 3, b'\n');
2666 assert_eq!(p1, p3);
2667 }
2668
2669 #[test]
2670 fn partition_property_boundary_after_delimiter() {
2671 let data = b"record1\nrecord2\nrecord3\n";
2672 for &n in &[2, 3, 4] {
2673 let partitions = find_partition_boundaries(data, n, b'\n');
2674 for (_start, end) in partitions.iter().take(partitions.len().saturating_sub(1)) {
2676 assert_eq!(
2677 data[end.wrapping_sub(1)],
2678 b'\n',
2679 "non-final partition must end after delimiter"
2680 );
2681 assert!(*end > 0, "partition must be non-empty");
2682 }
2683 }
2684 }
2685
2686 #[test]
2687 fn partition_property_no_empty_partitions() {
2688 let data = b"a\nb\nc\n";
2689 for &n in &[2, 3, 4, 8] {
2690 let partitions = find_partition_boundaries(data, n, b'\n');
2691 for &(start, end) in &partitions {
2692 assert!(end > start, "zero-length partition: n={n}");
2693 }
2694 }
2695 }
2696
2697 #[test]
2698 fn partition_property_monotonic() {
2699 let data = b"x\nxx\nxxx\nxxxx\nxxxxx\n";
2700 for &n in &[2, 3, 4, 8] {
2701 let partitions = find_partition_boundaries(data, n, b'\n');
2702 let mut last_end = 0usize;
2703 for (start, end) in &partitions {
2704 assert!(*start >= last_end);
2705 assert!(*end > *start);
2706 last_end = *end;
2707 }
2708 }
2709 }
2710
2711 #[test]
2714 fn partition_record_integrity_numbered() {
2715 let delim = b'\n';
2716 let mut data = Vec::new();
2717 let mut original_records: Vec<Vec<u8>> = Vec::new();
2718 for i in 0..100u32 {
2719 let record = format!("record-{i:06}\n").into_bytes();
2720 data.extend_from_slice(&record);
2721 original_records.push(record);
2722 }
2723
2724 let file_len = data.len();
2726
2727 for &n in &[1, 2, 3, 5, 7, 8, 13, 17, 20, 37, 50] {
2728 let partitions = find_partition_boundaries(&data, n, delim);
2729
2730 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
2732 assert_eq!(total, file_len);
2733
2734 let mut recovered: Vec<Vec<u8>> = Vec::new();
2736 for &(start, end) in &partitions {
2737 let chunk = &data[start..end];
2738 let mut pos = 0;
2740 for (j, &b) in chunk.iter().enumerate() {
2741 if b == delim {
2742 let rec = chunk[pos..=j].to_vec();
2743 if !rec.is_empty() {
2744 recovered.push(rec);
2745 }
2746 pos = j + 1;
2747 }
2748 }
2749 if pos < chunk.len() {
2751 recovered.push(chunk[pos..].to_vec());
2752 }
2753 }
2754
2755 assert_eq!(
2757 recovered, original_records,
2758 "record integrity violated for n={n}"
2759 );
2760 }
2761 }
2762
2763 #[test]
2764 fn partition_record_integrity_giant_record() {
2765 let delim = b'\n';
2767 let mut data = b"short1\n".to_vec();
2768 let giant = vec![b'x'; 5000];
2769 data.extend_from_slice(&giant);
2771 data.extend_from_slice(b"short2\n");
2772 let file_len = data.len();
2773
2774 for &n in &[2, 4, 8, 16, 32] {
2775 let partitions = find_partition_boundaries(&data, n, delim);
2776
2777 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
2779 assert_eq!(total, file_len);
2780
2781 for (_start, end) in partitions.iter().take(partitions.len().saturating_sub(1)) {
2784 assert_eq!(
2785 data[end.wrapping_sub(1)],
2786 delim,
2787 "non-final partition must end after delimiter at n={n}"
2788 );
2789 }
2790
2791 assert!(partitions.len() <= 3 + 1);
2794 }
2795 }
2796
2797 #[test]
2800 fn partition_balance_uniform_100b() {
2801 let delim = b'\n';
2802 let record_size = 100;
2803 let record_count = 1000;
2804 let mut data = Vec::new();
2805 for i in 0..record_count {
2806 let fill = (i as u8).wrapping_add(0x41);
2807 let payload = vec![fill; record_size - 1];
2808 data.extend_from_slice(&payload);
2809 data.push(delim);
2810 }
2811 let file_len = data.len();
2812
2813 fn max_abs_deviation(partitions: &[(usize, usize)], ideal: usize) -> usize {
2814 partitions
2815 .iter()
2816 .map(|(s, e)| {
2817 let size = e - s;
2818 size.abs_diff(ideal)
2819 })
2820 .max()
2821 .unwrap_or(0)
2822 }
2823
2824 for &n in &[2, 4, 8, 16, 32] {
2825 let partitions = find_partition_boundaries(&data, n, delim);
2826 let actual_n = partitions.len();
2827 let ideal = file_len / actual_n;
2828 let mad = max_abs_deviation(&partitions, ideal);
2829
2830 assert!(
2832 mad <= 2 * record_size + 10,
2833 "n={n}, ideal={ideal}, mad={mad}, too much deviation"
2834 );
2835
2836 for (_start, end) in partitions.iter().take(actual_n.saturating_sub(1)) {
2838 assert_eq!(data[end - 1], delim);
2839 }
2840 }
2841 }
2842
2843 #[test]
2844 fn partition_balance_variable_records() {
2845 let delim = b'\n';
2846 let pattern: &[usize] = &[20, 100, 4096]; let mut data = Vec::new();
2848 let mut i = 0;
2849 while data.len() < 1_000_000 {
2850 let payload_size = pattern[i % pattern.len()].saturating_sub(1);
2851 let fill = (i as u8).wrapping_add(0x41);
2852 data.extend(std::iter::repeat(fill).take(payload_size));
2853 data.push(delim);
2854 i += 1;
2855 }
2856 let file_len = data.len();
2857
2858 for &n in &[2, 4, 8, 16, 32] {
2859 let partitions = find_partition_boundaries(&data, n, delim);
2860 let actual_n = partitions.len();
2861 let _ideal = file_len / actual_n;
2862
2863 for (start, end) in &partitions {
2865 let size = end - start;
2866 assert!(size > 0, "zero-length partition at n={n}");
2868 }
2869
2870 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
2872 assert_eq!(total, file_len);
2873 }
2874 }
2875
2876 #[test]
2877 fn partition_absolute_vs_iterative_drift() {
2878 let delim = b'\n';
2886 let mut data = Vec::new();
2887 data.extend_from_slice(b"giant_record_with_no_delimiter_here");
2889 for i in 0..100usize {
2891 let record = format!("line-{i:04}\n").into_bytes();
2892 data.extend_from_slice(&record);
2893 }
2894 let file_len = data.len();
2895 let n = 8;
2896
2897 let partitions = find_partition_boundaries(&data, n, delim);
2898 let actual_n = partitions.len();
2899
2900 assert_eq!(partitions[0].0, 0);
2902 let remaining_data = file_len - partitions[0].1;
2904 let remaining_partitions = actual_n - 1;
2905 let ideal_remaining = remaining_data / remaining_partitions;
2906
2907 let max_dev: usize = partitions[1..]
2908 .iter()
2909 .map(|(s, e)| {
2910 let size = e - s;
2911 size.abs_diff(ideal_remaining)
2912 })
2913 .max()
2914 .unwrap_or(0);
2915
2916 let max_record = b"giant_record_with_no_delimiter_here".len() + 1 + 10; assert!(
2920 max_dev <= max_record * 2 + 50,
2921 "remaining partitions unbalanced: max_dev={max_dev}, ideal_remaining={ideal_remaining}"
2922 );
2923
2924 assert!(partitions.len() <= actual_n);
2926 }
2927
2928 #[test]
2929 fn partition_sparse_64k_records() {
2930 let delim = b'\n';
2931 let record_size = 65536;
2932 let record_count = 50;
2933 let mut data = Vec::with_capacity(record_count * record_size);
2934 for i in 0..record_count {
2935 let fill = (i as u8).wrapping_add(0x41);
2936 data.extend(std::iter::repeat(fill).take(record_size - 1));
2937 data.push(delim);
2938 }
2939 let file_len = data.len();
2940
2941 for &n in &[2, 4, 8, 16] {
2942 let partitions = find_partition_boundaries(&data, n, delim);
2943
2944 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
2946 assert_eq!(total, file_len);
2947
2948 for (_start, end) in partitions.iter().take(partitions.len().saturating_sub(1)) {
2950 assert_eq!(data[end.wrapping_sub(1)], delim, "64KB record split? n={n}");
2952 }
2953 }
2954 }
2955
2956 #[test]
2957 fn partition_no_empty_to_hit_n() {
2958 let data = b"record1\nrecord2\n";
2960 let partitions = find_partition_boundaries(data, 100, b'\n');
2961 assert!(!partitions.is_empty());
2962 for &(s, e) in &partitions {
2963 assert!(e > s, "must not create empty partitions");
2964 }
2965 assert!(
2966 partitions.len() < 100,
2967 "should produce fewer partitions than N"
2968 );
2969 }
2970
2971 #[test]
2972 fn partition_consecutive_delimiters() {
2973 let data = b"\n\n\n\n\n";
2974 for &n in &[2, 3, 4] {
2975 let partitions = find_partition_boundaries(data, n, b'\n');
2976 if partitions.is_empty() {
2977 continue;
2978 }
2979 assert_eq!(partitions[0].0, 0);
2980 for i in 1..partitions.len() {
2981 assert_eq!(partitions[i].0, partitions[i - 1].1);
2982 }
2983 assert_eq!(partitions.last().unwrap().1, data.len());
2984 }
2985 }
2986
2987 #[test]
2988 fn partition_only_newlines() {
2989 let data = b"\n\n\n";
2990 for &n in &[1, 2, 3, 4] {
2991 let partitions = find_partition_boundaries(data, n, b'\n');
2992 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
2993 assert_eq!(total, data.len());
2994 }
2995 }
2996
2997 fn pattern_equals_single_byte(data: &[u8], chunk_size: usize, delimiter: u8) {
3001 let single = find_chunk_boundaries(data, chunk_size, delimiter);
3002 let pattern = find_chunk_boundaries_pattern(data, chunk_size, &[delimiter]);
3003 assert_eq!(
3004 single, pattern,
3005 "pattern != single: chunk_size={chunk_size} delim={delimiter:#04x}"
3006 );
3007 }
3008
3009 fn cursor_pattern_equals_single_byte(data: &[u8], chunk_size: usize, delimiter: u8) {
3011 let single: Vec<&[u8]> = ChunkCursor::new(data, chunk_size, delimiter).collect();
3012 let pattern: Vec<&[u8]> = PatternChunkCursor::new(data, chunk_size, &[delimiter]).collect();
3013 assert_eq!(
3014 single, pattern,
3015 "pattern cursor != single cursor: chunk_size={chunk_size} delim={delimiter:#04x}",
3016 );
3017 }
3018
3019 #[test]
3020 fn pattern_single_byte_equivalence_all_delimiters() {
3021 let cases: &[(&[u8], usize, u8)] = &[
3022 (b"hello\nworld\n", 4, b'\n'),
3023 (b"a,b,c,d", 2, b','),
3024 (b"one\ttwo\tthree", 4, b'\t'),
3025 (b"a|b|c|d|e|f", 3, b'|'),
3026 (b"x\x00y\x00z", 2, b'\x00'),
3027 (b"single", 1024, b'\n'),
3028 (b"\n\n\n\n\n", 1, b'\n'),
3029 (b"", 1024, b'\n'),
3030 (b"\n", 1, b'\n'),
3031 (b"a", 1024, b'\n'),
3032 (b"line1\nline2\nline3\nline4\nline5\n", 10, b'\n'),
3033 (b"no_newlines_here", 5, b'\n'),
3034 (b"xxxx\n", 5, b'\n'),
3035 (b"a\nb\nc\n", 1, b'\n'),
3036 (b"tiny\nverylongrecordwithnobreaksanywhere\nend\n", 6, b'\n'),
3037 ];
3038 for &(data, chunk_size, delim) in cases {
3039 pattern_equals_single_byte(data, chunk_size, delim);
3040 cursor_pattern_equals_single_byte(data, chunk_size, delim);
3041 }
3042 }
3043
3044 #[test]
3045 fn pattern_empty_delimiter_panics() {
3046 let result = std::panic::catch_unwind(|| {
3047 find_chunk_boundaries_pattern(b"hello", 10, b"");
3048 });
3049 assert!(result.is_err(), "empty delimiter must panic");
3050 }
3051
3052 #[test]
3053 fn pattern_empty_data() {
3054 assert_eq!(find_chunk_boundaries_pattern(b"", 1024, b"\r\n"), vec![]);
3055 assert_eq!(
3056 PatternChunkCursor::new(b"", 1024, b"\r\n").collect::<Vec<_>>(),
3057 Vec::<&[u8]>::new()
3058 );
3059 }
3060
3061 #[test]
3062 fn pattern_crlf_basic() {
3063 let data = b"a\r\nb\r\nc\r\n";
3064 let chunks = find_chunk_boundaries_pattern(data, 4, b"\r\n");
3065 assert_eq!(chunks, vec![(0, 6), (6, 9)]);
3066
3067 let cursor: Vec<&[u8]> = PatternChunkCursor::new(data, 4, b"\r\n").collect();
3068 assert_eq!(cursor, vec![b"a\r\nb\r\n" as &[u8], b"c\r\n" as &[u8]]);
3069 }
3070
3071 #[test]
3072 fn pattern_crlf_no_trailing_delimiter() {
3073 let data = b"a\r\nb\r\nc";
3074 let chunks = find_chunk_boundaries_pattern(data, 1024, b"\r\n");
3075 assert_eq!(chunks, vec![(0, data.len())]);
3076 }
3077
3078 #[test]
3079 fn pattern_consecutive_crlf() {
3080 let data = b"\r\n\r\n\r\n";
3081 let chunks = find_chunk_boundaries_pattern(data, 1, b"\r\n");
3082 assert_eq!(chunks, vec![(0, 4), (4, 6)]);
3083 }
3084
3085 #[test]
3086 fn pattern_double_crlf_http_style() {
3087 let data = b"Header: val\r\n\r\nbody";
3088 let chunks = find_chunk_boundaries_pattern(data, 1024, b"\r\n\r\n");
3089 assert_eq!(chunks, vec![(0, 19)]);
3090 }
3091
3092 #[test]
3093 fn pattern_double_crlf_split_at_delimiter() {
3094 let data = b"Header: val\r\n\r\nbody line 2\r\n\r\nmore";
3095 let chunks = find_chunk_boundaries_pattern(data, 1, b"\r\n\r\n");
3096 assert_eq!(chunks.len(), 3);
3097 let total: usize = chunks.iter().map(|(s, e)| e - s).sum();
3098 assert_eq!(total, data.len());
3099 assert_eq!(&data[chunks[0].0..chunks[0].1], b"Header: val\r\n\r\n");
3100 }
3101
3102 #[test]
3103 fn pattern_custom_double_separator() {
3104 let data = b"a||b||c||d";
3105 let chunks = find_chunk_boundaries_pattern(data, 4, b"||");
3106 assert_eq!(chunks, vec![(0, 6), (6, 10)]);
3107
3108 let cursor: Vec<&[u8]> = PatternChunkCursor::new(data, 4, b"||").collect();
3109 assert_eq!(cursor, vec![b"a||b||" as &[u8], b"c||d" as &[u8]]);
3110 }
3111
3112 #[test]
3113 fn pattern_binary_delimiter() {
3114 let data = b"AB\x00\xFF\x00CD\x00\xFF\x00EF";
3115 let chunks = find_chunk_boundaries_pattern(data, 10, b"\x00\xff\x00");
3116 assert_eq!(chunks, vec![(0, data.len())]);
3117 }
3118
3119 #[test]
3120 fn pattern_binary_delimiter_small_chunk() {
3121 let data = b"AB\x00\xFF\x00CD\x00\xFF\x00EF";
3122 let chunks = find_chunk_boundaries_pattern(data, 4, b"\x00\xff\x00");
3123 assert_eq!(chunks, vec![(0, 10), (10, 12)]);
3124 }
3125
3126 #[test]
3127 fn pattern_at_eof() {
3128 let data = b"hello\r\nworld\r\n";
3129 let chunks = find_chunk_boundaries_pattern(data, 50, b"\r\n");
3130 assert_eq!(chunks, vec![(0, data.len())]);
3131 }
3132
3133 #[test]
3134 fn pattern_partial_at_eof() {
3135 let data = b"hello\r\nworld\r";
3136 let chunks = find_chunk_boundaries_pattern(data, 50, b"\r\n");
3137 assert_eq!(chunks, vec![(0, data.len())]);
3138 }
3139
3140 #[test]
3141 fn pattern_exactly_at_target() {
3142 let data = b"xxxx\r\n";
3143 let chunks = find_chunk_boundaries_pattern(data, 6, b"\r\n");
3144 assert_eq!(chunks, vec![(0, 6)]);
3145 }
3146
3147 #[test]
3148 fn pattern_starts_one_byte_before_target() {
3149 let data = b"xxxy\r\n";
3150 let chunks = find_chunk_boundaries_pattern(data, 6, b"\r\n");
3151 assert_eq!(chunks, vec![(0, data.len())]);
3152 assert_eq!(&data[..chunks[0].1], b"xxxy\r\n");
3153 }
3154
3155 #[test]
3156 fn pattern_no_delimiter() {
3157 let data = b"no_delimiter_here";
3158 let chunks = find_chunk_boundaries_pattern(data, 5, b"\r\n");
3159 assert_eq!(chunks, vec![(0, data.len())]);
3160 }
3161
3162 #[test]
3163 fn pattern_delimiter_longer_than_data() {
3164 let data = b"hi";
3165 let chunks = find_chunk_boundaries_pattern(data, 1024, b"\r\n\r\n\r\n");
3166 assert_eq!(chunks, vec![(0, data.len())]);
3167 }
3168
3169 #[test]
3170 fn pattern_chunk_size_zero() {
3171 let data = b"a\r\nb\r\nc\r\n";
3172 let chunks = find_chunk_boundaries_pattern(data, 0, b"\r\n");
3173 assert!(!chunks.is_empty());
3174 let total: usize = chunks.iter().map(|(s, e)| e - s).sum();
3175 assert_eq!(total, data.len());
3176 }
3177
3178 #[test]
3179 fn pattern_chunk_size_one() {
3180 let data = b"a\r\nb\r\nc\r\n";
3181 let chunks = find_chunk_boundaries_pattern(data, 1, b"\r\n");
3182 let mut pos = 0;
3183 for (start, end) in &chunks {
3184 assert_eq!(*start, pos);
3185 pos = *end;
3186 }
3187 assert_eq!(pos, data.len());
3188 }
3189
3190 #[test]
3191 fn pattern_huge_chunk_size() {
3192 let data = b"a\r\nb\r\nc\r\n";
3193 let chunks = find_chunk_boundaries_pattern(data, 1_000_000, b"\r\n");
3194 assert_eq!(chunks, vec![(0, data.len())]);
3195 }
3196
3197 #[test]
3198 fn pattern_overlapping_pattern() {
3199 let data = b"xaaaay";
3201 let chunks = find_chunk_boundaries_pattern(data, 1, b"aa");
3202 let total: usize = chunks.iter().map(|(s, e)| e - s).sum();
3203 assert_eq!(total, data.len());
3204 }
3205
3206 #[test]
3207 fn pattern_cursor_equivalence_crlf() {
3208 let data = b"line1\r\nline2\r\nline3\r\n";
3209 let eager = find_chunk_boundaries_pattern(data, 6, b"\r\n");
3210 let cursor: Vec<&[u8]> = PatternChunkCursor::new(data, 6, b"\r\n").collect();
3211 let lazy_ranges: Vec<(usize, usize)> = cursor
3212 .iter()
3213 .scan(0usize, |pos, &chunk| {
3214 let start = *pos;
3215 *pos += chunk.len();
3216 Some((start, *pos))
3217 })
3218 .collect();
3219 assert_eq!(lazy_ranges, eager);
3220 }
3221
3222 #[test]
3223 fn pattern_deterministic_corpus() {
3224 let mut data = Vec::new();
3225 for i in 0..1000u32 {
3226 data.extend_from_slice(format!("line_{i:04}\r\n").as_bytes());
3227 }
3228 let eager = find_chunk_boundaries_pattern(&data, 64, b"\r\n");
3229 let cursor: Vec<&[u8]> = PatternChunkCursor::new(&data, 64, b"\r\n").collect();
3230 let cursor_ranges: Vec<(usize, usize)> = cursor
3231 .iter()
3232 .scan(0usize, |pos, &chunk| {
3233 let start = *pos;
3234 *pos += chunk.len();
3235 Some((start, *pos))
3236 })
3237 .collect();
3238 assert_eq!(cursor_ranges, eager);
3239 }
3240
3241 #[test]
3242 fn pattern_property_no_gaps() {
3243 let data = b"rec1\r\nrec2\r\nrec3\r\nrec4\r\n";
3244 let chunks = find_chunk_boundaries_pattern(data, 6, b"\r\n");
3245 if chunks.is_empty() {
3246 return;
3247 }
3248 assert_eq!(chunks[0].0, 0);
3249 for i in 1..chunks.len() {
3250 assert_eq!(chunks[i].0, chunks[i - 1].1);
3251 }
3252 assert_eq!(chunks.last().unwrap().1, data.len());
3253 }
3254
3255 #[test]
3256 fn pattern_property_concatenation() {
3257 let cases: &[(&[u8], usize, &[u8])] = &[
3258 (b"a\r\nb\r\nc\r\n", 4, b"\r\n"),
3259 (b"ab||cd||ef", 4, b"||"),
3260 (b"single", 1024, b"\r\n"),
3261 (b"AB\xff\x00CD\xff\x00EF", 4, b"\xff\x00"),
3262 ];
3263 for &(data, cs, delim) in cases {
3264 let chunks = find_chunk_boundaries_pattern(data, cs, delim);
3265 let total: usize = chunks.iter().map(|(s, e)| e - s).sum();
3266 assert_eq!(total, data.len());
3267 }
3268 }
3269
3270 #[test]
3271 fn pattern_property_determinism() {
3272 let data = b"x||y||z||w||";
3273 let c1 = find_chunk_boundaries_pattern(data, 2, b"||");
3274 let c2 = find_chunk_boundaries_pattern(data, 2, b"||");
3275 assert_eq!(c1, c2);
3276 }
3277
3278 #[test]
3279 #[should_panic(expected = "delimiter must not be empty")]
3280 fn pattern_empty_delimiter_cursor_panics() {
3281 let _ = PatternChunkCursor::new(b"hello", 10, b"");
3282 }
3283
3284 #[test]
3285 fn pattern_empty_delimiter_cursor_panics_catch() {
3286 let result = std::panic::catch_unwind(|| {
3287 PatternChunkCursor::new(b"hello", 10, b"");
3288 });
3289 assert!(result.is_err());
3290 }
3291
3292 #[test]
3293 fn pattern_repeated_prefix_adversarial() {
3294 let delimiter = b"aaaaab";
3297 let hay = vec![b'a'; 10_000];
3298 let data: Vec<u8> = hay.iter().chain(delimiter.iter()).copied().collect();
3299 let chunks = find_chunk_boundaries_pattern(&data, 5000, delimiter);
3300 assert!(!chunks.is_empty());
3301 let total: usize = chunks.iter().map(|(s, e)| e - s).sum();
3302 assert_eq!(total, data.len());
3303 }
3304
3305 #[test]
3308 fn overflow_safe_partition_target_u128() {
3309 let huge: usize = usize::MAX;
3311 let target = (huge as u128) * (2u128) / (3u128);
3314 assert!(target < huge as u128);
3315
3316 let data = b"aa\nbb\ncc\ndd\nee\n";
3319 let partitions = find_partition_boundaries(data, 3, b'\n');
3320 let total: usize = partitions.iter().map(|(s, e)| e - s).sum();
3321 assert_eq!(total, data.len());
3322 }
3323
3324 #[test]
3325 fn overflow_safe_scanner_target_saturates() {
3326 let data = b"hello\nworld\n";
3329 let chunks = find_chunk_boundaries(data, usize::MAX, b'\n');
3330 assert_eq!(chunks, vec![(0, data.len())]);
3331 }
3332
3333 #[test]
3334 fn overflow_safe_cursor_target_saturates() {
3335 let data = b"hello\nworld\n";
3336 let cursor: Vec<&[u8]> = ChunkCursor::new(data, usize::MAX, b'\n').collect();
3337 let total: usize = cursor.iter().map(|c| c.len()).sum();
3338 assert_eq!(total, data.len());
3339 }
3340
3341 #[test]
3342 fn overflow_safe_pattern_target_saturates() {
3343 let data = b"a\r\nb\r\nc\r\n";
3344 let chunks = find_chunk_boundaries_pattern(data, usize::MAX, b"\r\n");
3345 assert_eq!(chunks, vec![(0, data.len())]);
3346 }
3347
3348 #[test]
3349 fn overflow_safe_pattern_cursor_target_saturates() {
3350 let data = b"a\r\nb\r\nc\r\n";
3351 let cursor: Vec<&[u8]> = PatternChunkCursor::new(data, usize::MAX, b"\r\n").collect();
3352 let total: usize = cursor.iter().map(|c| c.len()).sum();
3353 assert_eq!(total, data.len());
3354 }
3355
3356 #[test]
3357 fn overflow_safe_fixed_bounds_large_values() {
3358 assert_eq!(fixed_chunk_count(0, usize::MAX), 0);
3361 assert_eq!(fixed_chunk_count(1024, usize::MAX), 1);
3362 assert_eq!(fixed_chunk_bounds(1024, usize::MAX, 0), Some((0, 1024)));
3363
3364 let len: usize = 1000;
3366 assert_eq!(fixed_chunk_count(len, 1), len);
3367 assert_eq!(fixed_chunk_bounds(len, 1, len - 1), Some((len - 1, len)));
3368
3369 assert_eq!(fixed_chunk_bounds(0, usize::MAX, 0), None);
3371 }
3372}