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