Skip to main content

dvb_si/descriptors/
nordig.rs

1//! NorDig Logical Channel Descriptor v1 & v2 — NorDig Unified Requirements
2//! v3.1.1 §12.2.9.2–12.2.9.3 (tags 0x83, 0x87), scoped by
3//! PDS_NORDIG = 0x00000029.
4//!
5//! v1 (tag 0x83) assigns a 14-bit LCN per service (4 bytes/entry).
6//! v2 (tag 0x87) groups services into named channel-lists with a 10-bit LCN,
7//! plus a per-list country_code and Annex-A channel_list_name.
8
9use super::descriptor_body;
10use crate::error::{Error, Result};
11use crate::text::LangCode;
12use alloc::vec::Vec;
13use dvb_common::{Parse, Serialize};
14
15// ---------------------------------------------------------------------------
16// NordigLogicalChannelV1 — tag 0x83, 14-bit LCN
17// ---------------------------------------------------------------------------
18
19/// Descriptor tag for NorDig Logical Channel Descriptor v1.
20pub const TAG_V1: u8 = 0x83;
21const V1_HEADER_LEN: usize = 2;
22const V1_ENTRY_LEN: usize = 4;
23const V1_VISIBLE_MASK: u8 = 0x80;
24const V1_RESERVED_MASK: u8 = 0x40;
25const V1_LCN_HI_MASK: u8 = 0x3F;
26
27/// One NorDig LCD v1 LCN assignment.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30pub struct NordigLogicalChannelV1Entry {
31    /// Service being numbered.
32    pub service_id: u16,
33    /// Visible in the viewer's channel list.
34    pub visible_service_flag: bool,
35    /// 14-bit logical channel number (0..=16383).
36    pub logical_channel_number: u16,
37}
38
39/// NorDig Logical Channel Descriptor version 1.
40#[derive(Debug, Clone, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize))]
42pub struct NordigLogicalChannelV1 {
43    /// Entries in wire order.
44    pub entries: Vec<NordigLogicalChannelV1Entry>,
45}
46
47impl<'a> Parse<'a> for NordigLogicalChannelV1 {
48    type Error = crate::error::Error;
49    fn parse(bytes: &'a [u8]) -> Result<Self> {
50        let body = descriptor_body(
51            bytes,
52            TAG_V1,
53            "NordigLogicalChannelV1",
54            "unexpected tag for nordig_logical_channel_descriptor_v1",
55        )?;
56        if body.len() % V1_ENTRY_LEN != 0 {
57            return Err(Error::InvalidDescriptor {
58                tag: TAG_V1,
59                reason: "descriptor_length must be a multiple of 4",
60            });
61        }
62        let mut entries = Vec::with_capacity(body.len() / V1_ENTRY_LEN);
63        for chunk in body.chunks_exact(V1_ENTRY_LEN) {
64            let (sid_bytes, rest) = chunk.split_first_chunk::<2>().unwrap();
65            let service_id = u16::from_be_bytes(*sid_bytes);
66            let flags = rest[0];
67            let visible_service_flag = flags & V1_VISIBLE_MASK != 0;
68            let lcn = (u16::from(flags & V1_LCN_HI_MASK) << 8) | u16::from(rest[1]);
69            entries.push(NordigLogicalChannelV1Entry {
70                service_id,
71                visible_service_flag,
72                logical_channel_number: lcn,
73            });
74        }
75        Ok(Self { entries })
76    }
77}
78
79impl Serialize for NordigLogicalChannelV1 {
80    type Error = crate::error::Error;
81    fn serialized_len(&self) -> usize {
82        V1_HEADER_LEN + V1_ENTRY_LEN * self.entries.len()
83    }
84
85    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
86        let len = self.serialized_len();
87        if buf.len() < len {
88            return Err(Error::OutputBufferTooSmall {
89                need: len,
90                have: buf.len(),
91            });
92        }
93        buf[0] = TAG_V1;
94        buf[1] = ((len - V1_HEADER_LEN) / V1_ENTRY_LEN * V1_ENTRY_LEN) as u8;
95        let mut offset = V1_HEADER_LEN;
96        for entry in &self.entries {
97            buf[offset..offset + 2].copy_from_slice(&entry.service_id.to_be_bytes());
98            let visible_byte = if entry.visible_service_flag {
99                V1_VISIBLE_MASK
100            } else {
101                0
102            };
103            let flags = visible_byte
104                | V1_RESERVED_MASK
105                | ((entry.logical_channel_number >> 8) as u8 & V1_LCN_HI_MASK);
106            buf[offset + 2] = flags;
107            buf[offset + 3] = (entry.logical_channel_number & 0xFF) as u8;
108            offset += V1_ENTRY_LEN;
109        }
110        Ok(len)
111    }
112}
113
114impl<'a> crate::traits::DescriptorDef<'a> for NordigLogicalChannelV1 {
115    const TAG: u8 = TAG_V1;
116    const NAME: &'static str = "NORDIG_LOGICAL_CHANNEL_V1";
117}
118
119// ---------------------------------------------------------------------------
120// NordigLogicalChannelV2 — tag 0x87, channel-list grouped
121// ---------------------------------------------------------------------------
122
123/// Descriptor tag for NorDig Logical Channel Descriptor v2.
124pub const TAG_V2: u8 = 0x87;
125const V2_HEADER_LEN: usize = 2;
126const V2_SERVICE_ENTRY_LEN: usize = 4;
127const V2_VISIBLE_MASK: u8 = 0x80;
128const V2_RESERVED_MASK: u8 = 0x7C;
129const V2_LCN_HI_MASK: u8 = 0x03;
130
131/// One service entry inside a NorDig LCD v2 channel list.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize))]
134pub struct NordigLogicalChannelV2Service {
135    /// Service being numbered.
136    pub service_id: u16,
137    /// Visible in the viewer's channel list.
138    pub visible_service_flag: bool,
139    /// 10-bit logical channel number (0..=1023).
140    pub logical_channel_number: u16,
141}
142
143/// One channel list inside a NorDig LCD v2 descriptor.
144#[derive(Debug, Clone, PartialEq, Eq)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize))]
146pub struct NordigLogicalChannelV2ChannelList {
147    /// Channel list identifier.
148    pub channel_list_id: u8,
149    /// Raw Annex-A bytes (EN 300 468) for the channel list name.
150    /// Use `crate::text::DvbText::new(&self.channel_list_name)` to decode.
151    pub channel_list_name: Vec<u8>,
152    /// ISO 3166 country code.
153    pub country_code: LangCode,
154    /// Services in this list, wire order.
155    pub services: Vec<NordigLogicalChannelV2Service>,
156}
157
158/// NorDig Logical Channel Descriptor version 2.
159#[derive(Debug, Clone, PartialEq, Eq)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize))]
161pub struct NordigLogicalChannelV2 {
162    /// Channel lists in wire order.
163    pub channel_lists: Vec<NordigLogicalChannelV2ChannelList>,
164}
165
166impl<'a> Parse<'a> for NordigLogicalChannelV2 {
167    type Error = crate::error::Error;
168    fn parse(bytes: &'a [u8]) -> Result<Self> {
169        let mut body = descriptor_body(
170            bytes,
171            TAG_V2,
172            "NordigLogicalChannelV2",
173            "unexpected tag for nordig_logical_channel_descriptor_v2",
174        )?;
175        let mut channel_lists = Vec::new();
176        while !body.is_empty() {
177            if body.len() < 2 {
178                return Err(Error::BufferTooShort {
179                    need: 2,
180                    have: body.len(),
181                    what: "NordigLogicalChannelV2 channel_list header",
182                });
183            }
184            let channel_list_id = body[0];
185            let name_len = body[1] as usize;
186            let name_start = 2;
187            let name_end = name_start + name_len;
188            if body.len() < name_end + 3 + 1 {
189                return Err(Error::BufferTooShort {
190                    need: name_end + 3 + 1,
191                    have: body.len(),
192                    what: "NordigLogicalChannelV2 channel_list country_code + descriptor_length",
193                });
194            }
195            let channel_list_name = body[name_start..name_end].to_vec();
196            let cc_start = name_end;
197            let country_code = LangCode([body[cc_start], body[cc_start + 1], body[cc_start + 2]]);
198            let desc_len = body[cc_start + 3] as usize;
199            let svc_start = cc_start + 4;
200            let svc_end = svc_start + desc_len;
201            if body.len() < svc_end {
202                return Err(Error::BufferTooShort {
203                    need: svc_end,
204                    have: body.len(),
205                    what: "NordigLogicalChannelV2 service loop",
206                });
207            }
208            if desc_len % V2_SERVICE_ENTRY_LEN != 0 {
209                return Err(Error::InvalidDescriptor {
210                    tag: TAG_V2,
211                    reason: "descriptor_length in channel list must be a multiple of 4",
212                });
213            }
214            let svc_body = &body[svc_start..svc_end];
215            let mut services = Vec::with_capacity(svc_body.len() / V2_SERVICE_ENTRY_LEN);
216            for chunk in svc_body.chunks_exact(V2_SERVICE_ENTRY_LEN) {
217                let (sid_bytes, rest) = chunk.split_first_chunk::<2>().unwrap();
218                let service_id = u16::from_be_bytes(*sid_bytes);
219                let flags = rest[0];
220                let visible_service_flag = flags & V2_VISIBLE_MASK != 0;
221                let lcn = (u16::from(flags & V2_LCN_HI_MASK) << 8) | u16::from(rest[1]);
222                services.push(NordigLogicalChannelV2Service {
223                    service_id,
224                    visible_service_flag,
225                    logical_channel_number: lcn,
226                });
227            }
228            channel_lists.push(NordigLogicalChannelV2ChannelList {
229                channel_list_id,
230                channel_list_name,
231                country_code,
232                services,
233            });
234            body = &body[svc_end..];
235        }
236        Ok(Self { channel_lists })
237    }
238}
239
240impl Serialize for NordigLogicalChannelV2 {
241    type Error = crate::error::Error;
242    fn serialized_len(&self) -> usize {
243        let mut total = V2_HEADER_LEN;
244        for cl in &self.channel_lists {
245            total += 2 // id + name_len
246                + cl.channel_list_name.len()
247                + 3 // country_code
248                + 1 // descriptor_length
249                + V2_SERVICE_ENTRY_LEN * cl.services.len();
250        }
251        total
252    }
253
254    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
255        let len = self.serialized_len();
256        if buf.len() < len {
257            return Err(Error::OutputBufferTooSmall {
258                need: len,
259                have: buf.len(),
260            });
261        }
262        buf[0] = TAG_V2;
263        buf[1] = (len - V2_HEADER_LEN) as u8;
264        let mut offset = V2_HEADER_LEN;
265        for cl in &self.channel_lists {
266            buf[offset] = cl.channel_list_id;
267            buf[offset + 1] = cl.channel_list_name.len() as u8;
268            offset += 2;
269            buf[offset..offset + cl.channel_list_name.len()].copy_from_slice(&cl.channel_list_name);
270            offset += cl.channel_list_name.len();
271            buf[offset..offset + 3].copy_from_slice(&cl.country_code.0);
272            offset += 3;
273            let desc_len = V2_SERVICE_ENTRY_LEN * cl.services.len();
274            buf[offset] = desc_len as u8;
275            offset += 1;
276            for svc in &cl.services {
277                buf[offset..offset + 2].copy_from_slice(&svc.service_id.to_be_bytes());
278                let visible_byte = if svc.visible_service_flag {
279                    V2_VISIBLE_MASK
280                } else {
281                    0
282                };
283                let flags = visible_byte
284                    | V2_RESERVED_MASK
285                    | ((svc.logical_channel_number >> 8) as u8 & V2_LCN_HI_MASK);
286                buf[offset + 2] = flags;
287                buf[offset + 3] = (svc.logical_channel_number & 0xFF) as u8;
288                offset += V2_SERVICE_ENTRY_LEN;
289            }
290        }
291        Ok(len)
292    }
293}
294
295impl<'a> crate::traits::DescriptorDef<'a> for NordigLogicalChannelV2 {
296    const TAG: u8 = TAG_V2;
297    const NAME: &'static str = "NORDIG_LOGICAL_CHANNEL_V2";
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    // -----------------------------------------------------------------------
305    // V1 tests
306    // -----------------------------------------------------------------------
307
308    #[test]
309    fn v1_parse_single_entry() {
310        // LCN=5: LCN_hi(6 bits)=0, LCN_lo=5
311        // Flags: visible=1<<7=0x80 | reserved=1<<6=0x40 | LCN_hi=0 = 0xC0
312        let bytes = [TAG_V1, 4, 0x00, 0x01, 0xC0, 0x05];
313        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
314        assert_eq!(d.entries.len(), 1);
315        assert_eq!(d.entries[0].service_id, 1);
316        assert!(d.entries[0].visible_service_flag);
317        assert_eq!(d.entries[0].logical_channel_number, 5);
318    }
319
320    #[test]
321    fn v1_parse_visible_service_false() {
322        // LCN=10: LCN_hi=0, LCN_lo=10
323        // Flags: visible=0 | reserved=1<<6=0x40 | LCN_hi=0 = 0x40
324        let bytes = [TAG_V1, 4, 0x00, 0x02, 0x40, 0x0A];
325        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
326        assert_eq!(d.entries.len(), 1);
327        assert!(!d.entries[0].visible_service_flag);
328        assert_eq!(d.entries[0].service_id, 2);
329        assert_eq!(d.entries[0].logical_channel_number, 10);
330    }
331
332    #[test]
333    fn v1_parse_full_14_bit_lcn_range() {
334        // LCN=16383 = 0x3FFF: LCN_hi(6)=0x3F, LCN_lo=0xFF
335        // Flags: visible=1 | reserved=1 | LCN_hi=0x3F
336        let bytes = [TAG_V1, 4, 0x00, 0x03, 0xFF, 0xFF];
337        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
338        assert_eq!(d.entries.len(), 1);
339        assert_eq!(d.entries[0].logical_channel_number, 16383);
340    }
341
342    #[test]
343    fn v1_parse_lcn_exceeds_10_bit_max() {
344        // 1024 = 0b_00 0100_0000_0000 — top 6 bits of 14-bit LCN in flags
345        let flags_lcn_hi = ((1024u16 >> 8) & 0x3F) as u8;
346        let lcn_lo = (1024u16 & 0xFF) as u8;
347        let bytes = [
348            TAG_V1,
349            4,
350            0x00,
351            0x01,
352            V1_VISIBLE_MASK | V1_RESERVED_MASK | flags_lcn_hi,
353            lcn_lo,
354        ];
355        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
356        assert_eq!(d.entries.len(), 1);
357        assert_eq!(d.entries[0].service_id, 1);
358        assert!(d.entries[0].visible_service_flag);
359        assert_eq!(d.entries[0].logical_channel_number, 1024);
360    }
361
362    #[test]
363    fn v1_parse_multiple_entries() {
364        // LCN=1: flags=0xC0(visible+reserved), lo=0x01
365        let bytes = [
366            TAG_V1, 12, 0x00, 0x01, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x02, 0x00, 0x03, 0xC0, 0x03,
367        ];
368        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
369        assert_eq!(d.entries.len(), 3);
370        assert_eq!(d.entries[0].service_id, 1);
371        assert_eq!(d.entries[0].logical_channel_number, 1);
372        assert_eq!(d.entries[1].service_id, 2);
373        assert_eq!(d.entries[1].logical_channel_number, 2);
374        assert_eq!(d.entries[2].service_id, 3);
375        assert_eq!(d.entries[2].logical_channel_number, 3);
376    }
377
378    #[test]
379    fn v1_parse_rejects_wrong_tag() {
380        let err = NordigLogicalChannelV1::parse(&[0x84, 4, 0x00, 0x01, 0xC0, 0x05]).unwrap_err();
381        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x84, .. }));
382    }
383
384    #[test]
385    fn v1_parse_rejects_length_not_multiple_of_4() {
386        let bytes = [TAG_V1, 5, 0x00, 0x01, 0xC0, 0x05, 0xFF];
387        let err = NordigLogicalChannelV1::parse(&bytes).unwrap_err();
388        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG_V1, .. }));
389    }
390
391    #[test]
392    fn v1_parse_tolerates_cleared_reserved_bit() {
393        // visible=1, reserved=0, LCN_hi=0, LCN_lo=5
394        let bytes = [TAG_V1, 4, 0x00, 0x01, 0x80, 0x05];
395        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
396        assert_eq!(d.entries.len(), 1);
397        assert_eq!(d.entries[0].service_id, 1);
398        assert_eq!(d.entries[0].logical_channel_number, 5);
399    }
400
401    #[test]
402    fn v1_empty_descriptor_valid() {
403        let bytes = [TAG_V1, 0];
404        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
405        assert!(d.entries.is_empty());
406    }
407
408    #[test]
409    fn v1_serialize_round_trip() {
410        let d = NordigLogicalChannelV1 {
411            entries: vec![
412                NordigLogicalChannelV1Entry {
413                    service_id: 1,
414                    visible_service_flag: true,
415                    logical_channel_number: 5,
416                },
417                NordigLogicalChannelV1Entry {
418                    service_id: 0x0102,
419                    visible_service_flag: false,
420                    logical_channel_number: 16383,
421                },
422            ],
423        };
424        let mut buf = vec![0u8; d.serialized_len()];
425        d.serialize_into(&mut buf).unwrap();
426        let re = NordigLogicalChannelV1::parse(&buf).unwrap();
427        assert_eq!(d, re);
428    }
429
430    #[test]
431    fn v1_serialize_round_trip_byte_identity() {
432        let bytes = [TAG_V1, 8, 0x00, 0x01, 0xC0, 0x05, 0x00, 0x02, 0x40, 0x0A];
433        let d = NordigLogicalChannelV1::parse(&bytes).unwrap();
434        let mut buf = vec![0u8; d.serialized_len()];
435        d.serialize_into(&mut buf).unwrap();
436        assert_eq!(buf, bytes);
437    }
438
439    // -----------------------------------------------------------------------
440    // V2 tests
441    // -----------------------------------------------------------------------
442
443    #[test]
444    fn v2_parse_single_channel_list_single_service() {
445        let name = b"Terrestrial";
446        let mut bytes = vec![TAG_V2, 0];
447        bytes.push(0x01); // channel_list_id
448        bytes.push(name.len() as u8);
449        bytes.extend_from_slice(name);
450        bytes.extend_from_slice(b"GBR"); // country_code
451        bytes.push(4); // descriptor_length
452        bytes.extend_from_slice(&[0x00, 0x01, 0xFC, 0x05]);
453        // Fix descriptor_length
454        bytes[1] = (bytes.len() - 2) as u8;
455
456        let d = NordigLogicalChannelV2::parse(&bytes).unwrap();
457        assert_eq!(d.channel_lists.len(), 1);
458        let cl = &d.channel_lists[0];
459        assert_eq!(cl.channel_list_id, 0x01);
460        assert_eq!(cl.channel_list_name, name);
461        assert_eq!(cl.country_code, LangCode(*b"GBR"));
462        assert_eq!(cl.services.len(), 1);
463        assert_eq!(cl.services[0].service_id, 1);
464        assert!(cl.services[0].visible_service_flag);
465        assert_eq!(cl.services[0].logical_channel_number, 5);
466    }
467
468    #[test]
469    fn v2_parse_multiple_channel_lists_multiple_services() {
470        let name1 = b"Terrestrial";
471        let name2 = b"Cable";
472        let mut bytes = vec![TAG_V2, 0];
473        // List 1
474        bytes.push(0x01);
475        bytes.push(name1.len() as u8);
476        bytes.extend_from_slice(name1);
477        bytes.extend_from_slice(b"GBR");
478        bytes.push(8);
479        bytes.extend_from_slice(&[0x00, 0x01, 0xFC, 0x01, 0x00, 0x02, 0xFC, 0x02]);
480        // List 2
481        bytes.push(0x02);
482        bytes.push(name2.len() as u8);
483        bytes.extend_from_slice(name2);
484        bytes.extend_from_slice(b"DEU");
485        bytes.push(4);
486        bytes.extend_from_slice(&[0x00, 0x03, 0xFC, 0x03]);
487        bytes[1] = (bytes.len() - 2) as u8;
488
489        let d = NordigLogicalChannelV2::parse(&bytes).unwrap();
490        assert_eq!(d.channel_lists.len(), 2);
491
492        let cl1 = &d.channel_lists[0];
493        assert_eq!(cl1.channel_list_id, 1);
494        assert_eq!(cl1.channel_list_name, name1);
495        assert_eq!(cl1.country_code, LangCode([b'G', b'B', b'R']));
496        assert_eq!(cl1.services.len(), 2);
497        assert_eq!(cl1.services[0].service_id, 1);
498        assert_eq!(cl1.services[0].logical_channel_number, 1);
499        assert_eq!(cl1.services[1].service_id, 2);
500        assert_eq!(cl1.services[1].logical_channel_number, 2);
501
502        let cl2 = &d.channel_lists[1];
503        assert_eq!(cl2.channel_list_id, 2);
504        assert_eq!(cl2.channel_list_name, name2);
505        assert_eq!(cl2.country_code, LangCode([b'D', b'E', b'U']));
506        assert_eq!(cl2.services.len(), 1);
507        assert_eq!(cl2.services[0].service_id, 3);
508        assert_eq!(cl2.services[0].logical_channel_number, 3);
509    }
510
511    #[test]
512    fn v2_parse_visible_service_false() {
513        let mut bytes = vec![TAG_V2, 0];
514        bytes.push(0x01);
515        bytes.push(0);
516        bytes.extend_from_slice(b"NOR");
517        bytes.push(4);
518        bytes.extend_from_slice(&[0x00, 0x01, 0x7C, 0x0A]);
519        bytes[1] = (bytes.len() - 2) as u8;
520
521        let d = NordigLogicalChannelV2::parse(&bytes).unwrap();
522        assert!(!d.channel_lists[0].services[0].visible_service_flag);
523        assert_eq!(d.channel_lists[0].services[0].logical_channel_number, 10);
524    }
525
526    #[test]
527    fn v2_parse_lcn_full_10_bit() {
528        let mut bytes = vec![TAG_V2, 0];
529        bytes.push(0x01);
530        bytes.push(0);
531        bytes.extend_from_slice(b"SWE");
532        bytes.push(4);
533        bytes.extend_from_slice(&[0x00, 0x01, 0xFF, 0xFF]);
534        bytes[1] = (bytes.len() - 2) as u8;
535
536        let d = NordigLogicalChannelV2::parse(&bytes).unwrap();
537        assert_eq!(d.channel_lists[0].services[0].logical_channel_number, 1023);
538    }
539
540    #[test]
541    fn v2_parse_empty_channel_list_name() {
542        let mut bytes = vec![TAG_V2, 0];
543        bytes.push(0x01);
544        bytes.push(0); // name_len = 0
545        bytes.extend_from_slice(b"FRA");
546        bytes.push(0); // descriptor_length = 0
547        bytes[1] = (bytes.len() - 2) as u8;
548
549        let d = NordigLogicalChannelV2::parse(&bytes).unwrap();
550        assert_eq!(d.channel_lists.len(), 1);
551        assert!(d.channel_lists[0].channel_list_name.is_empty());
552        assert!(d.channel_lists[0].services.is_empty());
553    }
554
555    #[test]
556    fn v2_parse_rejects_wrong_tag() {
557        let err = NordigLogicalChannelV2::parse(&[0x88, 0]).unwrap_err();
558        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x88, .. }));
559    }
560
561    #[test]
562    fn v2_serialize_round_trip() {
563        let d = NordigLogicalChannelV2 {
564            channel_lists: vec![
565                NordigLogicalChannelV2ChannelList {
566                    channel_list_id: 1,
567                    channel_list_name: b"Terrestrial".to_vec(),
568                    country_code: LangCode([b'G', b'B', b'R']),
569                    services: vec![
570                        NordigLogicalChannelV2Service {
571                            service_id: 1,
572                            visible_service_flag: true,
573                            logical_channel_number: 1,
574                        },
575                        NordigLogicalChannelV2Service {
576                            service_id: 2,
577                            visible_service_flag: false,
578                            logical_channel_number: 1023,
579                        },
580                    ],
581                },
582                NordigLogicalChannelV2ChannelList {
583                    channel_list_id: 2,
584                    channel_list_name: vec![],
585                    country_code: LangCode([b'D', b'E', b'U']),
586                    services: vec![NordigLogicalChannelV2Service {
587                        service_id: 3,
588                        visible_service_flag: true,
589                        logical_channel_number: 3,
590                    }],
591                },
592            ],
593        };
594        let mut buf = vec![0u8; d.serialized_len()];
595        d.serialize_into(&mut buf).unwrap();
596        let re = NordigLogicalChannelV2::parse(&buf).unwrap();
597        assert_eq!(d, re);
598    }
599
600    #[test]
601    fn v2_serialize_round_trip_byte_identity() {
602        let mut bytes = vec![TAG_V2, 0];
603        bytes.push(0x01);
604        bytes.push(0);
605        bytes.extend_from_slice(b"GBR");
606        bytes.push(4);
607        bytes.extend_from_slice(&[0x00, 0x01, 0xFC, 0x05]);
608        bytes[1] = (bytes.len() - 2) as u8;
609
610        let d = NordigLogicalChannelV2::parse(&bytes).unwrap();
611        let mut buf = vec![0u8; d.serialized_len()];
612        d.serialize_into(&mut buf).unwrap();
613        assert_eq!(buf, bytes);
614    }
615}