Skip to main content

dvb_si/descriptors/extension/
target_region.rs

1//! Target Region Descriptor — ETSI EN 300 468 §6.4.12 (tag_extension 0x09).
2use 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/// target_region body (Table 156, §6.4.12). The region loop is unfolded.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12pub struct TargetRegion {
13    /// Leading country_code(24).
14    pub country_code: LangCode,
15    /// Region entries (the loop).
16    pub regions: Vec<TargetRegionEntry>,
17}
18
19/// An entry in the target_region_descriptor region loop.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct TargetRegionEntry {
23    /// Per-entry country_code(24), present iff country_code_flag==1.
24    pub country_code: Option<LangCode>,
25    /// region_depth and its region codes.
26    pub region_codes: RegionCodes,
27}
28
29/// The `region_depth` field and its associated region codes (Table 156, §6.4.12).
30#[derive(Debug, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32#[non_exhaustive]
33pub enum RegionCodes {
34    /// region_depth == 0.
35    None,
36    /// region_depth == 1.
37    Primary {
38        /// primary_region_code(8).
39        primary_region_code: u8,
40    },
41    /// region_depth == 2.
42    PrimarySecondary {
43        /// primary_region_code(8).
44        primary_region_code: u8,
45        /// secondary_region_code(8).
46        secondary_region_code: u8,
47    },
48    /// region_depth == 3.
49    Full {
50        /// primary_region_code(8).
51        primary_region_code: u8,
52        /// secondary_region_code(8).
53        secondary_region_code: u8,
54        /// tertiary_region_code(16).
55        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
78/// Parse the region entry loop (the variable-length sequence of entries
79/// starting at byte offset `start` in `sel`). Shared by
80/// [`TargetRegion::parse`] and
81/// [`ServiceProminence`](super::service_prominence::ServiceProminence)
82/// target_region sub-loop parsing.
83pub(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 (tert_bytes, _) =
148                    sel[pos + 2..]
149                        .split_first_chunk::<2>()
150                        .ok_or(Error::BufferTooShort {
151                            need: pos + 4,
152                            have: sel.len(),
153                            what: "target_region body",
154                        })?;
155                let tertiary = u16::from_be_bytes(*tert_bytes);
156                pos += 4;
157                RegionCodes::Full {
158                    primary_region_code: primary,
159                    secondary_region_code: secondary,
160                    tertiary_region_code: tertiary,
161                }
162            }
163            _ => return Err(invalid("target_region: invalid region_depth")),
164        };
165        regions.push(TargetRegionEntry {
166            country_code,
167            region_codes,
168        });
169    }
170    Ok(regions)
171}
172
173/// Byte length of the serialized region entry loop (without the leading
174/// `country_code` of a `target_region_descriptor` body).
175pub(crate) fn region_entries_serialized_len(entries: &[TargetRegionEntry]) -> usize {
176    entries
177        .iter()
178        .map(|r| {
179            1 + if r.country_code.is_some() {
180                ISO_639_LEN
181            } else {
182                0
183            } + match &r.region_codes {
184                RegionCodes::None => 0,
185                RegionCodes::Primary { .. } => 1,
186                RegionCodes::PrimarySecondary { .. } => 2,
187                RegionCodes::Full { .. } => 4,
188            }
189        })
190        .sum()
191}
192
193/// Write the region entry loop into `buf` starting at `pos`. Returns the
194/// number of bytes written.
195pub(crate) fn write_region_entries(
196    entries: &[TargetRegionEntry],
197    buf: &mut [u8],
198    mut pos: usize,
199) -> usize {
200    let start = pos;
201    for region in entries {
202        let depth = match &region.region_codes {
203            RegionCodes::None => 0u8,
204            RegionCodes::Primary { .. } => 1,
205            RegionCodes::PrimarySecondary { .. } => 2,
206            RegionCodes::Full { .. } => 3,
207        };
208        buf[pos] = 0xF8 | ((region.country_code.is_some() as u8) << 2) | depth;
209        pos += 1;
210        if let Some(cc) = &region.country_code {
211            buf[pos..pos + ISO_639_LEN].copy_from_slice(&cc.0);
212            pos += ISO_639_LEN;
213        }
214        match &region.region_codes {
215            RegionCodes::None => {}
216            RegionCodes::Primary {
217                primary_region_code,
218            } => {
219                buf[pos] = *primary_region_code;
220                pos += 1;
221            }
222            RegionCodes::PrimarySecondary {
223                primary_region_code,
224                secondary_region_code,
225            } => {
226                buf[pos] = *primary_region_code;
227                buf[pos + 1] = *secondary_region_code;
228                pos += 2;
229            }
230            RegionCodes::Full {
231                primary_region_code,
232                secondary_region_code,
233                tertiary_region_code,
234            } => {
235                buf[pos] = *primary_region_code;
236                buf[pos + 1] = *secondary_region_code;
237                buf[pos + 2..pos + 4].copy_from_slice(&tertiary_region_code.to_be_bytes());
238                pos += 4;
239            }
240        }
241    }
242    pos - start
243}
244
245impl Serialize for TargetRegion {
246    type Error = crate::error::Error;
247    fn serialized_len(&self) -> usize {
248        ISO_639_LEN + region_entries_serialized_len(&self.regions)
249    }
250    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
251        let len = self.serialized_len();
252        if buf.len() < len {
253            return Err(Error::OutputBufferTooSmall {
254                need: len,
255                have: buf.len(),
256            });
257        }
258        buf[..ISO_639_LEN].copy_from_slice(&self.country_code.0);
259        write_region_entries(&self.regions, buf, ISO_639_LEN);
260        Ok(len)
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267    use crate::descriptors::extension::test_support::*;
268    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
269    use crate::text::LangCode;
270
271    #[test]
272    fn parse_target_region_structured() {
273        let sel = [b'g', b'b', b'r', 0xF9, 0x12];
274        let bytes = wrap(0x09, &sel);
275        let d = ExtensionDescriptor::parse(&bytes).unwrap();
276        match &d.body {
277            ExtensionBody::TargetRegion(b) => {
278                assert_eq!(b.country_code, LangCode(*b"gbr"));
279                assert_eq!(b.regions.len(), 1);
280                assert_eq!(b.regions[0].country_code, None);
281                assert_eq!(
282                    b.regions[0].region_codes,
283                    RegionCodes::Primary {
284                        primary_region_code: 0x12
285                    }
286                );
287            }
288            other => panic!("expected TargetRegion, got {other:?}"),
289        }
290        round_trip(&d);
291    }
292
293    #[test]
294    fn target_region_tsduck_empty() {
295        let bytes = from_hex("7f0409666f6f");
296        let d = ExtensionDescriptor::parse(&bytes).unwrap();
297        match &d.body {
298            ExtensionBody::TargetRegion(b) => {
299                assert_eq!(b.country_code, LangCode(*b"foo"));
300                assert!(b.regions.is_empty());
301            }
302            other => panic!("expected TargetRegion, got {other:?}"),
303        }
304        let mut buf = vec![0u8; d.serialized_len()];
305        d.serialize_into(&mut buf).unwrap();
306        assert_eq!(buf, bytes);
307    }
308
309    #[test]
310    fn target_region_tsduck_full() {
311        let bytes = from_hex("7f1509626172f8fd666f6f12fa3456ff616263789abcde");
312        let d = ExtensionDescriptor::parse(&bytes).unwrap();
313        match &d.body {
314            ExtensionBody::TargetRegion(b) => {
315                assert_eq!(b.country_code, LangCode(*b"bar"));
316                assert_eq!(b.regions.len(), 4);
317                // [0] cc=None, RegionCodes::None
318                assert_eq!(b.regions[0].country_code, None);
319                assert_eq!(b.regions[0].region_codes, RegionCodes::None);
320                // [1] cc=Some("foo"), Primary{primary:0x12}
321                assert_eq!(b.regions[1].country_code, Some(LangCode(*b"foo")));
322                assert_eq!(
323                    b.regions[1].region_codes,
324                    RegionCodes::Primary {
325                        primary_region_code: 0x12
326                    }
327                );
328                // [2] cc=None, PrimarySecondary{primary:0x34,secondary:0x56}
329                assert_eq!(b.regions[2].country_code, None);
330                assert_eq!(
331                    b.regions[2].region_codes,
332                    RegionCodes::PrimarySecondary {
333                        primary_region_code: 0x34,
334                        secondary_region_code: 0x56,
335                    }
336                );
337                // [3] cc=Some("abc"), Full{primary:0x78,secondary:0x9A,tertiary:0xBCDE}
338                assert_eq!(b.regions[3].country_code, Some(LangCode(*b"abc")));
339                assert_eq!(
340                    b.regions[3].region_codes,
341                    RegionCodes::Full {
342                        primary_region_code: 0x78,
343                        secondary_region_code: 0x9A,
344                        tertiary_region_code: 0xBCDE,
345                    }
346                );
347            }
348            other => panic!("expected TargetRegion, got {other:?}"),
349        }
350        let mut buf = vec![0u8; d.serialized_len()];
351        d.serialize_into(&mut buf).unwrap();
352        assert_eq!(buf, bytes);
353    }
354}