1use super::descriptor_body;
7use crate::error::{Error, Result};
8use crate::text::DvbText;
9use dvb_common::{Parse, Serialize};
10
11pub const TAG: u8 = 0x48;
13const HEADER_LEN: usize = 2;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[non_exhaustive]
27pub enum ServiceType {
28 DigitalTelevision,
30 DigitalRadioSound,
32 Teletext,
34 NvodReference,
36 NvodTimeShifted,
38 Mosaic,
40 FmRadio,
42 DvbSrm,
44 AdvancedCodecDigitalRadio,
46 AvcMosaic,
48 DataBroadcast,
50 RcsMap,
52 RcsFls,
54 Mhp,
56 HdDigitalTelevision,
58 AvcSdDigitalTelevision,
60 AvcSdNvodTimeShifted,
62 AvcSdNvodReference,
64 AvcHdDigitalTelevision,
66 AvcHdNvodTimeShifted,
68 AvcHdNvodReference,
70 AvcFrameCompatiblePlanoStereoscopicHd,
73 AvcFrameCompatiblePlanoStereoscopicHdNvodTimeShifted,
76 AvcFrameCompatiblePlanoStereoscopicHdNvodReference,
79 HevcDigitalTelevision,
81 HevcUhdDigitalTelevision,
83 VvcDigitalTelevision,
85 Avs3DigitalTelevision,
87 Reserved(u8),
89}
90
91impl ServiceType {
92 #[must_use]
93 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 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 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#[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 pub service_type: ServiceType,
216 pub provider_name: DvbText<'a>,
218 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 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 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 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}