1use crate::header::{Bbheader, Mode, BBHEADER_LEN};
24
25pub const NM_UP_SIZE: usize = 188;
27
28pub const HEM_UP_SIZE: usize = 187;
30
31pub const TS_SYNC_BYTE: u8 = 0x47;
33
34#[derive(Clone, Copy)]
39#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
40pub struct NmTsIter<'a> {
41 data: &'a [u8],
42 pos: usize,
43}
44
45impl<'a> NmTsIter<'a> {
46 pub fn new(data: &'a [u8]) -> Self {
51 Self { data, pos: 0 }
52 }
53
54 pub fn remaining(self) -> &'a [u8] {
56 &self.data[self.pos..]
57 }
58}
59
60impl Iterator for NmTsIter<'_> {
61 type Item = [u8; NM_UP_SIZE];
62
63 fn next(&mut self) -> Option<Self::Item> {
64 if self.pos + NM_UP_SIZE > self.data.len() {
65 return None;
66 }
67 let mut pkt = [0u8; NM_UP_SIZE];
68 pkt[0] = TS_SYNC_BYTE; pkt[1..].copy_from_slice(&self.data[self.pos + 1..self.pos + NM_UP_SIZE]);
70 self.pos += NM_UP_SIZE;
71 Some(pkt)
72 }
73
74 fn size_hint(&self) -> (usize, Option<usize>) {
75 let remaining = self.data.len().saturating_sub(self.pos);
76 let count = remaining / NM_UP_SIZE;
77 (count, Some(count))
78 }
79}
80
81#[derive(Clone, Copy)]
87#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
88pub struct HemTsIter<'a> {
89 data: &'a [u8],
90 pos: usize,
91 npd: bool,
92}
93
94impl<'a> HemTsIter<'a> {
95 pub fn new(data: &'a [u8], npd: bool) -> Self {
100 Self { data, pos: 0, npd }
101 }
102
103 pub fn remaining(self) -> &'a [u8] {
105 &self.data[self.pos..]
106 }
107}
108
109impl Iterator for HemTsIter<'_> {
110 type Item = [u8; NM_UP_SIZE];
111
112 fn next(&mut self) -> Option<Self::Item> {
113 let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
114 if self.pos + stride > self.data.len() {
115 return None;
116 }
117 let mut pkt = [0u8; NM_UP_SIZE];
118 pkt[0] = TS_SYNC_BYTE;
119 pkt[1..].copy_from_slice(&self.data[self.pos..self.pos + HEM_UP_SIZE]);
120 self.pos += stride;
121 Some(pkt)
122 }
123
124 fn size_hint(&self) -> (usize, Option<usize>) {
125 let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
126 let remaining = self.data.len().saturating_sub(self.pos);
127 let count = remaining / stride;
128 (count, Some(count))
129 }
130}
131
132pub fn up_iter<'a>(
138 data: &'a [u8],
139 bbheader: &Bbheader,
140) -> Box<dyn Iterator<Item = [u8; NM_UP_SIZE]> + 'a> {
141 match bbheader.mode {
142 Mode::Normal => Box::new(NmTsIter::new(data)),
143 Mode::HighEfficiency => Box::new(HemTsIter::new(data, bbheader.matype.npd)),
144 }
145}
146
147pub struct CarryOverExtractor {
154 buf: [u8; NM_UP_SIZE],
156 pos: usize,
158}
159
160impl Default for CarryOverExtractor {
161 fn default() -> Self {
162 Self {
163 buf: [0u8; NM_UP_SIZE],
164 pos: 0,
165 }
166 }
167}
168
169impl CarryOverExtractor {
170 pub fn new() -> Self {
172 Self::default()
173 }
174
175 pub fn feed_hem(
183 &mut self,
184 bbheader_bytes: &[u8; BBHEADER_LEN],
185 data_field: &[u8],
186 npd: bool,
187 ) -> Vec<[u8; NM_UP_SIZE]> {
188 let mut out = Vec::new();
189 self.feed_hem_into(bbheader_bytes, data_field, npd, &mut out);
190 out
191 }
192
193 pub fn feed_hem_into(
197 &mut self,
198 bbheader_bytes: &[u8; BBHEADER_LEN],
199 data_field: &[u8],
200 npd: bool,
201 out: &mut Vec<[u8; NM_UP_SIZE]>,
202 ) {
203 out.clear();
204 assert!(
205 !npd,
206 "CarryOverExtractor does not yet implement NPD/DNP reinsertion"
207 );
208 let hdr = match Bbheader::parse(bbheader_bytes) {
209 Ok(h) => h,
210 Err(_) => return,
211 };
212 assert_eq!(
213 hdr.mode,
214 Mode::HighEfficiency,
215 "feed_hem called on non-HEM header"
216 );
217
218 let stride = HEM_UP_SIZE;
219 let syncd_bytes = (hdr.syncd / 8) as usize;
220 let dfl_bytes = (hdr.dfl / 8) as usize;
221 let data = &data_field[..dfl_bytes.min(data_field.len())];
222
223 if self.pos > 0 {
225 let need = stride + 1 - self.pos; if syncd_bytes == need && data.len() >= need {
227 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
228 self.buf[0] = 0x47;
229 out.push(self.buf);
230 self.pos = 0;
231 } else {
232 self.pos = 0;
234 }
235 }
236
237 let mut i = syncd_bytes;
239 while i + stride <= data.len() {
240 self.buf[0] = 0x47;
242 self.buf[1..1 + stride].copy_from_slice(&data[i..i + stride]);
243 out.push(self.buf);
244 i += stride;
245 }
246
247 if i < data.len() {
249 let tail = (data.len() - i).min(stride);
250 self.buf[1..1 + tail].copy_from_slice(&data[i..i + tail]);
252 self.pos = 1 + tail;
253 } else {
254 self.pos = 0;
255 }
256 }
257
258 pub fn feed_nm(
261 &mut self,
262 bbheader_bytes: &[u8; BBHEADER_LEN],
263 data_field: &[u8],
264 ) -> Vec<[u8; NM_UP_SIZE]> {
265 let mut out = Vec::new();
266 self.feed_nm_into(bbheader_bytes, data_field, &mut out);
267 out
268 }
269
270 pub fn feed_nm_into(
274 &mut self,
275 bbheader_bytes: &[u8; BBHEADER_LEN],
276 data_field: &[u8],
277 out: &mut Vec<[u8; NM_UP_SIZE]>,
278 ) {
279 out.clear();
280 let hdr = match Bbheader::parse(bbheader_bytes) {
281 Ok(h) => h,
282 Err(_) => return,
283 };
284 assert_eq!(hdr.mode, Mode::Normal, "feed_nm called on non-NM header");
285
286 let stride = NM_UP_SIZE;
287 let syncd_bytes = (hdr.syncd / 8) as usize;
288 let dfl_bytes = (hdr.dfl / 8) as usize;
289 let data = &data_field[..dfl_bytes.min(data_field.len())];
290
291 if self.pos > 0 {
293 let need = stride - self.pos;
294 if syncd_bytes == need && data.len() >= need {
295 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
296 self.buf[0] = 0x47; out.push(self.buf);
298 self.pos = 0;
299 } else {
300 self.pos = 0;
301 }
302 }
303
304 let mut i = syncd_bytes;
306 while i + stride <= data.len() {
307 self.buf.copy_from_slice(&data[i..i + stride]);
308 self.buf[0] = 0x47; out.push(self.buf);
310 i += stride;
311 }
312
313 if i < data.len() {
315 let tail = (data.len() - i).min(stride);
316 self.buf[..tail].copy_from_slice(&data[i..i + tail]);
317 self.pos = tail;
318 } else {
319 self.pos = 0;
320 }
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::header::{Bbheader, Matype, Mode, TsGs};
328
329 fn make_nm_header(syncd: u16) -> Bbheader {
330 Bbheader {
331 matype: Matype {
332 ts_gs: TsGs::Ts,
333 sis: true,
334 ccm: true,
335 issyi: false,
336 npd: false,
337 ext: 0,
338 isi: 0,
339 },
340 upl: 0,
341 sync: 0x47,
342 dfl: 0,
343 syncd,
344 mode: Mode::Normal,
345 issy_in_header: None,
346 }
347 }
348
349 fn make_hem_header(npd: bool) -> Bbheader {
350 Bbheader {
351 matype: Matype {
352 ts_gs: TsGs::Ts,
353 sis: true,
354 ccm: true,
355 issyi: false,
356 npd,
357 ext: 0,
358 isi: 0,
359 },
360 upl: 0,
361 sync: 0,
362 dfl: 0,
363 syncd: 0,
364 mode: Mode::HighEfficiency,
365 issy_in_header: None,
366 }
367 }
368
369 #[test]
370 fn nm_extracts_single_complete_up() {
371 let mut data = vec![0xAA; NM_UP_SIZE];
372 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
374 *byte = i as u8;
375 }
376
377 let _hdr = make_nm_header(0);
378 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
379
380 assert_eq!(pkts.len(), 1);
381 assert_eq!(pkts[0][0], TS_SYNC_BYTE);
382 assert_eq!(&pkts[0][1..], &data[1..]);
383 }
384
385 #[test]
386 fn nm_multiple_back_to_back_ups() {
387 let num_ups = 3;
388 let mut data = Vec::with_capacity(num_ups * NM_UP_SIZE);
389
390 for i in 0..num_ups {
391 data.push(0x00); for j in 1..NM_UP_SIZE {
393 data.push((i * 10 + j) as u8);
394 }
395 }
396
397 let _hdr = make_nm_header(0);
398 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
399
400 assert_eq!(pkts.len(), num_ups);
401 for pkt in &pkts {
402 assert_eq!(pkt[0], TS_SYNC_BYTE);
403 }
404 }
405
406 #[test]
407 fn nm_partial_tail_does_not_yield() {
408 let mut data = vec![0xAA; NM_UP_SIZE + 50];
409 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
411 *byte = i as u8;
412 }
413
414 let _hdr = make_nm_header(0);
415 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
416
417 assert_eq!(pkts.len(), 1); }
419
420 #[test]
421 fn nm_crc_byte_replaced_with_sync_only() {
422 let mut data = vec![0u8; NM_UP_SIZE];
423 data[0] = 0x42; data[1] = 0x47; for (i, byte) in data.iter_mut().enumerate().skip(2) {
426 *byte = i as u8;
427 }
428
429 let _hdr = make_nm_header(0);
430 let pkt = up_iter(&data, &_hdr).next().unwrap();
431
432 assert_eq!(pkt[0], TS_SYNC_BYTE); assert_eq!(pkt[1], 0x47); assert_eq!(&pkt[2..], &data[2..]);
435 }
436
437 #[test]
438 fn hem_extracts_up_with_sync_prepend() {
439 let data: Vec<u8> = (0..HEM_UP_SIZE as u8).cycle().take(HEM_UP_SIZE).collect();
440 let mut expected = [0u8; NM_UP_SIZE];
441 expected[0] = TS_SYNC_BYTE;
442 expected[1..].copy_from_slice(&data[..HEM_UP_SIZE]);
443
444 let _hdr = make_hem_header(false);
445 let pkt = up_iter(&data, &_hdr).next().unwrap();
446
447 assert_eq!(pkt, expected);
448 }
449
450 #[test]
451 fn hem_multiple_ups_without_npd() {
452 let num_ups = 3;
453 let data = vec![0xAB; num_ups * HEM_UP_SIZE];
454 let expected: [u8; NM_UP_SIZE] = {
455 let mut e = [0xAB; NM_UP_SIZE];
456 e[0] = TS_SYNC_BYTE;
457 e
458 };
459
460 let _hdr = make_hem_header(false);
461 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
462
463 assert_eq!(pkts.len(), num_ups);
464 for pkt in &pkts {
465 assert_eq!(*pkt, expected);
466 }
467 }
468
469 #[test]
470 fn hem_with_npd_skips_dnp_bytes() {
471 let up_size = HEM_UP_SIZE;
472 let num_ups = 2;
473 let stride = up_size + 1;
475 let mut data = Vec::with_capacity(num_ups * stride);
476
477 for i in 0..num_ups {
478 for j in 0..up_size {
479 data.push((i * up_size + j) as u8);
480 }
481 data.push(i as u8); }
483
484 let _hdr = make_hem_header(true);
485 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
486
487 assert_eq!(pkts.len(), num_ups);
488 for (i, pkt) in pkts.iter().enumerate() {
489 assert_eq!(pkt[0], TS_SYNC_BYTE);
490 let offset = i * stride;
492 assert_eq!(&pkt[1..], &data[offset..offset + up_size]);
493 }
494 }
495
496 #[test]
497 fn nm_remaining_returns_unconsumed_tail() {
498 let data = vec![0xAA; NM_UP_SIZE * 2 + 50];
499 let _hdr = make_nm_header(0);
500 let mut iter = NmTsIter::new(&data);
501
502 let _p1 = iter.next().unwrap();
503 let _p2 = iter.next().unwrap();
504 let remaining = iter.remaining();
505
506 assert_eq!(remaining.len(), 50);
507 }
508
509 #[test]
510 fn hem_remaining_returns_unconsumed_tail() {
511 let data = vec![0xAA; HEM_UP_SIZE * 2 + 30];
512 let _hdr = make_hem_header(false);
513 let mut iter = HemTsIter::new(&data, false);
514
515 let _p1 = iter.next().unwrap();
516 let _p2 = iter.next().unwrap();
517 let remaining = iter.remaining();
518
519 assert_eq!(remaining.len(), 30);
520 }
521
522 #[test]
523 fn empty_data_yields_nothing() {
524 let _hdr = make_nm_header(0);
525 let pkts: Vec<_> = up_iter(&[], &_hdr).collect();
526 assert!(pkts.is_empty());
527 }
528
529 #[test]
530 fn data_shorter_than_one_up_yields_nothing() {
531 let data = vec![0xAA; 100]; let _hdr = make_nm_header(0);
533 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
534 assert!(pkts.is_empty());
535 }
536
537 #[test]
538 fn carry_over_extractor_emits_ts_across_two_bbframes_hem() {
539 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
542 let mut h = [0u8; 10];
543 h[0] = 0xF0;
545 h[1] = 0x00; h[2] = 0x00;
548 h[3] = 0x00;
549 h[4] = (dfl_bits >> 8) as u8;
550 h[5] = (dfl_bits & 0xFF) as u8;
551 h[6] = 0x00; h[7] = (syncd_bits >> 8) as u8;
553 h[8] = (syncd_bits & 0xFF) as u8;
554 let crc = crate::crc::crc8(&h[..9]);
556 h[9] = crc ^ 1;
557 h
558 };
559
560 let frame1_data = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
564 let frame2_data = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
565 let hdr1 = make_hem_header(0, (frame1_data.len() * 8) as u16);
566 let hdr2 = make_hem_header(0, (frame2_data.len() * 8) as u16);
567
568 let mut extractor = CarryOverExtractor::new();
569 let packets1 = extractor.feed_hem(&hdr1, &frame1_data, false);
570 assert_eq!(packets1.len(), 0, "70 bytes (< 187) must not yet emit a UP");
571
572 let packets2 = extractor.feed_hem(&hdr2, &frame2_data, false);
573 assert!(!packets2.is_empty(), "boundary UP should complete");
574 assert_eq!(
575 packets2[0][0], 0x47,
576 "first emitted packet has sync byte prepended"
577 );
578 }
579
580 #[test]
581 fn feed_into_matches_allocating_api() {
582 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
588 let mut h = [0u8; 10];
589 h[0] = 0xF0;
590 h[4] = (dfl_bits >> 8) as u8;
591 h[5] = (dfl_bits & 0xFF) as u8;
592 h[7] = (syncd_bits >> 8) as u8;
593 h[8] = (syncd_bits & 0xFF) as u8;
594 let crc = crate::crc::crc8(&h[..9]);
595 h[9] = crc ^ 1;
596 h
597 };
598 let f1 = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
599 let f2 = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
600 let h1 = make_hem_header(0, (f1.len() * 8) as u16);
601 let h2 = make_hem_header(0, (f2.len() * 8) as u16);
602
603 let mut alloc = CarryOverExtractor::new();
604 let a1 = alloc.feed_hem(&h1, &f1, false);
605 let a2 = alloc.feed_hem(&h2, &f2, false);
606
607 let mut reuse = CarryOverExtractor::new();
608 let mut buf = Vec::new();
609 reuse.feed_hem_into(&h1, &f1, false, &mut buf);
610 let b1 = buf.clone();
611 reuse.feed_hem_into(&h2, &f2, false, &mut buf);
612 let b2 = buf.clone();
613
614 assert_eq!(a1, b1, "frame 1 output matches across APIs");
615 assert_eq!(
616 a2, b2,
617 "frame 2 (carry-over) output matches; buffer was cleared"
618 );
619 }
620}