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 assert!(
189 !npd,
190 "CarryOverExtractor does not yet implement NPD/DNP reinsertion"
191 );
192 let hdr = match Bbheader::parse(bbheader_bytes) {
193 Ok(h) => h,
194 Err(_) => return Vec::new(),
195 };
196 assert_eq!(
197 hdr.mode,
198 Mode::HighEfficiency,
199 "feed_hem called on non-HEM header"
200 );
201
202 let stride = HEM_UP_SIZE;
203 let syncd_bytes = (hdr.syncd / 8) as usize;
204 let dfl_bytes = (hdr.dfl / 8) as usize;
205 let data = &data_field[..dfl_bytes.min(data_field.len())];
206
207 let mut out = Vec::new();
208
209 if self.pos > 0 {
211 let need = stride + 1 - self.pos; if syncd_bytes == need && data.len() >= need {
213 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
214 self.buf[0] = 0x47;
215 out.push(self.buf);
216 self.pos = 0;
217 } else {
218 self.pos = 0;
220 }
221 }
222
223 let mut i = syncd_bytes;
225 while i + stride <= data.len() {
226 self.buf[0] = 0x47;
228 self.buf[1..1 + stride].copy_from_slice(&data[i..i + stride]);
229 out.push(self.buf);
230 i += stride;
231 }
232
233 if i < data.len() {
235 let tail = (data.len() - i).min(stride);
236 self.buf[1..1 + tail].copy_from_slice(&data[i..i + tail]);
238 self.pos = 1 + tail;
239 } else {
240 self.pos = 0;
241 }
242
243 out
244 }
245
246 pub fn feed_nm(
249 &mut self,
250 bbheader_bytes: &[u8; BBHEADER_LEN],
251 data_field: &[u8],
252 ) -> Vec<[u8; NM_UP_SIZE]> {
253 let hdr = match Bbheader::parse(bbheader_bytes) {
254 Ok(h) => h,
255 Err(_) => return Vec::new(),
256 };
257 assert_eq!(hdr.mode, Mode::Normal, "feed_nm called on non-NM header");
258
259 let stride = NM_UP_SIZE;
260 let syncd_bytes = (hdr.syncd / 8) as usize;
261 let dfl_bytes = (hdr.dfl / 8) as usize;
262 let data = &data_field[..dfl_bytes.min(data_field.len())];
263
264 let mut out = Vec::new();
265
266 if self.pos > 0 {
268 let need = stride - self.pos;
269 if syncd_bytes == need && data.len() >= need {
270 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
271 self.buf[0] = 0x47; out.push(self.buf);
273 self.pos = 0;
274 } else {
275 self.pos = 0;
276 }
277 }
278
279 let mut i = syncd_bytes;
281 while i + stride <= data.len() {
282 self.buf.copy_from_slice(&data[i..i + stride]);
283 self.buf[0] = 0x47; out.push(self.buf);
285 i += stride;
286 }
287
288 if i < data.len() {
290 let tail = (data.len() - i).min(stride);
291 self.buf[..tail].copy_from_slice(&data[i..i + tail]);
292 self.pos = tail;
293 } else {
294 self.pos = 0;
295 }
296
297 out
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::header::{Bbheader, Matype, Mode, TsGs};
305
306 fn make_nm_header(syncd: u16) -> Bbheader {
307 Bbheader {
308 matype: Matype {
309 ts_gs: TsGs::Ts,
310 sis: true,
311 ccm: true,
312 issyi: false,
313 npd: false,
314 ext: 0,
315 isi: 0,
316 },
317 upl: 0,
318 sync: 0x47,
319 dfl: 0,
320 syncd,
321 mode: Mode::Normal,
322 issy_in_header: None,
323 }
324 }
325
326 fn make_hem_header(npd: bool) -> Bbheader {
327 Bbheader {
328 matype: Matype {
329 ts_gs: TsGs::Ts,
330 sis: true,
331 ccm: true,
332 issyi: false,
333 npd,
334 ext: 0,
335 isi: 0,
336 },
337 upl: 0,
338 sync: 0,
339 dfl: 0,
340 syncd: 0,
341 mode: Mode::HighEfficiency,
342 issy_in_header: None,
343 }
344 }
345
346 #[test]
347 fn nm_extracts_single_complete_up() {
348 let mut data = vec![0xAA; NM_UP_SIZE];
349 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
351 *byte = i as u8;
352 }
353
354 let _hdr = make_nm_header(0);
355 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
356
357 assert_eq!(pkts.len(), 1);
358 assert_eq!(pkts[0][0], TS_SYNC_BYTE);
359 assert_eq!(&pkts[0][1..], &data[1..]);
360 }
361
362 #[test]
363 fn nm_multiple_back_to_back_ups() {
364 let num_ups = 3;
365 let mut data = Vec::with_capacity(num_ups * NM_UP_SIZE);
366
367 for i in 0..num_ups {
368 data.push(0x00); for j in 1..NM_UP_SIZE {
370 data.push((i * 10 + j) as u8);
371 }
372 }
373
374 let _hdr = make_nm_header(0);
375 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
376
377 assert_eq!(pkts.len(), num_ups);
378 for pkt in &pkts {
379 assert_eq!(pkt[0], TS_SYNC_BYTE);
380 }
381 }
382
383 #[test]
384 fn nm_partial_tail_does_not_yield() {
385 let mut data = vec![0xAA; NM_UP_SIZE + 50];
386 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
388 *byte = i as u8;
389 }
390
391 let _hdr = make_nm_header(0);
392 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
393
394 assert_eq!(pkts.len(), 1); }
396
397 #[test]
398 fn nm_crc_byte_replaced_with_sync_only() {
399 let mut data = vec![0u8; NM_UP_SIZE];
400 data[0] = 0x42; data[1] = 0x47; for (i, byte) in data.iter_mut().enumerate().skip(2) {
403 *byte = i as u8;
404 }
405
406 let _hdr = make_nm_header(0);
407 let pkt = up_iter(&data, &_hdr).next().unwrap();
408
409 assert_eq!(pkt[0], TS_SYNC_BYTE); assert_eq!(pkt[1], 0x47); assert_eq!(&pkt[2..], &data[2..]);
412 }
413
414 #[test]
415 fn hem_extracts_up_with_sync_prepend() {
416 let data: Vec<u8> = (0..HEM_UP_SIZE as u8).cycle().take(HEM_UP_SIZE).collect();
417 let mut expected = [0u8; NM_UP_SIZE];
418 expected[0] = TS_SYNC_BYTE;
419 expected[1..].copy_from_slice(&data[..HEM_UP_SIZE]);
420
421 let _hdr = make_hem_header(false);
422 let pkt = up_iter(&data, &_hdr).next().unwrap();
423
424 assert_eq!(pkt, expected);
425 }
426
427 #[test]
428 fn hem_multiple_ups_without_npd() {
429 let num_ups = 3;
430 let data = vec![0xAB; num_ups * HEM_UP_SIZE];
431 let expected: [u8; NM_UP_SIZE] = {
432 let mut e = [0xAB; NM_UP_SIZE];
433 e[0] = TS_SYNC_BYTE;
434 e
435 };
436
437 let _hdr = make_hem_header(false);
438 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
439
440 assert_eq!(pkts.len(), num_ups);
441 for pkt in &pkts {
442 assert_eq!(*pkt, expected);
443 }
444 }
445
446 #[test]
447 fn hem_with_npd_skips_dnp_bytes() {
448 let up_size = HEM_UP_SIZE;
449 let num_ups = 2;
450 let stride = up_size + 1;
452 let mut data = Vec::with_capacity(num_ups * stride);
453
454 for i in 0..num_ups {
455 for j in 0..up_size {
456 data.push((i * up_size + j) as u8);
457 }
458 data.push(i as u8); }
460
461 let _hdr = make_hem_header(true);
462 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
463
464 assert_eq!(pkts.len(), num_ups);
465 for (i, pkt) in pkts.iter().enumerate() {
466 assert_eq!(pkt[0], TS_SYNC_BYTE);
467 let offset = i * stride;
469 assert_eq!(&pkt[1..], &data[offset..offset + up_size]);
470 }
471 }
472
473 #[test]
474 fn nm_remaining_returns_unconsumed_tail() {
475 let data = vec![0xAA; NM_UP_SIZE * 2 + 50];
476 let _hdr = make_nm_header(0);
477 let mut iter = NmTsIter::new(&data);
478
479 let _p1 = iter.next().unwrap();
480 let _p2 = iter.next().unwrap();
481 let remaining = iter.remaining();
482
483 assert_eq!(remaining.len(), 50);
484 }
485
486 #[test]
487 fn hem_remaining_returns_unconsumed_tail() {
488 let data = vec![0xAA; HEM_UP_SIZE * 2 + 30];
489 let _hdr = make_hem_header(false);
490 let mut iter = HemTsIter::new(&data, false);
491
492 let _p1 = iter.next().unwrap();
493 let _p2 = iter.next().unwrap();
494 let remaining = iter.remaining();
495
496 assert_eq!(remaining.len(), 30);
497 }
498
499 #[test]
500 fn empty_data_yields_nothing() {
501 let _hdr = make_nm_header(0);
502 let pkts: Vec<_> = up_iter(&[], &_hdr).collect();
503 assert!(pkts.is_empty());
504 }
505
506 #[test]
507 fn data_shorter_than_one_up_yields_nothing() {
508 let data = vec![0xAA; 100]; let _hdr = make_nm_header(0);
510 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
511 assert!(pkts.is_empty());
512 }
513
514 #[test]
515 fn carry_over_extractor_emits_ts_across_two_bbframes_hem() {
516 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
519 let mut h = [0u8; 10];
520 h[0] = 0xF0;
522 h[1] = 0x00; h[2] = 0x00;
525 h[3] = 0x00;
526 h[4] = (dfl_bits >> 8) as u8;
527 h[5] = (dfl_bits & 0xFF) as u8;
528 h[6] = 0x00; h[7] = (syncd_bits >> 8) as u8;
530 h[8] = (syncd_bits & 0xFF) as u8;
531 let crc = crate::crc::crc8(&h[..9]);
533 h[9] = crc ^ 1;
534 h
535 };
536
537 let frame1_data = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
541 let frame2_data = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
542 let hdr1 = make_hem_header(0, (frame1_data.len() * 8) as u16);
543 let hdr2 = make_hem_header(0, (frame2_data.len() * 8) as u16);
544
545 let mut extractor = CarryOverExtractor::new();
546 let packets1 = extractor.feed_hem(&hdr1, &frame1_data, false);
547 assert_eq!(packets1.len(), 0, "70 bytes (< 187) must not yet emit a UP");
548
549 let packets2 = extractor.feed_hem(&hdr2, &frame2_data, false);
550 assert!(!packets2.is_empty(), "boundary UP should complete");
551 assert_eq!(
552 packets2[0][0], 0x47,
553 "first emitted packet has sync byte prepended"
554 );
555 }
556}