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