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