1use crate::error::{Error, Result};
24use crate::traits::Table;
25use dvb_common::{Parse, Serialize};
26
27pub const TABLE_ID: u8 = 0x4C;
29
30pub const PID: u16 = 0x0000;
37
38pub const ACTION_TYPE_STREAM_ANNOUNCEMENT: u8 = 0x01;
40
41const OUTER_HEADER_LEN: usize = 3;
45
46const INT_FIXED_LEN: usize = 9;
51
52const LOOP_LEN_FIELD: usize = 2;
54
55const CRC_LEN: usize = 4;
57
58const MIN_SECTION_LEN: usize = OUTER_HEADER_LEN + INT_FIXED_LEN + LOOP_LEN_FIELD + CRC_LEN;
61
62const OFF_ACTION_TYPE: usize = 3;
66const OFF_PLATFORM_ID_HASH: usize = 4;
68const OFF_VERSION_BYTE: usize = 5;
70const OFF_SECTION_NUMBER: usize = 6;
72const OFF_LAST_SECTION_NUMBER: usize = 7;
74const OFF_PLATFORM_ID: usize = 8;
76const OFF_PROCESSING_ORDER: usize = 11;
78const OFF_PLATFORM_DESC_LEN: usize = 12;
80
81#[derive(Debug, Clone, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct Int<'a> {
94 pub action_type: u8,
96
97 pub platform_id_hash: u8,
101
102 pub version_number: u8,
104
105 pub current_next_indicator: bool,
108
109 pub section_number: u8,
111
112 pub last_section_number: u8,
114
115 pub platform_id: u32,
118
119 pub processing_order: u8,
122
123 #[cfg_attr(feature = "serde", serde(borrow))]
126 pub platform_descriptors: &'a [u8],
127
128 #[cfg_attr(feature = "serde", serde(borrow))]
137 pub loops: &'a [u8],
138}
139
140impl<'a> Parse<'a> for Int<'a> {
143 type Error = crate::error::Error;
144
145 fn parse(bytes: &'a [u8]) -> Result<Self> {
146 if bytes.len() < MIN_SECTION_LEN {
148 return Err(Error::BufferTooShort {
149 need: MIN_SECTION_LEN,
150 have: bytes.len(),
151 what: "Int",
152 });
153 }
154
155 if bytes[0] != TABLE_ID {
157 return Err(Error::UnexpectedTableId {
158 table_id: bytes[0],
159 what: "Int",
160 expected: &[TABLE_ID],
161 });
162 }
163
164 let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
166 let total = OUTER_HEADER_LEN + section_length;
167 if bytes.len() < total {
168 return Err(Error::SectionLengthOverflow {
169 declared: section_length,
170 available: bytes.len() - OUTER_HEADER_LEN,
171 });
172 }
173
174 let action_type = bytes[OFF_ACTION_TYPE];
176 let platform_id_hash = bytes[OFF_PLATFORM_ID_HASH];
177 let version_byte = bytes[OFF_VERSION_BYTE];
178 let version_number = (version_byte >> 1) & 0x1F;
179 let current_next_indicator = (version_byte & 0x01) != 0;
180 let section_number = bytes[OFF_SECTION_NUMBER];
181 let last_section_number = bytes[OFF_LAST_SECTION_NUMBER];
182 let platform_id = ((bytes[OFF_PLATFORM_ID] as u32) << 16)
183 | ((bytes[OFF_PLATFORM_ID + 1] as u32) << 8)
184 | bytes[OFF_PLATFORM_ID + 2] as u32;
185 let processing_order = bytes[OFF_PROCESSING_ORDER];
186
187 let plat_desc_len = (((bytes[OFF_PLATFORM_DESC_LEN] & 0x0F) as usize) << 8)
189 | bytes[OFF_PLATFORM_DESC_LEN + 1] as usize;
190
191 let plat_desc_start = OFF_PLATFORM_DESC_LEN + LOOP_LEN_FIELD;
192 let plat_desc_end = plat_desc_start + plat_desc_len;
193
194 if plat_desc_end > total - CRC_LEN {
196 return Err(Error::SectionLengthOverflow {
197 declared: plat_desc_len,
198 available: (total - CRC_LEN).saturating_sub(plat_desc_start),
199 });
200 }
201
202 let platform_descriptors = &bytes[plat_desc_start..plat_desc_end];
203
204 let loops_start = plat_desc_end;
207 let loops_end = total - CRC_LEN;
208 let loops = &bytes[loops_start..loops_end];
209
210 Ok(Int {
211 action_type,
212 platform_id_hash,
213 version_number,
214 current_next_indicator,
215 section_number,
216 last_section_number,
217 platform_id,
218 processing_order,
219 platform_descriptors,
220 loops,
221 })
222 }
223}
224
225impl Serialize for Int<'_> {
228 type Error = crate::error::Error;
229
230 fn serialized_len(&self) -> usize {
231 OUTER_HEADER_LEN
232 + INT_FIXED_LEN
233 + LOOP_LEN_FIELD + self.platform_descriptors.len()
235 + self.loops.len()
236 + CRC_LEN
237 }
238
239 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
240 let len = self.serialized_len();
241 if buf.len() < len {
242 return Err(Error::OutputBufferTooSmall {
243 need: len,
244 have: buf.len(),
245 });
246 }
247
248 let section_length = (len - OUTER_HEADER_LEN) as u16;
250 buf[0] = TABLE_ID;
251 buf[1] = 0xF0 | ((section_length >> 8) as u8 & 0x0F);
253 buf[2] = (section_length & 0xFF) as u8;
254
255 buf[OFF_ACTION_TYPE] = self.action_type;
257 buf[OFF_PLATFORM_ID_HASH] = self.platform_id_hash;
258 buf[OFF_VERSION_BYTE] =
259 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
260 buf[OFF_SECTION_NUMBER] = self.section_number;
261 buf[OFF_LAST_SECTION_NUMBER] = self.last_section_number;
262 buf[OFF_PLATFORM_ID] = ((self.platform_id >> 16) & 0xFF) as u8;
264 buf[OFF_PLATFORM_ID + 1] = ((self.platform_id >> 8) & 0xFF) as u8;
265 buf[OFF_PLATFORM_ID + 2] = (self.platform_id & 0xFF) as u8;
266 buf[OFF_PROCESSING_ORDER] = self.processing_order;
267
268 let pdl = self.platform_descriptors.len() as u16;
270 buf[OFF_PLATFORM_DESC_LEN] = 0xF0 | ((pdl >> 8) as u8 & 0x0F);
271 buf[OFF_PLATFORM_DESC_LEN + 1] = (pdl & 0xFF) as u8;
272
273 let plat_start = OFF_PLATFORM_DESC_LEN + LOOP_LEN_FIELD;
275 let plat_end = plat_start + self.platform_descriptors.len();
276 buf[plat_start..plat_end].copy_from_slice(self.platform_descriptors);
277
278 let loops_start = plat_end;
280 let loops_end = loops_start + self.loops.len();
281 buf[loops_start..loops_end].copy_from_slice(self.loops);
282
283 let crc_pos = len - CRC_LEN;
285 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
286 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
287
288 Ok(len)
289 }
290}
291
292impl<'a> Table<'a> for Int<'a> {
295 const TABLE_ID: u8 = TABLE_ID;
296 const PID: u16 = PID;
297}
298
299impl<'a> crate::traits::TableDef<'a> for Int<'a> {
300 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
301 const NAME: &'static str = "IP_MAC_NOTIFICATION";
302}
303
304#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[allow(clippy::too_many_arguments)]
316 fn build_int(
317 action_type: u8,
318 platform_id_hash: u8,
319 version_number: u8,
320 current_next_indicator: bool,
321 section_number: u8,
322 last_section_number: u8,
323 platform_id: u32,
324 processing_order: u8,
325 platform_desc: &[u8],
326 loops: &[u8],
327 ) -> Vec<u8> {
328 let int = Int {
329 action_type,
330 platform_id_hash,
331 version_number,
332 current_next_indicator,
333 section_number,
334 last_section_number,
335 platform_id,
336 processing_order,
337 platform_descriptors: platform_desc,
338 loops,
339 };
340 let mut buf = vec![0u8; int.serialized_len()];
341 int.serialize_into(&mut buf).unwrap();
342 buf
343 }
344
345 #[test]
346 fn parse_happy_path() {
347 let bytes = build_int(
349 ACTION_TYPE_STREAM_ANNOUNCEMENT,
350 0x12 ^ 0x34, 3,
353 true,
354 0,
355 0,
356 0x00_12_34,
357 0x00,
358 &[0x81, 0x02, 0xAB, 0xCD],
359 &[],
360 );
361 let int = Int::parse(&bytes).unwrap();
362
363 assert_eq!(int.action_type, ACTION_TYPE_STREAM_ANNOUNCEMENT);
364 assert_eq!(int.platform_id_hash, 0x12 ^ 0x34);
365 assert_eq!(int.version_number, 3);
366 assert!(int.current_next_indicator);
367 assert_eq!(int.section_number, 0);
368 assert_eq!(int.last_section_number, 0);
369 assert_eq!(int.platform_id, 0x00_12_34);
370 assert_eq!(int.processing_order, 0x00);
371 assert_eq!(int.platform_descriptors, &[0x81, 0x02, 0xAB, 0xCD][..]);
372 assert_eq!(int.loops, &[] as &[u8]);
373 }
374
375 #[test]
376 fn parse_happy_path_with_loops() {
377 let fake_loops: [u8; 4] = [
380 0xF0, 0x00, 0xF0, 0x00, ];
383 let bytes = build_int(
384 0x01,
385 0x56,
386 5,
387 false,
388 1,
389 1,
390 0x00_56_78,
391 0x01,
392 &[],
393 &fake_loops,
394 );
395 let int = Int::parse(&bytes).unwrap();
396 assert_eq!(int.platform_id, 0x00_56_78);
397 assert_eq!(int.version_number, 5);
398 assert!(!int.current_next_indicator);
399 assert_eq!(int.loops, &fake_loops[..]);
400 }
401
402 #[test]
403 fn parse_rejects_wrong_table_id() {
404 let mut bytes = build_int(0x01, 0x00, 0, true, 0, 0, 0x000001, 0x00, &[], &[]);
405 bytes[0] = 0x4B; let err = Int::parse(&bytes).unwrap_err();
407 assert!(matches!(
408 err,
409 Error::UnexpectedTableId { table_id: 0x4B, .. }
410 ));
411 }
412
413 #[test]
414 fn parse_rejects_buffer_too_short() {
415 let err = Int::parse(&[TABLE_ID, 0xF0]).unwrap_err();
416 assert!(matches!(err, Error::BufferTooShort { what: "Int", .. }));
417 }
418
419 #[test]
420 fn serialize_round_trip() {
421 let plat_desc = [0x7C, 0x04, 0x01, 0x02, 0x03, 0x04];
422 let fake_loops: [u8; 4] = [0xF0, 0x00, 0xF0, 0x00];
423 let bytes = build_int(
424 ACTION_TYPE_STREAM_ANNOUNCEMENT,
425 0xAB,
426 15,
427 true,
428 2,
429 3,
430 0x00_AB_CD,
431 0x00,
432 &plat_desc,
433 &fake_loops,
434 );
435
436 let int = Int::parse(&bytes).unwrap();
437
438 let mut buf = vec![0u8; int.serialized_len()];
440 int.serialize_into(&mut buf).unwrap();
441
442 let re = Int::parse(&buf).unwrap();
444
445 assert_eq!(int, re);
446 assert_eq!(re.action_type, ACTION_TYPE_STREAM_ANNOUNCEMENT);
447 assert_eq!(re.platform_id_hash, 0xAB);
448 assert_eq!(re.version_number, 15);
449 assert!(re.current_next_indicator);
450 assert_eq!(re.section_number, 2);
451 assert_eq!(re.last_section_number, 3);
452 assert_eq!(re.platform_id, 0x00_AB_CD);
453 assert_eq!(re.processing_order, 0x00);
454 assert_eq!(re.platform_descriptors, &plat_desc[..]);
455 assert_eq!(re.loops, &fake_loops[..]);
456 }
457
458 #[test]
459 fn serialize_rejects_too_small_output_buffer() {
460 let int = Int {
461 action_type: 0x01,
462 platform_id_hash: 0x00,
463 version_number: 0,
464 current_next_indicator: true,
465 section_number: 0,
466 last_section_number: 0,
467 platform_id: 0,
468 processing_order: 0,
469 platform_descriptors: &[],
470 loops: &[],
471 };
472 let mut buf = vec![0u8; 2]; let err = int.serialize_into(&mut buf).unwrap_err();
474 assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
475 }
476
477 #[test]
478 fn platform_id_24bit_boundary() {
479 let bytes = build_int(0x01, 0xFF, 0, true, 0, 0, 0x00FF_FFFF, 0x00, &[], &[]);
481 let int = Int::parse(&bytes).unwrap();
482 assert_eq!(int.platform_id, 0x00FF_FFFF);
483 }
484}