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
34pub struct NmTsIter<'a> {
39 data: &'a [u8],
40 pos: usize,
41}
42
43impl<'a> NmTsIter<'a> {
44 pub fn new(data: &'a [u8]) -> Self {
49 Self { data, pos: 0 }
50 }
51
52 pub fn remaining(self) -> &'a [u8] {
54 &self.data[self.pos..]
55 }
56}
57
58impl Iterator for NmTsIter<'_> {
59 type Item = [u8; NM_UP_SIZE];
60
61 fn next(&mut self) -> Option<Self::Item> {
62 if self.pos + NM_UP_SIZE > self.data.len() {
63 return None;
64 }
65 let mut pkt = [0u8; NM_UP_SIZE];
66 pkt[0] = TS_SYNC_BYTE; pkt[1..].copy_from_slice(&self.data[self.pos + 1..self.pos + NM_UP_SIZE]);
68 self.pos += NM_UP_SIZE;
69 Some(pkt)
70 }
71
72 fn size_hint(&self) -> (usize, Option<usize>) {
73 let remaining = self.data.len().saturating_sub(self.pos);
74 let count = remaining / NM_UP_SIZE;
75 (count, Some(count))
76 }
77}
78
79pub struct HemTsIter<'a> {
85 data: &'a [u8],
86 pos: usize,
87 npd: bool,
88}
89
90impl<'a> HemTsIter<'a> {
91 pub fn new(data: &'a [u8], npd: bool) -> Self {
96 Self { data, pos: 0, npd }
97 }
98
99 pub fn remaining(self) -> &'a [u8] {
101 &self.data[self.pos..]
102 }
103}
104
105impl Iterator for HemTsIter<'_> {
106 type Item = [u8; NM_UP_SIZE];
107
108 fn next(&mut self) -> Option<Self::Item> {
109 let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
110 if self.pos + stride > self.data.len() {
111 return None;
112 }
113 let mut pkt = [0u8; NM_UP_SIZE];
114 pkt[0] = TS_SYNC_BYTE;
115 pkt[1..].copy_from_slice(&self.data[self.pos..self.pos + HEM_UP_SIZE]);
116 self.pos += stride;
117 Some(pkt)
118 }
119
120 fn size_hint(&self) -> (usize, Option<usize>) {
121 let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
122 let remaining = self.data.len().saturating_sub(self.pos);
123 let count = remaining / stride;
124 (count, Some(count))
125 }
126}
127
128pub fn up_iter<'a>(
134 data: &'a [u8],
135 bbheader: &Bbheader,
136) -> Box<dyn Iterator<Item = [u8; NM_UP_SIZE]> + 'a> {
137 match bbheader.mode {
138 Mode::Normal => Box::new(NmTsIter::new(data)),
139 Mode::HighEfficiency => Box::new(HemTsIter::new(data, bbheader.matype.npd)),
140 }
141}
142
143pub struct CarryOverExtractor {
150 buf: [u8; NM_UP_SIZE],
152 pos: usize,
154}
155
156impl Default for CarryOverExtractor {
157 fn default() -> Self {
158 Self {
159 buf: [0u8; NM_UP_SIZE],
160 pos: 0,
161 }
162 }
163}
164
165impl CarryOverExtractor {
166 pub fn new() -> Self {
168 Self::default()
169 }
170
171 pub fn feed_hem(
179 &mut self,
180 bbheader_bytes: &[u8; BBHEADER_LEN],
181 data_field: &[u8],
182 npd: bool,
183 ) -> Vec<[u8; NM_UP_SIZE]> {
184 assert!(
185 !npd,
186 "CarryOverExtractor does not yet implement NPD/DNP reinsertion"
187 );
188 let hdr = match Bbheader::parse(bbheader_bytes) {
189 Ok(h) => h,
190 Err(_) => return Vec::new(),
191 };
192 assert_eq!(
193 hdr.mode,
194 Mode::HighEfficiency,
195 "feed_hem called on non-HEM header"
196 );
197
198 let stride = HEM_UP_SIZE;
199 let syncd_bytes = (hdr.syncd / 8) as usize;
200 let dfl_bytes = (hdr.dfl / 8) as usize;
201 let data = &data_field[..dfl_bytes.min(data_field.len())];
202
203 let mut out = Vec::new();
204
205 if self.pos > 0 {
207 let need = stride + 1 - self.pos; if syncd_bytes == need && data.len() >= need {
209 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
210 self.buf[0] = 0x47;
211 out.push(self.buf);
212 self.pos = 0;
213 } else {
214 self.pos = 0;
216 }
217 }
218
219 let mut i = syncd_bytes;
221 while i + stride <= data.len() {
222 self.buf[0] = 0x47;
224 self.buf[1..1 + stride].copy_from_slice(&data[i..i + stride]);
225 out.push(self.buf);
226 i += stride;
227 }
228
229 if i < data.len() {
231 let tail = (data.len() - i).min(stride);
232 self.buf[1..1 + tail].copy_from_slice(&data[i..i + tail]);
234 self.pos = 1 + tail;
235 } else {
236 self.pos = 0;
237 }
238
239 out
240 }
241
242 pub fn feed_nm(
245 &mut self,
246 bbheader_bytes: &[u8; BBHEADER_LEN],
247 data_field: &[u8],
248 ) -> Vec<[u8; NM_UP_SIZE]> {
249 let hdr = match Bbheader::parse(bbheader_bytes) {
250 Ok(h) => h,
251 Err(_) => return Vec::new(),
252 };
253 assert_eq!(hdr.mode, Mode::Normal, "feed_nm called on non-NM header");
254
255 let stride = NM_UP_SIZE;
256 let syncd_bytes = (hdr.syncd / 8) as usize;
257 let dfl_bytes = (hdr.dfl / 8) as usize;
258 let data = &data_field[..dfl_bytes.min(data_field.len())];
259
260 let mut out = Vec::new();
261
262 if self.pos > 0 {
264 let need = stride - self.pos;
265 if syncd_bytes == need && data.len() >= need {
266 self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
267 self.buf[0] = 0x47; out.push(self.buf);
269 self.pos = 0;
270 } else {
271 self.pos = 0;
272 }
273 }
274
275 let mut i = syncd_bytes;
277 while i + stride <= data.len() {
278 self.buf.copy_from_slice(&data[i..i + stride]);
279 self.buf[0] = 0x47; out.push(self.buf);
281 i += stride;
282 }
283
284 if i < data.len() {
286 let tail = (data.len() - i).min(stride);
287 self.buf[..tail].copy_from_slice(&data[i..i + tail]);
288 self.pos = tail;
289 } else {
290 self.pos = 0;
291 }
292
293 out
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::header::{Bbheader, Matype, Mode, TsGs};
301
302 fn make_nm_header(syncd: u16) -> Bbheader {
303 Bbheader {
304 matype: Matype {
305 ts_gs: TsGs::Ts,
306 sis: true,
307 ccm: true,
308 issyi: false,
309 npd: false,
310 ext: 0,
311 isi: 0,
312 },
313 upl: 0,
314 sync: 0x47,
315 dfl: 0,
316 syncd,
317 mode: Mode::Normal,
318 issy_in_header: None,
319 }
320 }
321
322 fn make_hem_header(npd: bool) -> Bbheader {
323 Bbheader {
324 matype: Matype {
325 ts_gs: TsGs::Ts,
326 sis: true,
327 ccm: true,
328 issyi: false,
329 npd,
330 ext: 0,
331 isi: 0,
332 },
333 upl: 0,
334 sync: 0,
335 dfl: 0,
336 syncd: 0,
337 mode: Mode::HighEfficiency,
338 issy_in_header: None,
339 }
340 }
341
342 #[test]
343 fn nm_extracts_single_complete_up() {
344 let mut data = vec![0xAA; NM_UP_SIZE];
345 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
347 *byte = i as u8;
348 }
349
350 let _hdr = make_nm_header(0);
351 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
352
353 assert_eq!(pkts.len(), 1);
354 assert_eq!(pkts[0][0], TS_SYNC_BYTE);
355 assert_eq!(&pkts[0][1..], &data[1..]);
356 }
357
358 #[test]
359 fn nm_multiple_back_to_back_ups() {
360 let num_ups = 3;
361 let mut data = Vec::with_capacity(num_ups * NM_UP_SIZE);
362
363 for i in 0..num_ups {
364 data.push(0x00); for j in 1..NM_UP_SIZE {
366 data.push((i * 10 + j) as u8);
367 }
368 }
369
370 let _hdr = make_nm_header(0);
371 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
372
373 assert_eq!(pkts.len(), num_ups);
374 for pkt in &pkts {
375 assert_eq!(pkt[0], TS_SYNC_BYTE);
376 }
377 }
378
379 #[test]
380 fn nm_partial_tail_does_not_yield() {
381 let mut data = vec![0xAA; NM_UP_SIZE + 50];
382 data[0] = 0xFF; for (i, byte) in data.iter_mut().enumerate().skip(1) {
384 *byte = i as u8;
385 }
386
387 let _hdr = make_nm_header(0);
388 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
389
390 assert_eq!(pkts.len(), 1); }
392
393 #[test]
394 fn nm_crc_byte_replaced_with_sync_only() {
395 let mut data = vec![0u8; NM_UP_SIZE];
396 data[0] = 0x42; data[1] = 0x47; for (i, byte) in data.iter_mut().enumerate().skip(2) {
399 *byte = i as u8;
400 }
401
402 let _hdr = make_nm_header(0);
403 let pkt = up_iter(&data, &_hdr).next().unwrap();
404
405 assert_eq!(pkt[0], TS_SYNC_BYTE); assert_eq!(pkt[1], 0x47); assert_eq!(&pkt[2..], &data[2..]);
408 }
409
410 #[test]
411 fn hem_extracts_up_with_sync_prepend() {
412 let data: Vec<u8> = (0..HEM_UP_SIZE as u8).cycle().take(HEM_UP_SIZE).collect();
413 let mut expected = [0u8; NM_UP_SIZE];
414 expected[0] = TS_SYNC_BYTE;
415 expected[1..].copy_from_slice(&data[..HEM_UP_SIZE]);
416
417 let _hdr = make_hem_header(false);
418 let pkt = up_iter(&data, &_hdr).next().unwrap();
419
420 assert_eq!(pkt, expected);
421 }
422
423 #[test]
424 fn hem_multiple_ups_without_npd() {
425 let num_ups = 3;
426 let data = vec![0xAB; num_ups * HEM_UP_SIZE];
427 let expected: [u8; NM_UP_SIZE] = {
428 let mut e = [0xAB; NM_UP_SIZE];
429 e[0] = TS_SYNC_BYTE;
430 e
431 };
432
433 let _hdr = make_hem_header(false);
434 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
435
436 assert_eq!(pkts.len(), num_ups);
437 for pkt in &pkts {
438 assert_eq!(*pkt, expected);
439 }
440 }
441
442 #[test]
443 fn hem_with_npd_skips_dnp_bytes() {
444 let up_size = HEM_UP_SIZE;
445 let num_ups = 2;
446 let stride = up_size + 1;
448 let mut data = Vec::with_capacity(num_ups * stride);
449
450 for i in 0..num_ups {
451 for j in 0..up_size {
452 data.push((i * up_size + j) as u8);
453 }
454 data.push(i as u8); }
456
457 let _hdr = make_hem_header(true);
458 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
459
460 assert_eq!(pkts.len(), num_ups);
461 for (i, pkt) in pkts.iter().enumerate() {
462 assert_eq!(pkt[0], TS_SYNC_BYTE);
463 let offset = i * stride;
465 assert_eq!(&pkt[1..], &data[offset..offset + up_size]);
466 }
467 }
468
469 #[test]
470 fn nm_remaining_returns_unconsumed_tail() {
471 let data = vec![0xAA; NM_UP_SIZE * 2 + 50];
472 let _hdr = make_nm_header(0);
473 let mut iter = NmTsIter::new(&data);
474
475 let _p1 = iter.next().unwrap();
476 let _p2 = iter.next().unwrap();
477 let remaining = iter.remaining();
478
479 assert_eq!(remaining.len(), 50);
480 }
481
482 #[test]
483 fn hem_remaining_returns_unconsumed_tail() {
484 let data = vec![0xAA; HEM_UP_SIZE * 2 + 30];
485 let _hdr = make_hem_header(false);
486 let mut iter = HemTsIter::new(&data, false);
487
488 let _p1 = iter.next().unwrap();
489 let _p2 = iter.next().unwrap();
490 let remaining = iter.remaining();
491
492 assert_eq!(remaining.len(), 30);
493 }
494
495 #[test]
496 fn empty_data_yields_nothing() {
497 let _hdr = make_nm_header(0);
498 let pkts: Vec<_> = up_iter(&[], &_hdr).collect();
499 assert!(pkts.is_empty());
500 }
501
502 #[test]
503 fn data_shorter_than_one_up_yields_nothing() {
504 let data = vec![0xAA; 100]; let _hdr = make_nm_header(0);
506 let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
507 assert!(pkts.is_empty());
508 }
509
510 #[test]
511 fn carry_over_extractor_emits_ts_across_two_bbframes_hem() {
512 let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
515 let mut h = [0u8; 10];
516 h[0] = 0xF0;
518 h[1] = 0x00; h[2] = 0x00;
521 h[3] = 0x00;
522 h[4] = (dfl_bits >> 8) as u8;
523 h[5] = (dfl_bits & 0xFF) as u8;
524 h[6] = 0x00; h[7] = (syncd_bits >> 8) as u8;
526 h[8] = (syncd_bits & 0xFF) as u8;
527 let crc = crate::crc::crc8(&h[..9]);
529 h[9] = crc ^ 1;
530 h
531 };
532
533 let frame1_data = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
537 let frame2_data = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
538 let hdr1 = make_hem_header(0, (frame1_data.len() * 8) as u16);
539 let hdr2 = make_hem_header(0, (frame2_data.len() * 8) as u16);
540
541 let mut extractor = CarryOverExtractor::new();
542 let packets1 = extractor.feed_hem(&hdr1, &frame1_data, false);
543 assert_eq!(packets1.len(), 0, "70 bytes (< 187) must not yet emit a UP");
544
545 let packets2 = extractor.feed_hem(&hdr2, &frame2_data, false);
546 assert!(!packets2.is_empty(), "boundary UP should complete");
547 assert_eq!(
548 packets2[0][0], 0x47,
549 "first emitted packet has sync byte prepended"
550 );
551 }
552}