Skip to main content

dvb_si/descriptors/
service_identifier.rs

1//! Service Identifier Descriptor — ETSI TS 102 809 §7.2, Table 39 (tag 0x71).
2//!
3//! Carried in the SDT/service loop to give a service a stable textual
4//! identifier. The body is a run of `textual_service_identifier_bytes`
5//! (ASCII), per ts_102_809_apps.md "Table 39 — Service identifier descriptor"
6//! (PDF p. 62).
7
8use super::descriptor_body;
9use crate::error::{Error, Result};
10use crate::text::DvbText;
11use dvb_common::{Parse, Serialize};
12
13/// Descriptor tag for service_identifier_descriptor.
14pub const TAG: u8 = 0x71;
15const HEADER_LEN: usize = 2;
16
17/// Service Identifier Descriptor.
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
21pub struct ServiceIdentifierDescriptor<'a> {
22    /// Textual service identifier (ASCII per TS 102 809 Table 39).
23    pub textual_service_identifier: DvbText<'a>,
24}
25
26impl<'a> Parse<'a> for ServiceIdentifierDescriptor<'a> {
27    type Error = crate::error::Error;
28    fn parse(bytes: &'a [u8]) -> Result<Self> {
29        let body = descriptor_body(
30            bytes,
31            TAG,
32            "ServiceIdentifierDescriptor",
33            "unexpected tag for service_identifier_descriptor",
34        )?;
35        Ok(Self {
36            textual_service_identifier: DvbText::new(body),
37        })
38    }
39}
40
41impl Serialize for ServiceIdentifierDescriptor<'_> {
42    type Error = crate::error::Error;
43    fn serialized_len(&self) -> usize {
44        HEADER_LEN + self.textual_service_identifier.raw().len()
45    }
46
47    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
48        if self.textual_service_identifier.raw().len() > u8::MAX as usize {
49            return Err(Error::InvalidDescriptor {
50                tag: TAG,
51                reason: "textual_service_identifier exceeds 255 bytes",
52            });
53        }
54        let len = self.serialized_len();
55        if buf.len() < len {
56            return Err(Error::OutputBufferTooSmall {
57                need: len,
58                have: buf.len(),
59            });
60        }
61        buf[0] = TAG;
62        buf[1] = self.textual_service_identifier.raw().len() as u8;
63        buf[HEADER_LEN..len].copy_from_slice(self.textual_service_identifier.raw());
64        Ok(len)
65    }
66}
67impl<'a> crate::traits::DescriptorDef<'a> for ServiceIdentifierDescriptor<'a> {
68    const TAG: u8 = TAG;
69    const NAME: &'static str = "SERVICE_IDENTIFIER";
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn parse_extracts_identifier_text() {
78        let bytes = [TAG, 6, b'B', b'B', b'C', b'O', b'N', b'E'];
79        let d = ServiceIdentifierDescriptor::parse(&bytes).unwrap();
80        assert_eq!(d.textual_service_identifier.raw(), b"BBCONE");
81        assert_eq!(d.textual_service_identifier.decode(), "BBCONE");
82    }
83
84    #[test]
85    fn parse_rejects_wrong_tag() {
86        assert!(matches!(
87            ServiceIdentifierDescriptor::parse(&[0x70, 1, 0]).unwrap_err(),
88            Error::InvalidDescriptor { tag: 0x70, .. }
89        ));
90    }
91
92    #[test]
93    fn parse_rejects_short_header() {
94        assert!(matches!(
95            ServiceIdentifierDescriptor::parse(&[TAG]).unwrap_err(),
96            Error::BufferTooShort { .. }
97        ));
98    }
99
100    #[test]
101    fn parse_rejects_length_overrunning_buffer() {
102        let bytes = [TAG, 5, 1, 2, 3];
103        assert!(matches!(
104            ServiceIdentifierDescriptor::parse(&bytes).unwrap_err(),
105            Error::BufferTooShort { .. }
106        ));
107    }
108
109    #[test]
110    fn empty_identifier_is_valid() {
111        let bytes = [TAG, 0];
112        let d = ServiceIdentifierDescriptor::parse(&bytes).unwrap();
113        assert!(d.textual_service_identifier.raw().is_empty());
114    }
115
116    #[test]
117    fn serialize_round_trip() {
118        let d = ServiceIdentifierDescriptor {
119            textual_service_identifier: DvbText::new(b"CH4-HD"),
120        };
121        let mut buf = vec![0u8; d.serialized_len()];
122        d.serialize_into(&mut buf).unwrap();
123        assert_eq!(ServiceIdentifierDescriptor::parse(&buf).unwrap(), d);
124    }
125
126    #[test]
127    fn serialize_rejects_too_small_buffer() {
128        let d = ServiceIdentifierDescriptor {
129            textual_service_identifier: DvbText::new(b"test"),
130        };
131        let mut buf = vec![0u8; 1];
132        assert!(matches!(
133            d.serialize_into(&mut buf).unwrap_err(),
134            Error::OutputBufferTooSmall { .. }
135        ));
136    }
137
138    #[cfg(feature = "serde")]
139    #[test]
140    fn serde_serializes_to_stable_json() {
141        let d = ServiceIdentifierDescriptor {
142            textual_service_identifier: DvbText::new(b"ITV1"),
143        };
144        let j = serde_json::to_string(&d).unwrap();
145        let _v: serde_json::Value = serde_json::from_str(&j).unwrap();
146        assert!(j.contains("textual_service_identifier"));
147    }
148}