1use crate::error::{Error, Result};
8use crate::text::{DvbText, LangCode};
9use crate::traits::Descriptor;
10use dvb_common::{Parse, Serialize};
11
12pub const TAG: u8 = 0x50;
14const HEADER_LEN: usize = 2;
15const PRE_TEXT_LEN: usize = 6;
17const STREAM_CONTENT_MASK: u8 = 0x0F;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
23pub struct ComponentDescriptor<'a> {
24 pub stream_content_ext: u8,
27 pub stream_content: u8,
29 pub component_type: u8,
31 pub component_tag: u8,
33 pub language_code: LangCode,
35 pub text: DvbText<'a>,
37}
38
39impl<'a> Parse<'a> for ComponentDescriptor<'a> {
40 type Error = crate::error::Error;
41 fn parse(bytes: &'a [u8]) -> Result<Self> {
42 if bytes.len() < HEADER_LEN {
43 return Err(Error::BufferTooShort {
44 need: HEADER_LEN,
45 have: bytes.len(),
46 what: "ComponentDescriptor header",
47 });
48 }
49 if bytes[0] != TAG {
50 return Err(Error::InvalidDescriptor {
51 tag: bytes[0],
52 reason: "unexpected tag for component_descriptor",
53 });
54 }
55 let length = bytes[1] as usize;
56 let end = HEADER_LEN + length;
57 if bytes.len() < end {
58 return Err(Error::BufferTooShort {
59 need: end,
60 have: bytes.len(),
61 what: "ComponentDescriptor body",
62 });
63 }
64 if length < PRE_TEXT_LEN {
65 return Err(Error::InvalidDescriptor {
66 tag: TAG,
67 reason: "component_descriptor body shorter than minimum 6 bytes",
68 });
69 }
70 let stream_content_ext = bytes[HEADER_LEN] >> 4;
71 let stream_content = bytes[HEADER_LEN] & STREAM_CONTENT_MASK;
72 let component_type = bytes[HEADER_LEN + 1];
73 let component_tag = bytes[HEADER_LEN + 2];
74 let language_code = LangCode([
75 bytes[HEADER_LEN + 3],
76 bytes[HEADER_LEN + 4],
77 bytes[HEADER_LEN + 5],
78 ]);
79 let text = DvbText::new(&bytes[HEADER_LEN + PRE_TEXT_LEN..end]);
80 Ok(Self {
81 stream_content_ext,
82 stream_content,
83 component_type,
84 component_tag,
85 language_code,
86 text,
87 })
88 }
89}
90
91impl Serialize for ComponentDescriptor<'_> {
92 type Error = crate::error::Error;
93 fn serialized_len(&self) -> usize {
94 HEADER_LEN + PRE_TEXT_LEN + self.text.len()
95 }
96
97 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
98 let len = self.serialized_len();
99 if buf.len() < len {
100 return Err(Error::OutputBufferTooSmall {
101 need: len,
102 have: buf.len(),
103 });
104 }
105 buf[0] = TAG;
106 buf[1] = (len - HEADER_LEN) as u8;
107 buf[HEADER_LEN] =
109 (self.stream_content_ext << 4) | (self.stream_content & STREAM_CONTENT_MASK);
110 buf[HEADER_LEN + 1] = self.component_type;
111 buf[HEADER_LEN + 2] = self.component_tag;
112 buf[HEADER_LEN + 3..HEADER_LEN + 6].copy_from_slice(&self.language_code.0);
113 buf[HEADER_LEN + PRE_TEXT_LEN..len].copy_from_slice(self.text.raw());
114 Ok(len)
115 }
116}
117
118impl<'a> Descriptor<'a> for ComponentDescriptor<'a> {
119 const TAG: u8 = TAG;
120 fn descriptor_length(&self) -> u8 {
121 (self.serialized_len() - HEADER_LEN) as u8
122 }
123}
124
125impl<'a> crate::traits::DescriptorDef<'a> for ComponentDescriptor<'a> {
126 const TAG: u8 = TAG;
127 const NAME: &'static str = "COMPONENT";
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn parse_extracts_all_fields() {
136 let bytes = [
137 TAG, 12, 0x02, 0x01, 0x42, b'e', b'n', b'g', b'S', b'T', b'E', b'R', b'E', b'O',
138 ];
139 let d = ComponentDescriptor::parse(&bytes).unwrap();
140 assert_eq!(d.stream_content, 2);
141 assert_eq!(d.component_type, 0x01);
142 assert_eq!(d.component_tag, 0x42);
143 assert_eq!(d.language_code, LangCode(*b"eng"));
144 assert_eq!(d.text.raw(), b"STEREO");
145 }
146
147 #[test]
148 fn parse_rejects_wrong_tag() {
149 assert!(matches!(
150 ComponentDescriptor::parse(&[0x51, 6, 0, 0, 0, b'e', b'n', b'g']).unwrap_err(),
151 Error::InvalidDescriptor { tag: 0x51, .. }
152 ));
153 }
154
155 #[test]
156 fn parse_rejects_short_body() {
157 let bytes = [TAG, 5, 0x01, 0x00, 0x00, b'e', b'n'];
158 assert!(matches!(
159 ComponentDescriptor::parse(&bytes).unwrap_err(),
160 Error::InvalidDescriptor { .. }
161 ));
162 }
163
164 #[test]
165 fn parse_rejects_truncated_buffer() {
166 let bytes = [TAG, 6, 0x01];
167 assert!(matches!(
168 ComponentDescriptor::parse(&bytes).unwrap_err(),
169 Error::BufferTooShort { .. }
170 ));
171 }
172
173 #[test]
174 fn parse_with_empty_text_valid() {
175 let bytes = [TAG, 6, 0x01, 0x01, 0x01, b'e', b'n', b'g'];
176 let d = ComponentDescriptor::parse(&bytes).unwrap();
177 assert!(d.text.raw().is_empty());
178 }
179
180 #[test]
181 fn serialize_round_trip() {
182 let d = ComponentDescriptor {
183 stream_content_ext: 0x0F,
184 stream_content: 0x03,
185 component_type: 0x10,
186 component_tag: 0x05,
187 language_code: LangCode(*b"fra"),
188 text: DvbText::new(b"Sous-titres"),
189 };
190 let mut buf = vec![0u8; d.serialized_len()];
191 d.serialize_into(&mut buf).unwrap();
192 assert_eq!(ComponentDescriptor::parse(&buf).unwrap(), d);
193 }
194
195 #[test]
196 fn stream_content_masked_to_low_nibble() {
197 let bytes = [TAG, 6, 0xF2, 0x00, 0x00, b'e', b'n', b'g'];
199 let d = ComponentDescriptor::parse(&bytes).unwrap();
200 assert_eq!(d.stream_content, 2);
201 }
202}