cdp_types/parser.rs
1// Copyright (C) 2023 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the MIT license <LICENSE-MIT> or
4// http://opensource.org/licenses/MIT>, at your option. This file may not be
5// copied, modified, or distributed except according to those terms.
6
7use crate::{Flags, Framerate, ParserError, ServiceInfo, TimeCode};
8
9/// Parses CDP packets.
10///
11/// # Examples
12///
13/// ```
14/// # use cdp_types::*;
15/// # use cdp_types::cea708_types::Cea608;
16/// let mut parser = CDPParser::new();
17/// let data = [
18/// 0x96, 0x69, // magic
19/// 0x27, // cdp_len
20/// 0x3f, // framerate
21/// 0x80 | 0x40 | 0x20 | 0x10 | 0x04 | 0x02 | 0x01, // flags
22/// 0x12, 0x34, // sequence counter
23/// 0x71, // time code id
24/// 0xc0 | 0x17, // hours
25/// 0x80 | 0x59, // minutes
26/// 0x80 | 0x57, // seconds
27/// 0x80 | 0x18, // frames
28/// 0x72, // cc_data id
29/// 0xe0 | 0x04, // cc_count
30/// 0xFC, 0x20, 0x41, // CEA608 field 1
31/// 0xFD, 0x42, 0x43, // CEA608 field 2
32/// 0xFF, 0x02, 0x21, // start CEA708 data
33/// 0xFE, 0x41, 0x00,
34/// 0x73, // svc_info id
35/// 0x80 | 0x40 | 0x10 | 0x01, // reserved | start | change | complete | count
36/// 0x80, // reserved | service number
37/// b'e', b'n', b'g', // language
38/// 0x40 | 0x3e, // is_digital | reserved | field/service
39/// 0x3f, // reader | wide | reserved
40/// 0xff, // reserved
41/// 0x74, // cdp footer
42/// 0x12, 0x34, // sequence counter
43/// 0xc4, // checksum
44/// ];
45/// parser.parse(&data).unwrap();
46///
47/// assert_eq!(parser.sequence(), 0x1234);
48/// assert_eq!(parser.framerate(), Framerate::from_id(0x3));
49///
50/// // Service information
51/// let service_info = parser.service_info().unwrap();
52/// assert!(service_info.is_start());
53/// assert!(!service_info.is_change());
54/// assert!(service_info.is_complete());
55/// let entries = service_info.services();
56/// assert_eq!(entries[0].language(), [b'e', b'n', b'g']);
57/// let FieldOrService::Field(field) = entries[0].service() else {
58/// unreachable!();
59/// };
60/// assert!(field);
61///
62/// // Time code information
63/// let time_code = parser.time_code().unwrap();
64/// assert_eq!(time_code.hours(), 17);
65/// assert_eq!(time_code.minutes(), 59);
66/// assert_eq!(time_code.seconds(), 57);
67/// assert_eq!(time_code.frames(), 18);
68/// assert!(time_code.field());
69/// assert!(time_code.drop_frame());
70///
71/// // CEA-708 cc_data
72/// let packet = parser.pop_packet().unwrap();
73/// assert_eq!(packet.sequence_no(), 0);
74///
75/// // CEA-608 data
76/// let cea608 = parser.cea608().unwrap();
77/// assert_eq!(cea608, &[Cea608::Field1(0x20, 0x41), Cea608::Field2(0x42, 0x43)]);
78/// ```
79#[derive(Debug)]
80pub struct CDPParser {
81 cc_data_parser: cea708_types::CCDataParser,
82 time_code: Option<TimeCode>,
83 framerate: Option<Framerate>,
84 service_info: Option<ServiceInfo>,
85 sequence: u16,
86}
87
88impl Default for CDPParser {
89 fn default() -> Self {
90 let mut cc_data_parser = cea708_types::CCDataParser::default();
91 cc_data_parser.handle_cea608();
92 Self {
93 cc_data_parser,
94 time_code: None,
95 framerate: None,
96 service_info: None,
97 sequence: 0,
98 }
99 }
100}
101
102impl CDPParser {
103 const MIN_PACKET_LEN: usize = 11;
104 const TIME_CODE_ID: u8 = 0x71;
105 const CC_DATA_ID: u8 = 0x72;
106 const SVC_INFO_ID: u8 = 0x73;
107 const CDP_FOOTER_ID: u8 = 0x74;
108
109 /// Create a new [CDPParser]
110 pub fn new() -> Self {
111 Self::default()
112 }
113
114 /// Push a complete `CDP` packet into the parser for processing.
115 pub fn parse(&mut self, data: &[u8]) -> Result<(), ParserError> {
116 self.time_code = None;
117 self.framerate = None;
118 self.sequence = 0;
119
120 trace!("parsing {data:?}");
121
122 if data.len() < Self::MIN_PACKET_LEN {
123 return Err(ParserError::LengthMismatch {
124 expected: Self::MIN_PACKET_LEN,
125 actual: data.len(),
126 });
127 }
128
129 if (data[0], data[1]) != (0x96, 0x69) {
130 return Err(ParserError::WrongMagic);
131 }
132
133 let len = data[2] as usize;
134 if data.len() != len {
135 return Err(ParserError::LengthMismatch {
136 expected: len,
137 actual: data.len(),
138 });
139 }
140
141 let framerate =
142 Framerate::from_id((data[3] & 0xf0) >> 4).ok_or(ParserError::UnknownFramerate)?;
143
144 let flags: Flags = data[4].into();
145
146 let sequence_count = (data[5] as u16) << 8 | data[6] as u16;
147
148 let mut idx = 7;
149 let time_code = if flags.time_code {
150 trace!("attempting to parse time code");
151 if data.len() < idx + 5 {
152 return Err(ParserError::LengthMismatch {
153 expected: idx + 5,
154 actual: data.len(),
155 });
156 }
157 if data[idx] != Self::TIME_CODE_ID {
158 return Err(ParserError::WrongMagic);
159 }
160
161 idx += 1;
162 if (data[idx] & 0xc0) != 0xc0 {
163 return Err(ParserError::InvalidFixedBits);
164 }
165 let hours = ((data[idx] & 0x30) >> 4) * 10 + (data[idx] & 0x0f);
166
167 idx += 1;
168 if (data[idx] & 0x80) != 0x80 {
169 return Err(ParserError::InvalidFixedBits);
170 }
171 let minutes = ((data[idx] & 0x70) >> 4) * 10 + (data[idx] & 0x0f);
172
173 idx += 1;
174 let field = ((data[idx] & 0x80) >> 7) > 0;
175 let seconds = ((data[idx] & 0x70) >> 4) * 10 + (data[idx] & 0x0f);
176
177 idx += 1;
178 let drop_frame = (data[idx] & 0x80) > 0;
179 if (data[idx] & 0x40) != 0x00 {
180 return Err(ParserError::InvalidFixedBits);
181 }
182 let frames = ((data[idx] & 0x30) >> 4) * 10 + (data[idx] & 0x0f);
183
184 idx += 1;
185 Some(TimeCode {
186 hours,
187 minutes,
188 seconds,
189 frames,
190 field,
191 drop_frame,
192 })
193 } else {
194 None
195 };
196
197 let cc_data = if flags.cc_data {
198 trace!("attempting to parse cc_data");
199 if data.len() < idx + 2 {
200 return Err(ParserError::LengthMismatch {
201 expected: idx + 2,
202 actual: data.len(),
203 });
204 }
205 if data[idx] != Self::CC_DATA_ID {
206 return Err(ParserError::WrongMagic);
207 }
208 idx += 1;
209
210 if (data[idx] & 0xe0) != 0xe0 {
211 return Err(ParserError::InvalidFixedBits);
212 }
213 let cc_count = (data[idx] & 0x1f) as usize;
214 idx += 1;
215 if data.len() < idx + cc_count * 3 {
216 return Err(ParserError::LengthMismatch {
217 expected: idx + cc_count * 3,
218 actual: data.len(),
219 });
220 }
221 let mut cc_data = vec![0x80 | 0x40 | cc_count as u8, 0xFF];
222 cc_data.extend_from_slice(&data[idx..idx + cc_count * 3]);
223 idx += cc_count * 3;
224 Some(cc_data)
225 } else {
226 None
227 };
228
229 let service_info = if flags.svc_info {
230 trace!("attempting to parse svc info");
231 if data.len() < idx + 2 {
232 return Err(ParserError::LengthMismatch {
233 expected: idx + 2,
234 actual: data.len(),
235 });
236 }
237 if data[idx] != Self::SVC_INFO_ID {
238 return Err(ParserError::WrongMagic);
239 }
240 let svc_count = (data[idx + 1] & 0x0f) as usize;
241 let svc_size = 2 + 7 * svc_count;
242 if data.len() < idx + svc_size {
243 return Err(ParserError::LengthMismatch {
244 expected: idx + svc_size,
245 actual: data.len(),
246 });
247 }
248 let service_info = ServiceInfo::parse(&data[idx..idx + svc_size])?;
249 if service_info.is_start() != flags.svc_info_start {
250 return Err(ParserError::ServiceFlagsMismatched);
251 }
252 if service_info.is_change() != flags.svc_info_change {
253 return Err(ParserError::ServiceFlagsMismatched);
254 }
255 if service_info.is_complete() != flags.svc_info_complete {
256 return Err(ParserError::ServiceFlagsMismatched);
257 }
258 idx += svc_size;
259 Some(service_info)
260 } else {
261 None
262 };
263
264 if data.len() < idx + 2 {
265 return Err(ParserError::LengthMismatch {
266 expected: idx + 2,
267 actual: data.len(),
268 });
269 }
270
271 // future section handling
272 while data[idx] != Self::CDP_FOOTER_ID {
273 trace!("attempting to parse future section");
274 if data[idx] < 0x75 || data[idx] > 0xEF {
275 return Err(ParserError::WrongMagic);
276 }
277 idx += 1;
278 let len = data[idx] as usize;
279 if data.len() < idx + len {
280 return Err(ParserError::LengthMismatch {
281 expected: idx + len,
282 actual: data.len(),
283 });
284 }
285 idx += 1;
286 // TODO: handle future_section
287 idx += len;
288 if data.len() < idx + 2 {
289 return Err(ParserError::LengthMismatch {
290 expected: idx + 2,
291 actual: data.len(),
292 });
293 }
294 }
295
296 // handle cdp footer
297 trace!("attempting to parse footer");
298 if data.len() < idx + 4 {
299 return Err(ParserError::LengthMismatch {
300 expected: idx + 4,
301 actual: data.len(),
302 });
303 }
304 if data[idx] != Self::CDP_FOOTER_ID {
305 return Err(ParserError::WrongMagic);
306 }
307 idx += 1;
308 let footer_sequence_count = (data[idx] as u16) << 8 | data[idx + 1] as u16;
309 if sequence_count != footer_sequence_count {
310 return Err(ParserError::SequenceCountMismatch);
311 }
312 idx += 2;
313
314 let mut checksum: u8 = 0;
315 for d in data[..data.len() - 1].iter() {
316 checksum = checksum.wrapping_add(*d);
317 }
318 // 256 - checksum without having to use a type larger than u8
319 let checksum_byte = (!checksum).wrapping_add(1);
320 trace!(
321 "calculate checksum {checksum_byte:#x}, checksum in data {:#x}",
322 data[idx]
323 );
324 if checksum_byte != data[idx] {
325 return Err(ParserError::ChecksumFailed);
326 }
327
328 if let Some(cc_data) = cc_data {
329 self.cc_data_parser.push(&cc_data)?;
330 }
331 self.framerate = Some(framerate);
332 self.time_code = time_code;
333 self.sequence = sequence_count;
334 self.service_info = service_info;
335
336 Ok(())
337 }
338
339 /// Clear any internal buffers
340 pub fn flush(&mut self) {
341 *self = Self::default();
342 }
343
344 /// The latest CDP time code that has been parsed
345 pub fn time_code(&self) -> Option<TimeCode> {
346 self.time_code
347 }
348
349 /// The latest CDP framerate that has been parsed
350 pub fn framerate(&self) -> Option<Framerate> {
351 self.framerate
352 }
353
354 /// The latest CDP sequence number that has been parsed
355 pub fn sequence(&self) -> u16 {
356 self.sequence
357 }
358
359 /// The latest Service Descriptor that has been parsed.
360 pub fn service_info(&self) -> Option<&ServiceInfo> {
361 self.service_info.as_ref()
362 }
363
364 /// Pop a valid [`cea708_types::DTVCCPacket`] or None if no packet could be parsed
365 pub fn pop_packet(&mut self) -> Option<cea708_types::DTVCCPacket> {
366 self.cc_data_parser.pop_packet()
367 }
368
369 /// Pop the list of [`cea708_types::Cea608`] contained in this packet
370 pub fn cea608(&mut self) -> Option<&[cea708_types::Cea608]> {
371 self.cc_data_parser.cea608()
372 }
373}
374
375#[cfg(test)]
376mod test {
377 use super::*;
378 use crate::tests::*;
379 use crate::*;
380 use cea708_types::{tables, Cea608};
381
382 static PARSE_CDP: [TestCCData; 5] = [
383 // simple packet with cc_data and a time code
384 TestCCData {
385 framerate: FRAMERATES[2],
386 cdp_data: &[CDPPacketData {
387 data: &[
388 0x96, // magic
389 0x69,
390 0x18, // cdp_len
391 0x3f, // framerate
392 0x80 | 0x40 | 0x01, // flags
393 0x12, // sequence counter
394 0x34,
395 0x71, // time code id
396 0xc0 | 0x17, // hours
397 0x80 | 0x59, // minutes
398 0x80 | 0x57, // seconds
399 0x80 | 0x18, // frames
400 0x72, // cc_data id
401 0xe0 | 0x02, // cc_count
402 0xFF,
403 0x02,
404 0x21,
405 0xFE,
406 0x41,
407 0x00,
408 0x74, // cdp footer
409 0x12,
410 0x34,
411 0xA4, // checksum
412 ],
413 sequence_count: 0x1234,
414 time_code: Some(TimeCode {
415 hours: 17,
416 minutes: 59,
417 seconds: 57,
418 frames: 18,
419 field: true,
420 drop_frame: true,
421 }),
422 packets: &[CCPacketData {
423 sequence_no: 0,
424 services: &[ServiceData {
425 service_no: 1,
426 codes: &[tables::Code::LatinCapitalA],
427 }],
428 }],
429 cea608: &[],
430 }],
431 },
432 // simple packet with no time code
433 TestCCData {
434 framerate: FRAMERATES[2],
435 cdp_data: &[CDPPacketData {
436 data: &[
437 0x96, // magic
438 0x69,
439 0x13, // cdp_len
440 0x3f, // framerate
441 0x40 | 0x01, // flags
442 0x12, // sequence counter
443 0x34,
444 0x72, // cc_data id
445 0xe0 | 0x02, // cc_count
446 0xFF,
447 0x02,
448 0x21,
449 0xFE,
450 0x41,
451 0x00,
452 0x74, // cdp footer
453 0x12,
454 0x34,
455 0xB9, // checksum
456 ],
457 sequence_count: 0x1234,
458 time_code: None,
459 packets: &[CCPacketData {
460 sequence_no: 0,
461 services: &[ServiceData {
462 service_no: 1,
463 codes: &[tables::Code::LatinCapitalA],
464 }],
465 }],
466 cea608: &[],
467 }],
468 },
469 // simple packet with svc_info (that is currently ignored)
470 TestCCData {
471 framerate: FRAMERATES[2],
472 cdp_data: &[CDPPacketData {
473 data: &[
474 0x96, // magic
475 0x69,
476 0x14, // cdp_len
477 0x3f, // framerate
478 0x20 | 0x10 | 0x04 | 0x01, // flags
479 0x12, // sequence counter
480 0x34,
481 0x73, // svc_info id
482 0x80 | 0x40 | 0x10 | 0x01, // reserved | start | change | complete | count
483 0x80, // reserved | service number
484 b'e',
485 b'n',
486 b'g',
487 0x40 | 0x3e, // is_digital | reserved | field/service
488 0x3f, // reader | wide | reserved
489 0xff, // reserved
490 0x74, // cdp footer
491 0x12,
492 0x34,
493 0xbf, // checksum
494 ],
495 sequence_count: 0x1234,
496 time_code: None,
497 packets: &[],
498 cea608: &[],
499 }],
500 },
501 // simple packet with future section (that is currently ignored)
502 TestCCData {
503 framerate: FRAMERATES[2],
504 cdp_data: &[CDPPacketData {
505 data: &[
506 0x96, // magic
507 0x69, 0x0F, // cdp_len
508 0x3f, // framerate
509 0x01, // flags
510 0x12, // sequence counter
511 0x34, 0x75, // svc_info id
512 0x02, 0x45, 0x67, 0x74, // cdp footer
513 0x12, 0x34, 0x8F, // checksum
514 ],
515 sequence_count: 0x1234,
516 time_code: None,
517 packets: &[],
518 cea608: &[],
519 }],
520 },
521 // simple packet with CEA-608 data
522 TestCCData {
523 framerate: FRAMERATES[2],
524 cdp_data: &[CDPPacketData {
525 data: &[
526 0x96, // magic
527 0x69,
528 0x13, // cdp_len
529 0x3f, // framerate
530 0x40 | 0x01, // flags
531 0x12, // sequence counter
532 0x34,
533 0x72, // cc_data id
534 0xe0 | 0x02, // cc_count
535 0xFC,
536 0x20,
537 0x41,
538 0xFD,
539 0x42,
540 0x80,
541 0x74, // cdp footer
542 0x12,
543 0x34,
544 0xFE, // checksum
545 ],
546 sequence_count: 0x1234,
547 time_code: None,
548 packets: &[],
549 cea608: &[Cea608::Field1(0x20, 0x41), Cea608::Field2(0x42, 0x80)],
550 }],
551 },
552 ];
553
554 #[test]
555 fn cdp_parse() {
556 test_init_log();
557 for (i, test_data) in PARSE_CDP.iter().enumerate() {
558 info!("parsing {i}: {test_data:?}");
559 let mut parser = CDPParser::new();
560 for cdp in test_data.cdp_data.iter() {
561 parser.parse(cdp.data).unwrap();
562 assert_eq!(parser.time_code(), cdp.time_code);
563 assert_eq!(parser.sequence(), cdp.sequence_count);
564 assert_eq!(parser.framerate(), Some(test_data.framerate));
565 let mut expected_packet_iter = cdp.packets.iter();
566 while let Some(packet) = parser.pop_packet() {
567 let expected = expected_packet_iter.next().unwrap();
568 assert_eq!(expected.sequence_no, packet.sequence_no());
569 let services = packet.services();
570 let mut expected_service_iter = expected.services.iter();
571 for parsed_service in services.iter() {
572 let expected_service = expected_service_iter.next().unwrap();
573 assert_eq!(parsed_service.number(), expected_service.service_no);
574 assert_eq!(expected_service.codes, parsed_service.codes());
575 }
576 assert!(expected_service_iter.next().is_none());
577 }
578 assert_eq!(parser.cea608().unwrap_or(&[]), cdp.cea608);
579 assert!(expected_packet_iter.next().is_none());
580 }
581 assert!(parser.pop_packet().is_none());
582 }
583 }
584}