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 Descriptor.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize))]
18#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
19pub struct ServiceDescriptor<'a> {
20    /// service_type byte (ETSI Table 87).
21    pub service_type: u8,
22    /// DVB Annex-A encoded provider name.
23    pub provider_name: DvbText<'a>,
24    /// DVB Annex-A encoded service name.
25    pub service_name: DvbText<'a>,
26}
27
28impl<'a> Parse<'a> for ServiceDescriptor<'a> {
29    type Error = crate::error::Error;
30    fn parse(bytes: &'a [u8]) -> Result<Self> {
31        let body = descriptor_body(
32            bytes,
33            TAG,
34            "ServiceDescriptor",
35            "unexpected tag for service_descriptor",
36        )?;
37        if body.len() < 3 {
38            return Err(Error::InvalidDescriptor {
39                tag: TAG,
40                reason: "service_descriptor body too short for service_type + two length fields",
41            });
42        }
43        let service_type = body[0];
44        let provider_len = body[1] as usize;
45        let provider_end = 2 + provider_len;
46        if provider_end + 1 > body.len() {
47            return Err(Error::InvalidDescriptor {
48                tag: TAG,
49                reason: "service_provider_name_length runs past descriptor end",
50            });
51        }
52        let provider_name = DvbText::new(&body[2..provider_end]);
53        let service_len = body[provider_end] as usize;
54        let service_end = provider_end + 1 + service_len;
55        if service_end > body.len() {
56            return Err(Error::InvalidDescriptor {
57                tag: TAG,
58                reason: "service_name_length runs past descriptor end",
59            });
60        }
61        let service_name = DvbText::new(&body[provider_end + 1..service_end]);
62        Ok(Self {
63            service_type,
64            provider_name,
65            service_name,
66        })
67    }
68}
69
70impl Serialize for ServiceDescriptor<'_> {
71    type Error = crate::error::Error;
72    fn serialized_len(&self) -> usize {
73        HEADER_LEN + 1 + 1 + self.provider_name.len() + 1 + self.service_name.len()
74    }
75
76    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
77        let len = self.serialized_len();
78        if buf.len() < len {
79            return Err(Error::OutputBufferTooSmall {
80                need: len,
81                have: buf.len(),
82            });
83        }
84        buf[0] = TAG;
85        buf[1] = (len - HEADER_LEN) as u8;
86        buf[2] = self.service_type;
87        buf[3] = self.provider_name.len() as u8;
88        let p_start = 4;
89        let p_end = p_start + self.provider_name.len();
90        buf[p_start..p_end].copy_from_slice(self.provider_name.raw());
91        buf[p_end] = self.service_name.len() as u8;
92        let s_start = p_end + 1;
93        buf[s_start..s_start + self.service_name.len()].copy_from_slice(self.service_name.raw());
94        Ok(len)
95    }
96}
97impl<'a> crate::traits::DescriptorDef<'a> for ServiceDescriptor<'a> {
98    const TAG: u8 = TAG;
99    const NAME: &'static str = "SERVICE";
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn parse_extracts_all_fields() {
108        // service_type=1, provider="EUTE", service="TF1"
109        let bytes = [
110            TAG, 10, 0x01, 4, b'E', b'U', b'T', b'E', 3, b'T', b'F', b'1',
111        ];
112        let d = ServiceDescriptor::parse(&bytes).unwrap();
113        assert_eq!(d.service_type, 1);
114        assert_eq!(d.provider_name.raw(), b"EUTE");
115        assert_eq!(d.service_name.raw(), b"TF1");
116    }
117
118    #[test]
119    fn parse_rejects_wrong_tag() {
120        let err = ServiceDescriptor::parse(&[0x49, 0]).unwrap_err();
121        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x49, .. }));
122    }
123
124    #[test]
125    fn parse_rejects_short_header() {
126        let err = ServiceDescriptor::parse(&[TAG]).unwrap_err();
127        assert!(matches!(err, Error::BufferTooShort { .. }));
128    }
129
130    #[test]
131    fn parse_rejects_truncated_body() {
132        let err = ServiceDescriptor::parse(&[TAG, 5, 0x01, 0xFF]).unwrap_err();
133        assert!(matches!(err, Error::BufferTooShort { .. }));
134    }
135
136    #[test]
137    fn parse_rejects_provider_length_overrun() {
138        // provider_len says 100 but descriptor body only 5 bytes.
139        let bytes = [TAG, 5, 0x01, 100, b'A', b'B', b'C'];
140        let err = ServiceDescriptor::parse(&bytes).unwrap_err();
141        assert!(matches!(err, Error::InvalidDescriptor { .. }));
142    }
143
144    #[test]
145    fn empty_provider_and_service_names_valid() {
146        let bytes = [TAG, 3, 0x01, 0, 0];
147        let d = ServiceDescriptor::parse(&bytes).unwrap();
148        assert!(d.provider_name.raw().is_empty());
149        assert!(d.service_name.raw().is_empty());
150    }
151
152    #[test]
153    fn serialize_round_trip() {
154        let d = ServiceDescriptor {
155            service_type: 0x19,
156            provider_name: DvbText::new(b"BBC"),
157            service_name: DvbText::new(b"BBC ONE HD"),
158        };
159        let mut buf = vec![0u8; d.serialized_len()];
160        d.serialize_into(&mut buf).unwrap();
161        let re = ServiceDescriptor::parse(&buf).unwrap();
162        assert_eq!(d, re);
163    }
164
165    #[test]
166    fn descriptor_length_matches_payload() {
167        let d = ServiceDescriptor {
168            service_type: 1,
169            provider_name: DvbText::new(b"AA"),
170            service_name: DvbText::new(b"BBB"),
171        };
172        // 1 (type) + 1 (p_len) + 2 (p) + 1 (s_len) + 3 (s) = 8
173        assert_eq!(d.serialized_len() - 2, 8);
174    }
175}