1use crate::compatibility::CompatibilityDescriptor;
20use crate::descriptors::carousel::CarouselDescriptorLoop;
21use crate::error::{Error, Result};
22use alloc::vec::Vec;
23use broadcast_common::{Parse, Serialize};
24
25pub const PROTOCOL_DISCRIMINATOR: u8 = 0x11;
27pub const DSMCC_TYPE_UN_DOWNLOAD: u8 = 0x03;
29pub const MESSAGE_ID_DII: u16 = 0x1002;
31pub const MESSAGE_ID_DDB: u16 = 0x1003;
33pub const MESSAGE_ID_DSI: u16 = 0x1006;
35
36const MESSAGE_HEADER_LEN: usize = 12;
40const SERVER_ID_LEN: usize = 20;
42const PRIVATE_LEN_FIELD: usize = 2;
44const DII_FIXED_LEN: usize = 16;
48const MODULE_HEADER_LEN: usize = 8;
51const DDB_FIXED_LEN: usize = 6;
54
55const GII_NUMBER_OF_GROUPS_LEN: usize = 2;
58const GII_GROUP_ID_LEN: usize = 4;
60const GII_GROUP_SIZE_LEN: usize = 4;
62const GII_GROUP_INFO_LEN_FIELD: usize = 2;
64const GII_PRIVATE_DATA_LEN_FIELD: usize = 2;
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70pub struct GroupInfo<'a> {
71 pub group_id: u32,
73 pub group_size: u32,
75 pub group_compatibility: CompatibilityDescriptor<'a>,
78 #[cfg_attr(feature = "serde", serde(borrow))]
80 pub group_info: &'a [u8],
81 #[cfg_attr(feature = "serde", serde(borrow))]
83 pub private_data: &'a [u8],
84}
85
86impl<'a> GroupInfo<'a> {
87 #[must_use]
92 pub const fn descriptors(&self) -> CarouselDescriptorLoop<'a> {
93 CarouselDescriptorLoop::new(self.group_info)
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize))]
123pub struct GroupInfoIndication<'a> {
124 pub groups: Vec<GroupInfo<'a>>,
126}
127
128impl<'a> Parse<'a> for GroupInfoIndication<'a> {
129 type Error = crate::error::Error;
130
131 fn parse(bytes: &'a [u8]) -> Result<Self> {
132 let (b, _) = bytes
133 .split_first_chunk::<2>()
134 .ok_or(Error::BufferTooShort {
135 need: GII_NUMBER_OF_GROUPS_LEN,
136 have: bytes.len(),
137 what: "GroupInfoIndication NumberOfGroups",
138 })?;
139 let number_of_groups = u16::from_be_bytes(*b) as usize;
140 let mut pos = GII_NUMBER_OF_GROUPS_LEN;
141 let end = bytes.len();
142 let mut groups = Vec::with_capacity(number_of_groups.min(256));
143
144 for _ in 0..number_of_groups {
145 let fixed = GII_GROUP_ID_LEN + GII_GROUP_SIZE_LEN;
147 let (hdr, _) =
148 bytes[pos..end]
149 .split_first_chunk::<8>()
150 .ok_or(Error::BufferTooShort {
151 need: pos + fixed,
152 have: end,
153 what: "GroupInfo GroupId/GroupSize",
154 })?;
155 let group_id = u32::from_be_bytes([hdr[0], hdr[1], hdr[2], hdr[3]]);
156 let group_size = u32::from_be_bytes([hdr[4], hdr[5], hdr[6], hdr[7]]);
157 pos += fixed;
158
159 use crate::compatibility::COMPAT_DESC_LEN_FIELD;
163 let (bc, _) =
164 bytes[pos..end]
165 .split_first_chunk::<2>()
166 .ok_or(Error::BufferTooShort {
167 need: pos + COMPAT_DESC_LEN_FIELD,
168 have: end,
169 what: "GroupCompatibility length field",
170 })?;
171 let compat_len = u16::from_be_bytes(*bc) as usize;
172 let compat_total = COMPAT_DESC_LEN_FIELD + compat_len;
173 if pos + compat_total > end {
174 return Err(Error::SectionLengthOverflow {
175 declared: compat_len,
176 available: end - pos - COMPAT_DESC_LEN_FIELD,
177 });
178 }
179 let group_compatibility =
180 CompatibilityDescriptor::parse(&bytes[pos..pos + compat_total])?;
181 pos += compat_total;
182
183 let (bgi, _) =
185 bytes[pos..end]
186 .split_first_chunk::<2>()
187 .ok_or(Error::BufferTooShort {
188 need: pos + GII_GROUP_INFO_LEN_FIELD,
189 have: end,
190 what: "GroupInfo GroupInfoLength",
191 })?;
192 let group_info_len = u16::from_be_bytes(*bgi) as usize;
193 pos += GII_GROUP_INFO_LEN_FIELD;
194 if pos + group_info_len > end {
195 return Err(Error::SectionLengthOverflow {
196 declared: group_info_len,
197 available: end - pos,
198 });
199 }
200 let group_info = &bytes[pos..pos + group_info_len];
201 pos += group_info_len;
202
203 let (bpd, _) =
205 bytes[pos..end]
206 .split_first_chunk::<2>()
207 .ok_or(Error::BufferTooShort {
208 need: pos + GII_PRIVATE_DATA_LEN_FIELD,
209 have: end,
210 what: "GroupInfo PrivateDataLength",
211 })?;
212 let private_data_len = u16::from_be_bytes(*bpd) as usize;
213 pos += GII_PRIVATE_DATA_LEN_FIELD;
214 if pos + private_data_len > end {
215 return Err(Error::SectionLengthOverflow {
216 declared: private_data_len,
217 available: end - pos,
218 });
219 }
220 let private_data = &bytes[pos..pos + private_data_len];
221 pos += private_data_len;
222
223 groups.push(GroupInfo {
224 group_id,
225 group_size,
226 group_compatibility,
227 group_info,
228 private_data,
229 });
230 }
231
232 Ok(GroupInfoIndication { groups })
233 }
234}
235
236impl Serialize for GroupInfoIndication<'_> {
237 type Error = crate::error::Error;
238
239 fn serialized_len(&self) -> usize {
240 GII_NUMBER_OF_GROUPS_LEN
241 + self
242 .groups
243 .iter()
244 .map(|g| {
245 GII_GROUP_ID_LEN
246 + GII_GROUP_SIZE_LEN
247 + g.group_compatibility.serialized_len()
248 + GII_GROUP_INFO_LEN_FIELD
249 + g.group_info.len()
250 + GII_PRIVATE_DATA_LEN_FIELD
251 + g.private_data.len()
252 })
253 .sum::<usize>()
254 }
255
256 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
257 let len = self.serialized_len();
258 if buf.len() < len {
259 return Err(Error::OutputBufferTooSmall {
260 need: len,
261 have: buf.len(),
262 });
263 }
264 if self.groups.len() > u16::MAX as usize {
265 return Err(Error::SectionLengthOverflow {
266 declared: self.groups.len(),
267 available: u16::MAX as usize,
268 });
269 }
270 buf[0..2].copy_from_slice(
271 &broadcast_common::len::fit_u16(self.groups.len(), "self.groups")?.to_be_bytes(),
272 );
273 let mut pos = GII_NUMBER_OF_GROUPS_LEN;
274
275 for g in &self.groups {
276 buf[pos..pos + 4].copy_from_slice(&g.group_id.to_be_bytes());
277 buf[pos + 4..pos + 8].copy_from_slice(&g.group_size.to_be_bytes());
278 pos += GII_GROUP_ID_LEN + GII_GROUP_SIZE_LEN;
279
280 let written = g.group_compatibility.serialize_into(&mut buf[pos..])?;
281 pos += written;
282
283 if g.group_info.len() > u16::MAX as usize {
284 return Err(Error::SectionLengthOverflow {
285 declared: g.group_info.len(),
286 available: u16::MAX as usize,
287 });
288 }
289 buf[pos..pos + 2].copy_from_slice(
290 &broadcast_common::len::fit_u16(g.group_info.len(), "g.group_info")?.to_be_bytes(),
291 );
292 pos += GII_GROUP_INFO_LEN_FIELD;
293 buf[pos..pos + g.group_info.len()].copy_from_slice(g.group_info);
294 pos += g.group_info.len();
295
296 if g.private_data.len() > u16::MAX as usize {
297 return Err(Error::SectionLengthOverflow {
298 declared: g.private_data.len(),
299 available: u16::MAX as usize,
300 });
301 }
302 buf[pos..pos + 2].copy_from_slice(
303 &broadcast_common::len::fit_u16(g.private_data.len(), "g.private_data")?
304 .to_be_bytes(),
305 );
306 pos += GII_PRIVATE_DATA_LEN_FIELD;
307 buf[pos..pos + g.private_data.len()].copy_from_slice(g.private_data);
308 pos += g.private_data.len();
309 }
310
311 Ok(len)
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
317#[cfg_attr(feature = "serde", derive(serde::Serialize))]
318pub struct Dsi<'a> {
319 pub transaction_id: u32,
322 pub adaptation: &'a [u8],
324 pub server_id: [u8; SERVER_ID_LEN],
326 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
328 pub private_data: &'a [u8],
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
335#[cfg_attr(feature = "serde", derive(serde::Serialize))]
336pub struct DiiModule<'a> {
337 pub module_id: u16,
339 pub module_size: u32,
341 pub module_version: u8,
343 pub module_info: &'a [u8],
346}
347
348impl<'a> DiiModule<'a> {
349 #[must_use]
357 pub const fn descriptors(&self) -> CarouselDescriptorLoop<'a> {
358 CarouselDescriptorLoop::new(self.module_info)
359 }
360}
361
362#[derive(Debug, Clone, PartialEq, Eq)]
364#[cfg_attr(feature = "serde", derive(serde::Serialize))]
365pub struct Dii<'a> {
366 pub transaction_id: u32,
368 pub adaptation: &'a [u8],
370 pub download_id: u32,
372 pub block_size: u16,
374 pub window_size: u8,
376 pub ack_period: u8,
378 pub t_c_download_window: u32,
380 pub t_c_download_scenario: u32,
382 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
384 pub modules: Vec<DiiModule<'a>>,
386 pub private_data: &'a [u8],
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
393#[cfg_attr(feature = "serde", derive(serde::Serialize))]
394#[non_exhaustive]
395pub enum UnMessage<'a> {
396 Dsi(Dsi<'a>),
398 Dii(Dii<'a>),
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
405#[cfg_attr(feature = "serde", derive(serde::Serialize))]
406pub struct DownloadDataBlock<'a> {
407 pub download_id: u32,
409 pub adaptation: &'a [u8],
411 pub module_id: u16,
413 pub module_version: u8,
415 pub block_number: u16,
417 pub block_data: &'a [u8],
419}
420
421fn parse_header<'a>(bytes: &'a [u8], what: &'static str) -> Result<(u16, u32, &'a [u8], &'a [u8])> {
425 let (hdr, _) =
426 bytes
427 .split_first_chunk::<MESSAGE_HEADER_LEN>()
428 .ok_or(Error::BufferTooShort {
429 need: MESSAGE_HEADER_LEN,
430 have: bytes.len(),
431 what,
432 })?;
433 if hdr[0] != PROTOCOL_DISCRIMINATOR {
434 return Err(Error::ReservedBitsViolation {
435 field: "protocolDiscriminator",
436 reason: "must be 0x11 (ISO/IEC 13818-6 §7.2)",
437 });
438 }
439 if hdr[1] != DSMCC_TYPE_UN_DOWNLOAD {
440 return Err(Error::ReservedBitsViolation {
441 field: "dsmccType",
442 reason: "must be 0x03 — U-N download (ISO/IEC 13818-6 §7.2)",
443 });
444 }
445 let message_id = u16::from_be_bytes([hdr[2], hdr[3]]);
446 let id = u32::from_be_bytes([hdr[4], hdr[5], hdr[6], hdr[7]]);
447 let adaptation_length = hdr[9] as usize;
448 let message_length = u16::from_be_bytes([hdr[10], hdr[11]]) as usize;
449 let total = MESSAGE_HEADER_LEN + message_length;
450 if bytes.len() < total {
451 return Err(Error::SectionLengthOverflow {
452 declared: message_length,
453 available: bytes.len() - MESSAGE_HEADER_LEN,
454 });
455 }
456 if adaptation_length > message_length {
457 return Err(Error::SectionLengthOverflow {
458 declared: adaptation_length,
459 available: message_length,
460 });
461 }
462 let adaptation = &bytes[MESSAGE_HEADER_LEN..MESSAGE_HEADER_LEN + adaptation_length];
463 let payload = &bytes[MESSAGE_HEADER_LEN + adaptation_length..total];
464 Ok((message_id, id, adaptation, payload))
465}
466
467fn serialize_header(
470 buf: &mut [u8],
471 message_id: u16,
472 id: u32,
473 adaptation: &[u8],
474 payload_len: usize,
475) -> Result<usize> {
476 let message_length = adaptation.len() + payload_len;
477 if adaptation.len() > u8::MAX as usize {
478 return Err(Error::SectionLengthOverflow {
479 declared: adaptation.len(),
480 available: u8::MAX as usize,
481 });
482 }
483 if message_length > u16::MAX as usize {
484 return Err(Error::SectionLengthOverflow {
485 declared: message_length,
486 available: u16::MAX as usize,
487 });
488 }
489 buf[0] = PROTOCOL_DISCRIMINATOR;
490 buf[1] = DSMCC_TYPE_UN_DOWNLOAD;
491 buf[2..4].copy_from_slice(&message_id.to_be_bytes());
492 buf[4..8].copy_from_slice(&id.to_be_bytes());
493 buf[8] = 0xFF; buf[9] = broadcast_common::len::fit_u8(adaptation.len(), "adaptation")?;
495 buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
496 buf[MESSAGE_HEADER_LEN..MESSAGE_HEADER_LEN + adaptation.len()].copy_from_slice(adaptation);
497 Ok(MESSAGE_HEADER_LEN + adaptation.len())
498}
499
500fn length_prefixed(bytes: &[u8], pos: usize, end: usize) -> Result<(&[u8], usize)> {
502 let (b, _) = bytes[pos..end]
503 .split_first_chunk::<2>()
504 .ok_or(Error::BufferTooShort {
505 need: pos + 2,
506 have: end,
507 what: "DSM-CC 16-bit length field",
508 })?;
509 let len = u16::from_be_bytes(*b) as usize;
510 let start = pos + 2;
511 if start + len > end {
512 return Err(Error::SectionLengthOverflow {
513 declared: len,
514 available: end - start,
515 });
516 }
517 Ok((&bytes[start..start + len], start + len))
518}
519
520fn parse_compat_block(
523 payload: &[u8],
524 offset: usize,
525 end: usize,
526) -> Result<(CompatibilityDescriptor<'_>, usize)> {
527 use crate::compatibility::COMPAT_DESC_LEN_FIELD;
528 let (b, _) = payload[offset..]
529 .split_first_chunk::<2>()
530 .ok_or(Error::BufferTooShort {
531 need: offset + COMPAT_DESC_LEN_FIELD,
532 have: end,
533 what: "compatibilityDescriptor in DSM-CC message",
534 })?;
535 let compat_desc_len = u16::from_be_bytes(*b) as usize;
536 let compat_end = offset + COMPAT_DESC_LEN_FIELD + compat_desc_len;
537 if compat_end > end {
538 return Err(Error::SectionLengthOverflow {
539 declared: compat_desc_len,
540 available: end - offset - COMPAT_DESC_LEN_FIELD,
541 });
542 }
543 let cd = CompatibilityDescriptor::parse(&payload[offset..compat_end])?;
544 Ok((cd, compat_end))
545}
546
547impl<'a> Parse<'a> for UnMessage<'a> {
548 type Error = crate::error::Error;
549
550 fn parse(bytes: &'a [u8]) -> Result<Self> {
551 let (message_id, transaction_id, adaptation, payload) =
552 parse_header(bytes, "UnMessage header")?;
553 let end = payload.len();
554 match message_id {
555 MESSAGE_ID_DSI => {
556 if end < SERVER_ID_LEN {
557 return Err(Error::BufferTooShort {
558 need: SERVER_ID_LEN,
559 have: end,
560 what: "Dsi body",
561 });
562 }
563 let mut server_id = [0u8; SERVER_ID_LEN];
564 server_id.copy_from_slice(&payload[..SERVER_ID_LEN]);
565 let (compatibility_descriptor, pos) =
566 parse_compat_block(payload, SERVER_ID_LEN, end)?;
567 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
568 Ok(UnMessage::Dsi(Dsi {
569 transaction_id,
570 adaptation,
571 server_id,
572 compatibility_descriptor,
573 private_data,
574 }))
575 }
576 MESSAGE_ID_DII => {
577 let (dii_hdr, _) =
578 payload
579 .split_first_chunk::<DII_FIXED_LEN>()
580 .ok_or(Error::BufferTooShort {
581 need: DII_FIXED_LEN,
582 have: end,
583 what: "Dii body",
584 })?;
585 let download_id =
586 u32::from_be_bytes([dii_hdr[0], dii_hdr[1], dii_hdr[2], dii_hdr[3]]);
587 let block_size = u16::from_be_bytes([dii_hdr[4], dii_hdr[5]]);
588 let window_size = dii_hdr[6];
589 let ack_period = dii_hdr[7];
590 let t_c_download_window =
591 u32::from_be_bytes([dii_hdr[8], dii_hdr[9], dii_hdr[10], dii_hdr[11]]);
592 let t_c_download_scenario =
593 u32::from_be_bytes([dii_hdr[12], dii_hdr[13], dii_hdr[14], dii_hdr[15]]);
594 let (compatibility_descriptor, mut pos) =
595 parse_compat_block(payload, DII_FIXED_LEN, end)?;
596 let (bnm, _) = payload
597 .get(pos..)
598 .and_then(|s| s.split_first_chunk::<2>())
599 .ok_or(Error::BufferTooShort {
600 need: pos + 2,
601 have: end,
602 what: "Dii numberOfModules",
603 })?;
604 let number_of_modules = u16::from_be_bytes(*bnm) as usize;
605 pos += 2;
606 let mut modules = Vec::with_capacity(number_of_modules.min(256));
607 for _ in 0..number_of_modules {
608 let (mhdr, _) = payload
609 .get(pos..)
610 .and_then(|s| s.split_first_chunk::<MODULE_HEADER_LEN>())
611 .ok_or(Error::BufferTooShort {
612 need: pos + MODULE_HEADER_LEN,
613 have: end,
614 what: "Dii module entry",
615 })?;
616 let module_id = u16::from_be_bytes([mhdr[0], mhdr[1]]);
617 let module_size = u32::from_be_bytes([mhdr[2], mhdr[3], mhdr[4], mhdr[5]]);
618 let module_version = mhdr[6];
619 let module_info_length = mhdr[7] as usize;
620 let info_start = pos + MODULE_HEADER_LEN;
621 if info_start + module_info_length > end {
622 return Err(Error::SectionLengthOverflow {
623 declared: module_info_length,
624 available: end - info_start,
625 });
626 }
627 modules.push(DiiModule {
628 module_id,
629 module_size,
630 module_version,
631 module_info: &payload[info_start..info_start + module_info_length],
632 });
633 pos = info_start + module_info_length;
634 }
635 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
636 Ok(UnMessage::Dii(Dii {
637 transaction_id,
638 adaptation,
639 download_id,
640 block_size,
641 window_size,
642 ack_period,
643 t_c_download_window,
644 t_c_download_scenario,
645 compatibility_descriptor,
646 modules,
647 private_data,
648 }))
649 }
650 _ => Err(Error::ReservedBitsViolation {
651 field: "messageId",
652 reason: "expected 0x1002 (DII) or 0x1006 (DSI) on table_id 0x3B \
653 (ISO/IEC 13818-6 §7.3)",
654 }),
655 }
656 }
657}
658
659impl Serialize for UnMessage<'_> {
660 type Error = crate::error::Error;
661
662 fn serialized_len(&self) -> usize {
663 match self {
664 UnMessage::Dsi(dsi) => {
665 MESSAGE_HEADER_LEN
666 + dsi.adaptation.len()
667 + SERVER_ID_LEN
668 + dsi.compatibility_descriptor.serialized_len()
669 + PRIVATE_LEN_FIELD
670 + dsi.private_data.len()
671 }
672 UnMessage::Dii(dii) => {
673 MESSAGE_HEADER_LEN
674 + dii.adaptation.len()
675 + DII_FIXED_LEN
676 + dii.compatibility_descriptor.serialized_len()
677 + 2 + dii
679 .modules
680 .iter()
681 .map(|m| MODULE_HEADER_LEN + m.module_info.len())
682 .sum::<usize>()
683 + PRIVATE_LEN_FIELD
684 + dii.private_data.len()
685 }
686 }
687 }
688
689 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
690 let len = self.serialized_len();
691 if buf.len() < len {
692 return Err(Error::OutputBufferTooSmall {
693 need: len,
694 have: buf.len(),
695 });
696 }
697 match self {
698 UnMessage::Dsi(dsi) => {
699 let payload_len = len - MESSAGE_HEADER_LEN - dsi.adaptation.len();
700 let mut pos = serialize_header(
701 buf,
702 MESSAGE_ID_DSI,
703 dsi.transaction_id,
704 dsi.adaptation,
705 payload_len,
706 )?;
707 buf[pos..pos + SERVER_ID_LEN].copy_from_slice(&dsi.server_id);
708 pos += SERVER_ID_LEN;
709 let written = dsi
710 .compatibility_descriptor
711 .serialize_into(&mut buf[pos..])?;
712 pos += written;
713 put_length_prefixed(buf, pos, dsi.private_data)?;
714 }
715 UnMessage::Dii(dii) => {
716 let payload_len = len - MESSAGE_HEADER_LEN - dii.adaptation.len();
717 let mut pos = serialize_header(
718 buf,
719 MESSAGE_ID_DII,
720 dii.transaction_id,
721 dii.adaptation,
722 payload_len,
723 )?;
724 buf[pos..pos + 4].copy_from_slice(&dii.download_id.to_be_bytes());
725 buf[pos + 4..pos + 6].copy_from_slice(&dii.block_size.to_be_bytes());
726 buf[pos + 6] = dii.window_size;
727 buf[pos + 7] = dii.ack_period;
728 buf[pos + 8..pos + 12].copy_from_slice(&dii.t_c_download_window.to_be_bytes());
729 buf[pos + 12..pos + 16].copy_from_slice(&dii.t_c_download_scenario.to_be_bytes());
730 pos += DII_FIXED_LEN;
731 let written = dii
732 .compatibility_descriptor
733 .serialize_into(&mut buf[pos..])?;
734 pos += written;
735 if dii.modules.len() > u16::MAX as usize {
736 return Err(Error::SectionLengthOverflow {
737 declared: dii.modules.len(),
738 available: u16::MAX as usize,
739 });
740 }
741 buf[pos..pos + 2].copy_from_slice(
742 &broadcast_common::len::fit_u16(dii.modules.len(), "dii.modules")?
743 .to_be_bytes(),
744 );
745 pos += 2;
746 for m in &dii.modules {
747 if m.module_info.len() > u8::MAX as usize {
748 return Err(Error::SectionLengthOverflow {
749 declared: m.module_info.len(),
750 available: u8::MAX as usize,
751 });
752 }
753 buf[pos..pos + 2].copy_from_slice(&m.module_id.to_be_bytes());
754 buf[pos + 2..pos + 6].copy_from_slice(&m.module_size.to_be_bytes());
755 buf[pos + 6] = m.module_version;
756 buf[pos + 7] =
757 broadcast_common::len::fit_u8(m.module_info.len(), "m.module_info")?;
758 pos += MODULE_HEADER_LEN;
759 buf[pos..pos + m.module_info.len()].copy_from_slice(m.module_info);
760 pos += m.module_info.len();
761 }
762 put_length_prefixed(buf, pos, dii.private_data)?;
763 }
764 }
765 Ok(len)
766 }
767}
768
769fn put_length_prefixed(buf: &mut [u8], pos: usize, data: &[u8]) -> Result<usize> {
771 if data.len() > u16::MAX as usize {
772 return Err(Error::SectionLengthOverflow {
773 declared: data.len(),
774 available: u16::MAX as usize,
775 });
776 }
777 buf[pos..pos + 2]
778 .copy_from_slice(&broadcast_common::len::fit_u16(data.len(), "data")?.to_be_bytes());
779 buf[pos + 2..pos + 2 + data.len()].copy_from_slice(data);
780 Ok(pos + 2 + data.len())
781}
782
783impl<'a> Parse<'a> for DownloadDataBlock<'a> {
784 type Error = crate::error::Error;
785
786 fn parse(bytes: &'a [u8]) -> Result<Self> {
787 let (message_id, download_id, adaptation, payload) =
788 parse_header(bytes, "DownloadDataBlock header")?;
789 if message_id != MESSAGE_ID_DDB {
790 return Err(Error::ReservedBitsViolation {
791 field: "messageId",
792 reason: "expected 0x1003 (DDB) on table_id 0x3C (ISO/IEC 13818-6 §7.3.7)",
793 });
794 }
795 let (ddb_hdr, _) =
796 payload
797 .split_first_chunk::<DDB_FIXED_LEN>()
798 .ok_or(Error::BufferTooShort {
799 need: DDB_FIXED_LEN,
800 have: payload.len(),
801 what: "DownloadDataBlock body",
802 })?;
803 Ok(DownloadDataBlock {
804 download_id,
805 adaptation,
806 module_id: u16::from_be_bytes([ddb_hdr[0], ddb_hdr[1]]),
807 module_version: ddb_hdr[2],
808 block_number: u16::from_be_bytes([ddb_hdr[4], ddb_hdr[5]]),
809 block_data: &payload[DDB_FIXED_LEN..],
810 })
811 }
812}
813
814impl Serialize for DownloadDataBlock<'_> {
815 type Error = crate::error::Error;
816
817 fn serialized_len(&self) -> usize {
818 MESSAGE_HEADER_LEN + self.adaptation.len() + DDB_FIXED_LEN + self.block_data.len()
819 }
820
821 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
822 let len = self.serialized_len();
823 if buf.len() < len {
824 return Err(Error::OutputBufferTooSmall {
825 need: len,
826 have: buf.len(),
827 });
828 }
829 let payload_len = DDB_FIXED_LEN + self.block_data.len();
830 let pos = serialize_header(
831 buf,
832 MESSAGE_ID_DDB,
833 self.download_id,
834 self.adaptation,
835 payload_len,
836 )?;
837 buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
838 buf[pos + 2] = self.module_version;
839 buf[pos + 3] = 0xFF; buf[pos + 4..pos + 6].copy_from_slice(&self.block_number.to_be_bytes());
841 buf[pos + DDB_FIXED_LEN..pos + DDB_FIXED_LEN + self.block_data.len()]
842 .copy_from_slice(self.block_data);
843 Ok(len)
844 }
845}
846
847#[cfg(test)]
848mod tests {
849 use super::*;
850
851 fn sample_dsi() -> UnMessage<'static> {
852 UnMessage::Dsi(Dsi {
853 transaction_id: 0x8000_0000,
854 adaptation: &[],
855 server_id: [0xFF; 20],
856 compatibility_descriptor: CompatibilityDescriptor {
857 empty_count_present: false,
858 descriptors: vec![],
859 },
860 private_data: &[0x0A, 0x0B],
861 })
862 }
863
864 fn sample_dii() -> UnMessage<'static> {
865 UnMessage::Dii(Dii {
866 transaction_id: 0x8002_0002,
867 adaptation: &[],
868 download_id: 0x0000_00AB,
869 block_size: 4066,
870 window_size: 0,
871 ack_period: 0,
872 t_c_download_window: 0,
873 t_c_download_scenario: 0,
874 compatibility_descriptor: CompatibilityDescriptor {
875 empty_count_present: false,
876 descriptors: vec![],
877 },
878 modules: vec![
879 DiiModule {
880 module_id: 1,
881 module_size: 8000,
882 module_version: 3,
883 module_info: &[0xDE, 0xAD],
884 },
885 DiiModule {
886 module_id: 2,
887 module_size: 100,
888 module_version: 1,
889 module_info: &[],
890 },
891 ],
892 private_data: &[],
893 })
894 }
895
896 #[test]
897 fn dsi_round_trip() {
898 let msg = sample_dsi();
899 let mut buf = vec![0u8; msg.serialized_len()];
900 msg.serialize_into(&mut buf).unwrap();
901 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
902 }
903
904 #[test]
905 fn dii_round_trip() {
906 let msg = sample_dii();
907 let mut buf = vec![0u8; msg.serialized_len()];
908 msg.serialize_into(&mut buf).unwrap();
909 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
910 }
911
912 fn nonempty_compat() -> CompatibilityDescriptor<'static> {
916 CompatibilityDescriptor {
917 empty_count_present: false,
918 descriptors: vec![crate::compatibility::CompatibilityDescriptorEntry {
919 descriptor_type: crate::compatibility::DescriptorType::SystemHardware,
920 specifier_type: crate::compatibility::SpecifierType::IeeeOui,
921 specifier_data: [0x00, 0x15, 0x0A],
922 model: 0x1234,
923 version: 0x0001,
924 sub_descriptors: vec![crate::compatibility::SubDescriptor {
925 sub_descriptor_type: crate::compatibility::SubDescriptorType::Unallocated(0x05),
926 data: &[0xAA, 0xBB],
927 }],
928 }],
929 }
930 }
931
932 #[test]
933 fn dsi_with_compat_round_trip() {
934 let msg = UnMessage::Dsi(Dsi {
935 transaction_id: 0x8000_0000,
936 adaptation: &[],
937 server_id: [0xFF; 20],
938 compatibility_descriptor: nonempty_compat(),
939 private_data: &[0x0A, 0x0B],
940 });
941 let mut buf = vec![0u8; msg.serialized_len()];
942 msg.serialize_into(&mut buf).unwrap();
943 let re = UnMessage::parse(&buf).unwrap();
944 assert_eq!(re, msg);
945 let mut buf2 = vec![0u8; re.serialized_len()];
946 re.serialize_into(&mut buf2).unwrap();
947 assert_eq!(buf, buf2, "byte-exact re-serialize");
948 }
949
950 #[test]
951 fn dii_with_compat_round_trip() {
952 let msg = UnMessage::Dii(Dii {
953 transaction_id: 0x8002_0002,
954 adaptation: &[],
955 download_id: 0x0000_00AB,
956 block_size: 4066,
957 window_size: 0,
958 ack_period: 0,
959 t_c_download_window: 0,
960 t_c_download_scenario: 0,
961 compatibility_descriptor: nonempty_compat(),
962 modules: vec![DiiModule {
963 module_id: 1,
964 module_size: 8000,
965 module_version: 3,
966 module_info: &[0xDE, 0xAD],
967 }],
968 private_data: &[],
969 });
970 let mut buf = vec![0u8; msg.serialized_len()];
971 msg.serialize_into(&mut buf).unwrap();
972 let re = UnMessage::parse(&buf).unwrap();
973 assert_eq!(re, msg);
974 let mut buf2 = vec![0u8; re.serialized_len()];
975 re.serialize_into(&mut buf2).unwrap();
976 assert_eq!(buf, buf2, "byte-exact re-serialize");
977 }
978
979 #[test]
980 fn ddb_round_trip() {
981 let ddb = DownloadDataBlock {
982 download_id: 0xAB,
983 adaptation: &[],
984 module_id: 1,
985 module_version: 3,
986 block_number: 2,
987 block_data: &[0x55; 64],
988 };
989 let mut buf = vec![0u8; ddb.serialized_len()];
990 ddb.serialize_into(&mut buf).unwrap();
991 assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
992 }
993
994 #[test]
995 fn header_fields_on_wire() {
996 let msg = sample_dsi();
997 let mut buf = vec![0u8; msg.serialized_len()];
998 msg.serialize_into(&mut buf).unwrap();
999 assert_eq!(buf[0], 0x11); assert_eq!(buf[1], 0x03); assert_eq!(u16::from_be_bytes([buf[2], buf[3]]), MESSAGE_ID_DSI);
1002 assert_eq!(buf[8], 0xFF); let ml = u16::from_be_bytes([buf[10], buf[11]]) as usize;
1005 assert_eq!(ml, buf.len() - 12);
1006 }
1007
1008 #[test]
1009 fn parse_rejects_wrong_protocol_discriminator() {
1010 let msg = sample_dsi();
1011 let mut buf = vec![0u8; msg.serialized_len()];
1012 msg.serialize_into(&mut buf).unwrap();
1013 buf[0] = 0x12;
1014 assert!(matches!(
1015 UnMessage::parse(&buf).unwrap_err(),
1016 Error::ReservedBitsViolation {
1017 field: "protocolDiscriminator",
1018 ..
1019 }
1020 ));
1021 }
1022
1023 #[test]
1024 fn parse_rejects_unknown_message_id() {
1025 let msg = sample_dsi();
1026 let mut buf = vec![0u8; msg.serialized_len()];
1027 msg.serialize_into(&mut buf).unwrap();
1028 buf[2] = 0x10;
1029 buf[3] = 0x01; assert!(matches!(
1031 UnMessage::parse(&buf).unwrap_err(),
1032 Error::ReservedBitsViolation {
1033 field: "messageId",
1034 ..
1035 }
1036 ));
1037 }
1038
1039 #[test]
1040 fn parse_rejects_short_buffer() {
1041 assert!(matches!(
1042 UnMessage::parse(&[0x11, 0x03]).unwrap_err(),
1043 Error::BufferTooShort { .. }
1044 ));
1045 }
1046
1047 #[test]
1048 fn parse_rejects_message_length_overflow() {
1049 let msg = sample_dsi();
1050 let mut buf = vec![0u8; msg.serialized_len()];
1051 msg.serialize_into(&mut buf).unwrap();
1052 buf[10] = 0xFF;
1053 buf[11] = 0xFF; assert!(matches!(
1055 UnMessage::parse(&buf).unwrap_err(),
1056 Error::SectionLengthOverflow { .. }
1057 ));
1058 }
1059
1060 #[test]
1061 fn dii_module_info_overflow_rejected() {
1062 let msg = sample_dii();
1063 let mut buf = vec![0u8; msg.serialized_len()];
1064 msg.serialize_into(&mut buf).unwrap();
1065 buf[39] = 0xFF;
1068 assert!(matches!(
1069 UnMessage::parse(&buf).unwrap_err(),
1070 Error::SectionLengthOverflow { .. }
1071 ));
1072 }
1073
1074 #[test]
1075 fn ddb_rejects_un_message_id() {
1076 let msg = sample_dsi();
1077 let mut buf = vec![0u8; msg.serialized_len()];
1078 msg.serialize_into(&mut buf).unwrap();
1079 assert!(matches!(
1080 DownloadDataBlock::parse(&buf).unwrap_err(),
1081 Error::ReservedBitsViolation {
1082 field: "messageId",
1083 ..
1084 }
1085 ));
1086 }
1087
1088 #[test]
1089 fn adaptation_bytes_round_trip() {
1090 let ddb = DownloadDataBlock {
1091 download_id: 1,
1092 adaptation: &[0x01, 0x02, 0x03],
1093 module_id: 9,
1094 module_version: 0,
1095 block_number: 0,
1096 block_data: &[0xAA],
1097 };
1098 let mut buf = vec![0u8; ddb.serialized_len()];
1099 ddb.serialize_into(&mut buf).unwrap();
1100 assert_eq!(buf[9], 3); assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
1102 }
1103
1104 #[cfg(feature = "serde")]
1105 #[test]
1106 fn dii_serializes_to_valid_json() {
1107 let msg = sample_dii();
1108 let j = serde_json::to_string(&msg).unwrap();
1109 assert!(j.contains("\"download_id\":171"));
1110 assert!(j.contains("\"block_size\":4066"));
1111 }
1112
1113 fn sample_gii() -> GroupInfoIndication<'static> {
1132 GroupInfoIndication {
1133 groups: vec![GroupInfo {
1134 group_id: 0x0000_0001,
1135 group_size: 500_000,
1136 group_compatibility: CompatibilityDescriptor {
1137 empty_count_present: false,
1138 descriptors: vec![],
1139 },
1140 group_info: &[0xCA, 0xFE],
1141 private_data: &[0xBB],
1142 }],
1143 }
1144 }
1145
1146 #[test]
1147 fn gii_round_trip() {
1148 let gii = sample_gii();
1149 let mut buf = vec![0u8; gii.serialized_len()];
1150 gii.serialize_into(&mut buf).unwrap();
1151 let re = GroupInfoIndication::parse(&buf).unwrap();
1152 assert_eq!(re, gii);
1153 let mut buf2 = vec![0u8; re.serialized_len()];
1155 re.serialize_into(&mut buf2).unwrap();
1156 assert_eq!(buf, buf2, "GII byte-exact re-serialize");
1157 }
1158
1159 #[test]
1163 fn gii_hand_built_byte_anchor() {
1164 #[rustfmt::skip]
1168 let expected: &[u8] = &[
1169 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0xA1, 0x20, 0x00, 0x00, 0x00, 0x02, 0xCA, 0xFE, 0x00, 0x01, 0xBB, ];
1178 let gii = sample_gii();
1179 let mut buf = vec![0u8; gii.serialized_len()];
1180 gii.serialize_into(&mut buf).unwrap();
1181 assert_eq!(buf.as_slice(), expected);
1182 let re = GroupInfoIndication::parse(expected).unwrap();
1183 assert_eq!(re, gii);
1184 }
1185
1186 #[test]
1187 fn gii_empty_groups() {
1188 let gii = GroupInfoIndication { groups: vec![] };
1189 let mut buf = vec![0u8; gii.serialized_len()];
1190 gii.serialize_into(&mut buf).unwrap();
1191 assert_eq!(buf, &[0x00, 0x00]); let re = GroupInfoIndication::parse(&buf).unwrap();
1193 assert!(re.groups.is_empty());
1194 }
1195
1196 #[test]
1197 fn gii_with_compat_round_trip() {
1198 let gii = GroupInfoIndication {
1199 groups: vec![GroupInfo {
1200 group_id: 0xDEAD_BEEF,
1201 group_size: 0x0001_0000,
1202 group_compatibility: nonempty_compat(),
1203 group_info: &[0x01, 0x02, 0x03],
1204 private_data: &[],
1205 }],
1206 };
1207 let mut buf = vec![0u8; gii.serialized_len()];
1208 gii.serialize_into(&mut buf).unwrap();
1209 let re = GroupInfoIndication::parse(&buf).unwrap();
1210 assert_eq!(re, gii);
1211 let mut buf2 = vec![0u8; re.serialized_len()];
1212 re.serialize_into(&mut buf2).unwrap();
1213 assert_eq!(buf, buf2, "GII with compat byte-exact re-serialize");
1214 }
1215
1216 #[test]
1217 fn gii_parse_rejects_short_buffer() {
1218 assert!(matches!(
1219 GroupInfoIndication::parse(&[0x00]).unwrap_err(),
1220 Error::BufferTooShort { .. }
1221 ));
1222 }
1223
1224 #[test]
1225 fn gii_parse_rejects_truncated_group() {
1226 let bytes = &[0x00, 0x01, 0x00, 0x00, 0x00];
1228 assert!(matches!(
1229 GroupInfoIndication::parse(bytes).unwrap_err(),
1230 Error::BufferTooShort { .. }
1231 ));
1232 }
1233
1234 #[cfg(feature = "serde")]
1235 #[test]
1236 fn gii_serde_round_trip() {
1237 let gii = sample_gii();
1238 let json = serde_json::to_string(&gii).unwrap();
1239 assert!(json.contains("\"group_id\":1"));
1240 assert!(json.contains("\"group_size\":500000"));
1241 assert!(json.contains("\"group_info\""));
1242 }
1243}