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