Skip to main content

dvb_si/descriptors/extension/
target_region_name.rs

1//! Target Region Name Descriptor — ETSI EN 300 468 §6.4.13 (tag_extension 0x0A).
2use super::*;
3
4impl<'a> ExtensionBodyDef<'a> for TargetRegionName<'a> {
5    const TAG_EXTENSION: u8 = 0x0A;
6    const NAME: &'static str = "TARGET_REGION_NAME";
7}
8/// target_region_name body (Table 157, §6.4.13). The region loop is unfolded.
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
12pub struct TargetRegionName<'a> {
13    /// country_code(24).
14    pub country_code: LangCode,
15    /// ISO_639_language_code(24).
16    pub iso_639_language_code: LangCode,
17    /// Region name entries (the loop).
18    pub regions: Vec<TargetRegionNameEntry<'a>>,
19}
20
21/// An entry in the target_region_name_descriptor region loop.
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
25pub struct TargetRegionNameEntry<'a> {
26    /// region_depth(2).
27    pub region_depth: u8,
28    /// region name (char run, name_length bytes) — DVB Annex-A text.
29    pub region_name: DvbText<'a>,
30    /// primary_region_code(8), always present.
31    pub primary_region_code: u8,
32    /// secondary_region_code(8), present iff region_depth>=2.
33    pub secondary_region_code: Option<u8>,
34    /// tertiary_region_code(16), present iff region_depth==3.
35    pub tertiary_region_code: Option<u16>,
36}
37
38impl<'a> Parse<'a> for TargetRegionName<'a> {
39    type Error = crate::error::Error;
40    fn parse(sel: &'a [u8]) -> Result<Self> {
41        if sel.len() < 2 * ISO_639_LEN {
42            return Err(Error::BufferTooShort {
43                need: 2 * ISO_639_LEN,
44                have: sel.len(),
45                what: "target_region_name body",
46            });
47        }
48        let country_code = LangCode([sel[0], sel[1], sel[2]]);
49        let iso_639_language_code = LangCode([sel[3], sel[4], sel[5]]);
50        let mut regions = Vec::new();
51        let mut pos = 2 * ISO_639_LEN;
52        while pos < sel.len() {
53            let byte = sel[pos];
54            pos += 1;
55            let region_depth = byte >> 6;
56            let name_length = (byte & 0x3F) as usize;
57            if pos + name_length > sel.len() {
58                return Err(Error::BufferTooShort {
59                    need: pos + name_length,
60                    have: sel.len(),
61                    what: "target_region_name body",
62                });
63            }
64            let region_name = DvbText::new(&sel[pos..pos + name_length]);
65            pos += name_length;
66            if pos >= sel.len() {
67                return Err(Error::BufferTooShort {
68                    need: pos + 1,
69                    have: sel.len(),
70                    what: "target_region_name body",
71                });
72            }
73            let primary_region_code = sel[pos];
74            pos += 1;
75            let secondary_region_code = if region_depth >= 2 {
76                if pos >= sel.len() {
77                    return Err(Error::BufferTooShort {
78                        need: pos + 1,
79                        have: sel.len(),
80                        what: "target_region_name body",
81                    });
82                }
83                let val = sel[pos];
84                pos += 1;
85                Some(val)
86            } else {
87                None
88            };
89            let tertiary_region_code = if region_depth == 3 {
90                if pos + 1 >= sel.len() {
91                    return Err(Error::BufferTooShort {
92                        need: pos + 2,
93                        have: sel.len(),
94                        what: "target_region_name body",
95                    });
96                }
97                let val = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
98                pos += 2;
99                Some(val)
100            } else {
101                None
102            };
103            regions.push(TargetRegionNameEntry {
104                region_depth,
105                region_name,
106                primary_region_code,
107                secondary_region_code,
108                tertiary_region_code,
109            });
110        }
111        Ok(TargetRegionName {
112            country_code,
113            iso_639_language_code,
114            regions,
115        })
116    }
117}
118
119impl Serialize for TargetRegionName<'_> {
120    type Error = crate::error::Error;
121    fn serialized_len(&self) -> usize {
122        2 * ISO_639_LEN
123            + self
124                .regions
125                .iter()
126                .map(|r| {
127                    1 + r.region_name.raw().len()
128                        + 1
129                        + if r.region_depth >= 2 { 1 } else { 0 }
130                        + if r.region_depth == 3 { 2 } else { 0 }
131                })
132                .sum::<usize>()
133    }
134    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
135        let len = self.serialized_len();
136        if buf.len() < len {
137            return Err(Error::OutputBufferTooSmall {
138                need: len,
139                have: buf.len(),
140            });
141        }
142        buf[..ISO_639_LEN].copy_from_slice(&self.country_code.0);
143        buf[ISO_639_LEN..2 * ISO_639_LEN].copy_from_slice(&self.iso_639_language_code.0);
144        let mut pos = 2 * ISO_639_LEN;
145        for region in &self.regions {
146            let raw = region.region_name.raw();
147            buf[pos] = ((region.region_depth & 0x03) << 6) | (raw.len() as u8 & 0x3F);
148            pos += 1;
149            buf[pos..pos + raw.len()].copy_from_slice(raw);
150            pos += raw.len();
151            buf[pos] = region.primary_region_code;
152            pos += 1;
153            if let Some(sec) = region.secondary_region_code {
154                buf[pos] = sec;
155                pos += 1;
156            }
157            if let Some(tert) = region.tertiary_region_code {
158                buf[pos..pos + 2].copy_from_slice(&tert.to_be_bytes());
159                pos += 2;
160            }
161        }
162        Ok(len)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::descriptors::extension::test_support::*;
170    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
171    use crate::text::LangCode;
172
173    #[test]
174    fn parse_target_region_name_structured() {
175        let sel = [b'g', b'b', b'r', b'e', b'n', b'g', 0x42, b'H', b'i', 0x12];
176        let bytes = wrap(0x0A, &sel);
177        let d = ExtensionDescriptor::parse(&bytes).unwrap();
178        match &d.body {
179            ExtensionBody::TargetRegionName(b) => {
180                assert_eq!(b.country_code, LangCode(*b"gbr"));
181                assert_eq!(b.iso_639_language_code, LangCode(*b"eng"));
182                assert_eq!(b.regions.len(), 1);
183                assert_eq!(b.regions[0].region_depth, 1);
184                assert_eq!(b.regions[0].region_name.raw(), b"Hi");
185                assert_eq!(b.regions[0].primary_region_code, 0x12);
186                assert_eq!(b.regions[0].secondary_region_code, None);
187                assert_eq!(b.regions[0].tertiary_region_code, None);
188            }
189            other => panic!("expected TargetRegionName, got {other:?}"),
190        }
191        round_trip(&d);
192    }
193
194    #[test]
195    fn target_region_name_tsduck_empty() {
196        let bytes = from_hex("7f070a666f6f626172");
197        let d = ExtensionDescriptor::parse(&bytes).unwrap();
198        match &d.body {
199            ExtensionBody::TargetRegionName(b) => {
200                assert_eq!(b.country_code, LangCode(*b"foo"));
201                assert_eq!(b.iso_639_language_code, LangCode(*b"bar"));
202                assert!(b.regions.is_empty());
203            }
204            other => panic!("expected TargetRegionName, got {other:?}"),
205        }
206        let mut buf = vec![0u8; d.serialized_len()];
207        d.serialize_into(&mut buf).unwrap();
208        assert_eq!(buf, bytes);
209    }
210
211    #[test]
212    fn target_region_name_tsduck_full() {
213        let bytes = from_hex(
214            "7f260a4142434445464c726567696f6e20666f6f203112cc726567696f6e2062617220323456789a",
215        );
216        let d = ExtensionDescriptor::parse(&bytes).unwrap();
217        match &d.body {
218            ExtensionBody::TargetRegionName(b) => {
219                assert_eq!(b.country_code, LangCode(*b"ABC"));
220                assert_eq!(b.iso_639_language_code, LangCode(*b"DEF"));
221                assert_eq!(b.regions.len(), 2);
222                // [0] region_depth=1, region_name="region foo 1", primary=0x12
223                assert_eq!(b.regions[0].region_depth, 1);
224                assert_eq!(b.regions[0].region_name.raw(), b"region foo 1");
225                assert_eq!(b.regions[0].primary_region_code, 0x12);
226                assert_eq!(b.regions[0].secondary_region_code, None);
227                assert_eq!(b.regions[0].tertiary_region_code, None);
228                // [1] region_depth=3, region_name="region bar 2", primary=0x34, sec=0x56, tert=0x789A
229                assert_eq!(b.regions[1].region_depth, 3);
230                assert_eq!(b.regions[1].region_name.raw(), b"region bar 2");
231                assert_eq!(b.regions[1].primary_region_code, 0x34);
232                assert_eq!(b.regions[1].secondary_region_code, Some(0x56));
233                assert_eq!(b.regions[1].tertiary_region_code, Some(0x789A));
234            }
235            other => panic!("expected TargetRegionName, got {other:?}"),
236        }
237        let mut buf = vec![0u8; d.serialized_len()];
238        d.serialize_into(&mut buf).unwrap();
239        assert_eq!(buf, bytes);
240    }
241}