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