dvb_si/descriptors/extension/
target_region.rs1use super::*;
3use alloc::vec::Vec;
4
5impl<'a> ExtensionBodyDef<'a> for TargetRegion {
6 const TAG_EXTENSION: u8 = 0x09;
7 const NAME: &'static str = "TARGET_REGION";
8}
9#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12pub struct TargetRegion {
13 pub country_code: LangCode,
15 pub regions: Vec<TargetRegionEntry>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct TargetRegionEntry {
23 pub country_code: Option<LangCode>,
25 pub region_codes: RegionCodes,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32#[non_exhaustive]
33pub enum RegionCodes {
34 None,
36 Primary {
38 primary_region_code: u8,
40 },
41 PrimarySecondary {
43 primary_region_code: u8,
45 secondary_region_code: u8,
47 },
48 Full {
50 primary_region_code: u8,
52 secondary_region_code: u8,
54 tertiary_region_code: u16,
56 },
57}
58
59impl<'a> Parse<'a> for TargetRegion {
60 type Error = crate::error::Error;
61 fn parse(sel: &'a [u8]) -> Result<Self> {
62 if sel.len() < ISO_639_LEN {
63 return Err(Error::BufferTooShort {
64 need: ISO_639_LEN,
65 have: sel.len(),
66 what: "target_region body",
67 });
68 }
69 let country_code = LangCode([sel[0], sel[1], sel[2]]);
70 let regions = parse_region_entries(sel, ISO_639_LEN)?;
71 Ok(TargetRegion {
72 country_code,
73 regions,
74 })
75 }
76}
77
78pub(crate) fn parse_region_entries(sel: &[u8], start: usize) -> Result<Vec<TargetRegionEntry>> {
84 let mut regions = Vec::new();
85 let mut pos = start;
86 while pos < sel.len() {
87 let flags = sel[pos];
88 pos += 1;
89 let country_code_flag = (flags >> 2) & 1;
90 let region_depth = flags & 0x03;
91 let country_code = if country_code_flag == 1 {
92 if pos + ISO_639_LEN > sel.len() {
93 return Err(Error::BufferTooShort {
94 need: pos + ISO_639_LEN,
95 have: sel.len(),
96 what: "target_region body",
97 });
98 }
99 let cc = LangCode([sel[pos], sel[pos + 1], sel[pos + 2]]);
100 pos += ISO_639_LEN;
101 Some(cc)
102 } else {
103 None
104 };
105 let region_codes = match region_depth {
106 0 => RegionCodes::None,
107 1 => {
108 if pos >= sel.len() {
109 return Err(Error::BufferTooShort {
110 need: pos + 1,
111 have: sel.len(),
112 what: "target_region body",
113 });
114 }
115 let primary = sel[pos];
116 pos += 1;
117 RegionCodes::Primary {
118 primary_region_code: primary,
119 }
120 }
121 2 => {
122 if pos + 1 >= sel.len() {
123 return Err(Error::BufferTooShort {
124 need: pos + 2,
125 have: sel.len(),
126 what: "target_region body",
127 });
128 }
129 let primary = sel[pos];
130 let secondary = sel[pos + 1];
131 pos += 2;
132 RegionCodes::PrimarySecondary {
133 primary_region_code: primary,
134 secondary_region_code: secondary,
135 }
136 }
137 3 => {
138 if pos + 3 >= sel.len() {
139 return Err(Error::BufferTooShort {
140 need: pos + 4,
141 have: sel.len(),
142 what: "target_region body",
143 });
144 }
145 let primary = sel[pos];
146 let secondary = sel[pos + 1];
147 let tertiary = u16::from_be_bytes([sel[pos + 2], sel[pos + 3]]);
148 pos += 4;
149 RegionCodes::Full {
150 primary_region_code: primary,
151 secondary_region_code: secondary,
152 tertiary_region_code: tertiary,
153 }
154 }
155 _ => return Err(invalid("target_region: invalid region_depth")),
156 };
157 regions.push(TargetRegionEntry {
158 country_code,
159 region_codes,
160 });
161 }
162 Ok(regions)
163}
164
165pub(crate) fn region_entries_serialized_len(entries: &[TargetRegionEntry]) -> usize {
168 entries
169 .iter()
170 .map(|r| {
171 1 + if r.country_code.is_some() {
172 ISO_639_LEN
173 } else {
174 0
175 } + match &r.region_codes {
176 RegionCodes::None => 0,
177 RegionCodes::Primary { .. } => 1,
178 RegionCodes::PrimarySecondary { .. } => 2,
179 RegionCodes::Full { .. } => 4,
180 }
181 })
182 .sum()
183}
184
185pub(crate) fn write_region_entries(
188 entries: &[TargetRegionEntry],
189 buf: &mut [u8],
190 mut pos: usize,
191) -> usize {
192 let start = pos;
193 for region in entries {
194 let depth = match ®ion.region_codes {
195 RegionCodes::None => 0u8,
196 RegionCodes::Primary { .. } => 1,
197 RegionCodes::PrimarySecondary { .. } => 2,
198 RegionCodes::Full { .. } => 3,
199 };
200 buf[pos] = 0xF8 | ((region.country_code.is_some() as u8) << 2) | depth;
201 pos += 1;
202 if let Some(cc) = ®ion.country_code {
203 buf[pos..pos + ISO_639_LEN].copy_from_slice(&cc.0);
204 pos += ISO_639_LEN;
205 }
206 match ®ion.region_codes {
207 RegionCodes::None => {}
208 RegionCodes::Primary {
209 primary_region_code,
210 } => {
211 buf[pos] = *primary_region_code;
212 pos += 1;
213 }
214 RegionCodes::PrimarySecondary {
215 primary_region_code,
216 secondary_region_code,
217 } => {
218 buf[pos] = *primary_region_code;
219 buf[pos + 1] = *secondary_region_code;
220 pos += 2;
221 }
222 RegionCodes::Full {
223 primary_region_code,
224 secondary_region_code,
225 tertiary_region_code,
226 } => {
227 buf[pos] = *primary_region_code;
228 buf[pos + 1] = *secondary_region_code;
229 buf[pos + 2..pos + 4].copy_from_slice(&tertiary_region_code.to_be_bytes());
230 pos += 4;
231 }
232 }
233 }
234 pos - start
235}
236
237impl Serialize for TargetRegion {
238 type Error = crate::error::Error;
239 fn serialized_len(&self) -> usize {
240 ISO_639_LEN + region_entries_serialized_len(&self.regions)
241 }
242 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
243 let len = self.serialized_len();
244 if buf.len() < len {
245 return Err(Error::OutputBufferTooSmall {
246 need: len,
247 have: buf.len(),
248 });
249 }
250 buf[..ISO_639_LEN].copy_from_slice(&self.country_code.0);
251 write_region_entries(&self.regions, buf, ISO_639_LEN);
252 Ok(len)
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::descriptors::extension::test_support::*;
260 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
261 use crate::text::LangCode;
262
263 #[test]
264 fn parse_target_region_structured() {
265 let sel = [b'g', b'b', b'r', 0xF9, 0x12];
266 let bytes = wrap(0x09, &sel);
267 let d = ExtensionDescriptor::parse(&bytes).unwrap();
268 match &d.body {
269 ExtensionBody::TargetRegion(b) => {
270 assert_eq!(b.country_code, LangCode(*b"gbr"));
271 assert_eq!(b.regions.len(), 1);
272 assert_eq!(b.regions[0].country_code, None);
273 assert_eq!(
274 b.regions[0].region_codes,
275 RegionCodes::Primary {
276 primary_region_code: 0x12
277 }
278 );
279 }
280 other => panic!("expected TargetRegion, got {other:?}"),
281 }
282 round_trip(&d);
283 }
284
285 #[test]
286 fn target_region_tsduck_empty() {
287 let bytes = from_hex("7f0409666f6f");
288 let d = ExtensionDescriptor::parse(&bytes).unwrap();
289 match &d.body {
290 ExtensionBody::TargetRegion(b) => {
291 assert_eq!(b.country_code, LangCode(*b"foo"));
292 assert!(b.regions.is_empty());
293 }
294 other => panic!("expected TargetRegion, got {other:?}"),
295 }
296 let mut buf = vec![0u8; d.serialized_len()];
297 d.serialize_into(&mut buf).unwrap();
298 assert_eq!(buf, bytes);
299 }
300
301 #[test]
302 fn target_region_tsduck_full() {
303 let bytes = from_hex("7f1509626172f8fd666f6f12fa3456ff616263789abcde");
304 let d = ExtensionDescriptor::parse(&bytes).unwrap();
305 match &d.body {
306 ExtensionBody::TargetRegion(b) => {
307 assert_eq!(b.country_code, LangCode(*b"bar"));
308 assert_eq!(b.regions.len(), 4);
309 assert_eq!(b.regions[0].country_code, None);
311 assert_eq!(b.regions[0].region_codes, RegionCodes::None);
312 assert_eq!(b.regions[1].country_code, Some(LangCode(*b"foo")));
314 assert_eq!(
315 b.regions[1].region_codes,
316 RegionCodes::Primary {
317 primary_region_code: 0x12
318 }
319 );
320 assert_eq!(b.regions[2].country_code, None);
322 assert_eq!(
323 b.regions[2].region_codes,
324 RegionCodes::PrimarySecondary {
325 primary_region_code: 0x34,
326 secondary_region_code: 0x56,
327 }
328 );
329 assert_eq!(b.regions[3].country_code, Some(LangCode(*b"abc")));
331 assert_eq!(
332 b.regions[3].region_codes,
333 RegionCodes::Full {
334 primary_region_code: 0x78,
335 secondary_region_code: 0x9A,
336 tertiary_region_code: 0xBCDE,
337 }
338 );
339 }
340 other => panic!("expected TargetRegion, got {other:?}"),
341 }
342 let mut buf = vec![0u8; d.serialized_len()];
343 d.serialize_into(&mut buf).unwrap();
344 assert_eq!(buf, bytes);
345 }
346}