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