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
54impl PacketType {
55 #[must_use]
57 pub fn name(&self) -> &'static str {
58 match self {
59 Self::BasebandFrame => "BBFRAME",
60 Self::AuxiliaryIqData => "Auxiliary stream I/Q data",
61 Self::ArbitraryCellInsertion => "Arbitrary cell insertion",
62 Self::L1Current => "L1-current",
63 Self::L1Future => "L1-future",
64 Self::P2BiasBalancing => "P2 bias balancing cells",
65 Self::Timestamp => "Timestamp",
66 Self::IndividualAddressing => "Individual addressing",
67 Self::FefPartNull => "FEF part: Null",
68 Self::FefPartIqData => "FEF part: I/Q data",
69 Self::FefPartComposite => "FEF part: composite",
70 Self::FefSubPart => "FEF sub-part",
71 }
72 }
73}
74broadcast_common::impl_spec_display!(PacketType);
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize))]
88pub struct Header {
89 pub packet_type: PacketType,
91 pub packet_count: u8,
93 pub superframe_idx: u8,
95 pub t2mi_stream_id: u8,
97 pub payload_len_bits: u16,
99}
100
101impl<'a> broadcast_common::Parse<'a> for Header {
102 type Error = crate::error::Error;
103
104 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
105 use super::error::Error;
106
107 let (hdr, _) = bytes
108 .split_first_chunk::<HEADER_LEN>()
109 .ok_or(Error::BufferTooShort {
110 need: HEADER_LEN,
111 have: bytes.len(),
112 what: "T2MI Header",
113 })?;
114
115 let packet_type = PacketType::try_from(hdr[0])?;
116 let packet_count = hdr[1];
117
118 let superframe_idx = (hdr[2] >> 4) & 0x0F;
120
121 if hdr[2] & 0x08 != 0 {
123 return Err(Error::ReservedBitsViolation {
124 field: "byte 2 bit 3",
125 reason: "RFU must be zero (ETSI TS 102 773 §5.1)",
126 });
127 }
128
129 let t2mi_stream_id = hdr[2] & 0x07;
131
132 if hdr[3] != 0 {
134 return Err(Error::ReservedBitsViolation {
135 field: "byte 3",
136 reason: "All 8 RFU bits must be zero (ETSI TS 102 773 §5.1)",
137 });
138 }
139
140 let payload_len_bits = u16::from_be_bytes([hdr[4], hdr[5]]);
141
142 Ok(Header {
143 packet_type,
144 packet_count,
145 superframe_idx,
146 t2mi_stream_id,
147 payload_len_bits,
148 })
149 }
150}
151
152impl Header {
153 #[must_use]
155 pub fn payload_len_bytes(&self) -> usize {
156 (self.payload_len_bits as usize).div_ceil(8)
157 }
158
159 #[must_use]
163 pub fn total_bytes(&self) -> usize {
164 HEADER_LEN + self.payload_len_bytes() + super::crc::CRC_LEN
165 }
166
167 pub fn raw_payload_bytes(packet: &[u8]) -> Result<&[u8], crate::error::Error> {
181 let (hdr, _) = packet.split_first_chunk::<HEADER_LEN>().ok_or(
182 crate::error::Error::BufferTooShort {
183 need: HEADER_LEN,
184 have: packet.len(),
185 what: "T2MI Header",
186 },
187 )?;
188 let payload_len_bits = u16::from_be_bytes([hdr[4], hdr[5]]);
189 let payload_len_bytes = (payload_len_bits as usize).div_ceil(8);
190 let end = HEADER_LEN + payload_len_bytes;
191 if packet.len() < end {
192 return Err(crate::error::Error::PayloadLengthMismatch {
193 declared_bits: payload_len_bits,
194 remaining_bytes: packet.len().saturating_sub(HEADER_LEN),
195 });
196 }
197 Ok(&packet[HEADER_LEN..end])
198 }
199
200 pub fn payload_bytes<'a>(&self, packet: &'a [u8]) -> Result<&'a [u8], crate::error::Error> {
207 let end = HEADER_LEN + self.payload_len_bytes();
208 if packet.len() < end {
209 return Err(crate::error::Error::PayloadLengthMismatch {
210 declared_bits: self.payload_len_bits,
211 remaining_bytes: packet.len().saturating_sub(HEADER_LEN),
212 });
213 }
214 Ok(&packet[HEADER_LEN..end])
215 }
216}
217
218impl broadcast_common::Serialize for Header {
219 type Error = crate::error::Error;
220
221 fn serialized_len(&self) -> usize {
222 HEADER_LEN
223 }
224
225 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
226 use super::error::Error;
227
228 if buf.len() < HEADER_LEN {
229 return Err(Error::OutputBufferTooSmall {
230 need: HEADER_LEN,
231 have: buf.len(),
232 });
233 }
234
235 if self.t2mi_stream_id > 7 {
236 return Err(Error::ReservedBitsViolation {
237 field: "t2mi_stream_id",
238 reason: "Must be in range 0..=7 (3-bit field)",
239 });
240 }
241
242 buf[0] = self.packet_type.into();
243 buf[1] = self.packet_count;
244 buf[2] = (self.superframe_idx & 0x0F) << 4 | (self.t2mi_stream_id & 0x07);
245 buf[3] = 0; let len_be = self.payload_len_bits.to_be_bytes();
247 buf[4] = len_be[0];
248 buf[5] = len_be[1];
249
250 Ok(HEADER_LEN)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use broadcast_common::{Parse, Serialize};
258
259 #[test]
260 fn packet_type_try_from_all_valid() {
261 let valid_types = [
263 0x00, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
264 ];
265 for v in valid_types {
266 let result = PacketType::try_from(v);
267 assert!(result.is_ok(), "PacketType::try_from({v:#04x}) failed");
268 }
269 }
270
271 #[test]
272 fn packet_type_rejects_reserved() {
273 for v in 0x22..=0x2F {
275 assert!(
276 PacketType::try_from(v).is_err(),
277 "0x{v:02x} should be rejected"
278 );
279 }
280 for v in 0x34..=0xFF {
281 assert!(
282 PacketType::try_from(v).is_err(),
283 "0x{v:02x} should be rejected"
284 );
285 }
286 }
287
288 #[test]
289 fn exhaustive_byte_sweep() {
290 let mut matched = 0u16;
291 for byte in 0u8..=0xFF {
292 if let Ok(v) = PacketType::try_from(byte) {
293 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
294 matched += 1;
295 }
296 }
297 assert_eq!(matched, 12, "expected 12 matched variants");
298 }
299
300 #[test]
301 fn parse_rejects_buffer_shorter_than_6() {
302 let buf = [0x00u8; 5];
303 let result = Header::parse(&buf);
304 assert!(result.is_err());
305 let err = result.unwrap_err();
306 if let crate::Error::BufferTooShort { need, have, what } = err {
307 assert_eq!(need, HEADER_LEN);
308 assert_eq!(have, 5);
309 assert_eq!(what, "T2MI Header");
310 } else {
311 panic!("Expected BufferTooShort, got {err:?}");
312 }
313 }
314
315 #[test]
316 fn parse_extracts_packet_type_and_count() {
317 let buf = [0x10u8, 0xAB, 0x00, 0x00, 0x00, 0x08];
318 let hdr = Header::parse(&buf).unwrap();
319 assert_eq!(hdr.packet_type, PacketType::L1Current);
320 assert_eq!(hdr.packet_count, 0xAB);
321 }
322
323 #[test]
324 fn parse_extracts_superframe_idx() {
325 let buf = [0x00u8, 0x00, 0x50, 0x00, 0x00, 0x08];
326 let hdr = Header::parse(&buf).unwrap();
327 assert_eq!(hdr.superframe_idx, 5);
328 }
329
330 #[test]
331 fn parse_accepts_all_defined_packet_types() {
332 let types = [
333 0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
334 ];
335 for &t in &types {
336 let buf = [t, 0x00, 0x00, 0x00, 0x00, 0x08];
337 let result = Header::parse(&buf);
338 assert!(
339 result.is_ok(),
340 "parse failed for packet_type {t:#04x}: {result:?}"
341 );
342 assert_eq!(
343 result.unwrap().packet_type,
344 PacketType::try_from(t).unwrap()
345 );
346 }
347 }
348
349 #[test]
350 fn parse_rejects_reserved_packet_type_0x22() {
351 let buf = [0x22u8, 0x00, 0x00, 0x00, 0x00, 0x08];
352 let result = Header::parse(&buf);
353 assert!(result.is_err());
354 assert!(matches!(
355 result.unwrap_err(),
356 crate::Error::InvalidPacketType { found: 0x22 }
357 ));
358 }
359
360 #[test]
361 fn parse_extracts_t2mi_stream_id_0_through_7() {
362 for id in 0..=7 {
363 let buf = [0x00u8, 0x00, id, 0x00, 0x00, 0x08];
364 let hdr = Header::parse(&buf).unwrap();
365 assert_eq!(hdr.t2mi_stream_id, id, "stream_id mismatch for id={id}");
366 }
367 }
368
369 #[test]
370 fn parse_rejects_nonzero_rfu_bits_in_byte2() {
371 let buf = [0x00u8, 0x00, 0x08, 0x00, 0x00, 0x08];
373 let result = Header::parse(&buf);
374 assert!(result.is_err());
375 assert!(matches!(
376 result.unwrap_err(),
377 crate::Error::ReservedBitsViolation { .. }
378 ));
379 }
380
381 #[test]
382 fn parse_rejects_nonzero_byte3() {
383 let buf = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x08];
384 let result = Header::parse(&buf);
385 assert!(result.is_err());
386 }
387
388 #[test]
389 fn parse_extracts_payload_len_bits() {
390 let buf = [0x00u8, 0x00, 0x00, 0x00, 0x01, 0x00];
391 let hdr = Header::parse(&buf).unwrap();
392 assert_eq!(hdr.payload_len_bits, 0x0100);
393 }
394
395 #[test]
396 fn payload_len_bytes_rounds_up() {
397 let hdr = Header {
398 packet_type: PacketType::BasebandFrame,
399 packet_count: 0,
400 superframe_idx: 0,
401 t2mi_stream_id: 0,
402 payload_len_bits: 13,
403 };
404 assert_eq!(hdr.payload_len_bytes(), 2); let hdr2 = Header {
407 payload_len_bits: 16,
408 ..hdr
409 };
410 assert_eq!(hdr2.payload_len_bytes(), 2);
411
412 let hdr3 = Header {
413 payload_len_bits: 0,
414 ..hdr
415 };
416 assert_eq!(hdr3.payload_len_bytes(), 0);
417 }
418
419 #[test]
422 fn serialize_writes_6_bytes() {
423 let hdr = Header {
424 packet_type: PacketType::BasebandFrame,
425 packet_count: 42,
426 superframe_idx: 7,
427 t2mi_stream_id: 3,
428 payload_len_bits: 128,
429 };
430 let mut buf = [0u8; 256];
431 let written = hdr.serialize_into(&mut buf).unwrap();
432 assert_eq!(written, HEADER_LEN);
433 assert_eq!(buf[0], 0x00);
434 assert_eq!(buf[1], 42);
435 assert_eq!(buf[2], (7 << 4) | 3);
436 assert_eq!(buf[3], 0);
437 assert_eq!(buf[4], 0);
438 assert_eq!(buf[5], 128);
439 }
440
441 #[test]
442 fn serialize_round_trip_identity_for_every_packet_type() {
443 let types = [
444 0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
445 ];
446 for &t in &types {
447 let original = Header {
448 packet_type: PacketType::try_from(t).unwrap(),
449 packet_count: 13,
450 superframe_idx: 7,
451 t2mi_stream_id: 2,
452 payload_len_bits: 512,
453 };
454 let mut buf = [0u8; HEADER_LEN];
455 original.serialize_into(&mut buf).unwrap();
456 let parsed = Header::parse(&buf).unwrap();
457 assert_eq!(original, parsed, "Round-trip failed for type {t:#04x}");
458 }
459 }
460
461 #[test]
462 fn serialize_rejects_too_small_buffer() {
463 let hdr = Header {
464 packet_type: PacketType::BasebandFrame,
465 packet_count: 0,
466 superframe_idx: 0,
467 t2mi_stream_id: 0,
468 payload_len_bits: 0,
469 };
470 let mut buf = [0u8; 5];
471 let result = hdr.serialize_into(&mut buf);
472 assert!(result.is_err());
473 }
474
475 #[test]
476 fn serialize_rejects_t2mi_stream_id_above_7() {
477 let hdr = Header {
478 packet_type: PacketType::BasebandFrame,
479 packet_count: 0,
480 superframe_idx: 0,
481 t2mi_stream_id: 8,
482 payload_len_bits: 0,
483 };
484 let mut buf = [0u8; HEADER_LEN];
485 let result = hdr.serialize_into(&mut buf);
486 assert!(result.is_err());
487 }
488
489 #[test]
490 fn payload_bytes_slices_declared_payload() {
491 let buf = [
493 0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00,
494 ];
495 let hdr = Header::parse(&buf).unwrap();
496 assert_eq!(hdr.payload_bytes(&buf).unwrap(), &[0xAA, 0xBB, 0xCC]);
497 }
498
499 #[test]
500 fn payload_bytes_rejects_truncated_buffer() {
501 let buf = [0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA];
503 let hdr = Header::parse(&buf).unwrap();
504 assert!(matches!(
505 hdr.payload_bytes(&buf),
506 Err(crate::Error::PayloadLengthMismatch { .. })
507 ));
508 }
509}