cdp_types/
svc.rs

1// Copyright (C) 2025 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::ParserError;
8use crate::WriterError;
9
10/// A Closed Caption Service Information block as stored in CDP (SMPTE 334-2).
11#[derive(Debug, PartialEq, Eq, Default, Clone)]
12pub struct ServiceInfo {
13    start: bool,
14    change: bool,
15    complete: bool,
16    services: Vec<ServiceEntry>,
17}
18
19impl ServiceInfo {
20    /// Parse a sequence of bytes into a valid Service Descriptor.
21    pub fn parse(data: &[u8]) -> Result<Self, ParserError> {
22        if data.len() < 2 {
23            return Err(ParserError::LengthMismatch {
24                expected: 2,
25                actual: data.len(),
26            });
27        }
28        if data[0] != 0x73 {
29            return Err(ParserError::WrongMagic);
30        }
31        if data[1] & 0x80 != 0x80 {
32            return Err(ParserError::InvalidFixedBits);
33        }
34        let svc_count = (data[1] & 0xf) as usize;
35        let expected = svc_count * 7 + 2;
36        if data.len() != expected {
37            return Err(ParserError::LengthMismatch {
38                expected,
39                actual: data.len(),
40            });
41        }
42        let start = data[1] & 0x40 > 0;
43        let change = data[1] & 0x20 > 0;
44        let complete = data[1] & 0x10 > 0;
45        let mut ret = Self {
46            start,
47            change,
48            complete,
49            services: vec![],
50        };
51        let mut data = &data[2..];
52        for _ in 0..svc_count {
53            trace!("parsing entry {:x?}", &data[..7]);
54            if data[0] & 0x80 != 0x80 {
55                return Err(ParserError::InvalidFixedBits);
56            }
57            let service_large = data[0] & 0x40 > 0;
58            let service_no = if service_large {
59                if data[0] & 0x20 != 0x20 {
60                    return Err(ParserError::InvalidFixedBits);
61                }
62                data[0] & 0x1f
63            } else {
64                data[0] & 0x3f
65            };
66            let service =
67                ServiceEntry::parse([data[1], data[2], data[3], data[4], data[5], data[6]])?;
68            match &service.service {
69                FieldOrService::Service(digital) => {
70                    if digital.service != service_no {
71                        return Err(ParserError::ServiceNumberMismatch);
72                    }
73                }
74                FieldOrService::Field(_field1) => {
75                    if service_no != 0 {
76                        return Err(ParserError::ServiceNumberMismatch);
77                    }
78                }
79            }
80            data = &data[7..];
81            ret.services.push(service);
82        }
83        Ok(ret)
84    }
85
86    /// This packet begins a complete set of Service Information.
87    pub fn is_start(&self) -> bool {
88        self.start
89    }
90
91    /// Set the start flag in this Service Information.
92    pub fn set_start(&mut self, start: bool) {
93        self.start = start;
94    }
95
96    /// This packet is an update to a previously sent Service Information.  Can only be `true`
97    /// when [is_start](ServiceInfo::is_start) is also `true`.
98    pub fn is_change(&self) -> bool {
99        self.change
100    }
101
102    /// Set the change flag in this Service Information.  If true, then the start flag will also be
103    /// set to true.
104    pub fn set_change(&mut self, change: bool) {
105        self.change = change;
106        if change {
107            self.start = true;
108        }
109    }
110
111    /// This packet concludes a complete set of Service Information.
112    pub fn is_complete(&self) -> bool {
113        self.complete
114    }
115
116    /// Set the complete flag in this Service Information.
117    pub fn set_complete(&mut self, complete: bool) {
118        self.complete = complete;
119    }
120
121    /// The list of services described by this Service Information.
122    pub fn services(&self) -> &[ServiceEntry] {
123        &self.services
124    }
125
126    /// Remove all services from this Service Information block.
127    pub fn clear_services(&mut self) {
128        self.services.clear();
129    }
130
131    /// Add a service to this Service Information block.
132    pub fn add_service(&mut self, service: ServiceEntry) -> Result<(), WriterError> {
133        if self.services.len() >= 15 {
134            return Err(WriterError::WouldOverflow(1));
135        }
136        self.services.push(service);
137        Ok(())
138    }
139
140    /// The length in bytes of this Service Information.
141    pub fn byte_len(&self) -> usize {
142        self.services.len() * 7 + 2
143    }
144
145    /// Write this Service Information to a sequence of bytes.
146    pub fn write<W: std::io::Write>(&mut self, w: &mut W) -> Result<(), std::io::Error> {
147        let mut header = [0; 2];
148        self.write_header_unchecked(&mut header);
149        w.write_all(&header)?;
150        for svc in self.services.iter() {
151            let mut data = [0; 7];
152            self.write_svc_header_unchecked(svc, &mut data[..1]);
153            svc.write_into_unchecked(&mut data[1..7]);
154            w.write_all(&data)?;
155        }
156        Ok(())
157    }
158
159    fn write_header_unchecked(&self, data: &mut [u8]) {
160        data[0] = 0x73;
161        let mut byte = 0x80;
162        if self.start {
163            byte |= 0x40;
164        }
165        if self.change {
166            byte |= 0x20;
167        }
168        if self.complete {
169            byte |= 0x10;
170        }
171        let byte_len = self.services.len() & 0xf;
172        byte |= byte_len as u8;
173        data[1] = byte;
174    }
175
176    fn write_svc_header_unchecked(&self, svc: &ServiceEntry, data: &mut [u8]) {
177        match &svc.service {
178            FieldOrService::Field(field) => {
179                let mut byte = 0x80;
180                if !*field {
181                    byte |= 0x01
182                }
183                data[0] = byte;
184            }
185            FieldOrService::Service(digital) => {
186                data[0] = 0x80 | digital.service;
187            }
188        }
189    }
190
191    /// Write this Service Information into a preallocated sequence of bytes.  `data` must be at
192    /// least [byte_len](ServiceInfo::byte_len) bytes.
193    pub fn write_into_unchecked(&self, data: &mut [u8]) -> usize {
194        self.write_header_unchecked(data);
195        let mut idx = 2;
196        for svc in self.services.iter() {
197            self.write_svc_header_unchecked(svc, &mut data[idx..idx + 1]);
198            svc.write_into_unchecked(&mut data[idx + 1..idx + 7]);
199            idx += 7;
200        }
201        idx
202    }
203}
204
205/// An entry for a caption service as specified in ATSC A/65 (2013) 6.9.2 Caption Service
206/// Descriptor - Table 6.26
207#[derive(Debug, PartialEq, Eq, Clone, Copy)]
208pub struct ServiceEntry {
209    language: [u8; 3],
210    service: FieldOrService,
211}
212
213impl ServiceEntry {
214    /// Construct a new [`ServiceEntry`].
215    pub fn new(language: [u8; 3], service: FieldOrService) -> Self {
216        Self { language, service }
217    }
218
219    /// Parse a Caption Service Descriptor as specified in ATSC A/65.
220    pub fn parse(data: [u8; 6]) -> Result<Self, ParserError> {
221        let digital_cc = data[3] & 0x80 > 0;
222        if data[3] & 0x40 != 0x40 {
223            return Err(ParserError::InvalidFixedBits);
224        }
225        let atsc_service_no = data[3] & 0x3f;
226        let easy_reader = data[4] & 0x80 > 0;
227        let wide_aspect_ratio = data[4] & 0x40 > 0;
228        let service = if digital_cc {
229            if atsc_service_no == 0 {
230                return Err(ParserError::InvalidServiceNumber);
231            }
232            FieldOrService::Service(DigitalServiceEntry {
233                service: atsc_service_no,
234                easy_reader,
235                wide_aspect_ratio,
236            })
237        } else {
238            if data[3] & 0x3e != 0x3e {
239                return Err(ParserError::InvalidFixedBits);
240            }
241            FieldOrService::Field(atsc_service_no & 0x01 == 0)
242        };
243        if data[4] & 0x3f != 0x3f {
244            return Err(ParserError::InvalidFixedBits);
245        }
246        if data[5] != 0xff {
247            return Err(ParserError::InvalidFixedBits);
248        }
249        Ok(Self {
250            language: [data[0], data[1], data[2]],
251            service,
252        })
253    }
254
255    /// Language code as specified in ISO 639.2/B encoded in ISO 8859-1 (latin-1).
256    pub fn language(&self) -> [u8; 3] {
257        self.language
258    }
259
260    /// The CEA-608 field or CEA-708 service referenced by this entry.
261    pub fn service(&self) -> &FieldOrService {
262        &self.service
263    }
264
265    /// Write this entry into a byte sequence.
266    pub fn write<W: std::io::Write>(&mut self, w: &mut W) -> Result<(), std::io::Error> {
267        let mut data = [0; 6];
268        self.write_into_unchecked(&mut data);
269        w.write_all(&data)
270    }
271
272    /// Write this entry into a preallocated sequence of bytes. The destination `buf` must have
273    /// a length of at least 6 bytes.
274    pub fn write_into_unchecked(&self, data: &mut [u8]) {
275        data[0] = self.language[0];
276        data[1] = self.language[1];
277        data[2] = self.language[2];
278        match &self.service {
279            FieldOrService::Field(field) => {
280                let mut byte = 0x7e;
281                if !*field {
282                    byte |= 0x01;
283                }
284                data[3] = byte;
285                data[4] = 0x3f;
286            }
287            FieldOrService::Service(digital) => {
288                data[3] = 0xc0 | digital.service;
289                let mut byte = 0x3f;
290                if digital.easy_reader {
291                    byte |= 0x80;
292                }
293                if digital.wide_aspect_ratio {
294                    byte |= 0x40;
295                }
296                data[4] = byte;
297            }
298        }
299        data[5] = 0xff;
300    }
301}
302
303/// A value that is either a CEA-608 field or a CEA-708 service.
304#[derive(Debug, PartialEq, Eq, Copy, Clone)]
305pub enum FieldOrService {
306    /// A CEA-608 field. Field 1 == true, Field 2 == false.
307    Field(bool),
308    /// A CEA-708 service.
309    Service(DigitalServiceEntry),
310}
311
312/// A service entry for digital closed captions, i.e. CEA-708 captions.
313#[derive(Debug, PartialEq, Eq, Copy, Clone)]
314pub struct DigitalServiceEntry {
315    service: u8,
316    easy_reader: bool,
317    wide_aspect_ratio: bool,
318}
319
320impl DigitalServiceEntry {
321    /// Construct a new [`DigitalServiceEntry`]
322    pub fn new(service: u8, easy_reader: bool, wide_aspect_ratio: bool) -> Self {
323        Self {
324            service,
325            easy_reader,
326            wide_aspect_ratio,
327        }
328    }
329
330    /// The service number of this entry.
331    pub fn service_no(&self) -> u8 {
332        self.service
333    }
334
335    /// Whether this service is an easy reader type.
336    pub fn easy_reader(&self) -> bool {
337        self.easy_reader
338    }
339
340    /// Whether a wide aspect ratio (16:9) is being used here or not (4:3).
341    pub fn wide_aspect_ratio(&self) -> bool {
342        self.wide_aspect_ratio
343    }
344}
345
346#[cfg(test)]
347mod test {
348    use std::sync::LazyLock;
349
350    use super::*;
351    use crate::tests::test_init_log;
352
353    static LANG_TAG: [u8; 3] = [b'e', b'n', b'g'];
354
355    #[derive(Debug)]
356    struct TestSVCData {
357        data: Vec<u8>,
358        service_info: ServiceInfo,
359    }
360
361    static PARSE_SERVICE: LazyLock<[TestSVCData; 1]> = LazyLock::new(|| {
362        [TestSVCData {
363            data: vec![
364                0x73, // magic
365                0xd2, // start | change | complete | count
366                0x80, // service_no
367                LANG_TAG[0],
368                LANG_TAG[1],
369                LANG_TAG[2],
370                0x7e, // is_digital | service_no
371                0x3f, // easy reader | wide aspect_ratio
372                0xff, // reserved
373                0xe1,
374                LANG_TAG[0],
375                LANG_TAG[1],
376                LANG_TAG[2],
377                0xc1,
378                0xff,
379                0xff,
380            ],
381            service_info: ServiceInfo {
382                start: true,
383                change: false,
384                complete: true,
385                services: vec![
386                    ServiceEntry {
387                        language: LANG_TAG,
388                        service: FieldOrService::Field(true),
389                    },
390                    ServiceEntry {
391                        language: LANG_TAG,
392                        service: FieldOrService::Service(DigitalServiceEntry {
393                            service: 1,
394                            easy_reader: true,
395                            wide_aspect_ratio: true,
396                        }),
397                    },
398                ],
399            },
400        }]
401    });
402
403    #[test]
404    fn parse_service_descriptor() {
405        test_init_log();
406
407        for service in PARSE_SERVICE.iter() {
408            debug!("parsing service info data: {:x?}", service.data);
409            let parsed = ServiceInfo::parse(&service.data).unwrap();
410            assert_eq!(parsed, service.service_info);
411        }
412    }
413
414    #[test]
415    fn roundtrip_service_descriptor() {
416        test_init_log();
417
418        for svc in PARSE_SERVICE.iter() {
419            debug!("writing service {:?}", svc.service_info);
420            debug!("existing data {:x?}", svc.data);
421            let byte_len = svc.service_info.byte_len();
422            let mut data = vec![0; byte_len];
423            svc.service_info.write_into_unchecked(&mut data);
424            debug!("wrote service data {data:x?}");
425            let service = ServiceInfo::parse(&data).unwrap();
426            debug!("parsed service {service:?}");
427            assert_eq!(service, svc.service_info);
428        }
429    }
430
431    #[test]
432    fn add_service_overflow() {
433        test_init_log();
434
435        let mut info = ServiceInfo::default();
436        let lang_tag = [b'e', b'n', b'g'];
437        for i in 0..15 {
438            let entry = ServiceEntry::new(
439                lang_tag,
440                FieldOrService::Service(DigitalServiceEntry::new(i, false, false)),
441            );
442            info.add_service(entry).unwrap();
443        }
444        let entry = ServiceEntry::new(
445            lang_tag,
446            FieldOrService::Service(DigitalServiceEntry::new(1, false, false)),
447        );
448        assert_eq!(info.add_service(entry), Err(WriterError::WouldOverflow(1)));
449    }
450}