Skip to main content

dvb_si/descriptors/
telephone.rs

1//! Telephone Descriptor — ETSI EN 300 468 §6.2.42 (tag 0x57).
2//!
3//! Table 100 (PDF p. 107). Carries a telephone number (for IRD-initiated
4//! return-channel calls) split into five length-prefixed character fields.
5//!
6//! Fixed 3-byte header:
7//!   byte0: reserved(2) | foreign_availability(1) | connection_type(5)
8//!   byte1: reserved(1) | country_prefix_length(2)
9//!          | international_area_code_length(3) | operator_code_length(2)
10//!   byte2: reserved(1) | national_area_code_length(3) | core_number_length(4)
11//! followed by the five char loops, each `<field>_length` ISO 8859-1 bytes.
12//!
13//! Each char field is exposed as [`DvbText`] so the
14//! public API is decoded text rather than raw bytes; the four/three/two-bit
15//! length fields are derived from the raw byte length on serialize and ERROR
16//! on over-range rather than truncating (crate idiom).
17
18use super::descriptor_body;
19use crate::error::{Error, Result};
20use crate::text::DvbText;
21use dvb_common::{Parse, Serialize};
22
23/// Descriptor tag for telephone_descriptor.
24pub const TAG: u8 = 0x57;
25const HEADER_LEN: usize = 2;
26/// Three fixed flag/length bytes precede the char loops.
27const FIXED_LEN: usize = 3;
28
29const FOREIGN_AVAIL_MASK: u8 = 0x20;
30const CONNECTION_TYPE_MASK: u8 = 0x1F;
31const BYTE0_RESERVED: u8 = 0xC0;
32const BYTE1_RESERVED: u8 = 0x80;
33const BYTE2_RESERVED: u8 = 0x80;
34
35const MAX_COUNTRY_PREFIX: usize = 0x03;
36const MAX_INTL_AREA: usize = 0x07;
37const MAX_OPERATOR: usize = 0x03;
38const MAX_NATIONAL_AREA: usize = 0x07;
39const MAX_CORE_NUMBER: usize = 0x0F;
40
41/// Telephone Descriptor.
42#[derive(Debug, Clone, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
45pub struct TelephoneDescriptor<'a> {
46    /// When true, the number may be dialled from outside the prefix's country.
47    pub foreign_availability: bool,
48    /// 5-bit connection_type (meaning out of scope of EN 300 468).
49    pub connection_type: u8,
50    /// country_prefix_char bytes (≤ 3, ISO 8859-1).
51    pub country_prefix: DvbText<'a>,
52    /// international_area_code_char bytes (≤ 7).
53    pub international_area_code: DvbText<'a>,
54    /// operator_code_char bytes (≤ 3).
55    pub operator_code: DvbText<'a>,
56    /// national_area_code_char bytes (≤ 7).
57    pub national_area_code: DvbText<'a>,
58    /// core_number_char bytes (≤ 15).
59    pub core_number: DvbText<'a>,
60}
61
62impl<'a> Parse<'a> for TelephoneDescriptor<'a> {
63    type Error = crate::error::Error;
64    fn parse(bytes: &'a [u8]) -> Result<Self> {
65        let body = descriptor_body(
66            bytes,
67            TAG,
68            "TelephoneDescriptor",
69            "unexpected tag for telephone_descriptor",
70        )?;
71        if body.len() < FIXED_LEN {
72            return Err(Error::InvalidDescriptor {
73                tag: TAG,
74                reason: "telephone_descriptor length too short for fixed fields",
75            });
76        }
77        let foreign_availability = body[0] & FOREIGN_AVAIL_MASK != 0;
78        let connection_type = body[0] & CONNECTION_TYPE_MASK;
79        let country_prefix_length = ((body[1] >> 5) & 0x03) as usize;
80        let international_area_code_length = ((body[1] >> 2) & 0x07) as usize;
81        let operator_code_length = (body[1] & 0x03) as usize;
82        let national_area_code_length = ((body[2] >> 4) & 0x07) as usize;
83        let core_number_length = (body[2] & 0x0F) as usize;
84        let total_chars = country_prefix_length
85            + international_area_code_length
86            + operator_code_length
87            + national_area_code_length
88            + core_number_length;
89        if FIXED_LEN + total_chars > body.len() {
90            return Err(Error::InvalidDescriptor {
91                tag: TAG,
92                reason: "sum of telephone char-field lengths exceeds descriptor body",
93            });
94        }
95        let mut pos = FIXED_LEN;
96        let mut take = |n: usize| {
97            let s = &body[pos..pos + n];
98            pos += n;
99            s
100        };
101        let country_prefix = take(country_prefix_length);
102        let international_area_code = take(international_area_code_length);
103        let operator_code = take(operator_code_length);
104        let national_area_code = take(national_area_code_length);
105        let core_number = take(core_number_length);
106        Ok(Self {
107            foreign_availability,
108            connection_type,
109            country_prefix: DvbText::new(country_prefix),
110            international_area_code: DvbText::new(international_area_code),
111            operator_code: DvbText::new(operator_code),
112            national_area_code: DvbText::new(national_area_code),
113            core_number: DvbText::new(core_number),
114        })
115    }
116}
117
118impl Serialize for TelephoneDescriptor<'_> {
119    type Error = crate::error::Error;
120    fn serialized_len(&self) -> usize {
121        HEADER_LEN
122            + FIXED_LEN
123            + self.country_prefix.raw().len()
124            + self.international_area_code.raw().len()
125            + self.operator_code.raw().len()
126            + self.national_area_code.raw().len()
127            + self.core_number.raw().len()
128    }
129
130    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
131        let len = self.serialized_len();
132        if buf.len() < len {
133            return Err(Error::OutputBufferTooSmall {
134                need: len,
135                have: buf.len(),
136            });
137        }
138        if self.country_prefix.raw().len() > MAX_COUNTRY_PREFIX
139            || self.international_area_code.raw().len() > MAX_INTL_AREA
140            || self.operator_code.raw().len() > MAX_OPERATOR
141            || self.national_area_code.raw().len() > MAX_NATIONAL_AREA
142            || self.core_number.raw().len() > MAX_CORE_NUMBER
143        {
144            return Err(Error::InvalidDescriptor {
145                tag: TAG,
146                reason: "telephone char-field exceeds its length-field capacity",
147            });
148        }
149        buf[0] = TAG;
150        buf[1] = (len - HEADER_LEN) as u8;
151        buf[2] = BYTE0_RESERVED
152            | if self.foreign_availability {
153                FOREIGN_AVAIL_MASK
154            } else {
155                0
156            }
157            | (self.connection_type & CONNECTION_TYPE_MASK);
158        buf[3] = BYTE1_RESERVED
159            | ((self.country_prefix.raw().len() as u8 & 0x03) << 5)
160            | ((self.international_area_code.raw().len() as u8 & 0x07) << 2)
161            | (self.operator_code.raw().len() as u8 & 0x03);
162        buf[4] = BYTE2_RESERVED
163            | ((self.national_area_code.raw().len() as u8 & 0x07) << 4)
164            | (self.core_number.raw().len() as u8 & 0x0F);
165        let mut pos = HEADER_LEN + FIXED_LEN;
166        for field in [
167            self.country_prefix.raw(),
168            self.international_area_code.raw(),
169            self.operator_code.raw(),
170            self.national_area_code.raw(),
171            self.core_number.raw(),
172        ] {
173            buf[pos..pos + field.len()].copy_from_slice(field);
174            pos += field.len();
175        }
176        Ok(len)
177    }
178}
179impl<'a> crate::traits::DescriptorDef<'a> for TelephoneDescriptor<'a> {
180    const TAG: u8 = TAG;
181    const NAME: &'static str = "TELEPHONE";
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    fn sample() -> TelephoneDescriptor<'static> {
189        TelephoneDescriptor {
190            foreign_availability: true,
191            connection_type: 0x05,
192            country_prefix: DvbText::new(b"44"),
193            international_area_code: DvbText::new(b"171"),
194            operator_code: DvbText::new(b"01"),
195            national_area_code: DvbText::new(b"207"),
196            core_number: DvbText::new(b"123456"),
197        }
198    }
199
200    #[test]
201    fn parse_extracts_all_fields() {
202        let d = sample();
203        let mut buf = vec![0u8; d.serialized_len()];
204        d.serialize_into(&mut buf).unwrap();
205        let p = TelephoneDescriptor::parse(&buf).unwrap();
206        assert!(p.foreign_availability);
207        assert_eq!(p.connection_type, 0x05);
208        assert_eq!(p.country_prefix.raw(), b"44");
209        assert_eq!(p.international_area_code.raw(), b"171");
210        assert_eq!(p.operator_code.raw(), b"01");
211        assert_eq!(p.national_area_code.raw(), b"207");
212        assert_eq!(p.core_number.raw(), b"123456");
213    }
214
215    #[test]
216    fn parse_minimal_no_chars() {
217        let bytes = [TAG, 3, 0x20, 0x00, 0x00];
218        let d = TelephoneDescriptor::parse(&bytes).unwrap();
219        assert!(d.foreign_availability);
220        assert!(d.country_prefix.raw().is_empty());
221        assert!(d.core_number.raw().is_empty());
222    }
223
224    #[test]
225    fn parse_rejects_wrong_tag() {
226        assert!(matches!(
227            TelephoneDescriptor::parse(&[0x58, 3, 0, 0, 0]).unwrap_err(),
228            Error::InvalidDescriptor { tag: 0x58, .. }
229        ));
230    }
231
232    #[test]
233    fn parse_rejects_short_buffer() {
234        let bytes = [TAG, 3, 0x20, 0x00];
235        assert!(matches!(
236            TelephoneDescriptor::parse(&bytes).unwrap_err(),
237            Error::BufferTooShort { .. }
238        ));
239    }
240
241    #[test]
242    fn parse_rejects_length_below_fixed() {
243        let bytes = [TAG, 2, 0x20, 0x00];
244        assert!(matches!(
245            TelephoneDescriptor::parse(&bytes).unwrap_err(),
246            Error::InvalidDescriptor { tag: TAG, .. }
247        ));
248    }
249
250    #[test]
251    fn parse_rejects_char_lengths_overrun() {
252        let bytes = [TAG, 3, 0x20, 0x60, 0x00];
253        assert!(matches!(
254            TelephoneDescriptor::parse(&bytes).unwrap_err(),
255            Error::InvalidDescriptor { tag: TAG, .. }
256        ));
257    }
258
259    #[test]
260    fn serialize_round_trip() {
261        let d = sample();
262        let mut buf = vec![0u8; d.serialized_len()];
263        d.serialize_into(&mut buf).unwrap();
264        assert_eq!(TelephoneDescriptor::parse(&buf).unwrap(), d);
265    }
266
267    #[test]
268    fn serialize_emits_reserved_ones() {
269        let d = TelephoneDescriptor {
270            foreign_availability: false,
271            connection_type: 0,
272            country_prefix: DvbText::new(b""),
273            international_area_code: DvbText::new(b""),
274            operator_code: DvbText::new(b""),
275            national_area_code: DvbText::new(b""),
276            core_number: DvbText::new(b""),
277        };
278        let mut buf = vec![0u8; d.serialized_len()];
279        d.serialize_into(&mut buf).unwrap();
280        assert_eq!(buf[2] & BYTE0_RESERVED, BYTE0_RESERVED);
281        assert_eq!(buf[3] & BYTE1_RESERVED, BYTE1_RESERVED);
282        assert_eq!(buf[4] & BYTE2_RESERVED, BYTE2_RESERVED);
283    }
284
285    #[test]
286    fn serialize_rejects_over_range_field() {
287        let d = TelephoneDescriptor {
288            foreign_availability: false,
289            connection_type: 0,
290            country_prefix: DvbText::new(b""),
291            international_area_code: DvbText::new(b""),
292            operator_code: DvbText::new(b"1234"),
293            national_area_code: DvbText::new(b""),
294            core_number: DvbText::new(b""),
295        };
296        let mut buf = vec![0u8; d.serialized_len()];
297        assert!(matches!(
298            d.serialize_into(&mut buf).unwrap_err(),
299            Error::InvalidDescriptor { tag: TAG, .. }
300        ));
301    }
302
303    #[cfg(feature = "serde")]
304    #[test]
305    fn serde_serialize_stable() {
306        let d = sample();
307        let json = serde_json::to_string(&d).unwrap();
308        assert!(json.contains("connection_type"));
309        assert!(json.contains("foreign_availability"));
310        assert_eq!(json, serde_json::to_string(&sample()).unwrap());
311    }
312}