1use crate::descriptors::DescriptorLoop;
10use crate::error::{Error, Result};
11use alloc::vec::Vec;
12use dvb_common::{Parse, Serialize};
13
14pub const TABLE_ID: u8 = 0x4C;
16
17pub const PID: u16 = 0x0000;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27#[non_exhaustive]
28pub enum IntActionType {
29 Reserved,
31 IpMacStreamLocation,
33 DvbReserved(u8),
35}
36
37impl IntActionType {
38 #[must_use]
39 pub fn from_u8(v: u8) -> Self {
41 match v {
42 0x00 => Self::Reserved,
43 0x01 => Self::IpMacStreamLocation,
44 _ => Self::DvbReserved(v),
45 }
46 }
47
48 #[must_use]
49 pub fn to_u8(self) -> u8 {
51 match self {
52 Self::Reserved => 0x00,
53 Self::IpMacStreamLocation => 0x01,
54 Self::DvbReserved(v) => v,
55 }
56 }
57
58 #[must_use]
59 pub fn name(self) -> &'static str {
61 match self {
62 Self::Reserved => "Reserved",
63 Self::IpMacStreamLocation => "IP/MAC Stream Location",
64 Self::DvbReserved(_) => "DVB Reserved",
65 }
66 }
67}
68dvb_common::impl_spec_display!(IntActionType, DvbReserved);
69
70const OUTER_HEADER_LEN: usize = 3;
71const INT_FIXED_LEN: usize = 9;
72const LOOP_LEN_FIELD: usize = 2;
73const CRC_LEN: usize = 4;
74const MIN_SECTION_LEN: usize = OUTER_HEADER_LEN + INT_FIXED_LEN + LOOP_LEN_FIELD + CRC_LEN;
75
76const OFF_ACTION_TYPE: usize = 3;
77const OFF_PLATFORM_ID_HASH: usize = 4;
78const OFF_VERSION_BYTE: usize = 5;
79const OFF_SECTION_NUMBER: usize = 6;
80const OFF_LAST_SECTION_NUMBER: usize = 7;
81const OFF_PLATFORM_ID: usize = 8;
82const OFF_PROCESSING_ORDER: usize = 11;
83const OFF_PLATFORM_DESC_LEN: usize = 12;
84
85const RESERVED_NIBBLE: u8 = 0xF0;
86
87#[derive(Debug, Clone, PartialEq, Eq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize))]
91pub struct IntLoopEntry<'a> {
92 pub target_descriptors: DescriptorLoop<'a>,
96 pub operational_descriptors: DescriptorLoop<'a>,
100}
101
102fn int_loop_entry_serialized_len(e: &IntLoopEntry) -> usize {
103 LOOP_LEN_FIELD + e.target_descriptors.len() + LOOP_LEN_FIELD + e.operational_descriptors.len()
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize))]
112#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
113pub struct IntSection<'a> {
114 pub action_type: IntActionType,
116 pub platform_id_hash: u8,
118 pub version_number: u8,
120 pub current_next_indicator: bool,
122 pub section_number: u8,
124 pub last_section_number: u8,
126 pub platform_id: u32,
128 pub processing_order: u8,
130 pub platform_descriptors: DescriptorLoop<'a>,
134 pub loops: Vec<IntLoopEntry<'a>>,
136}
137
138impl<'a> Parse<'a> for IntSection<'a> {
139 type Error = crate::error::Error;
140
141 fn parse(bytes: &'a [u8]) -> Result<Self> {
142 if bytes.len() < MIN_SECTION_LEN {
143 return Err(Error::BufferTooShort {
144 need: MIN_SECTION_LEN,
145 have: bytes.len(),
146 what: "IntSection",
147 });
148 }
149 if bytes[0] != TABLE_ID {
150 return Err(Error::UnexpectedTableId {
151 table_id: bytes[0],
152 what: "IntSection",
153 expected: &[TABLE_ID],
154 });
155 }
156
157 let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
158 let total = super::check_section_length(
159 bytes.len(),
160 OUTER_HEADER_LEN,
161 section_length,
162 MIN_SECTION_LEN,
163 )?;
164
165 let action_type = IntActionType::from_u8(bytes[OFF_ACTION_TYPE]);
166 let platform_id_hash = bytes[OFF_PLATFORM_ID_HASH];
167 let version_byte = bytes[OFF_VERSION_BYTE];
168 let version_number = (version_byte >> 1) & 0x1F;
169 let current_next_indicator = (version_byte & 0x01) != 0;
170 let section_number = bytes[OFF_SECTION_NUMBER];
171 let last_section_number = bytes[OFF_LAST_SECTION_NUMBER];
172 let platform_id = ((bytes[OFF_PLATFORM_ID] as u32) << 16)
173 | ((bytes[OFF_PLATFORM_ID + 1] as u32) << 8)
174 | bytes[OFF_PLATFORM_ID + 2] as u32;
175 let processing_order = bytes[OFF_PROCESSING_ORDER];
176
177 let plat_desc_len = (((bytes[OFF_PLATFORM_DESC_LEN] & 0x0F) as usize) << 8)
178 | bytes[OFF_PLATFORM_DESC_LEN + 1] as usize;
179 let plat_desc_start = OFF_PLATFORM_DESC_LEN + LOOP_LEN_FIELD;
180 let plat_desc_end = plat_desc_start + plat_desc_len;
181 if plat_desc_end > total - CRC_LEN {
182 return Err(Error::SectionLengthOverflow {
183 declared: plat_desc_len,
184 available: (total - CRC_LEN).saturating_sub(plat_desc_start),
185 });
186 }
187 let platform_descriptors = DescriptorLoop::new(&bytes[plat_desc_start..plat_desc_end]);
188
189 let payload_end = total - CRC_LEN;
190 let mut pos = plat_desc_end;
191 let mut loops = Vec::new();
192 while pos < payload_end {
193 if pos + LOOP_LEN_FIELD > payload_end {
194 return Err(Error::BufferTooShort {
195 need: pos + LOOP_LEN_FIELD,
196 have: payload_end,
197 what: "IntSection target_descriptor_loop length",
198 });
199 }
200 let target_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
201 let target_start = pos + LOOP_LEN_FIELD;
202 let target_end = target_start + target_len;
203 if target_end > payload_end {
204 return Err(Error::SectionLengthOverflow {
205 declared: target_len,
206 available: payload_end.saturating_sub(target_start),
207 });
208 }
209 let target_descriptors = DescriptorLoop::new(&bytes[target_start..target_end]);
210 pos = target_end;
211
212 if pos + LOOP_LEN_FIELD > payload_end {
213 return Err(Error::BufferTooShort {
214 need: pos + LOOP_LEN_FIELD,
215 have: payload_end,
216 what: "IntSection operational_descriptor_loop length",
217 });
218 }
219 let op_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
220 let op_start = pos + LOOP_LEN_FIELD;
221 let op_end = op_start + op_len;
222 if op_end > payload_end {
223 return Err(Error::SectionLengthOverflow {
224 declared: op_len,
225 available: payload_end.saturating_sub(op_start),
226 });
227 }
228 let operational_descriptors = DescriptorLoop::new(&bytes[op_start..op_end]);
229 pos = op_end;
230
231 loops.push(IntLoopEntry {
232 target_descriptors,
233 operational_descriptors,
234 });
235 }
236
237 Ok(IntSection {
238 action_type,
239 platform_id_hash,
240 version_number,
241 current_next_indicator,
242 section_number,
243 last_section_number,
244 platform_id,
245 processing_order,
246 platform_descriptors,
247 loops,
248 })
249 }
250}
251
252impl Serialize for IntSection<'_> {
253 type Error = crate::error::Error;
254
255 fn serialized_len(&self) -> usize {
256 OUTER_HEADER_LEN
257 + INT_FIXED_LEN
258 + LOOP_LEN_FIELD
259 + self.platform_descriptors.len()
260 + self
261 .loops
262 .iter()
263 .map(int_loop_entry_serialized_len)
264 .sum::<usize>()
265 + CRC_LEN
266 }
267
268 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
269 let len = self.serialized_len();
270 if buf.len() < len {
271 return Err(Error::OutputBufferTooSmall {
272 need: len,
273 have: buf.len(),
274 });
275 }
276
277 let section_length = (len - OUTER_HEADER_LEN) as u16;
278 buf[0] = TABLE_ID;
279 buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
280 buf[2] = (section_length & 0xFF) as u8;
281
282 buf[OFF_ACTION_TYPE] = self.action_type.to_u8();
283 buf[OFF_PLATFORM_ID_HASH] = self.platform_id_hash;
284 buf[OFF_VERSION_BYTE] =
285 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
286 buf[OFF_SECTION_NUMBER] = self.section_number;
287 buf[OFF_LAST_SECTION_NUMBER] = self.last_section_number;
288 buf[OFF_PLATFORM_ID] = ((self.platform_id >> 16) & 0xFF) as u8;
289 buf[OFF_PLATFORM_ID + 1] = ((self.platform_id >> 8) & 0xFF) as u8;
290 buf[OFF_PLATFORM_ID + 2] = (self.platform_id & 0xFF) as u8;
291 buf[OFF_PROCESSING_ORDER] = self.processing_order;
292
293 let pdl = self.platform_descriptors.len() as u16;
294 buf[OFF_PLATFORM_DESC_LEN] = RESERVED_NIBBLE | ((pdl >> 8) as u8 & 0x0F);
295 buf[OFF_PLATFORM_DESC_LEN + 1] = (pdl & 0xFF) as u8;
296
297 let plat_start = OFF_PLATFORM_DESC_LEN + LOOP_LEN_FIELD;
298 let plat_end = plat_start + self.platform_descriptors.len();
299 buf[plat_start..plat_end].copy_from_slice(self.platform_descriptors.raw());
300
301 let mut pos = plat_end;
302 for entry in &self.loops {
303 let tl = entry.target_descriptors.len() as u16;
304 buf[pos] = RESERVED_NIBBLE | ((tl >> 8) as u8 & 0x0F);
305 buf[pos + 1] = (tl & 0xFF) as u8;
306 pos += LOOP_LEN_FIELD;
307 buf[pos..pos + entry.target_descriptors.len()]
308 .copy_from_slice(entry.target_descriptors.raw());
309 pos += entry.target_descriptors.len();
310
311 let ol = entry.operational_descriptors.len() as u16;
312 buf[pos] = RESERVED_NIBBLE | ((ol >> 8) as u8 & 0x0F);
313 buf[pos + 1] = (ol & 0xFF) as u8;
314 pos += LOOP_LEN_FIELD;
315 buf[pos..pos + entry.operational_descriptors.len()]
316 .copy_from_slice(entry.operational_descriptors.raw());
317 pos += entry.operational_descriptors.len();
318 }
319
320 let crc_pos = len - CRC_LEN;
321 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
322 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
323 Ok(len)
324 }
325}
326impl<'a> crate::traits::TableDef<'a> for IntSection<'a> {
327 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
328 const NAME: &'static str = "IP_MAC_NOTIFICATION";
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn parse_happy_path_no_loops() {
337 let plat_desc: &[u8] = &[0x81, 0x02, 0xAB, 0xCD];
338 let int = IntSection {
339 action_type: IntActionType::IpMacStreamLocation,
340 platform_id_hash: 0x12 ^ 0x34,
341 version_number: 3,
342 current_next_indicator: true,
343 section_number: 0,
344 last_section_number: 0,
345 platform_id: 0x00_12_34,
346 processing_order: 0x00,
347 platform_descriptors: DescriptorLoop::new(plat_desc),
348 loops: Vec::new(),
349 };
350 let mut buf = vec![0u8; int.serialized_len()];
351 int.serialize_into(&mut buf).unwrap();
352 let parsed = IntSection::parse(&buf).unwrap();
353 assert_eq!(parsed.action_type, IntActionType::IpMacStreamLocation);
354 assert_eq!(parsed.platform_id, 0x00_12_34);
355 assert_eq!(parsed.platform_descriptors.raw(), plat_desc);
356 assert!(parsed.loops.is_empty());
357 }
358
359 #[test]
360 fn parse_happy_path_with_loops() {
361 let target_desc: &[u8] = &[0x09, 0x01, 0xAA];
362 let op_desc: &[u8] = &[0x0A, 0x01, 0xBB];
363 let int = IntSection {
364 action_type: IntActionType::IpMacStreamLocation,
365 platform_id_hash: 0x56,
366 version_number: 5,
367 current_next_indicator: false,
368 section_number: 1,
369 last_section_number: 1,
370 platform_id: 0x00_56_78,
371 processing_order: 0x01,
372 platform_descriptors: DescriptorLoop::new(&[]),
373 loops: vec![
374 IntLoopEntry {
375 target_descriptors: DescriptorLoop::new(target_desc),
376 operational_descriptors: DescriptorLoop::new(op_desc),
377 },
378 IntLoopEntry {
379 target_descriptors: DescriptorLoop::new(&[]),
380 operational_descriptors: DescriptorLoop::new(&[]),
381 },
382 ],
383 };
384 let mut buf = vec![0u8; int.serialized_len()];
385 int.serialize_into(&mut buf).unwrap();
386 let parsed = IntSection::parse(&buf).unwrap();
387 assert_eq!(parsed.loops.len(), 2);
388 assert_eq!(parsed.loops[0].target_descriptors.raw(), target_desc);
389 assert_eq!(parsed.loops[0].operational_descriptors.raw(), op_desc);
390 assert_eq!(parsed.loops[1].target_descriptors.len(), 0);
391 assert_eq!(parsed.loops[1].operational_descriptors.len(), 0);
392 }
393
394 #[test]
395 fn byte_exact_round_trip() {
396 let plat_desc: &[u8] = &[0x7C, 0x04, 0x01, 0x02, 0x03, 0x04];
397 let int = IntSection {
398 action_type: IntActionType::IpMacStreamLocation,
399 platform_id_hash: 0xAB,
400 version_number: 15,
401 current_next_indicator: true,
402 section_number: 2,
403 last_section_number: 3,
404 platform_id: 0x00_AB_CD,
405 processing_order: 0x00,
406 platform_descriptors: DescriptorLoop::new(plat_desc),
407 loops: vec![IntLoopEntry {
408 target_descriptors: DescriptorLoop::new(&[]),
409 operational_descriptors: DescriptorLoop::new(&[]),
410 }],
411 };
412 let mut buf = vec![0u8; int.serialized_len()];
413 int.serialize_into(&mut buf).unwrap();
414 let re = IntSection::parse(&buf).unwrap();
415 let mut buf2 = vec![0u8; re.serialized_len()];
416 re.serialize_into(&mut buf2).unwrap();
417 assert_eq!(buf, buf2, "byte-exact re-serialize");
418 assert_eq!(re, int);
419 }
420
421 #[test]
422 fn parse_rejects_wrong_table_id() {
423 let int = IntSection {
424 action_type: IntActionType::IpMacStreamLocation,
425 platform_id_hash: 0x00,
426 version_number: 0,
427 current_next_indicator: true,
428 section_number: 0,
429 last_section_number: 0,
430 platform_id: 0,
431 processing_order: 0,
432 platform_descriptors: DescriptorLoop::new(&[]),
433 loops: Vec::new(),
434 };
435 let mut buf = vec![0u8; int.serialized_len()];
436 int.serialize_into(&mut buf).unwrap();
437 buf[0] = 0x4B;
438 assert!(matches!(
439 IntSection::parse(&buf).unwrap_err(),
440 Error::UnexpectedTableId { table_id: 0x4B, .. }
441 ));
442 }
443
444 #[test]
445 fn parse_rejects_buffer_too_short() {
446 assert!(matches!(
447 IntSection::parse(&[TABLE_ID, 0xF0]).unwrap_err(),
448 Error::BufferTooShort {
449 what: "IntSection",
450 ..
451 }
452 ));
453 }
454
455 #[test]
456 fn serialize_rejects_too_small_output_buffer() {
457 let int = IntSection {
458 action_type: IntActionType::IpMacStreamLocation,
459 platform_id_hash: 0x00,
460 version_number: 0,
461 current_next_indicator: true,
462 section_number: 0,
463 last_section_number: 0,
464 platform_id: 0,
465 processing_order: 0,
466 platform_descriptors: DescriptorLoop::new(&[]),
467 loops: Vec::new(),
468 };
469 let mut buf = vec![0u8; 2];
470 assert!(matches!(
471 int.serialize_into(&mut buf).unwrap_err(),
472 Error::OutputBufferTooSmall { .. }
473 ));
474 }
475
476 #[test]
477 fn parse_rejects_zero_section_length() {
478 let mut buf = vec![0u8; 64];
479 buf[0] = TABLE_ID;
480 buf[1] = 0xF0;
481 buf[2] = 0x00;
482 for b in &mut buf[3..] {
483 *b = 0xFF;
484 }
485 assert!(matches!(
486 IntSection::parse(&buf).unwrap_err(),
487 Error::SectionLengthOverflow { .. }
488 ));
489 }
490
491 #[test]
492 fn platform_id_24bit_boundary() {
493 let int = IntSection {
494 action_type: IntActionType::IpMacStreamLocation,
495 platform_id_hash: 0xFF,
496 version_number: 0,
497 current_next_indicator: true,
498 section_number: 0,
499 last_section_number: 0,
500 platform_id: 0x00FF_FFFF,
501 processing_order: 0x00,
502 platform_descriptors: DescriptorLoop::new(&[]),
503 loops: Vec::new(),
504 };
505 let mut buf = vec![0u8; int.serialized_len()];
506 int.serialize_into(&mut buf).unwrap();
507 let parsed = IntSection::parse(&buf).unwrap();
508 assert_eq!(parsed.platform_id, 0x00FF_FFFF);
509 }
510
511 #[test]
512 fn parse_handwritten_int_no_loops() {
513 let mut bytes: Vec<u8> = vec![
514 0x4C, 0xF0, 0x0F, 0x01, 0x00, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x00,
515 ];
516 let crc = dvb_common::crc32_mpeg2::compute(&bytes);
517 bytes.extend_from_slice(&crc.to_be_bytes());
518 let int = IntSection::parse(&bytes).unwrap();
519 assert_eq!(int.action_type, IntActionType::IpMacStreamLocation);
520 assert_eq!(int.platform_id, 0x000001);
521 assert_eq!(int.version_number, 3);
522 assert!(int.current_next_indicator);
523 assert!(int.loops.is_empty());
524 }
525
526 #[test]
527 fn action_type_full_range_round_trip() {
528 for byte in 0u8..=0xFF {
529 let at = IntActionType::from_u8(byte);
530 assert_eq!(
531 at.to_u8(),
532 byte,
533 "IntActionType round-trip failed for {byte:#04x}"
534 );
535 }
536 }
537}