1use super::descriptor_body;
10use crate::error::{Error, Result};
11use crate::text::LangCode;
12use alloc::vec::Vec;
13use dvb_common::{Parse, Serialize};
14
15pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30pub struct NordigLogicalChannelV1Entry {
31 pub service_id: u16,
33 pub visible_service_flag: bool,
35 pub logical_channel_number: u16,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize))]
42pub struct NordigLogicalChannelV1 {
43 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
119pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize))]
134pub struct NordigLogicalChannelV2Service {
135 pub service_id: u16,
137 pub visible_service_flag: bool,
139 pub logical_channel_number: u16,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize))]
146pub struct NordigLogicalChannelV2ChannelList {
147 pub channel_list_id: u8,
149 pub channel_list_name: Vec<u8>,
152 pub country_code: LangCode,
154 pub services: Vec<NordigLogicalChannelV2Service>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize))]
161pub struct NordigLogicalChannelV2 {
162 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 + cl.channel_list_name.len()
247 + 3 + 1 + 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 #[test]
309 fn v1_parse_single_entry() {
310 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 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 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 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 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 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 #[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); bytes.push(name.len() as u8);
449 bytes.extend_from_slice(name);
450 bytes.extend_from_slice(b"GBR"); bytes.push(4); bytes.extend_from_slice(&[0x00, 0x01, 0xFC, 0x05]);
453 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 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 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); bytes.extend_from_slice(b"FRA");
546 bytes.push(0); 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}