1use crate::error::{Error, Result};
7use crate::text::LangCode;
8use crate::traits::Descriptor;
9use dvb_common::{Parse, Serialize};
10
11pub const TAG: u8 = 0x58;
13const HEADER_LEN: usize = 2;
14const ENTRY_LEN: usize = 13;
15const POLARITY_MASK: u8 = 0x01;
16const REGION_ID_MASK: u8 = 0xFC;
17const RESERVED_BIT_MASK: u8 = 0x02;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct LocalTimeOffsetEntry {
23 pub country_code: LangCode,
25 pub country_region_id: u8,
27 pub local_time_offset_negative: bool,
30 pub local_time_offset_bcd: u16,
32 pub time_of_change_raw: [u8; 5],
34 pub next_time_offset_bcd: u16,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct LocalTimeOffsetDescriptor {
42 pub entries: Vec<LocalTimeOffsetEntry>,
44}
45
46impl<'a> Parse<'a> for LocalTimeOffsetDescriptor {
47 type Error = crate::error::Error;
48 fn parse(bytes: &'a [u8]) -> Result<Self> {
49 if bytes.len() < HEADER_LEN {
50 return Err(Error::BufferTooShort {
51 need: HEADER_LEN,
52 have: bytes.len(),
53 what: "LocalTimeOffsetDescriptor header",
54 });
55 }
56 if bytes[0] != TAG {
57 return Err(Error::InvalidDescriptor {
58 tag: bytes[0],
59 reason: "unexpected tag for local_time_offset_descriptor",
60 });
61 }
62 let length = bytes[1] as usize;
63 if length % ENTRY_LEN != 0 {
64 return Err(Error::InvalidDescriptor {
65 tag: TAG,
66 reason: "descriptor_length must be a multiple of 13",
67 });
68 }
69 let body_start = HEADER_LEN;
70 let body_end = body_start + length;
71 if bytes.len() < body_end {
72 return Err(Error::BufferTooShort {
73 need: body_end,
74 have: bytes.len(),
75 what: "LocalTimeOffsetDescriptor body",
76 });
77 }
78 let mut entries = Vec::with_capacity(length / ENTRY_LEN);
79 let mut offset = body_start;
80 while offset < body_end {
81 let country_code = LangCode([bytes[offset], bytes[offset + 1], bytes[offset + 2]]);
82 let flags = bytes[offset + 3];
83 let country_region_id = (flags & REGION_ID_MASK) >> 2;
86 let local_time_offset_negative = flags & POLARITY_MASK != 0;
87 let local_time_offset_bcd = u16::from_be_bytes([bytes[offset + 4], bytes[offset + 5]]);
88 let mut time_of_change_raw = [0u8; 5];
89 time_of_change_raw.copy_from_slice(&bytes[offset + 6..offset + 11]);
90 let next_time_offset_bcd = u16::from_be_bytes([bytes[offset + 11], bytes[offset + 12]]);
91 entries.push(LocalTimeOffsetEntry {
92 country_code,
93 country_region_id,
94 local_time_offset_negative,
95 local_time_offset_bcd,
96 time_of_change_raw,
97 next_time_offset_bcd,
98 });
99 offset += ENTRY_LEN;
100 }
101 Ok(Self { entries })
102 }
103}
104
105impl Serialize for LocalTimeOffsetDescriptor {
106 type Error = crate::error::Error;
107 fn serialized_len(&self) -> usize {
108 HEADER_LEN + ENTRY_LEN * self.entries.len()
109 }
110
111 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
112 let len = self.serialized_len();
113 if buf.len() < len {
114 return Err(Error::OutputBufferTooSmall {
115 need: len,
116 have: buf.len(),
117 });
118 }
119 buf[0] = TAG;
120 buf[1] = (len - HEADER_LEN) as u8;
121 let mut offset = HEADER_LEN;
122 for entry in &self.entries {
123 buf[offset..offset + 3].copy_from_slice(&entry.country_code.0);
124 let flags = ((entry.country_region_id << 2) & REGION_ID_MASK)
125 | RESERVED_BIT_MASK
126 | if entry.local_time_offset_negative {
127 POLARITY_MASK
128 } else {
129 0
130 };
131 buf[offset + 3] = flags;
132 buf[offset + 4..offset + 6].copy_from_slice(&entry.local_time_offset_bcd.to_be_bytes());
133 buf[offset + 6..offset + 11].copy_from_slice(&entry.time_of_change_raw);
134 buf[offset + 11..offset + 13]
135 .copy_from_slice(&entry.next_time_offset_bcd.to_be_bytes());
136 offset += ENTRY_LEN;
137 }
138 Ok(len)
139 }
140}
141
142impl<'a> Descriptor<'a> for LocalTimeOffsetDescriptor {
143 const TAG: u8 = TAG;
144 fn descriptor_length(&self) -> u8 {
145 (self.serialized_len() - HEADER_LEN) as u8
146 }
147}
148
149impl<'a> crate::traits::DescriptorDef<'a> for LocalTimeOffsetDescriptor {
150 const TAG: u8 = TAG;
151 const NAME: &'static str = "LOCAL_TIME_OFFSET";
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn parse_single_entry() {
160 let bytes = [
161 TAG, 13, 0x46, 0x52, 0x41, 0x02, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
162 ];
163 let d = LocalTimeOffsetDescriptor::parse(&bytes).unwrap();
164 assert_eq!(d.entries.len(), 1);
165 assert_eq!(d.entries[0].country_code, LangCode([0x46, 0x52, 0x41]));
166 assert_eq!(d.entries[0].country_region_id, 0);
167 assert!(!d.entries[0].local_time_offset_negative);
168 assert_eq!(d.entries[0].local_time_offset_bcd, 0x0100);
169 assert_eq!(
170 d.entries[0].time_of_change_raw,
171 [0xAB, 0xCD, 0xEF, 0x12, 0x34]
172 );
173 assert_eq!(d.entries[0].next_time_offset_bcd, 0x0200);
174 }
175
176 #[test]
177 fn parse_multiple_entries_preserves_order() {
178 let bytes = [
179 TAG, 26, 0x46, 0x52, 0x41, 0x02, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
180 0x47, 0x42, 0x52, 0x06, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x01, 0x00,
181 ];
182 let d = LocalTimeOffsetDescriptor::parse(&bytes).unwrap();
183 assert_eq!(d.entries.len(), 2);
184 assert_eq!(d.entries[0].country_code, LangCode([0x46, 0x52, 0x41]));
185 assert_eq!(d.entries[1].country_code, LangCode([0x47, 0x42, 0x52]));
186 }
187
188 #[test]
189 fn parse_extracts_polarity_negative() {
190 let bytes = [
191 TAG, 13, 0x46, 0x52, 0x41, 0x03, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
192 ];
193 let d = LocalTimeOffsetDescriptor::parse(&bytes).unwrap();
194 assert!(d.entries[0].local_time_offset_negative);
195 }
196
197 #[test]
198 fn parse_extracts_country_region_id() {
199 let bytes = [
200 TAG, 13, 0x46, 0x52, 0x41, 0x1A, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
201 ];
202 let d = LocalTimeOffsetDescriptor::parse(&bytes).unwrap();
203 assert_eq!(d.entries[0].country_region_id, 6);
204 }
205
206 #[test]
207 fn parse_rejects_wrong_tag() {
208 let err = LocalTimeOffsetDescriptor::parse(&[
209 0x59, 13, 0x46, 0x52, 0x41, 0x02, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
210 ])
211 .unwrap_err();
212 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x59, .. }));
213 }
214
215 #[test]
216 fn parse_rejects_length_not_multiple_of_13() {
217 let bytes = [
218 TAG, 14, 0x46, 0x52, 0x41, 0x02, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
219 0xFF,
220 ];
221 let err = LocalTimeOffsetDescriptor::parse(&bytes).unwrap_err();
222 assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
223 }
224
225 #[test]
226 fn parse_ignores_reserved_bit_not_set() {
227 let bytes = [
229 TAG, 13, 0x46, 0x52, 0x41, 0x00, 0x01, 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x02, 0x00,
230 ];
231 let d = LocalTimeOffsetDescriptor::parse(&bytes).unwrap();
232 assert_eq!(d.entries.len(), 1);
233 assert!(!d.entries[0].local_time_offset_negative);
234 }
235
236 #[test]
237 fn serialize_round_trip() {
238 let d = LocalTimeOffsetDescriptor {
239 entries: vec![LocalTimeOffsetEntry {
240 country_code: LangCode([0x46, 0x52, 0x41]),
241 country_region_id: 0,
242 local_time_offset_negative: false,
243 local_time_offset_bcd: 0x0100,
244 time_of_change_raw: [0xAB, 0xCD, 0xEF, 0x12, 0x34],
245 next_time_offset_bcd: 0x0200,
246 }],
247 };
248 let mut buf = vec![0u8; d.serialized_len()];
249 d.serialize_into(&mut buf).unwrap();
250 let re = LocalTimeOffsetDescriptor::parse(&buf).unwrap();
251 assert_eq!(d, re);
252 }
253
254 #[test]
255 fn empty_descriptor_valid() {
256 let bytes = [TAG, 0];
257 let d = LocalTimeOffsetDescriptor::parse(&bytes).unwrap();
258 assert!(d.entries.is_empty());
259 }
260}