Skip to main content

dvb_si/descriptors/
related_content.rs

1//! Related Content Descriptor โ€” ETSI TS 102 323 ยง10.4.1, Table 108 (tag 0x74).
2//!
3//! Carried in a PMT ES_info loop to flag that the elementary stream delivers
4//! a Related Content Table (RCT) sub_table. The descriptor body is empty โ€” it
5//! is a pure marker. Per the TVA PDF (etsi_ts_102_323_v01.04.01, p. 94,
6//! Table 108): the only fields are `descriptor_tag` and `descriptor_length`,
7//! and "descriptor_length ... shall be set to the number of bytes that follow
8//! it" (i.e. zero). At most one related_content descriptor per PMT sub_table.
9
10use crate::error::{Error, Result};
11use crate::traits::Descriptor;
12use dvb_common::{Parse, Serialize};
13
14/// Descriptor tag for related_content_descriptor.
15pub const TAG: u8 = 0x74;
16const HEADER_LEN: usize = 2;
17
18/// Related Content Descriptor.
19///
20/// A zero-length marker โ€” it carries no payload fields.
21#[derive(Debug, Clone, PartialEq, Eq, Default)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23pub struct RelatedContentDescriptor;
24
25impl<'a> Parse<'a> for RelatedContentDescriptor {
26    type Error = crate::error::Error;
27    fn parse(bytes: &'a [u8]) -> Result<Self> {
28        if bytes.len() < HEADER_LEN {
29            return Err(Error::BufferTooShort {
30                need: HEADER_LEN,
31                have: bytes.len(),
32                what: "RelatedContentDescriptor header",
33            });
34        }
35        if bytes[0] != TAG {
36            return Err(Error::InvalidDescriptor {
37                tag: bytes[0],
38                reason: "unexpected tag for related_content_descriptor",
39            });
40        }
41        let length = bytes[1] as usize;
42        let end = HEADER_LEN + length;
43        if bytes.len() < end {
44            return Err(Error::BufferTooShort {
45                need: end,
46                have: bytes.len(),
47                what: "RelatedContentDescriptor body",
48            });
49        }
50        // Body must be empty per Table 108. Trailing payload bytes are ignored
51        // (forward-compatibility), consistent with permissive parsing elsewhere.
52        Ok(Self)
53    }
54}
55
56impl Serialize for RelatedContentDescriptor {
57    type Error = crate::error::Error;
58    fn serialized_len(&self) -> usize {
59        HEADER_LEN
60    }
61
62    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
63        let len = self.serialized_len();
64        if buf.len() < len {
65            return Err(Error::OutputBufferTooSmall {
66                need: len,
67                have: buf.len(),
68            });
69        }
70        buf[0] = TAG;
71        buf[1] = 0;
72        Ok(len)
73    }
74}
75
76impl<'a> Descriptor<'a> for RelatedContentDescriptor {
77    const TAG: u8 = TAG;
78    fn descriptor_length(&self) -> u8 {
79        0
80    }
81}
82
83impl<'a> crate::traits::DescriptorDef<'a> for RelatedContentDescriptor {
84    const TAG: u8 = TAG;
85    const NAME: &'static str = "RELATED_CONTENT";
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn parse_empty_marker() {
94        let bytes = [TAG, 0];
95        let d = RelatedContentDescriptor::parse(&bytes).unwrap();
96        assert_eq!(d, RelatedContentDescriptor);
97    }
98
99    #[test]
100    fn parse_rejects_wrong_tag() {
101        assert!(matches!(
102            RelatedContentDescriptor::parse(&[0x73, 0]).unwrap_err(),
103            Error::InvalidDescriptor { tag: 0x73, .. }
104        ));
105    }
106
107    #[test]
108    fn parse_rejects_short_header() {
109        assert!(matches!(
110            RelatedContentDescriptor::parse(&[TAG]).unwrap_err(),
111            Error::BufferTooShort { .. }
112        ));
113    }
114
115    #[test]
116    fn parse_rejects_length_overrunning_buffer() {
117        let bytes = [TAG, 3, 1, 2];
118        assert!(matches!(
119            RelatedContentDescriptor::parse(&bytes).unwrap_err(),
120            Error::BufferTooShort { .. }
121        ));
122    }
123
124    #[test]
125    fn serialize_round_trip() {
126        let d = RelatedContentDescriptor;
127        let mut buf = vec![0u8; d.serialized_len()];
128        let n = d.serialize_into(&mut buf).unwrap();
129        assert_eq!(n, 2);
130        assert_eq!(buf, vec![TAG, 0]);
131        assert_eq!(RelatedContentDescriptor::parse(&buf).unwrap(), d);
132    }
133
134    #[test]
135    fn serialize_rejects_too_small_buffer() {
136        let d = RelatedContentDescriptor;
137        let mut buf = vec![0u8; 1];
138        assert!(matches!(
139            d.serialize_into(&mut buf).unwrap_err(),
140            Error::OutputBufferTooSmall { .. }
141        ));
142    }
143
144    #[cfg(feature = "serde")]
145    #[test]
146    fn serde_round_trip() {
147        let d = RelatedContentDescriptor;
148        let j = serde_json::to_string(&d).unwrap();
149        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
150        let _v: serde_json::Value = serde_json::from_str(&j).unwrap();
151    }
152}