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