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 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
165/// Byte length of the serialized region entry loop (without the leading
166/// `country_code` of a `target_region_descriptor` body).
167pub(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
185/// Write the region entry loop into `buf` starting at `pos`. Returns the
186/// number of bytes written.
187pub(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 &region.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) = &region.country_code {
203            buf[pos..pos + ISO_639_LEN].copy_from_slice(&cc.0);
204            pos += ISO_639_LEN;
205        }
206        match &region.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                // [0] cc=None, RegionCodes::None
310                assert_eq!(b.regions[0].country_code, None);
311                assert_eq!(b.regions[0].region_codes, RegionCodes::None);
312                // [1] cc=Some("foo"), Primary{primary:0x12}
313                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                // [2] cc=None, PrimarySecondary{primary:0x34,secondary:0x56}
321                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                // [3] cc=Some("abc"), Full{primary:0x78,secondary:0x9A,tertiary:0xBCDE}
330                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}