1use crate::error::{Error, Result};
8use crate::traits::Table;
9use dvb_common::{Parse, Serialize};
10
11pub const TABLE_ID: u8 = 0x77;
13
14pub const PID: u16 = 0x0012;
19
20const HEADER_LEN: usize = 3;
24
25const EXTENSION_LEN: usize = 10;
29
30const MIN_SECTION_LEN: usize = HEADER_LEN + EXTENSION_LEN + CRC_LEN;
32
33const CRC_LEN: usize = 4;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53pub struct Cit<'a> {
54 pub private_indicator: bool,
56
57 pub service_id: u16,
60
61 pub version_number: u8,
63
64 pub current_next_indicator: bool,
66
67 pub section_number: u8,
69
70 pub last_section_number: u8,
72
73 pub transport_stream_id: u16,
75
76 pub original_network_id: u16,
78
79 #[cfg_attr(feature = "serde", serde(borrow))]
84 pub prepend_strings: &'a [u8],
85
86 #[cfg_attr(feature = "serde", serde(borrow))]
89 pub crid_entries: &'a [u8],
90}
91
92impl<'a> Parse<'a> for Cit<'a> {
95 type Error = crate::error::Error;
96
97 fn parse(bytes: &'a [u8]) -> Result<Self> {
98 if bytes.len() < MIN_SECTION_LEN {
100 return Err(Error::BufferTooShort {
101 need: MIN_SECTION_LEN,
102 have: bytes.len(),
103 what: "Cit",
104 });
105 }
106
107 if bytes[0] != TABLE_ID {
109 return Err(Error::UnexpectedTableId {
110 table_id: bytes[0],
111 what: "Cit",
112 expected: &[TABLE_ID],
113 });
114 }
115
116 let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
118 let total = HEADER_LEN + section_length;
119 if bytes.len() < total {
120 return Err(Error::SectionLengthOverflow {
121 declared: section_length,
122 available: bytes.len() - HEADER_LEN,
123 });
124 }
125
126 let private_indicator = (bytes[1] & 0x40) != 0;
128
129 let service_id = u16::from_be_bytes([bytes[3], bytes[4]]);
131 let version_number = (bytes[5] >> 1) & 0x1F;
133 let current_next_indicator = (bytes[5] & 0x01) != 0;
134 let section_number = bytes[6];
135 let last_section_number = bytes[7];
136 let transport_stream_id = u16::from_be_bytes([bytes[8], bytes[9]]);
137 let original_network_id = u16::from_be_bytes([bytes[10], bytes[11]]);
138 let prepend_strings_length = bytes[12];
139
140 let ps_start = HEADER_LEN + EXTENSION_LEN;
142 let ps_end = ps_start + prepend_strings_length as usize;
143
144 let payload_end = total - CRC_LEN;
146 if ps_end > payload_end {
147 return Err(Error::SectionLengthOverflow {
148 declared: prepend_strings_length as usize,
149 available: payload_end.saturating_sub(ps_start),
150 });
151 }
152
153 let prepend_strings = &bytes[ps_start..ps_end];
154
155 let crid_entries = &bytes[ps_end..payload_end];
157
158 Ok(Cit {
159 private_indicator,
160 service_id,
161 version_number,
162 current_next_indicator,
163 section_number,
164 last_section_number,
165 transport_stream_id,
166 original_network_id,
167 prepend_strings,
168 crid_entries,
169 })
170 }
171}
172
173impl Serialize for Cit<'_> {
176 type Error = crate::error::Error;
177
178 fn serialized_len(&self) -> usize {
179 HEADER_LEN + EXTENSION_LEN + self.prepend_strings.len() + self.crid_entries.len() + CRC_LEN
180 }
181
182 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
183 let len = self.serialized_len();
184 if buf.len() < len {
185 return Err(Error::OutputBufferTooSmall {
186 need: len,
187 have: buf.len(),
188 });
189 }
190
191 if self.prepend_strings.len() > u8::MAX as usize {
193 return Err(Error::SectionLengthOverflow {
194 declared: self.prepend_strings.len(),
195 available: u8::MAX as usize,
196 });
197 }
198
199 let section_length = (len - HEADER_LEN) as u16;
200
201 buf[0] = TABLE_ID;
203
204 buf[1] = 0x80
207 | (u8::from(self.private_indicator) << 6)
208 | 0x30 | ((section_length >> 8) as u8 & 0x0F);
210
211 buf[2] = (section_length & 0xFF) as u8;
213
214 buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
216 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1)
218 | u8::from(self.current_next_indicator);
219 buf[6] = self.section_number;
220 buf[7] = self.last_section_number;
221 buf[8..10].copy_from_slice(&self.transport_stream_id.to_be_bytes());
222 buf[10..12].copy_from_slice(&self.original_network_id.to_be_bytes());
223 buf[12] = self.prepend_strings.len() as u8;
224
225 let ps_start = HEADER_LEN + EXTENSION_LEN;
227 let ps_end = ps_start + self.prepend_strings.len();
228 buf[ps_start..ps_end].copy_from_slice(self.prepend_strings);
229
230 let crid_end = ps_end + self.crid_entries.len();
232 buf[ps_end..crid_end].copy_from_slice(self.crid_entries);
233
234 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crid_end]);
236 buf[crid_end..len].copy_from_slice(&crc.to_be_bytes());
237
238 Ok(len)
239 }
240}
241
242impl<'a> Table<'a> for Cit<'a> {
245 const TABLE_ID: u8 = TABLE_ID;
246 const PID: u16 = PID;
247}
248
249impl<'a> crate::traits::TableDef<'a> for Cit<'a> {
250 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
251 const NAME: &'static str = "CONTENT_IDENTIFIER";
252}
253
254#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[allow(clippy::too_many_arguments)]
265 fn build_cit(
266 service_id: u16,
267 version: u8,
268 current_next: bool,
269 section_number: u8,
270 last_section_number: u8,
271 transport_stream_id: u16,
272 original_network_id: u16,
273 prepend_strings: &[u8],
274 crid_entries: &[u8],
275 ) -> Vec<u8> {
276 let cit = Cit {
277 private_indicator: false,
278 service_id,
279 version_number: version,
280 current_next_indicator: current_next,
281 section_number,
282 last_section_number,
283 transport_stream_id,
284 original_network_id,
285 prepend_strings,
286 crid_entries,
287 };
288 let mut buf = vec![0u8; cit.serialized_len()];
289 cit.serialize_into(&mut buf).unwrap();
290 buf
291 }
292
293 #[test]
294 fn parse_happy_path_no_crid_entries() {
295 let prepend = b"CRID://example.com\x00";
299 let bytes = build_cit(0x1234, 3, true, 0, 0, 0x0064, 0x0002, prepend, &[]);
300 let cit = Cit::parse(&bytes).unwrap();
301
302 assert_eq!(cit.service_id, 0x1234);
303 assert_eq!(cit.version_number, 3);
304 assert!(cit.current_next_indicator);
305 assert_eq!(cit.section_number, 0);
306 assert_eq!(cit.last_section_number, 0);
307 assert_eq!(cit.transport_stream_id, 0x0064);
308 assert_eq!(cit.original_network_id, 0x0002);
309 assert_eq!(cit.prepend_strings, prepend);
310 assert_eq!(cit.crid_entries, &[] as &[u8]);
311 }
312
313 #[test]
314 fn parse_happy_path_with_crid_entries() {
315 let prepend = b"crid://bbc.co.uk/\x00";
319 let mut crid_entries: Vec<u8> = Vec::new();
320 crid_entries.extend_from_slice(&0x0001u16.to_be_bytes()); crid_entries.push(0x00); let unique0 = b"ep1";
324 crid_entries.push(unique0.len() as u8); crid_entries.extend_from_slice(unique0);
326 crid_entries.extend_from_slice(&0x0002u16.to_be_bytes());
328 crid_entries.push(0xFF); let unique1 = b"crid://bbc.co.uk/EV-1";
330 crid_entries.push(unique1.len() as u8);
331 crid_entries.extend_from_slice(unique1);
332
333 let bytes = build_cit(
334 0xABCD,
335 7,
336 true,
337 1,
338 3,
339 0x01F4,
340 0x0028,
341 prepend,
342 &crid_entries,
343 );
344 let cit = Cit::parse(&bytes).unwrap();
345
346 assert_eq!(cit.service_id, 0xABCD);
347 assert_eq!(cit.version_number, 7);
348 assert_eq!(cit.section_number, 1);
349 assert_eq!(cit.last_section_number, 3);
350 assert_eq!(cit.transport_stream_id, 0x01F4);
351 assert_eq!(cit.original_network_id, 0x0028);
352 assert_eq!(cit.prepend_strings, prepend);
353 assert_eq!(cit.crid_entries, crid_entries.as_slice());
354 }
355
356 #[test]
357 fn parse_rejects_wrong_table_id() {
358 let mut bytes = build_cit(0x0001, 0, true, 0, 0, 0x0001, 0x0001, &[], &[]);
359 bytes[0] = 0x40; assert!(matches!(
361 Cit::parse(&bytes).unwrap_err(),
362 Error::UnexpectedTableId { table_id: 0x40, .. }
363 ));
364 }
365
366 #[test]
367 fn parse_rejects_buffer_too_short() {
368 let short = [TABLE_ID, 0x00];
370 assert!(matches!(
371 Cit::parse(&short).unwrap_err(),
372 Error::BufferTooShort { .. }
373 ));
374 }
375
376 #[test]
377 fn parse_rejects_section_length_overflow() {
378 let mut bytes = build_cit(0x0001, 0, true, 0, 0, 0x0001, 0x0001, &[], &[]);
379 let fake_sl: u16 = (bytes.len() as u16) + 100 - HEADER_LEN as u16;
381 bytes[1] = (bytes[1] & 0xF0) | ((fake_sl >> 8) as u8 & 0x0F);
382 bytes[2] = (fake_sl & 0xFF) as u8;
383 assert!(matches!(
384 Cit::parse(&bytes).unwrap_err(),
385 Error::SectionLengthOverflow { .. }
386 ));
387 }
388
389 #[test]
390 fn serialize_round_trip() {
391 let prepend = b"crid://example.com/\x00";
392 let crid_entries = {
393 let mut v: Vec<u8> = Vec::new();
394 v.extend_from_slice(&0x0042u16.to_be_bytes());
395 v.push(0x00);
396 let unique = b"episode42";
397 v.push(unique.len() as u8);
398 v.extend_from_slice(unique);
399 v
400 };
401
402 let original = Cit {
403 private_indicator: true,
404 service_id: 0x4321,
405 version_number: 15,
406 current_next_indicator: false,
407 section_number: 2,
408 last_section_number: 4,
409 transport_stream_id: 0x03E8,
410 original_network_id: 0x0050,
411 prepend_strings: prepend,
412 crid_entries: &crid_entries,
413 };
414
415 let mut buf = vec![0u8; original.serialized_len()];
416 original.serialize_into(&mut buf).unwrap();
417 let parsed = Cit::parse(&buf).unwrap();
418
419 assert_eq!(parsed.private_indicator, original.private_indicator);
420 assert_eq!(parsed.service_id, original.service_id);
421 assert_eq!(parsed.version_number, original.version_number);
422 assert_eq!(
423 parsed.current_next_indicator,
424 original.current_next_indicator
425 );
426 assert_eq!(parsed.section_number, original.section_number);
427 assert_eq!(parsed.last_section_number, original.last_section_number);
428 assert_eq!(parsed.transport_stream_id, original.transport_stream_id);
429 assert_eq!(parsed.original_network_id, original.original_network_id);
430 assert_eq!(parsed.prepend_strings, original.prepend_strings);
431 assert_eq!(parsed.crid_entries, original.crid_entries);
432 }
433
434 #[test]
435 fn serialize_rejects_output_buffer_too_small() {
436 let cit = Cit {
437 private_indicator: false,
438 service_id: 0x0001,
439 version_number: 0,
440 current_next_indicator: true,
441 section_number: 0,
442 last_section_number: 0,
443 transport_stream_id: 0x0001,
444 original_network_id: 0x0001,
445 prepend_strings: &[],
446 crid_entries: &[],
447 };
448 let mut buf = vec![0u8; 2]; assert!(matches!(
450 cit.serialize_into(&mut buf).unwrap_err(),
451 Error::OutputBufferTooSmall { .. }
452 ));
453 }
454}