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({:#04x}) failed", v);
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 {:#04x}: {:?}",
341 t,
342 result
343 );
344 assert_eq!(
345 result.unwrap().packet_type,
346 PacketType::try_from(t).unwrap()
347 );
348 }
349 }
350
351 #[test]
352 fn parse_rejects_reserved_packet_type_0x22() {
353 let buf = [0x22u8, 0x00, 0x00, 0x00, 0x00, 0x08];
354 let result = Header::parse(&buf);
355 assert!(result.is_err());
356 assert!(matches!(
357 result.unwrap_err(),
358 crate::Error::InvalidPacketType { found: 0x22 }
359 ));
360 }
361
362 #[test]
363 fn parse_extracts_t2mi_stream_id_0_through_7() {
364 for id in 0..=7 {
365 let buf = [0x00u8, 0x00, id, 0x00, 0x00, 0x08];
366 let hdr = Header::parse(&buf).unwrap();
367 assert_eq!(hdr.t2mi_stream_id, id, "stream_id mismatch for id={}", id);
368 }
369 }
370
371 #[test]
372 fn parse_rejects_nonzero_rfu_bits_in_byte2() {
373 let buf = [0x00u8, 0x00, 0x08, 0x00, 0x00, 0x08];
375 let result = Header::parse(&buf);
376 assert!(result.is_err());
377 assert!(matches!(
378 result.unwrap_err(),
379 crate::Error::ReservedBitsViolation { .. }
380 ));
381 }
382
383 #[test]
384 fn parse_rejects_nonzero_byte3() {
385 let buf = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x08];
386 let result = Header::parse(&buf);
387 assert!(result.is_err());
388 }
389
390 #[test]
391 fn parse_extracts_payload_len_bits() {
392 let buf = [0x00u8, 0x00, 0x00, 0x00, 0x01, 0x00];
393 let hdr = Header::parse(&buf).unwrap();
394 assert_eq!(hdr.payload_len_bits, 0x0100);
395 }
396
397 #[test]
398 fn payload_len_bytes_rounds_up() {
399 let hdr = Header {
400 packet_type: PacketType::BasebandFrame,
401 packet_count: 0,
402 superframe_idx: 0,
403 t2mi_stream_id: 0,
404 payload_len_bits: 13,
405 };
406 assert_eq!(hdr.payload_len_bytes(), 2); let hdr2 = Header {
409 payload_len_bits: 16,
410 ..hdr
411 };
412 assert_eq!(hdr2.payload_len_bytes(), 2);
413
414 let hdr3 = Header {
415 payload_len_bits: 0,
416 ..hdr
417 };
418 assert_eq!(hdr3.payload_len_bytes(), 0);
419 }
420
421 #[test]
424 fn serialize_writes_6_bytes() {
425 let hdr = Header {
426 packet_type: PacketType::BasebandFrame,
427 packet_count: 42,
428 superframe_idx: 7,
429 t2mi_stream_id: 3,
430 payload_len_bits: 128,
431 };
432 let mut buf = [0u8; 256];
433 let written = hdr.serialize_into(&mut buf).unwrap();
434 assert_eq!(written, HEADER_LEN);
435 assert_eq!(buf[0], 0x00);
436 assert_eq!(buf[1], 42);
437 assert_eq!(buf[2], (7 << 4) | 3);
438 assert_eq!(buf[3], 0);
439 assert_eq!(buf[4], 0);
440 assert_eq!(buf[5], 128);
441 }
442
443 #[test]
444 fn serialize_round_trip_identity_for_every_packet_type() {
445 let types = [
446 0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
447 ];
448 for &t in &types {
449 let original = Header {
450 packet_type: PacketType::try_from(t).unwrap(),
451 packet_count: 13,
452 superframe_idx: 7,
453 t2mi_stream_id: 2,
454 payload_len_bits: 512,
455 };
456 let mut buf = [0u8; HEADER_LEN];
457 original.serialize_into(&mut buf).unwrap();
458 let parsed = Header::parse(&buf).unwrap();
459 assert_eq!(original, parsed, "Round-trip failed for type {:#04x}", t);
460 }
461 }
462
463 #[test]
464 fn serialize_rejects_too_small_buffer() {
465 let hdr = Header {
466 packet_type: PacketType::BasebandFrame,
467 packet_count: 0,
468 superframe_idx: 0,
469 t2mi_stream_id: 0,
470 payload_len_bits: 0,
471 };
472 let mut buf = [0u8; 5];
473 let result = hdr.serialize_into(&mut buf);
474 assert!(result.is_err());
475 }
476
477 #[test]
478 fn serialize_rejects_t2mi_stream_id_above_7() {
479 let hdr = Header {
480 packet_type: PacketType::BasebandFrame,
481 packet_count: 0,
482 superframe_idx: 0,
483 t2mi_stream_id: 8,
484 payload_len_bits: 0,
485 };
486 let mut buf = [0u8; HEADER_LEN];
487 let result = hdr.serialize_into(&mut buf);
488 assert!(result.is_err());
489 }
490
491 #[test]
492 fn payload_bytes_slices_declared_payload() {
493 let buf = [
495 0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00,
496 ];
497 let hdr = Header::parse(&buf).unwrap();
498 assert_eq!(hdr.payload_bytes(&buf).unwrap(), &[0xAA, 0xBB, 0xCC]);
499 }
500
501 #[test]
502 fn payload_bytes_rejects_truncated_buffer() {
503 let buf = [0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA];
505 let hdr = Header::parse(&buf).unwrap();
506 assert!(matches!(
507 hdr.payload_bytes(&buf),
508 Err(crate::Error::PayloadLengthMismatch { .. })
509 ));
510 }
511}