1use super::art_source::ArtSource;
2use super::crc::{crc_shift_zeros, crc32, crc32_update};
3use crate::error::{FormatError, Result};
4
5pub const CAPTURE: &[u8; 4] = b"OggS";
6
7pub const FLAG_CONTINUED: u8 = 0x01;
9pub const FLAG_BOS: u8 = 0x02;
10#[allow(dead_code)]
11pub const FLAG_EOS: u8 = 0x04;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct PageHeader {
17 pub header_type: u8,
18 pub granule: u64,
19 pub serial: u32,
20 pub seq: u32,
21 pub crc: u32,
22 pub seg_count: u8,
23 pub header_len: usize,
24 pub payload_len: usize,
25}
26
27impl PageHeader {
28 pub fn total_len(&self) -> usize {
29 self.header_len + self.payload_len
30 }
31}
32
33pub fn parse_page(buf: &[u8], pos: usize) -> Result<PageHeader> {
36 if pos + 27 > buf.len() || &buf[pos..pos + 4] != CAPTURE {
37 return Err(FormatError::Malformed);
38 }
39 if buf[pos + 4] != 0 {
40 return Err(FormatError::Malformed);
41 }
42 let header_type = buf[pos + 5];
43 let granule = u64::from_le_bytes(buf[pos + 6..pos + 14].try_into().unwrap());
44 let serial = u32::from_le_bytes(buf[pos + 14..pos + 18].try_into().unwrap());
45 let seq = u32::from_le_bytes(buf[pos + 18..pos + 22].try_into().unwrap());
46 let crc = u32::from_le_bytes(buf[pos + 22..pos + 26].try_into().unwrap());
47 let seg_count = buf[pos + 26];
48 let table_start = pos + 27;
49 let table_end = table_start + seg_count as usize;
50 if table_end > buf.len() {
51 return Err(FormatError::Malformed);
52 }
53 let payload_len: usize = buf[table_start..table_end]
54 .iter()
55 .map(|&b| b as usize)
56 .sum();
57 Ok(PageHeader {
58 header_type,
59 granule,
60 serial,
61 seq,
62 crc,
63 seg_count,
64 header_len: 27 + seg_count as usize,
65 payload_len,
66 })
67}
68
69pub(crate) fn lacing_values(payload_len: usize) -> Vec<u8> {
73 let mut v = vec![255u8; payload_len / 255];
74 v.push(u8::try_from(payload_len % 255).expect("x % 255 < 256"));
75 v
76}
77
78pub fn lace_packet(
83 serial: u32,
84 seq_start: u32,
85 bos: bool,
86 granule: u64,
87 packet: &[u8],
88) -> (Vec<u8>, u32) {
89 let laces = lacing_values(packet.len());
90 let mut out = Vec::new();
91 let mut seq = seq_start;
92 let mut lace_pos = 0usize;
93 let mut payload_pos = 0usize;
94 let mut first = true;
95 while first || lace_pos < laces.len() {
97 let chunk = (laces.len() - lace_pos).min(255);
98 let table = &laces[lace_pos..lace_pos + chunk];
99 let page_payload: usize = table.iter().map(|&b| b as usize).sum();
100
101 let mut header_type = 0u8;
102 if bos && first {
103 header_type |= FLAG_BOS;
104 }
105 if !first {
106 header_type |= FLAG_CONTINUED;
107 }
108
109 let page_start = out.len();
110 out.extend_from_slice(CAPTURE);
111 out.push(0);
112 out.push(header_type);
113 out.extend_from_slice(&granule.to_le_bytes());
114 out.extend_from_slice(&serial.to_le_bytes());
115 out.extend_from_slice(&seq.to_le_bytes());
116 out.extend_from_slice(&0u32.to_le_bytes()); out.push(u8::try_from(chunk).expect("chunk is .min(255) so fits in u8"));
118 out.extend_from_slice(table);
119 out.extend_from_slice(&packet[payload_pos..payload_pos + page_payload]);
120
121 let crc = crc32(&out[page_start..]);
122 out[page_start + 22..page_start + 26].copy_from_slice(&crc.to_le_bytes());
123
124 lace_pos += chunk;
125 payload_pos += page_payload;
126 seq = seq.wrapping_add(1);
127 first = false;
128 }
129 (out, seq.wrapping_sub(seq_start))
130}
131
132pub fn build_header(serial: u32, packets: &[&[u8]]) -> (Vec<u8>, u32) {
136 let mut out = Vec::new();
137 let mut seq = 0u32;
138 for (i, pkt) in packets.iter().enumerate() {
139 let (bytes, used) = lace_packet(serial, seq, i == 0, 0, pkt);
140 out.extend_from_slice(&bytes);
141 seq += used;
142 }
143 (out, seq)
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct ReadPacket {
150 pub data: Vec<u8>,
151 pub end_offset: usize,
152 pub pages_through_end: u32,
153}
154
155pub fn read_packets(data: &[u8], want: usize) -> Result<Vec<ReadPacket>> {
160 let mut out: Vec<ReadPacket> = Vec::new();
161 let mut pos = 0usize;
162 let mut pages = 0u32;
163 let mut cur: Vec<u8> = Vec::new();
164 while out.len() < want {
165 let h = parse_page(data, pos)?;
166 pages += 1;
167 let table_start = pos + 27;
168 let mut payload_pos = h.header_len;
169 for i in 0..h.seg_count as usize {
170 let lace = data[table_start + i] as usize;
171 let seg_start = pos + payload_pos;
172 let seg_end = seg_start + lace;
173 if seg_end > data.len() {
174 return Err(FormatError::Malformed);
175 }
176 cur.extend_from_slice(&data[seg_start..seg_end]);
177 payload_pos += lace;
178 if lace < 255 {
179 out.push(ReadPacket {
180 data: std::mem::take(&mut cur),
181 end_offset: pos + h.total_len(),
182 pages_through_end: pages,
183 });
184 if out.len() == want {
185 break;
186 }
187 }
188 }
189 pos += h.total_len();
190 }
191 Ok(out)
192}
193
194pub fn patch_page_header(page: &[u8], new_seq: u32) -> Result<Vec<u8>> {
199 let h = parse_page(page, 0)?;
200 if page.len() < h.total_len() {
201 return Err(FormatError::Malformed);
202 }
203 let mut full = page[..h.total_len()].to_vec();
204 full[18..22].copy_from_slice(&new_seq.to_le_bytes());
205 full[22..26].copy_from_slice(&0u32.to_le_bytes());
206 let crc = crc32(&full);
207 full[22..26].copy_from_slice(&crc.to_le_bytes());
208 full.truncate(h.header_len);
209 Ok(full)
210}
211
212pub fn patch_page_header_algebraic(header: &[u8], new_seq: u32) -> Result<Vec<u8>> {
225 if header.len() < 27 {
226 return Err(FormatError::Malformed);
227 }
228 let seg_count = header[26] as usize;
229 let header_len = 27 + seg_count;
230 if header.len() < header_len {
231 return Err(FormatError::Malformed);
232 }
233 let payload_len: usize = header[27..header_len].iter().map(|&b| b as usize).sum();
234 let old_seq = u32::from_le_bytes(header[18..22].try_into().unwrap());
235 let old_crc = u32::from_le_bytes(header[22..26].try_into().unwrap());
236 let delta_bytes = (old_seq ^ new_seq).to_le_bytes();
239 let trailing = 5 + seg_count + payload_len; let delta_crc = crc_shift_zeros(crc32(&delta_bytes), trailing);
241 let new_crc = old_crc ^ delta_crc;
242 let mut out = header[..header_len].to_vec();
243 out[18..22].copy_from_slice(&new_seq.to_le_bytes());
244 out[22..26].copy_from_slice(&new_crc.to_le_bytes());
245 Ok(out)
246}
247
248pub fn verify_page_crc(page: &[u8]) -> Result<bool> {
253 let h = parse_page(page, 0)?;
254 if page.len() < h.total_len() {
255 return Err(FormatError::Malformed);
256 }
257 let mut buf = page[..h.total_len()].to_vec();
258 buf[22..26].copy_from_slice(&0u32.to_le_bytes());
259 Ok(crc32(&buf) == h.crc)
260}
261
262use crate::layout::Segment;
263
264pub(crate) enum PayloadChunk {
266 Bytes(Vec<u8>),
268 Art {
273 art_id: i64,
274 base64: bool,
275 art_total: u64,
276 },
277}
278
279impl PayloadChunk {
280 fn out_len(&self) -> usize {
281 match self {
282 PayloadChunk::Bytes(b) => b.len(),
283 PayloadChunk::Art {
284 base64, art_total, ..
285 } => {
286 let n = if *base64 {
287 crate::ogg::b64::b64_len(*art_total)
288 } else {
289 *art_total
290 };
291 crate::convert::usize_from(n)
292 }
293 }
294 }
295}
296
297pub(crate) fn lace_chunks_to_segments(
302 serial: u32,
303 seq_start: u32,
304 bos: bool,
305 chunks: &[PayloadChunk],
306 src: &dyn ArtSource,
307) -> crate::error::Result<(Vec<Segment>, u32)> {
308 let total: usize = chunks.iter().map(PayloadChunk::out_len).sum();
309 let laces = lacing_values(total);
310
311 let mut segments: Vec<Segment> = Vec::new();
312 let mut seq = seq_start;
313 let mut lace_pos = 0usize;
314 let mut payload_pos = 0usize;
315 let mut first = true;
316
317 while first || lace_pos < laces.len() {
318 let seg_count = (laces.len() - lace_pos).min(255);
319 let table = &laces[lace_pos..lace_pos + seg_count];
320 let page_payload: usize = table.iter().map(|&b| b as usize).sum();
321
322 let mut header_type = 0u8;
323 if bos && first {
324 header_type |= FLAG_BOS;
325 }
326 if !first {
327 header_type |= FLAG_CONTINUED;
328 }
329
330 let header_len = 27 + seg_count;
333 let mut header = Vec::with_capacity(header_len);
334 header.extend_from_slice(CAPTURE);
335 header.push(0);
336 header.push(header_type);
337 header.extend_from_slice(&0u64.to_le_bytes()); header.extend_from_slice(&serial.to_le_bytes());
339 header.extend_from_slice(&seq.to_le_bytes());
340 header.extend_from_slice(&0u32.to_le_bytes()); header.push(u8::try_from(seg_count).expect("seg_count is .min(255) so fits in u8"));
342 header.extend_from_slice(table);
343
344 let mut crc = crc32_update(0, &header);
345 crc_feed_payload(&mut crc, chunks, src, payload_pos, page_payload)?;
346 header[22..26].copy_from_slice(&crc.to_le_bytes());
347
348 emit_segments(&mut segments, &header, chunks, payload_pos, page_payload);
349
350 payload_pos += page_payload;
351 lace_pos += seg_count;
352 seq += 1;
353 first = false;
354 }
355 Ok((segments, seq - seq_start))
356}
357
358fn crc_feed_payload(
361 crc: &mut u32,
362 chunks: &[PayloadChunk],
363 src: &dyn ArtSource,
364 p0: usize,
365 plen: usize,
366) -> crate::error::Result<()> {
367 let end = p0 + plen;
368 let mut cs = 0usize;
369 for c in chunks {
370 let ce = cs + c.out_len();
371 let os = p0.max(cs);
372 let oe = end.min(ce);
373 if os < oe {
374 match c {
375 PayloadChunk::Bytes(b) => {
376 *crc = crc32_update(*crc, &b[os - cs..oe - cs]);
377 }
378 PayloadChunk::Art {
379 art_id,
380 base64,
381 art_total,
382 } => {
383 crc_feed_art(
384 crc,
385 src,
386 *art_id,
387 *base64,
388 *art_total,
389 (os - cs) as u64,
390 oe - os,
391 )?;
392 }
393 }
394 }
395 cs = ce;
396 }
397 Ok(())
398}
399
400fn crc_feed_art(
403 crc: &mut u32,
404 src: &dyn ArtSource,
405 art_id: i64,
406 base64: bool,
407 art_total: u64,
408 out_off: u64,
409 out_len: usize,
410) -> crate::error::Result<()> {
411 if base64 {
412 let w = crate::ogg::b64::b64_window(out_off, out_len as u64, art_total);
413 let mut raw = vec![0u8; crate::convert::usize_from(w.in_len)];
414 src.read_window(art_id, w.in_start, &mut raw)?;
415 let enc = crate::ogg::b64::encode_b64_slice(&raw, w.skip, out_len);
416 *crc = crc32_update(*crc, &enc);
417 } else {
418 let mut raw = vec![0u8; out_len];
419 src.read_window(art_id, out_off, &mut raw)?;
420 *crc = crc32_update(*crc, &raw);
421 }
422 Ok(())
423}
424
425fn emit_segments(
428 segments: &mut Vec<Segment>,
429 header: &[u8],
430 chunks: &[PayloadChunk],
431 p0: usize,
432 plen: usize,
433) {
434 let end = p0 + plen;
435 let mut buf: Vec<u8> = header.to_vec();
436 let mut cs = 0usize;
437 for c in chunks {
438 let ce = cs + c.out_len();
439 let os = p0.max(cs);
440 let oe = end.min(ce);
441 if os < oe {
442 match c {
443 PayloadChunk::Bytes(b) => buf.extend_from_slice(&b[os - cs..oe - cs]),
444 PayloadChunk::Art {
445 art_id,
446 base64,
447 art_total,
448 } => {
449 if !buf.is_empty() {
450 segments.push(Segment::Inline(std::mem::take(&mut buf)));
451 }
452 segments.push(Segment::OggArtSlice {
453 art_id: *art_id,
454 offset: (os - cs) as u64,
455 len: crate::BlobLen::new((oe - os) as u64)
456 .expect("ogg art slice span is non-empty"),
457 base64: *base64,
458 art_total: *art_total,
459 });
460 }
461 }
462 }
463 cs = ce;
464 }
465 if !buf.is_empty() {
466 segments.push(Segment::Inline(buf));
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 fn hand_page() -> Vec<u8> {
475 let mut p = Vec::new();
476 p.extend_from_slice(CAPTURE);
477 p.push(0); p.push(FLAG_BOS); p.extend_from_slice(&0u64.to_le_bytes()); p.extend_from_slice(&0xDEAD_BEEFu32.to_le_bytes()); p.extend_from_slice(&7u32.to_le_bytes()); p.extend_from_slice(&0x1122_3344u32.to_le_bytes()); p.push(2); p.push(0x10);
485 p.push(0x20); p.extend(std::iter::repeat_n(0xAB, 0x30));
487 p
488 }
489
490 #[test]
491 fn parses_fields_and_lengths() {
492 let p = hand_page();
493 let h = parse_page(&p, 0).unwrap();
494 assert_eq!(h.header_type, FLAG_BOS);
495 assert_eq!(h.serial, 0xDEAD_BEEF);
496 assert_eq!(h.seq, 7);
497 assert_eq!(h.crc, 0x1122_3344);
498 assert_eq!(h.seg_count, 2);
499 assert_eq!(h.payload_len, 0x30);
500 assert_eq!(h.header_len, 29);
501 assert_eq!(h.total_len(), 0x30 + 29);
502 }
503
504 #[test]
505 fn rejects_bad_capture() {
506 let mut p = hand_page();
507 p[0] = b'X';
508 assert_eq!(parse_page(&p, 0), Err(FormatError::Malformed));
509 }
510
511 #[test]
512 fn rejects_nonzero_version() {
513 let mut p = hand_page();
514 p[4] = 1; assert_eq!(parse_page(&p, 0), Err(FormatError::Malformed));
516 }
517
518 #[test]
519 fn single_page_packet_round_trips_and_crc_valid() {
520 let packet: Vec<u8> = (0..200u8).collect();
521 let (bytes, pages) = lace_packet(0xABCD, 0, true, 0, &packet);
522 assert_eq!(pages, 1);
523 let h = parse_page(&bytes, 0).unwrap();
524 assert_eq!(h.header_type, FLAG_BOS);
525 assert_eq!(h.payload_len, 200);
526 let mut z = bytes.clone();
528 z[22..26].copy_from_slice(&0u32.to_le_bytes());
529 assert_eq!(crc32(&z), h.crc);
530 }
531
532 #[test]
533 fn exact_multiple_of_255_appends_terminating_zero() {
534 let packet = vec![0u8; 255];
535 let (bytes, pages) = lace_packet(1, 0, false, 0, &packet);
536 assert_eq!(pages, 1);
537 let h = parse_page(&bytes, 0).unwrap();
538 assert_eq!(h.seg_count, 2);
540 assert_eq!(h.payload_len, 255);
541 }
542
543 #[test]
544 fn large_packet_spans_multiple_pages_with_continuation() {
545 let packet = vec![0x5Au8; 70_000]; let (bytes, pages) = lace_packet(2, 5, false, 0, &packet);
547 assert_eq!(pages, 2);
548 let p0 = parse_page(&bytes, 0).unwrap();
549 assert_eq!(p0.header_type & FLAG_CONTINUED, 0);
550 assert_eq!(p0.payload_len, 65_025);
551 let p1 = parse_page(&bytes, p0.total_len()).unwrap();
552 assert_eq!(p1.header_type & FLAG_CONTINUED, FLAG_CONTINUED);
553 assert_eq!(p1.seq, 6);
554 assert_eq!(p0.payload_len + p1.payload_len, 70_000);
555 }
556
557 #[test]
558 fn build_header_numbers_pages_and_sets_bos_once() {
559 let a = vec![1u8; 10];
560 let b = vec![2u8; 10];
561 let (bytes, count) = build_header(9, &[&a, &b]);
562 assert_eq!(count, 2);
563 let p0 = parse_page(&bytes, 0).unwrap();
564 let p1 = parse_page(&bytes, p0.total_len()).unwrap();
565 assert_eq!(p0.header_type & FLAG_BOS, FLAG_BOS);
566 assert_eq!(p1.header_type & FLAG_BOS, 0);
567 assert_eq!(p0.seq, 0);
568 assert_eq!(p1.seq, 1);
569 }
570
571 #[test]
572 fn read_packets_reassembles_multipage_packet() {
573 let small = vec![7u8; 5];
575 let big = vec![9u8; 70_000];
576 let (mut bytes, _) = lace_packet(3, 0, true, 0, &small);
577 let (b2, _) = lace_packet(3, 1, false, 0, &big);
578 bytes.extend_from_slice(&b2);
579
580 let pkts = read_packets(&bytes, 2).unwrap();
581 assert_eq!(pkts.len(), 2);
582 assert_eq!(pkts[0].data, small);
583 assert_eq!(pkts[1].data, big);
584 assert_eq!(pkts[1].pages_through_end, 3);
585 assert_eq!(pkts[1].end_offset, bytes.len());
586 }
587
588 #[test]
589 fn multipage_payload_cursor_advances_across_pages() {
590 let packet: Vec<u8> = (0..70_000u32).map(|i| (i % 251) as u8).collect();
595 let (bytes, pages) = lace_packet(2, 0, false, 0, &packet);
596 assert_eq!(pages, 2);
597 let pkts = read_packets(&bytes, 1).unwrap();
598 assert_eq!(pkts[0].data, packet);
599 }
600
601 #[test]
602 fn patch_page_header_updates_seq_and_crc() {
603 let packet = vec![0x42u8; 300];
604 let (page, _) = lace_packet(0xCAFE, 10, false, 7, &packet);
605 let patched = patch_page_header(&page, 12).unwrap();
606 let h0 = parse_page(&page, 0).unwrap();
607 assert_eq!(patched.len(), h0.header_len);
608 let mut full = patched.clone();
611 full.extend_from_slice(&page[h0.header_len..h0.total_len()]);
612 let h1 = parse_page(&full, 0).unwrap();
613 assert_eq!(h1.seq, 12);
614 let mut z = full.clone();
615 z[22..26].copy_from_slice(&0u32.to_le_bytes());
616 assert_eq!(crc32(&z), h1.crc);
617 }
618
619 use crate::layout::Segment;
620
621 fn flatten(segments: &[Segment], art_out: &[u8]) -> Vec<u8> {
624 let mut v = Vec::new();
625 for s in segments {
626 match s {
627 Segment::Inline(b) => v.extend_from_slice(b),
628 Segment::OggArtSlice {
629 offset,
630 len,
631 base64,
632 ..
633 } => {
634 assert!(*base64);
635 v.extend_from_slice(
636 &art_out[usize::try_from(*offset).unwrap()
637 ..usize::try_from(*offset + len.get()).unwrap()],
638 );
639 }
640 other => panic!("unexpected segment {other:?}"),
641 }
642 }
643 v
644 }
645
646 #[test]
647 fn chunk_lacer_splits_art_across_pages_and_crcs_validate() {
648 use super::super::art_source::MapArtSource;
649 let head = vec![0xA0u8; 50];
652 let image: Vec<u8> = (0..60_000u32).map(|i| (i % 251) as u8).collect();
654 let art_out = crate::ogg::b64::encode_b64_slice(
655 &image,
656 0,
657 crate::convert::usize_from(crate::ogg::b64::b64_len(image.len() as u64)),
658 );
659 let tail = vec![0xB0u8; 10];
660 let chunks = vec![
661 PayloadChunk::Bytes(head.clone()),
662 PayloadChunk::Art {
663 art_id: 42,
664 base64: true,
665 art_total: image.len() as u64,
666 },
667 PayloadChunk::Bytes(tail.clone()),
668 ];
669 let src = MapArtSource::new([(42i64, image.clone())]);
670 let (segments, pages) = lace_chunks_to_segments(0x1234, 0, true, &chunks, &src).unwrap();
671 assert!(pages >= 2, "art run should span multiple pages");
672
673 let flat = flatten(&segments, &art_out);
675 let mut pos = 0usize;
677 let mut payload = Vec::new();
678 let mut seq_expected = 0u32;
679 while pos < flat.len() {
680 let h = parse_page(&flat, pos).unwrap();
681 let is_first = seq_expected == 0;
684 if is_first {
685 assert_eq!(h.header_type & FLAG_BOS, FLAG_BOS);
686 assert_eq!(h.header_type & FLAG_CONTINUED, 0);
687 } else {
688 assert_eq!(h.header_type & FLAG_BOS, 0);
689 assert_eq!(h.header_type & FLAG_CONTINUED, FLAG_CONTINUED);
690 }
691 assert_eq!(h.seq, seq_expected);
692 seq_expected += 1;
693 let mut z = flat[pos..pos + h.total_len()].to_vec();
695 z[22..26].copy_from_slice(&0u32.to_le_bytes());
696 assert_eq!(crc32(&z), h.crc);
697 payload.extend_from_slice(&flat[pos + h.header_len..pos + h.total_len()]);
698 pos += h.total_len();
699 }
700 let mut expected = head.clone();
701 expected.extend_from_slice(&art_out);
702 expected.extend_from_slice(&tail);
703 assert_eq!(payload, expected);
704
705 let art_served: u64 = segments
707 .iter()
708 .filter_map(|s| match s {
709 Segment::OggArtSlice { len, .. } => Some(len.get()),
710 _ => None,
711 })
712 .sum();
713 assert_eq!(art_served, art_out.len() as u64);
714 }
715
716 #[test]
717 fn eos_bit_is_preserved_through_renumber() {
718 let (mut page, _) = lace_packet(0xEE, 3, false, 9, &[0x11u8; 120]);
721 page[5] |= FLAG_EOS; let mut z = page.clone();
724 z[22..26].copy_from_slice(&0u32.to_le_bytes());
725 let crc = crc32(&z);
726 page[22..26].copy_from_slice(&crc.to_le_bytes());
727
728 let patched = patch_page_header(&page, 99).unwrap();
729 let h0 = parse_page(&page, 0).unwrap();
730 let mut full = patched.clone();
731 full.extend_from_slice(&page[h0.header_len..h0.total_len()]);
732 let h1 = parse_page(&full, 0).unwrap();
733 assert_eq!(h1.seq, 99);
734 assert_eq!(h1.header_type & FLAG_EOS, FLAG_EOS, "EOS bit dropped");
735 assert_eq!(h1.header_type, h0.header_type, "header_type changed");
736 }
737
738 #[test]
739 fn parse_page_rejects_truncated_header_and_table() {
740 let p = hand_page();
742 assert_eq!(parse_page(&p[..26], 0), Err(FormatError::Malformed));
743 assert!(parse_page(&p[..27], 0).is_err()); assert_eq!(parse_page(&p[..28], 0), Err(FormatError::Malformed));
746 assert!(parse_page(&p, 0).is_ok());
748 }
749
750 #[test]
751 fn patch_page_header_rejects_truncated_page() {
752 let (page, _) = lace_packet(0xCAFE, 1, false, 0, &vec![0x42u8; 300]);
753 let h = parse_page(&page, 0).unwrap();
754 assert_eq!(
757 patch_page_header(&page[..h.total_len() - 10], 2),
758 Err(FormatError::Malformed)
759 );
760 }
761
762 #[test]
763 fn read_packets_stops_exactly_at_want_within_a_page() {
764 let mut page = Vec::new();
767 page.extend_from_slice(CAPTURE);
768 page.push(0);
769 page.push(FLAG_BOS);
770 page.extend_from_slice(&0u64.to_le_bytes());
771 page.extend_from_slice(&7u32.to_le_bytes());
772 page.extend_from_slice(&0u32.to_le_bytes());
773 page.extend_from_slice(&0u32.to_le_bytes()); page.push(2); page.push(3); page.push(4); page.extend_from_slice(&[1, 2, 3, 9, 9, 9, 9]);
778 let mut z = page.clone();
779 z[22..26].copy_from_slice(&0u32.to_le_bytes());
780 let crc = crc32(&z);
781 page[22..26].copy_from_slice(&crc.to_le_bytes());
782
783 let pkts = read_packets(&page, 1).unwrap();
784 assert_eq!(pkts.len(), 1);
785 assert_eq!(pkts[0].data, vec![1, 2, 3]);
786 }
787
788 #[test]
789 fn patch_algebraic_matches_full_page() {
790 for &payload_len in &[0usize, 1, 255, 3000, 65025] {
793 for &old_seq in &[0u32, 1, 42, u32::MAX - 5] {
794 for &new_seq in &[old_seq, old_seq.wrapping_add(1), old_seq.wrapping_add(10)] {
795 let payload = vec![0xA5u8; payload_len];
796 let (page_bytes, _) = lace_packet(0x1234, old_seq, false, 0, &payload);
797 let want = patch_page_header(&page_bytes, new_seq).unwrap();
799 let h = parse_page(&page_bytes, 0).unwrap();
801 let got =
802 patch_page_header_algebraic(&page_bytes[..h.header_len], new_seq).unwrap();
803 assert_eq!(
804 got, want,
805 "payload_len={payload_len} old_seq={old_seq} new_seq={new_seq}"
806 );
807 }
808 }
809 }
810 }
811
812 #[test]
813 fn verify_page_crc_accepts_valid_rejects_tampered() {
814 let (page, _) = lace_packet(0x55, 9, false, 42, &vec![0x7Eu8; 500]);
816 assert!(
817 verify_page_crc(&page).unwrap(),
818 "valid page must verify true"
819 );
820 let mut tampered = page.clone();
822 let h = parse_page(&page, 0).unwrap();
823 tampered[h.header_len] ^= 0xFF; assert!(
825 !verify_page_crc(&tampered).unwrap(),
826 "tampered payload must verify false"
827 );
828 let mut bad_crc = page.clone();
830 bad_crc[22] ^= 0x01;
831 assert!(
832 !verify_page_crc(&bad_crc).unwrap(),
833 "corrupt stored CRC must verify false"
834 );
835 }
836
837 #[test]
838 fn patch_algebraic_accepts_zero_segment_header() {
839 let mut hdr = vec![0u8; 27];
843 hdr[..4].copy_from_slice(b"OggS");
844 hdr[18..22].copy_from_slice(&7u32.to_le_bytes()); let out = patch_page_header_algebraic(&hdr, 9).unwrap();
847 assert_eq!(out.len(), 27);
848 assert_eq!(u32::from_le_bytes(out[18..22].try_into().unwrap()), 9);
849 }
850
851 #[test]
852 fn patch_algebraic_rejects_truncated_segment_table() {
853 let mut hdr = vec![0u8; 27];
857 hdr[..4].copy_from_slice(b"OggS");
858 hdr[26] = 5; assert!(patch_page_header_algebraic(&hdr, 1).is_err());
860 }
861
862 #[test]
863 fn verify_page_crc_rejects_truncated_page() {
864 let (page, _) = lace_packet(0x55, 1, false, 0, &vec![0u8; 300]);
867 let h = parse_page(&page, 0).unwrap();
868 let truncated = &page[..h.total_len() - 10];
869 assert!(verify_page_crc(truncated).is_err());
870 }
871}