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}