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