1use super::*;
3
4const CELL_HEADER_LEN: usize = 2; const LOOP_LENGTH_LEN: usize = 1; const CHANGE_BASE_LEN: usize = 12; const INVARIANT_TS_LEN: usize = 4; #[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12pub struct NetworkChangeNotify {
13 pub cells: Vec<NetworkChangeCell>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct NetworkChangeCell {
21 pub cell_id: u16,
23 pub changes: Vec<NetworkChange>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30pub struct NetworkChange {
31 pub network_change_id: u8,
33 pub network_change_version: u8,
35 pub start_time_of_change: u64,
37 pub change_duration: u32,
39 pub receiver_category: u8,
41 pub change_type: u8,
43 pub message_id: u8,
45 pub invariant_ts: Option<InvariantTs>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52pub struct InvariantTs {
53 pub tsid: u16,
55 pub onid: u16,
57}
58
59impl<'a> ExtensionBodyDef<'a> for NetworkChangeNotify {
60 const TAG_EXTENSION: u8 = 0x07;
61 const NAME: &'static str = "NETWORK_CHANGE_NOTIFY";
62}
63
64impl NetworkChange {
65 #[cfg(feature = "chrono")]
69 #[must_use]
70 pub fn start_time_of_change_utc(&self) -> Option<chrono::DateTime<chrono::Utc>> {
71 let raw = self.start_time_of_change;
72 let bytes = [
73 (raw >> 32) as u8,
74 (raw >> 24) as u8,
75 (raw >> 16) as u8,
76 (raw >> 8) as u8,
77 raw as u8,
78 ];
79 dvb_common::time::decode_mjd_bcd_utc(bytes)
80 }
81
82 #[cfg(feature = "chrono")]
85 #[must_use]
86 pub fn change_duration_secs(&self) -> Option<u32> {
87 let raw = self.change_duration;
88 let bytes = [(raw >> 16) as u8, (raw >> 8) as u8, raw as u8];
89 dvb_common::time::decode_bcd_duration(bytes).map(|d| d.as_secs() as u32)
90 }
91}
92
93fn change_serialized_len(ch: &NetworkChange) -> usize {
94 CHANGE_BASE_LEN
95 + if ch.invariant_ts.is_some() {
96 INVARIANT_TS_LEN
97 } else {
98 0
99 }
100}
101
102impl<'a> Parse<'a> for NetworkChangeNotify {
103 type Error = crate::error::Error;
104 fn parse(sel: &'a [u8]) -> Result<Self> {
105 let mut cells = Vec::new();
106 let mut pos = 0;
107 while pos < sel.len() {
108 if pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN > sel.len() {
109 return Err(Error::BufferTooShort {
110 need: pos + CELL_HEADER_LEN + LOOP_LENGTH_LEN,
111 have: sel.len(),
112 what: "network_change_notify body",
113 });
114 }
115 let cell_id = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
116 let loop_length = sel[pos + CELL_HEADER_LEN] as usize;
117 pos += CELL_HEADER_LEN + LOOP_LENGTH_LEN;
118
119 if pos + loop_length > sel.len() {
120 return Err(Error::BufferTooShort {
121 need: pos + loop_length,
122 have: sel.len(),
123 what: "network_change_notify body",
124 });
125 }
126
127 let inner_end = pos + loop_length;
128 let mut changes = Vec::new();
129 while pos < inner_end {
130 let remaining = inner_end - pos;
131 if remaining < CHANGE_BASE_LEN {
133 return Err(Error::BufferTooShort {
134 need: inner_end - remaining + CHANGE_BASE_LEN,
135 have: sel.len(),
136 what: "network_change_notify body",
137 });
138 }
139 let network_change_id = sel[pos];
140 let network_change_version = sel[pos + 1];
141 let start_time_of_change = (u64::from(sel[pos + 2]) << 32)
142 | (u64::from(sel[pos + 3]) << 24)
143 | (u64::from(sel[pos + 4]) << 16)
144 | (u64::from(sel[pos + 5]) << 8)
145 | u64::from(sel[pos + 6]);
146 let change_duration = (u32::from(sel[pos + 7]) << 16)
147 | (u32::from(sel[pos + 8]) << 8)
148 | u32::from(sel[pos + 9]);
149 let packed = sel[pos + 10];
150 let receiver_category = packed >> 5;
151 let invariant_ts_present = (packed >> 4) & 1;
152 let change_type = packed & 0x0F;
153 let message_id = sel[pos + 11];
154 pos += CHANGE_BASE_LEN;
155
156 let invariant_ts = if invariant_ts_present == 1 {
157 if pos + INVARIANT_TS_LEN > inner_end {
158 return Err(Error::BufferTooShort {
159 need: pos + INVARIANT_TS_LEN,
160 have: sel.len(),
161 what: "network_change_notify body",
162 });
163 }
164 let ts = InvariantTs {
165 tsid: u16::from_be_bytes([sel[pos], sel[pos + 1]]),
166 onid: u16::from_be_bytes([sel[pos + 2], sel[pos + 3]]),
167 };
168 pos += INVARIANT_TS_LEN;
169 Some(ts)
170 } else {
171 None
172 };
173
174 changes.push(NetworkChange {
175 network_change_id,
176 network_change_version,
177 start_time_of_change,
178 change_duration,
179 receiver_category,
180 change_type,
181 message_id,
182 invariant_ts,
183 });
184 }
185
186 if pos != inner_end {
187 return Err(invalid("network_change_notify: change entry overruns loop"));
188 }
189
190 cells.push(NetworkChangeCell { cell_id, changes });
191 }
192 Ok(NetworkChangeNotify { cells })
193 }
194}
195
196impl Serialize for NetworkChangeNotify {
197 type Error = crate::error::Error;
198 fn serialized_len(&self) -> usize {
199 self.cells
200 .iter()
201 .map(|cell| {
202 CELL_HEADER_LEN
203 + LOOP_LENGTH_LEN
204 + cell
205 .changes
206 .iter()
207 .map(change_serialized_len)
208 .sum::<usize>()
209 })
210 .sum()
211 }
212 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
213 let len = self.serialized_len();
214 if buf.len() < len {
215 return Err(Error::OutputBufferTooSmall {
216 need: len,
217 have: buf.len(),
218 });
219 }
220 let mut pos = 0;
221 for cell in &self.cells {
222 buf[pos..pos + 2].copy_from_slice(&cell.cell_id.to_be_bytes());
223 pos += 2;
224 let loop_length: usize = cell.changes.iter().map(change_serialized_len).sum();
225 buf[pos] = loop_length as u8;
226 pos += 1;
227 for ch in &cell.changes {
228 buf[pos] = ch.network_change_id;
229 buf[pos + 1] = ch.network_change_version;
230 let st = ch.start_time_of_change;
231 buf[pos + 2] = (st >> 32) as u8;
232 buf[pos + 3] = (st >> 24) as u8;
233 buf[pos + 4] = (st >> 16) as u8;
234 buf[pos + 5] = (st >> 8) as u8;
235 buf[pos + 6] = st as u8;
236 let dur = ch.change_duration;
237 buf[pos + 7] = (dur >> 16) as u8;
238 buf[pos + 8] = (dur >> 8) as u8;
239 buf[pos + 9] = dur as u8;
240 let packed = ((ch.receiver_category & 0x07) << 5)
241 | ((ch.invariant_ts.is_some() as u8) << 4)
242 | (ch.change_type & 0x0F);
243 buf[pos + 10] = packed;
244 buf[pos + 11] = ch.message_id;
245 pos += CHANGE_BASE_LEN;
246 if let Some(ref inv) = ch.invariant_ts {
247 buf[pos..pos + 2].copy_from_slice(&inv.tsid.to_be_bytes());
248 buf[pos + 2..pos + 4].copy_from_slice(&inv.onid.to_be_bytes());
249 pos += INVARIANT_TS_LEN;
250 }
251 }
252 }
253 Ok(len)
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::descriptors::extension::test_support::*;
261 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
262
263 #[test]
264 fn parse_network_change_notify_structured() {
265 let sel = [
268 0x00, 0x01, 0x00, 0x00, 0x02, 0x1C,
271 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x23, 0x40, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x54, 0x50, 0xAA, 0xAA, 0xBB, 0xBB, ];
288 let bytes = wrap(0x07, &sel);
289 let d = ExtensionDescriptor::parse(&bytes).unwrap();
290 match &d.body {
291 ExtensionBody::NetworkChangeNotify(b) => {
292 assert_eq!(b.cells.len(), 2);
293
294 assert_eq!(b.cells[0].cell_id, 0x0001);
295 assert!(b.cells[0].changes.is_empty());
296
297 assert_eq!(b.cells[1].cell_id, 0x0002);
298 assert_eq!(b.cells[1].changes.len(), 2);
299
300 let ch0 = &b.cells[1].changes[0];
301 assert_eq!(ch0.network_change_id, 0x10);
302 assert_eq!(ch0.network_change_version, 0x20);
303 assert_eq!(ch0.start_time_of_change, 1);
304 assert_eq!(ch0.change_duration, 1);
305 assert_eq!(ch0.receiver_category, 1);
306 assert_eq!(ch0.change_type, 3);
307 assert_eq!(ch0.message_id, 0x40);
308 assert!(ch0.invariant_ts.is_none());
309
310 let ch1 = &b.cells[1].changes[1];
311 assert_eq!(ch1.network_change_id, 0x30);
312 assert_eq!(ch1.network_change_version, 0x40);
313 assert_eq!(ch1.start_time_of_change, 2);
314 assert_eq!(ch1.change_duration, 2);
315 assert_eq!(ch1.receiver_category, 2);
316 assert_eq!(ch1.change_type, 4);
317 assert_eq!(ch1.message_id, 0x50);
318 let inv = ch1.invariant_ts.as_ref().unwrap();
319 assert_eq!(inv.tsid, 0xAAAA);
320 assert_eq!(inv.onid, 0xBBBB);
321 }
322 other => panic!("expected NetworkChangeNotify, got {other:?}"),
323 }
324 round_trip(&d);
325 }
326
327 #[test]
331 fn network_change_notify_tsduck_byte_exact() {
332 let bytes =
333 from_hex("7f230712340056781cabcde5cc2312340852030281ef67e5e20234561132453b83deadbeef");
334 let d = ExtensionDescriptor::parse(&bytes).unwrap();
335 assert_eq!(d.kind(), Some(ExtensionTag::NetworkChangeNotify));
336
337 match &d.body {
338 ExtensionBody::NetworkChangeNotify(b) => {
339 assert_eq!(b.cells.len(), 2);
340
341 assert_eq!(b.cells[0].cell_id, 0x1234);
343 assert!(b.cells[0].changes.is_empty());
344
345 assert_eq!(b.cells[1].cell_id, 0x5678);
347 assert_eq!(b.cells[1].changes.len(), 2);
348
349 let ch0 = &b.cells[1].changes[0];
351 assert_eq!(ch0.network_change_id, 0xAB);
352 assert_eq!(ch0.network_change_version, 0xCD);
353 assert_eq!(ch0.start_time_of_change, 0xE5CC231234);
354 assert_eq!(ch0.change_duration, 0x085203);
355 assert_eq!(ch0.receiver_category, 0);
356 assert_eq!(ch0.change_type, 2);
357 assert_eq!(ch0.message_id, 0x81);
358 assert!(ch0.invariant_ts.is_none());
359
360 let ch1 = &b.cells[1].changes[1];
362 assert_eq!(ch1.network_change_id, 0xEF);
363 assert_eq!(ch1.network_change_version, 0x67);
364 assert_eq!(ch1.start_time_of_change, 0xE5E2023456);
365 assert_eq!(ch1.change_duration, 0x113245);
366 assert_eq!(ch1.receiver_category, 1);
367 assert_eq!(ch1.change_type, 0xB);
368 assert_eq!(ch1.message_id, 0x83);
369 let inv = ch1.invariant_ts.as_ref().unwrap();
370 assert_eq!(inv.tsid, 0xDEAD);
371 assert_eq!(inv.onid, 0xBEEF);
372 }
373 other => panic!("expected NetworkChangeNotify, got {other:?}"),
374 }
375
376 let mut out = vec![0u8; d.serialized_len()];
377 let n = d.serialize_into(&mut out).unwrap();
378 assert_eq!(
379 out[..n],
380 bytes[..],
381 "byte-exact re-serialize for TSDuck vector"
382 );
383 }
384
385 #[cfg(feature = "chrono")]
386 #[test]
387 fn chrono_accessors_decode_start_time_and_duration() {
388 use chrono::{Datelike, Timelike};
389 let sel: Vec<u8> = vec![
394 0x00, 0x01, 12, 0x10, 0x20, 0xE4, 0x09, 0x12, 0x34, 0x56, 0x01, 0x30, 0x45, 0x23, 0x40, ];
403 let bytes = wrap(0x07, &sel);
404 let d = ExtensionDescriptor::parse(&bytes).unwrap();
405 match &d.body {
406 ExtensionBody::NetworkChangeNotify(b) => {
407 assert_eq!(b.cells.len(), 1);
408 let ch = &b.cells[0].changes[0];
409 let utc = ch.start_time_of_change_utc().expect("should decode");
410 assert_eq!(utc.year(), 2018);
411 assert_eq!((utc.hour(), utc.minute(), utc.second()), (12, 34, 56));
412 let secs = ch.change_duration_secs().expect("should decode");
413 assert_eq!(secs, 5445); }
415 other => panic!("expected NetworkChangeNotify, got {other:?}"),
416 }
417 }
418}