1use num_enum::TryFromPrimitive;
4
5const HEADER_LEN: usize = 6;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13#[repr(u8)]
14#[non_exhaustive]
15pub enum PacketType {
16 BasebandFrame = 0x00,
18 AuxiliaryIqData = 0x01,
20 ArbitraryCellInsertion = 0x02,
22 L1Current = 0x10,
24 L1Future = 0x11,
26 P2BiasBalancing = 0x12,
28 Timestamp = 0x20,
30 IndividualAddressing = 0x21,
32 FefPartNull = 0x30,
34 FefPartIqData = 0x31,
36 FefPartComposite = 0x32,
38 FefSubPart = 0x33,
40}
41
42impl From<PacketType> for u8 {
43 fn from(pt: PacketType) -> Self {
44 pt as u8
45 }
46}
47
48impl From<num_enum::TryFromPrimitiveError<PacketType>> for crate::error::Error {
49 fn from(e: num_enum::TryFromPrimitiveError<PacketType>) -> Self {
50 crate::error::Error::InvalidPacketType { found: e.number }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize))]
66pub struct Header {
67 pub packet_type: PacketType,
69 pub packet_count: u8,
71 pub superframe_idx: u8,
73 pub t2mi_stream_id: u8,
75 pub payload_len_bits: u16,
77}
78
79impl<'a> dvb_common::Parse<'a> for Header {
80 type Error = crate::error::Error;
81
82 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
83 use super::error::Error;
84
85 let len = bytes.len();
86 if len < HEADER_LEN {
87 return Err(Error::BufferTooShort {
88 need: HEADER_LEN,
89 have: len,
90 what: "T2MI Header",
91 });
92 }
93
94 let packet_type = PacketType::try_from(bytes[0])?;
95 let packet_count = bytes[1];
96
97 let superframe_idx = (bytes[2] >> 4) & 0x0F;
99
100 if bytes[2] & 0x08 != 0 {
102 return Err(Error::ReservedBitsViolation {
103 field: "byte 2 bit 3",
104 reason: "RFU must be zero (ETSI TS 102 773 §5.1)",
105 });
106 }
107
108 let t2mi_stream_id = bytes[2] & 0x07;
110
111 if bytes[3] != 0 {
113 return Err(Error::ReservedBitsViolation {
114 field: "byte 3",
115 reason: "All 8 RFU bits must be zero (ETSI TS 102 773 §5.1)",
116 });
117 }
118
119 let payload_len_bits = u16::from_be_bytes([bytes[4], bytes[5]]);
120
121 Ok(Header {
122 packet_type,
123 packet_count,
124 superframe_idx,
125 t2mi_stream_id,
126 payload_len_bits,
127 })
128 }
129}
130
131impl Header {
132 #[must_use]
134 pub fn payload_len_bytes(&self) -> usize {
135 (self.payload_len_bits as usize).div_ceil(8)
136 }
137
138 #[must_use]
142 pub fn total_bytes(&self) -> usize {
143 HEADER_LEN + self.payload_len_bytes() + super::crc::CRC_LEN
144 }
145
146 pub fn raw_payload_bytes(packet: &[u8]) -> Result<&[u8], crate::error::Error> {
160 if packet.len() < HEADER_LEN {
161 return Err(crate::error::Error::BufferTooShort {
162 need: HEADER_LEN,
163 have: packet.len(),
164 what: "T2MI Header",
165 });
166 }
167 let payload_len_bits = u16::from_be_bytes([packet[4], packet[5]]);
168 let payload_len_bytes = (payload_len_bits as usize).div_ceil(8);
169 let end = HEADER_LEN + payload_len_bytes;
170 if packet.len() < end {
171 return Err(crate::error::Error::PayloadLengthMismatch {
172 declared_bits: payload_len_bits,
173 remaining_bytes: packet.len().saturating_sub(HEADER_LEN),
174 });
175 }
176 Ok(&packet[HEADER_LEN..end])
177 }
178
179 pub fn payload_bytes<'a>(&self, packet: &'a [u8]) -> Result<&'a [u8], crate::error::Error> {
186 let end = HEADER_LEN + self.payload_len_bytes();
187 if packet.len() < end {
188 return Err(crate::error::Error::PayloadLengthMismatch {
189 declared_bits: self.payload_len_bits,
190 remaining_bytes: packet.len().saturating_sub(HEADER_LEN),
191 });
192 }
193 Ok(&packet[HEADER_LEN..end])
194 }
195}
196
197impl dvb_common::Serialize for Header {
198 type Error = crate::error::Error;
199
200 fn serialized_len(&self) -> usize {
201 HEADER_LEN
202 }
203
204 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
205 use super::error::Error;
206
207 if buf.len() < HEADER_LEN {
208 return Err(Error::OutputBufferTooSmall {
209 need: HEADER_LEN,
210 have: buf.len(),
211 });
212 }
213
214 if self.t2mi_stream_id > 7 {
215 return Err(Error::ReservedBitsViolation {
216 field: "t2mi_stream_id",
217 reason: "Must be in range 0..=7 (3-bit field)",
218 });
219 }
220
221 buf[0] = self.packet_type.into();
222 buf[1] = self.packet_count;
223 buf[2] = (self.superframe_idx & 0x0F) << 4 | (self.t2mi_stream_id & 0x07);
224 buf[3] = 0; let len_be = self.payload_len_bits.to_be_bytes();
226 buf[4] = len_be[0];
227 buf[5] = len_be[1];
228
229 Ok(HEADER_LEN)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use dvb_common::{Parse, Serialize};
237
238 #[test]
239 fn packet_type_try_from_all_valid() {
240 let valid_types = [
242 0x00, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
243 ];
244 for v in valid_types {
245 let result = PacketType::try_from(v);
246 assert!(result.is_ok(), "PacketType::try_from({:#04x}) failed", v);
247 }
248 }
249
250 #[test]
251 fn packet_type_rejects_reserved() {
252 for v in 0x22..=0x2F {
254 assert!(
255 PacketType::try_from(v).is_err(),
256 "0x{v:02x} should be rejected"
257 );
258 }
259 for v in 0x34..=0xFF {
260 assert!(
261 PacketType::try_from(v).is_err(),
262 "0x{v:02x} should be rejected"
263 );
264 }
265 }
266
267 #[test]
268 fn exhaustive_byte_sweep() {
269 let mut matched = 0u16;
270 for byte in 0u8..=0xFF {
271 if let Ok(v) = PacketType::try_from(byte) {
272 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
273 matched += 1;
274 }
275 }
276 assert_eq!(matched, 12, "expected 12 matched variants");
277 }
278
279 #[test]
280 fn parse_rejects_buffer_shorter_than_6() {
281 let buf = [0x00u8; 5];
282 let result = Header::parse(&buf);
283 assert!(result.is_err());
284 let err = result.unwrap_err();
285 if let crate::Error::BufferTooShort { need, have, what } = err {
286 assert_eq!(need, HEADER_LEN);
287 assert_eq!(have, 5);
288 assert_eq!(what, "T2MI Header");
289 } else {
290 panic!("Expected BufferTooShort, got {:?}", err);
291 }
292 }
293
294 #[test]
295 fn parse_extracts_packet_type_and_count() {
296 let buf = [0x10u8, 0xAB, 0x00, 0x00, 0x00, 0x08];
297 let hdr = Header::parse(&buf).unwrap();
298 assert_eq!(hdr.packet_type, PacketType::L1Current);
299 assert_eq!(hdr.packet_count, 0xAB);
300 }
301
302 #[test]
303 fn parse_extracts_superframe_idx() {
304 let buf = [0x00u8, 0x00, 0x50, 0x00, 0x00, 0x08];
305 let hdr = Header::parse(&buf).unwrap();
306 assert_eq!(hdr.superframe_idx, 5);
307 }
308
309 #[test]
310 fn parse_accepts_all_defined_packet_types() {
311 let types = [
312 0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
313 ];
314 for &t in &types {
315 let buf = [t, 0x00, 0x00, 0x00, 0x00, 0x08];
316 let result = Header::parse(&buf);
317 assert!(
318 result.is_ok(),
319 "parse failed for packet_type {:#04x}: {:?}",
320 t,
321 result
322 );
323 assert_eq!(
324 result.unwrap().packet_type,
325 PacketType::try_from(t).unwrap()
326 );
327 }
328 }
329
330 #[test]
331 fn parse_rejects_reserved_packet_type_0x22() {
332 let buf = [0x22u8, 0x00, 0x00, 0x00, 0x00, 0x08];
333 let result = Header::parse(&buf);
334 assert!(result.is_err());
335 assert!(matches!(
336 result.unwrap_err(),
337 crate::Error::InvalidPacketType { found: 0x22 }
338 ));
339 }
340
341 #[test]
342 fn parse_extracts_t2mi_stream_id_0_through_7() {
343 for id in 0..=7 {
344 let buf = [0x00u8, 0x00, id, 0x00, 0x00, 0x08];
345 let hdr = Header::parse(&buf).unwrap();
346 assert_eq!(hdr.t2mi_stream_id, id, "stream_id mismatch for id={}", id);
347 }
348 }
349
350 #[test]
351 fn parse_rejects_nonzero_rfu_bits_in_byte2() {
352 let buf = [0x00u8, 0x00, 0x08, 0x00, 0x00, 0x08];
354 let result = Header::parse(&buf);
355 assert!(result.is_err());
356 assert!(matches!(
357 result.unwrap_err(),
358 crate::Error::ReservedBitsViolation { .. }
359 ));
360 }
361
362 #[test]
363 fn parse_rejects_nonzero_byte3() {
364 let buf = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x08];
365 let result = Header::parse(&buf);
366 assert!(result.is_err());
367 }
368
369 #[test]
370 fn parse_extracts_payload_len_bits() {
371 let buf = [0x00u8, 0x00, 0x00, 0x00, 0x01, 0x00];
372 let hdr = Header::parse(&buf).unwrap();
373 assert_eq!(hdr.payload_len_bits, 0x0100);
374 }
375
376 #[test]
377 fn payload_len_bytes_rounds_up() {
378 let hdr = Header {
379 packet_type: PacketType::BasebandFrame,
380 packet_count: 0,
381 superframe_idx: 0,
382 t2mi_stream_id: 0,
383 payload_len_bits: 13,
384 };
385 assert_eq!(hdr.payload_len_bytes(), 2); let hdr2 = Header {
388 payload_len_bits: 16,
389 ..hdr
390 };
391 assert_eq!(hdr2.payload_len_bytes(), 2);
392
393 let hdr3 = Header {
394 payload_len_bits: 0,
395 ..hdr
396 };
397 assert_eq!(hdr3.payload_len_bytes(), 0);
398 }
399
400 #[test]
403 fn serialize_writes_6_bytes() {
404 let hdr = Header {
405 packet_type: PacketType::BasebandFrame,
406 packet_count: 42,
407 superframe_idx: 7,
408 t2mi_stream_id: 3,
409 payload_len_bits: 128,
410 };
411 let mut buf = [0u8; 256];
412 let written = hdr.serialize_into(&mut buf).unwrap();
413 assert_eq!(written, HEADER_LEN);
414 assert_eq!(buf[0], 0x00);
415 assert_eq!(buf[1], 42);
416 assert_eq!(buf[2], (7 << 4) | 3);
417 assert_eq!(buf[3], 0);
418 assert_eq!(buf[4], 0);
419 assert_eq!(buf[5], 128);
420 }
421
422 #[test]
423 fn serialize_round_trip_identity_for_every_packet_type() {
424 let types = [
425 0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
426 ];
427 for &t in &types {
428 let original = Header {
429 packet_type: PacketType::try_from(t).unwrap(),
430 packet_count: 13,
431 superframe_idx: 7,
432 t2mi_stream_id: 2,
433 payload_len_bits: 512,
434 };
435 let mut buf = [0u8; HEADER_LEN];
436 original.serialize_into(&mut buf).unwrap();
437 let parsed = Header::parse(&buf).unwrap();
438 assert_eq!(original, parsed, "Round-trip failed for type {:#04x}", t);
439 }
440 }
441
442 #[test]
443 fn serialize_rejects_too_small_buffer() {
444 let hdr = Header {
445 packet_type: PacketType::BasebandFrame,
446 packet_count: 0,
447 superframe_idx: 0,
448 t2mi_stream_id: 0,
449 payload_len_bits: 0,
450 };
451 let mut buf = [0u8; 5];
452 let result = hdr.serialize_into(&mut buf);
453 assert!(result.is_err());
454 }
455
456 #[test]
457 fn serialize_rejects_t2mi_stream_id_above_7() {
458 let hdr = Header {
459 packet_type: PacketType::BasebandFrame,
460 packet_count: 0,
461 superframe_idx: 0,
462 t2mi_stream_id: 8,
463 payload_len_bits: 0,
464 };
465 let mut buf = [0u8; HEADER_LEN];
466 let result = hdr.serialize_into(&mut buf);
467 assert!(result.is_err());
468 }
469
470 #[test]
471 fn payload_bytes_slices_declared_payload() {
472 let buf = [
474 0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00,
475 ];
476 let hdr = Header::parse(&buf).unwrap();
477 assert_eq!(hdr.payload_bytes(&buf).unwrap(), &[0xAA, 0xBB, 0xCC]);
478 }
479
480 #[test]
481 fn payload_bytes_rejects_truncated_buffer() {
482 let buf = [0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA];
484 let hdr = Header::parse(&buf).unwrap();
485 assert!(matches!(
486 hdr.payload_bytes(&buf),
487 Err(crate::Error::PayloadLengthMismatch { .. })
488 ));
489 }
490}