Skip to main content

dvb_si/descriptors/
service.rs

1//! Service Descriptor — ETSI EN 300 468 §6.2.33 (tag 0x48).
2//!
3//! Carried inside SDT. Provides the provider and service name plus a
4//! service_type byte classifying the service (TV SD, TV HD, radio, data, …).
5
6use super::descriptor_body;
7use crate::error::{Error, Result};
8use crate::text::DvbText;
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for service_descriptor.
12pub const TAG: u8 = 0x48;
13const HEADER_LEN: usize = 2;
14
15/// Service type — ETSI EN 300 468 Table 89.
16///
17/// # Examples
18/// ```
19/// use dvb_si::descriptors::service::ServiceType;
20///
21/// assert_eq!(ServiceType::from_u8(0x01).name(), "digital television service");
22/// assert_eq!(ServiceType::from_u8(0x19).to_u8(), 0x19); // advanced-codec HD, lossless
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[non_exhaustive]
27pub enum ServiceType {
28    /// 0x01 — digital television service.
29    DigitalTelevision,
30    /// 0x02 — digital radio sound service.
31    DigitalRadioSound,
32    /// 0x03 — teletext service.
33    Teletext,
34    /// 0x04 — NVOD reference service.
35    NvodReference,
36    /// 0x05 — NVOD time-shifted service.
37    NvodTimeShifted,
38    /// 0x06 — mosaic service.
39    Mosaic,
40    /// 0x07 — FM radio service.
41    FmRadio,
42    /// 0x08 — DVB SRM service.
43    DvbSrm,
44    /// 0x0A — advanced codec digital radio sound service.
45    AdvancedCodecDigitalRadio,
46    /// 0x0B — H.264/AVC mosaic service.
47    AvcMosaic,
48    /// 0x0C — data broadcast service.
49    DataBroadcast,
50    /// 0x0E — RCS Map.
51    RcsMap,
52    /// 0x0F — RCS FLS.
53    RcsFls,
54    /// 0x10 — DVB MHP service.
55    Mhp,
56    /// 0x11 — HD digital television service.
57    HdDigitalTelevision,
58    /// 0x16 — H.264/AVC SD digital television service.
59    AvcSdDigitalTelevision,
60    /// 0x17 — H.264/AVC SD NVOD time-shifted service.
61    AvcSdNvodTimeShifted,
62    /// 0x18 — H.264/AVC SD NVOD reference service.
63    AvcSdNvodReference,
64    /// 0x19 — H.264/AVC HD digital television service.
65    AvcHdDigitalTelevision,
66    /// 0x1A — H.264/AVC HD NVOD time-shifted service.
67    AvcHdNvodTimeShifted,
68    /// 0x1B — H.264/AVC HD NVOD reference service.
69    AvcHdNvodReference,
70    /// 0x1C — H.264/AVC frame compatible plano-stereoscopic HD digital
71    /// television service.
72    AvcFrameCompatiblePlanoStereoscopicHd,
73    /// 0x1D — H.264/AVC frame compatible plano-stereoscopic HD NVOD
74    /// time-shifted service.
75    AvcFrameCompatiblePlanoStereoscopicHdNvodTimeShifted,
76    /// 0x1E — H.264/AVC frame compatible plano-stereoscopic HD NVOD
77    /// reference service.
78    AvcFrameCompatiblePlanoStereoscopicHdNvodReference,
79    /// 0x1F — HEVC digital television service.
80    HevcDigitalTelevision,
81    /// 0x20 — HEVC UHD digital television service.
82    HevcUhdDigitalTelevision,
83    /// 0x21 — VVC digital television service.
84    VvcDigitalTelevision,
85    /// 0x22 — AVS3 digital television service.
86    Avs3DigitalTelevision,
87    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
88    Reserved(u8),
89}
90
91impl ServiceType {
92    #[must_use]
93    /// Creates a value from a wire byte, preserving every possible
94    /// byte value for lossless round-trip.
95    pub fn from_u8(v: u8) -> Self {
96        match v {
97            0x01 => Self::DigitalTelevision,
98            0x02 => Self::DigitalRadioSound,
99            0x03 => Self::Teletext,
100            0x04 => Self::NvodReference,
101            0x05 => Self::NvodTimeShifted,
102            0x06 => Self::Mosaic,
103            0x07 => Self::FmRadio,
104            0x08 => Self::DvbSrm,
105            0x0A => Self::AdvancedCodecDigitalRadio,
106            0x0B => Self::AvcMosaic,
107            0x0C => Self::DataBroadcast,
108            0x0E => Self::RcsMap,
109            0x0F => Self::RcsFls,
110            0x10 => Self::Mhp,
111            0x11 => Self::HdDigitalTelevision,
112            0x16 => Self::AvcSdDigitalTelevision,
113            0x17 => Self::AvcSdNvodTimeShifted,
114            0x18 => Self::AvcSdNvodReference,
115            0x19 => Self::AvcHdDigitalTelevision,
116            0x1A => Self::AvcHdNvodTimeShifted,
117            0x1B => Self::AvcHdNvodReference,
118            0x1C => Self::AvcFrameCompatiblePlanoStereoscopicHd,
119            0x1D => Self::AvcFrameCompatiblePlanoStereoscopicHdNvodTimeShifted,
120            0x1E => Self::AvcFrameCompatiblePlanoStereoscopicHdNvodReference,
121            0x1F => Self::HevcDigitalTelevision,
122            0x20 => Self::HevcUhdDigitalTelevision,
123            0x21 => Self::VvcDigitalTelevision,
124            0x22 => Self::Avs3DigitalTelevision,
125            v => Self::Reserved(v),
126        }
127    }
128
129    #[must_use]
130    /// Returns the wire byte for this value.
131    pub fn to_u8(self) -> u8 {
132        match self {
133            Self::DigitalTelevision => 0x01,
134            Self::DigitalRadioSound => 0x02,
135            Self::Teletext => 0x03,
136            Self::NvodReference => 0x04,
137            Self::NvodTimeShifted => 0x05,
138            Self::Mosaic => 0x06,
139            Self::FmRadio => 0x07,
140            Self::DvbSrm => 0x08,
141            Self::AdvancedCodecDigitalRadio => 0x0A,
142            Self::AvcMosaic => 0x0B,
143            Self::DataBroadcast => 0x0C,
144            Self::RcsMap => 0x0E,
145            Self::RcsFls => 0x0F,
146            Self::Mhp => 0x10,
147            Self::HdDigitalTelevision => 0x11,
148            Self::AvcSdDigitalTelevision => 0x16,
149            Self::AvcSdNvodTimeShifted => 0x17,
150            Self::AvcSdNvodReference => 0x18,
151            Self::AvcHdDigitalTelevision => 0x19,
152            Self::AvcHdNvodTimeShifted => 0x1A,
153            Self::AvcHdNvodReference => 0x1B,
154            Self::AvcFrameCompatiblePlanoStereoscopicHd => 0x1C,
155            Self::AvcFrameCompatiblePlanoStereoscopicHdNvodTimeShifted => 0x1D,
156            Self::AvcFrameCompatiblePlanoStereoscopicHdNvodReference => 0x1E,
157            Self::HevcDigitalTelevision => 0x1F,
158            Self::HevcUhdDigitalTelevision => 0x20,
159            Self::VvcDigitalTelevision => 0x21,
160            Self::Avs3DigitalTelevision => 0x22,
161            Self::Reserved(v) => v,
162        }
163    }
164
165    #[must_use]
166    /// Returns a human-readable spec name for this value.
167    pub fn name(self) -> &'static str {
168        match self {
169            Self::DigitalTelevision => "digital television service",
170            Self::DigitalRadioSound => "digital radio sound service",
171            Self::Teletext => "teletext service",
172            Self::NvodReference => "NVOD reference service",
173            Self::NvodTimeShifted => "NVOD time-shifted service",
174            Self::Mosaic => "mosaic service",
175            Self::FmRadio => "FM radio service",
176            Self::DvbSrm => "DVB SRM service",
177            Self::AdvancedCodecDigitalRadio => "advanced codec digital radio sound service",
178            Self::AvcMosaic => "H.264/AVC mosaic service",
179            Self::DataBroadcast => "data broadcast service",
180            Self::RcsMap => "RCS Map",
181            Self::RcsFls => "RCS FLS",
182            Self::Mhp => "DVB MHP service",
183            Self::HdDigitalTelevision => "HD digital television service",
184            Self::AvcSdDigitalTelevision => "H.264/AVC SD digital television service",
185            Self::AvcSdNvodTimeShifted => "H.264/AVC SD NVOD time-shifted service",
186            Self::AvcSdNvodReference => "H.264/AVC SD NVOD reference service",
187            Self::AvcHdDigitalTelevision => "H.264/AVC HD digital television service",
188            Self::AvcHdNvodTimeShifted => "H.264/AVC HD NVOD time-shifted service",
189            Self::AvcHdNvodReference => "H.264/AVC HD NVOD reference service",
190            Self::AvcFrameCompatiblePlanoStereoscopicHd => {
191                "H.264/AVC frame compatible plano-stereoscopic HD digital television service"
192            }
193            Self::AvcFrameCompatiblePlanoStereoscopicHdNvodTimeShifted => {
194                "H.264/AVC frame compatible plano-stereoscopic HD NVOD time-shifted service"
195            }
196            Self::AvcFrameCompatiblePlanoStereoscopicHdNvodReference => {
197                "H.264/AVC frame compatible plano-stereoscopic HD NVOD reference service"
198            }
199            Self::HevcDigitalTelevision => "HEVC digital television service",
200            Self::HevcUhdDigitalTelevision => "HEVC UHD digital television service",
201            Self::VvcDigitalTelevision => "VVC digital television service",
202            Self::Avs3DigitalTelevision => "AVS3 digital television service",
203            Self::Reserved(_) => "reserved",
204        }
205    }
206}
207dvb_common::impl_spec_display!(ServiceType, Reserved);
208
209/// Service Descriptor.
210#[derive(Debug, Clone, PartialEq, Eq)]
211#[cfg_attr(feature = "serde", derive(serde::Serialize))]
212#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
213pub struct ServiceDescriptor<'a> {
214    /// service_type byte (ETSI Table 89).
215    pub service_type: ServiceType,
216    /// DVB Annex-A encoded provider name.
217    pub provider_name: DvbText<'a>,
218    /// DVB Annex-A encoded service name.
219    pub service_name: DvbText<'a>,
220}
221
222impl<'a> Parse<'a> for ServiceDescriptor<'a> {
223    type Error = crate::error::Error;
224    fn parse(bytes: &'a [u8]) -> Result<Self> {
225        let body = descriptor_body(
226            bytes,
227            TAG,
228            "ServiceDescriptor",
229            "unexpected tag for service_descriptor",
230        )?;
231        if body.len() < 3 {
232            return Err(Error::InvalidDescriptor {
233                tag: TAG,
234                reason: "service_descriptor body too short for service_type + two length fields",
235            });
236        }
237        let service_type = ServiceType::from_u8(body[0]);
238        let provider_len = body[1] as usize;
239        let provider_end = 2 + provider_len;
240        if provider_end + 1 > body.len() {
241            return Err(Error::InvalidDescriptor {
242                tag: TAG,
243                reason: "service_provider_name_length runs past descriptor end",
244            });
245        }
246        let provider_name = DvbText::new(&body[2..provider_end]);
247        let service_len = body[provider_end] as usize;
248        let service_end = provider_end + 1 + service_len;
249        if service_end > body.len() {
250            return Err(Error::InvalidDescriptor {
251                tag: TAG,
252                reason: "service_name_length runs past descriptor end",
253            });
254        }
255        let service_name = DvbText::new(&body[provider_end + 1..service_end]);
256        Ok(Self {
257            service_type,
258            provider_name,
259            service_name,
260        })
261    }
262}
263
264impl Serialize for ServiceDescriptor<'_> {
265    type Error = crate::error::Error;
266    fn serialized_len(&self) -> usize {
267        HEADER_LEN + 1 + 1 + self.provider_name.len() + 1 + self.service_name.len()
268    }
269
270    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
271        let len = self.serialized_len();
272        if buf.len() < len {
273            return Err(Error::OutputBufferTooSmall {
274                need: len,
275                have: buf.len(),
276            });
277        }
278        buf[0] = TAG;
279        buf[1] = (len - HEADER_LEN) as u8;
280        buf[2] = self.service_type.to_u8();
281        buf[3] = self.provider_name.len() as u8;
282        let p_start = 4;
283        let p_end = p_start + self.provider_name.len();
284        buf[p_start..p_end].copy_from_slice(self.provider_name.raw());
285        buf[p_end] = self.service_name.len() as u8;
286        let s_start = p_end + 1;
287        buf[s_start..s_start + self.service_name.len()].copy_from_slice(self.service_name.raw());
288        Ok(len)
289    }
290}
291impl<'a> crate::traits::DescriptorDef<'a> for ServiceDescriptor<'a> {
292    const TAG: u8 = TAG;
293    const NAME: &'static str = "SERVICE";
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn parse_extracts_all_fields() {
302        // service_type=1 (DigitalTelevision), provider="EUTE", service="TF1"
303        let bytes = [
304            TAG, 10, 0x01, 4, b'E', b'U', b'T', b'E', 3, b'T', b'F', b'1',
305        ];
306        let d = ServiceDescriptor::parse(&bytes).unwrap();
307        assert_eq!(d.service_type, ServiceType::DigitalTelevision);
308        assert_eq!(d.provider_name.raw(), b"EUTE");
309        assert_eq!(d.service_name.raw(), b"TF1");
310    }
311
312    #[test]
313    fn parse_rejects_wrong_tag() {
314        let err = ServiceDescriptor::parse(&[0x49, 0]).unwrap_err();
315        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x49, .. }));
316    }
317
318    #[test]
319    fn parse_rejects_short_header() {
320        let err = ServiceDescriptor::parse(&[TAG]).unwrap_err();
321        assert!(matches!(err, Error::BufferTooShort { .. }));
322    }
323
324    #[test]
325    fn parse_rejects_truncated_body() {
326        let err = ServiceDescriptor::parse(&[TAG, 5, 0x01, 0xFF]).unwrap_err();
327        assert!(matches!(err, Error::BufferTooShort { .. }));
328    }
329
330    #[test]
331    fn parse_rejects_provider_length_overrun() {
332        // provider_len says 100 but descriptor body only 5 bytes.
333        let bytes = [TAG, 5, 0x01, 100, b'A', b'B', b'C'];
334        let err = ServiceDescriptor::parse(&bytes).unwrap_err();
335        assert!(matches!(err, Error::InvalidDescriptor { .. }));
336    }
337
338    #[test]
339    fn empty_provider_and_service_names_valid() {
340        let bytes = [TAG, 3, 0x01, 0, 0];
341        let d = ServiceDescriptor::parse(&bytes).unwrap();
342        assert_eq!(d.service_type, ServiceType::DigitalTelevision);
343        assert!(d.provider_name.raw().is_empty());
344        assert!(d.service_name.raw().is_empty());
345    }
346
347    #[test]
348    fn serialize_round_trip() {
349        let d = ServiceDescriptor {
350            service_type: ServiceType::AvcHdDigitalTelevision,
351            provider_name: DvbText::new(b"BBC"),
352            service_name: DvbText::new(b"BBC ONE HD"),
353        };
354        let mut buf = vec![0u8; d.serialized_len()];
355        d.serialize_into(&mut buf).unwrap();
356        let re = ServiceDescriptor::parse(&buf).unwrap();
357        assert_eq!(d, re);
358    }
359
360    #[test]
361    fn descriptor_length_matches_payload() {
362        let d = ServiceDescriptor {
363            service_type: ServiceType::DigitalTelevision,
364            provider_name: DvbText::new(b"AA"),
365            service_name: DvbText::new(b"BBB"),
366        };
367        // 1 (type) + 1 (p_len) + 2 (p) + 1 (s_len) + 3 (s) = 8
368        assert_eq!(d.serialized_len() - 2, 8);
369    }
370
371    #[test]
372    fn service_type_full_range_round_trip() {
373        for b in 0..=0xFF_u8 {
374            let st = ServiceType::from_u8(b);
375            assert_eq!(st.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
376        }
377    }
378
379    #[test]
380    fn service_type_name_for_known() {
381        assert_eq!(
382            ServiceType::DigitalTelevision.name(),
383            "digital television service"
384        );
385        assert_eq!(
386            ServiceType::HevcDigitalTelevision.name(),
387            "HEVC digital television service"
388        );
389        assert_eq!(
390            ServiceType::HevcUhdDigitalTelevision.name(),
391            "HEVC UHD digital television service"
392        );
393        assert_eq!(ServiceType::HevcUhdDigitalTelevision.to_u8(), 0x20);
394        assert_eq!(
395            ServiceType::from_u8(0x20),
396            ServiceType::HevcUhdDigitalTelevision
397        );
398        assert_eq!(ServiceType::Reserved(0x55).name(), "reserved");
399    }
400}