1use crate::compatibility::CompatibilityDescriptor;
20use crate::error::{Error, Result};
21use dvb_common::{Parse, Serialize};
22
23pub const PROTOCOL_DISCRIMINATOR: u8 = 0x11;
25pub const DSMCC_TYPE_UN_DOWNLOAD: u8 = 0x03;
27pub const MESSAGE_ID_DII: u16 = 0x1002;
29pub const MESSAGE_ID_DDB: u16 = 0x1003;
31pub const MESSAGE_ID_DSI: u16 = 0x1006;
33
34const MESSAGE_HEADER_LEN: usize = 12;
38const SERVER_ID_LEN: usize = 20;
40const PRIVATE_LEN_FIELD: usize = 2;
42const DII_FIXED_LEN: usize = 16;
46const MODULE_HEADER_LEN: usize = 8;
49const DDB_FIXED_LEN: usize = 6;
52
53const GII_NUMBER_OF_GROUPS_LEN: usize = 2;
56const GII_GROUP_ID_LEN: usize = 4;
58const GII_GROUP_SIZE_LEN: usize = 4;
60const GII_GROUP_INFO_LEN_FIELD: usize = 2;
62const GII_PRIVATE_DATA_LEN_FIELD: usize = 2;
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68pub struct GroupInfo<'a> {
69 pub group_id: u32,
71 pub group_size: u32,
73 pub group_compatibility: CompatibilityDescriptor<'a>,
76 #[cfg_attr(feature = "serde", serde(borrow))]
78 pub group_info: &'a [u8],
79 #[cfg_attr(feature = "serde", serde(borrow))]
81 pub private_data: &'a [u8],
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize))]
110pub struct GroupInfoIndication<'a> {
111 pub groups: Vec<GroupInfo<'a>>,
113}
114
115impl<'a> Parse<'a> for GroupInfoIndication<'a> {
116 type Error = crate::error::Error;
117
118 fn parse(bytes: &'a [u8]) -> Result<Self> {
119 if bytes.len() < GII_NUMBER_OF_GROUPS_LEN {
120 return Err(Error::BufferTooShort {
121 need: GII_NUMBER_OF_GROUPS_LEN,
122 have: bytes.len(),
123 what: "GroupInfoIndication NumberOfGroups",
124 });
125 }
126 let number_of_groups = u16::from_be_bytes([bytes[0], bytes[1]]) as usize;
127 let mut pos = GII_NUMBER_OF_GROUPS_LEN;
128 let end = bytes.len();
129 let mut groups = Vec::with_capacity(number_of_groups.min(256));
130
131 for _ in 0..number_of_groups {
132 let fixed = GII_GROUP_ID_LEN + GII_GROUP_SIZE_LEN;
134 if pos + fixed > end {
135 return Err(Error::BufferTooShort {
136 need: pos + fixed,
137 have: end,
138 what: "GroupInfo GroupId/GroupSize",
139 });
140 }
141 let group_id =
142 u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
143 let group_size = u32::from_be_bytes([
144 bytes[pos + 4],
145 bytes[pos + 5],
146 bytes[pos + 6],
147 bytes[pos + 7],
148 ]);
149 pos += fixed;
150
151 use crate::compatibility::COMPAT_DESC_LEN_FIELD;
155 if pos + COMPAT_DESC_LEN_FIELD > end {
156 return Err(Error::BufferTooShort {
157 need: pos + COMPAT_DESC_LEN_FIELD,
158 have: end,
159 what: "GroupCompatibility length field",
160 });
161 }
162 let compat_len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
163 let compat_total = COMPAT_DESC_LEN_FIELD + compat_len;
164 if pos + compat_total > end {
165 return Err(Error::SectionLengthOverflow {
166 declared: compat_len,
167 available: end - pos - COMPAT_DESC_LEN_FIELD,
168 });
169 }
170 let group_compatibility =
171 CompatibilityDescriptor::parse(&bytes[pos..pos + compat_total])?;
172 pos += compat_total;
173
174 if pos + GII_GROUP_INFO_LEN_FIELD > end {
176 return Err(Error::BufferTooShort {
177 need: pos + GII_GROUP_INFO_LEN_FIELD,
178 have: end,
179 what: "GroupInfo GroupInfoLength",
180 });
181 }
182 let group_info_len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
183 pos += GII_GROUP_INFO_LEN_FIELD;
184 if pos + group_info_len > end {
185 return Err(Error::SectionLengthOverflow {
186 declared: group_info_len,
187 available: end - pos,
188 });
189 }
190 let group_info = &bytes[pos..pos + group_info_len];
191 pos += group_info_len;
192
193 if pos + GII_PRIVATE_DATA_LEN_FIELD > end {
195 return Err(Error::BufferTooShort {
196 need: pos + GII_PRIVATE_DATA_LEN_FIELD,
197 have: end,
198 what: "GroupInfo PrivateDataLength",
199 });
200 }
201 let private_data_len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
202 pos += GII_PRIVATE_DATA_LEN_FIELD;
203 if pos + private_data_len > end {
204 return Err(Error::SectionLengthOverflow {
205 declared: private_data_len,
206 available: end - pos,
207 });
208 }
209 let private_data = &bytes[pos..pos + private_data_len];
210 pos += private_data_len;
211
212 groups.push(GroupInfo {
213 group_id,
214 group_size,
215 group_compatibility,
216 group_info,
217 private_data,
218 });
219 }
220
221 Ok(GroupInfoIndication { groups })
222 }
223}
224
225impl Serialize for GroupInfoIndication<'_> {
226 type Error = crate::error::Error;
227
228 fn serialized_len(&self) -> usize {
229 GII_NUMBER_OF_GROUPS_LEN
230 + self
231 .groups
232 .iter()
233 .map(|g| {
234 GII_GROUP_ID_LEN
235 + GII_GROUP_SIZE_LEN
236 + g.group_compatibility.serialized_len()
237 + GII_GROUP_INFO_LEN_FIELD
238 + g.group_info.len()
239 + GII_PRIVATE_DATA_LEN_FIELD
240 + g.private_data.len()
241 })
242 .sum::<usize>()
243 }
244
245 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
246 let len = self.serialized_len();
247 if buf.len() < len {
248 return Err(Error::OutputBufferTooSmall {
249 need: len,
250 have: buf.len(),
251 });
252 }
253 if self.groups.len() > u16::MAX as usize {
254 return Err(Error::SectionLengthOverflow {
255 declared: self.groups.len(),
256 available: u16::MAX as usize,
257 });
258 }
259 buf[0..2].copy_from_slice(&(self.groups.len() as u16).to_be_bytes());
260 let mut pos = GII_NUMBER_OF_GROUPS_LEN;
261
262 for g in &self.groups {
263 buf[pos..pos + 4].copy_from_slice(&g.group_id.to_be_bytes());
264 buf[pos + 4..pos + 8].copy_from_slice(&g.group_size.to_be_bytes());
265 pos += GII_GROUP_ID_LEN + GII_GROUP_SIZE_LEN;
266
267 let written = g.group_compatibility.serialize_into(&mut buf[pos..])?;
268 pos += written;
269
270 if g.group_info.len() > u16::MAX as usize {
271 return Err(Error::SectionLengthOverflow {
272 declared: g.group_info.len(),
273 available: u16::MAX as usize,
274 });
275 }
276 buf[pos..pos + 2].copy_from_slice(&(g.group_info.len() as u16).to_be_bytes());
277 pos += GII_GROUP_INFO_LEN_FIELD;
278 buf[pos..pos + g.group_info.len()].copy_from_slice(g.group_info);
279 pos += g.group_info.len();
280
281 if g.private_data.len() > u16::MAX as usize {
282 return Err(Error::SectionLengthOverflow {
283 declared: g.private_data.len(),
284 available: u16::MAX as usize,
285 });
286 }
287 buf[pos..pos + 2].copy_from_slice(&(g.private_data.len() as u16).to_be_bytes());
288 pos += GII_PRIVATE_DATA_LEN_FIELD;
289 buf[pos..pos + g.private_data.len()].copy_from_slice(g.private_data);
290 pos += g.private_data.len();
291 }
292
293 Ok(len)
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize))]
300pub struct Dsi<'a> {
301 pub transaction_id: u32,
304 pub adaptation: &'a [u8],
306 pub server_id: [u8; SERVER_ID_LEN],
308 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
310 pub private_data: &'a [u8],
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
317#[cfg_attr(feature = "serde", derive(serde::Serialize))]
318pub struct DiiModule<'a> {
319 pub module_id: u16,
321 pub module_size: u32,
323 pub module_version: u8,
325 pub module_info: &'a [u8],
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
332#[cfg_attr(feature = "serde", derive(serde::Serialize))]
333pub struct Dii<'a> {
334 pub transaction_id: u32,
336 pub adaptation: &'a [u8],
338 pub download_id: u32,
340 pub block_size: u16,
342 pub window_size: u8,
344 pub ack_period: u8,
346 pub t_c_download_window: u32,
348 pub t_c_download_scenario: u32,
350 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
352 pub modules: Vec<DiiModule<'a>>,
354 pub private_data: &'a [u8],
356}
357
358#[derive(Debug, Clone, PartialEq, Eq)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize))]
362#[non_exhaustive]
363pub enum UnMessage<'a> {
364 Dsi(Dsi<'a>),
366 Dii(Dii<'a>),
368}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
373#[cfg_attr(feature = "serde", derive(serde::Serialize))]
374pub struct DownloadDataBlock<'a> {
375 pub download_id: u32,
377 pub adaptation: &'a [u8],
379 pub module_id: u16,
381 pub module_version: u8,
383 pub block_number: u16,
385 pub block_data: &'a [u8],
387}
388
389fn parse_header<'a>(bytes: &'a [u8], what: &'static str) -> Result<(u16, u32, &'a [u8], &'a [u8])> {
393 if bytes.len() < MESSAGE_HEADER_LEN {
394 return Err(Error::BufferTooShort {
395 need: MESSAGE_HEADER_LEN,
396 have: bytes.len(),
397 what,
398 });
399 }
400 if bytes[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 bytes[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([bytes[2], bytes[3]]);
413 let id = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
414 let adaptation_length = bytes[9] as usize;
415 let message_length = u16::from_be_bytes([bytes[10], bytes[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 if pos + 2 > end {
470 return Err(Error::BufferTooShort {
471 need: pos + 2,
472 have: end,
473 what: "DSM-CC 16-bit length field",
474 });
475 }
476 let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) 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 if offset + COMPAT_DESC_LEN_FIELD > end {
496 return Err(Error::BufferTooShort {
497 need: offset + COMPAT_DESC_LEN_FIELD,
498 have: end,
499 what: "compatibilityDescriptor in DSM-CC message",
500 });
501 }
502 let compat_desc_len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) 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 if end < DII_FIXED_LEN {
545 return Err(Error::BufferTooShort {
546 need: DII_FIXED_LEN,
547 have: end,
548 what: "Dii body",
549 });
550 }
551 let download_id =
552 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
553 let block_size = u16::from_be_bytes([payload[4], payload[5]]);
554 let window_size = payload[6];
555 let ack_period = payload[7];
556 let t_c_download_window =
557 u32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
558 let t_c_download_scenario =
559 u32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
560 let (compatibility_descriptor, mut pos) =
561 parse_compat_block(payload, DII_FIXED_LEN, end)?;
562 if pos + 2 > end {
563 return Err(Error::BufferTooShort {
564 need: pos + 2,
565 have: end,
566 what: "Dii numberOfModules",
567 });
568 }
569 let number_of_modules =
570 u16::from_be_bytes([payload[pos], payload[pos + 1]]) as usize;
571 pos += 2;
572 let mut modules = Vec::with_capacity(number_of_modules.min(256));
573 for _ in 0..number_of_modules {
574 if pos + MODULE_HEADER_LEN > end {
575 return Err(Error::BufferTooShort {
576 need: pos + MODULE_HEADER_LEN,
577 have: end,
578 what: "Dii module entry",
579 });
580 }
581 let module_id = u16::from_be_bytes([payload[pos], payload[pos + 1]]);
582 let module_size = u32::from_be_bytes([
583 payload[pos + 2],
584 payload[pos + 3],
585 payload[pos + 4],
586 payload[pos + 5],
587 ]);
588 let module_version = payload[pos + 6];
589 let module_info_length = payload[pos + 7] as usize;
590 let info_start = pos + MODULE_HEADER_LEN;
591 if info_start + module_info_length > end {
592 return Err(Error::SectionLengthOverflow {
593 declared: module_info_length,
594 available: end - info_start,
595 });
596 }
597 modules.push(DiiModule {
598 module_id,
599 module_size,
600 module_version,
601 module_info: &payload[info_start..info_start + module_info_length],
602 });
603 pos = info_start + module_info_length;
604 }
605 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
606 Ok(UnMessage::Dii(Dii {
607 transaction_id,
608 adaptation,
609 download_id,
610 block_size,
611 window_size,
612 ack_period,
613 t_c_download_window,
614 t_c_download_scenario,
615 compatibility_descriptor,
616 modules,
617 private_data,
618 }))
619 }
620 _ => Err(Error::ReservedBitsViolation {
621 field: "messageId",
622 reason: "expected 0x1002 (DII) or 0x1006 (DSI) on table_id 0x3B \
623 (ISO/IEC 13818-6 §7.3)",
624 }),
625 }
626 }
627}
628
629impl Serialize for UnMessage<'_> {
630 type Error = crate::error::Error;
631
632 fn serialized_len(&self) -> usize {
633 match self {
634 UnMessage::Dsi(dsi) => {
635 MESSAGE_HEADER_LEN
636 + dsi.adaptation.len()
637 + SERVER_ID_LEN
638 + dsi.compatibility_descriptor.serialized_len()
639 + PRIVATE_LEN_FIELD
640 + dsi.private_data.len()
641 }
642 UnMessage::Dii(dii) => {
643 MESSAGE_HEADER_LEN
644 + dii.adaptation.len()
645 + DII_FIXED_LEN
646 + dii.compatibility_descriptor.serialized_len()
647 + 2 + dii
649 .modules
650 .iter()
651 .map(|m| MODULE_HEADER_LEN + m.module_info.len())
652 .sum::<usize>()
653 + PRIVATE_LEN_FIELD
654 + dii.private_data.len()
655 }
656 }
657 }
658
659 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
660 let len = self.serialized_len();
661 if buf.len() < len {
662 return Err(Error::OutputBufferTooSmall {
663 need: len,
664 have: buf.len(),
665 });
666 }
667 match self {
668 UnMessage::Dsi(dsi) => {
669 let payload_len = len - MESSAGE_HEADER_LEN - dsi.adaptation.len();
670 let mut pos = serialize_header(
671 buf,
672 MESSAGE_ID_DSI,
673 dsi.transaction_id,
674 dsi.adaptation,
675 payload_len,
676 )?;
677 buf[pos..pos + SERVER_ID_LEN].copy_from_slice(&dsi.server_id);
678 pos += SERVER_ID_LEN;
679 let written = dsi
680 .compatibility_descriptor
681 .serialize_into(&mut buf[pos..])?;
682 pos += written;
683 put_length_prefixed(buf, pos, dsi.private_data)?;
684 }
685 UnMessage::Dii(dii) => {
686 let payload_len = len - MESSAGE_HEADER_LEN - dii.adaptation.len();
687 let mut pos = serialize_header(
688 buf,
689 MESSAGE_ID_DII,
690 dii.transaction_id,
691 dii.adaptation,
692 payload_len,
693 )?;
694 buf[pos..pos + 4].copy_from_slice(&dii.download_id.to_be_bytes());
695 buf[pos + 4..pos + 6].copy_from_slice(&dii.block_size.to_be_bytes());
696 buf[pos + 6] = dii.window_size;
697 buf[pos + 7] = dii.ack_period;
698 buf[pos + 8..pos + 12].copy_from_slice(&dii.t_c_download_window.to_be_bytes());
699 buf[pos + 12..pos + 16].copy_from_slice(&dii.t_c_download_scenario.to_be_bytes());
700 pos += DII_FIXED_LEN;
701 let written = dii
702 .compatibility_descriptor
703 .serialize_into(&mut buf[pos..])?;
704 pos += written;
705 if dii.modules.len() > u16::MAX as usize {
706 return Err(Error::SectionLengthOverflow {
707 declared: dii.modules.len(),
708 available: u16::MAX as usize,
709 });
710 }
711 buf[pos..pos + 2].copy_from_slice(&(dii.modules.len() as u16).to_be_bytes());
712 pos += 2;
713 for m in &dii.modules {
714 if m.module_info.len() > u8::MAX as usize {
715 return Err(Error::SectionLengthOverflow {
716 declared: m.module_info.len(),
717 available: u8::MAX as usize,
718 });
719 }
720 buf[pos..pos + 2].copy_from_slice(&m.module_id.to_be_bytes());
721 buf[pos + 2..pos + 6].copy_from_slice(&m.module_size.to_be_bytes());
722 buf[pos + 6] = m.module_version;
723 buf[pos + 7] = m.module_info.len() as u8;
724 pos += MODULE_HEADER_LEN;
725 buf[pos..pos + m.module_info.len()].copy_from_slice(m.module_info);
726 pos += m.module_info.len();
727 }
728 put_length_prefixed(buf, pos, dii.private_data)?;
729 }
730 }
731 Ok(len)
732 }
733}
734
735fn put_length_prefixed(buf: &mut [u8], pos: usize, data: &[u8]) -> Result<usize> {
737 if data.len() > u16::MAX as usize {
738 return Err(Error::SectionLengthOverflow {
739 declared: data.len(),
740 available: u16::MAX as usize,
741 });
742 }
743 buf[pos..pos + 2].copy_from_slice(&(data.len() as u16).to_be_bytes());
744 buf[pos + 2..pos + 2 + data.len()].copy_from_slice(data);
745 Ok(pos + 2 + data.len())
746}
747
748impl<'a> Parse<'a> for DownloadDataBlock<'a> {
749 type Error = crate::error::Error;
750
751 fn parse(bytes: &'a [u8]) -> Result<Self> {
752 let (message_id, download_id, adaptation, payload) =
753 parse_header(bytes, "DownloadDataBlock header")?;
754 if message_id != MESSAGE_ID_DDB {
755 return Err(Error::ReservedBitsViolation {
756 field: "messageId",
757 reason: "expected 0x1003 (DDB) on table_id 0x3C (ISO/IEC 13818-6 §7.3.7)",
758 });
759 }
760 if payload.len() < DDB_FIXED_LEN {
761 return Err(Error::BufferTooShort {
762 need: DDB_FIXED_LEN,
763 have: payload.len(),
764 what: "DownloadDataBlock body",
765 });
766 }
767 Ok(DownloadDataBlock {
768 download_id,
769 adaptation,
770 module_id: u16::from_be_bytes([payload[0], payload[1]]),
771 module_version: payload[2],
772 block_number: u16::from_be_bytes([payload[4], payload[5]]),
773 block_data: &payload[DDB_FIXED_LEN..],
774 })
775 }
776}
777
778impl Serialize for DownloadDataBlock<'_> {
779 type Error = crate::error::Error;
780
781 fn serialized_len(&self) -> usize {
782 MESSAGE_HEADER_LEN + self.adaptation.len() + DDB_FIXED_LEN + self.block_data.len()
783 }
784
785 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
786 let len = self.serialized_len();
787 if buf.len() < len {
788 return Err(Error::OutputBufferTooSmall {
789 need: len,
790 have: buf.len(),
791 });
792 }
793 let payload_len = DDB_FIXED_LEN + self.block_data.len();
794 let pos = serialize_header(
795 buf,
796 MESSAGE_ID_DDB,
797 self.download_id,
798 self.adaptation,
799 payload_len,
800 )?;
801 buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
802 buf[pos + 2] = self.module_version;
803 buf[pos + 3] = 0xFF; buf[pos + 4..pos + 6].copy_from_slice(&self.block_number.to_be_bytes());
805 buf[pos + DDB_FIXED_LEN..pos + DDB_FIXED_LEN + self.block_data.len()]
806 .copy_from_slice(self.block_data);
807 Ok(len)
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814
815 fn sample_dsi() -> UnMessage<'static> {
816 UnMessage::Dsi(Dsi {
817 transaction_id: 0x8000_0000,
818 adaptation: &[],
819 server_id: [0xFF; 20],
820 compatibility_descriptor: CompatibilityDescriptor {
821 descriptors: vec![],
822 },
823 private_data: &[0x0A, 0x0B],
824 })
825 }
826
827 fn sample_dii() -> UnMessage<'static> {
828 UnMessage::Dii(Dii {
829 transaction_id: 0x8002_0002,
830 adaptation: &[],
831 download_id: 0x0000_00AB,
832 block_size: 4066,
833 window_size: 0,
834 ack_period: 0,
835 t_c_download_window: 0,
836 t_c_download_scenario: 0,
837 compatibility_descriptor: CompatibilityDescriptor {
838 descriptors: vec![],
839 },
840 modules: vec![
841 DiiModule {
842 module_id: 1,
843 module_size: 8000,
844 module_version: 3,
845 module_info: &[0xDE, 0xAD],
846 },
847 DiiModule {
848 module_id: 2,
849 module_size: 100,
850 module_version: 1,
851 module_info: &[],
852 },
853 ],
854 private_data: &[],
855 })
856 }
857
858 #[test]
859 fn dsi_round_trip() {
860 let msg = sample_dsi();
861 let mut buf = vec![0u8; msg.serialized_len()];
862 msg.serialize_into(&mut buf).unwrap();
863 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
864 }
865
866 #[test]
867 fn dii_round_trip() {
868 let msg = sample_dii();
869 let mut buf = vec![0u8; msg.serialized_len()];
870 msg.serialize_into(&mut buf).unwrap();
871 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
872 }
873
874 fn nonempty_compat() -> CompatibilityDescriptor<'static> {
878 CompatibilityDescriptor {
879 descriptors: vec![crate::compatibility::CompatibilityDescriptorEntry {
880 descriptor_type: crate::compatibility::DescriptorType::SystemHardware,
881 specifier_type: crate::compatibility::SpecifierType::IeeeOui,
882 specifier_data: [0x00, 0x15, 0x0A],
883 model: 0x1234,
884 version: 0x0001,
885 sub_descriptors: vec![crate::compatibility::SubDescriptor {
886 sub_descriptor_type: crate::compatibility::SubDescriptorType::Unallocated(0x05),
887 data: &[0xAA, 0xBB],
888 }],
889 }],
890 }
891 }
892
893 #[test]
894 fn dsi_with_compat_round_trip() {
895 let msg = UnMessage::Dsi(Dsi {
896 transaction_id: 0x8000_0000,
897 adaptation: &[],
898 server_id: [0xFF; 20],
899 compatibility_descriptor: nonempty_compat(),
900 private_data: &[0x0A, 0x0B],
901 });
902 let mut buf = vec![0u8; msg.serialized_len()];
903 msg.serialize_into(&mut buf).unwrap();
904 let re = UnMessage::parse(&buf).unwrap();
905 assert_eq!(re, msg);
906 let mut buf2 = vec![0u8; re.serialized_len()];
907 re.serialize_into(&mut buf2).unwrap();
908 assert_eq!(buf, buf2, "byte-exact re-serialize");
909 }
910
911 #[test]
912 fn dii_with_compat_round_trip() {
913 let msg = UnMessage::Dii(Dii {
914 transaction_id: 0x8002_0002,
915 adaptation: &[],
916 download_id: 0x0000_00AB,
917 block_size: 4066,
918 window_size: 0,
919 ack_period: 0,
920 t_c_download_window: 0,
921 t_c_download_scenario: 0,
922 compatibility_descriptor: nonempty_compat(),
923 modules: vec![DiiModule {
924 module_id: 1,
925 module_size: 8000,
926 module_version: 3,
927 module_info: &[0xDE, 0xAD],
928 }],
929 private_data: &[],
930 });
931 let mut buf = vec![0u8; msg.serialized_len()];
932 msg.serialize_into(&mut buf).unwrap();
933 let re = UnMessage::parse(&buf).unwrap();
934 assert_eq!(re, msg);
935 let mut buf2 = vec![0u8; re.serialized_len()];
936 re.serialize_into(&mut buf2).unwrap();
937 assert_eq!(buf, buf2, "byte-exact re-serialize");
938 }
939
940 #[test]
941 fn ddb_round_trip() {
942 let ddb = DownloadDataBlock {
943 download_id: 0xAB,
944 adaptation: &[],
945 module_id: 1,
946 module_version: 3,
947 block_number: 2,
948 block_data: &[0x55; 64],
949 };
950 let mut buf = vec![0u8; ddb.serialized_len()];
951 ddb.serialize_into(&mut buf).unwrap();
952 assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
953 }
954
955 #[test]
956 fn header_fields_on_wire() {
957 let msg = sample_dsi();
958 let mut buf = vec![0u8; msg.serialized_len()];
959 msg.serialize_into(&mut buf).unwrap();
960 assert_eq!(buf[0], 0x11); assert_eq!(buf[1], 0x03); assert_eq!(u16::from_be_bytes([buf[2], buf[3]]), MESSAGE_ID_DSI);
963 assert_eq!(buf[8], 0xFF); let ml = u16::from_be_bytes([buf[10], buf[11]]) as usize;
966 assert_eq!(ml, buf.len() - 12);
967 }
968
969 #[test]
970 fn parse_rejects_wrong_protocol_discriminator() {
971 let msg = sample_dsi();
972 let mut buf = vec![0u8; msg.serialized_len()];
973 msg.serialize_into(&mut buf).unwrap();
974 buf[0] = 0x12;
975 assert!(matches!(
976 UnMessage::parse(&buf).unwrap_err(),
977 Error::ReservedBitsViolation {
978 field: "protocolDiscriminator",
979 ..
980 }
981 ));
982 }
983
984 #[test]
985 fn parse_rejects_unknown_message_id() {
986 let msg = sample_dsi();
987 let mut buf = vec![0u8; msg.serialized_len()];
988 msg.serialize_into(&mut buf).unwrap();
989 buf[2] = 0x10;
990 buf[3] = 0x01; assert!(matches!(
992 UnMessage::parse(&buf).unwrap_err(),
993 Error::ReservedBitsViolation {
994 field: "messageId",
995 ..
996 }
997 ));
998 }
999
1000 #[test]
1001 fn parse_rejects_short_buffer() {
1002 assert!(matches!(
1003 UnMessage::parse(&[0x11, 0x03]).unwrap_err(),
1004 Error::BufferTooShort { .. }
1005 ));
1006 }
1007
1008 #[test]
1009 fn parse_rejects_message_length_overflow() {
1010 let msg = sample_dsi();
1011 let mut buf = vec![0u8; msg.serialized_len()];
1012 msg.serialize_into(&mut buf).unwrap();
1013 buf[10] = 0xFF;
1014 buf[11] = 0xFF; assert!(matches!(
1016 UnMessage::parse(&buf).unwrap_err(),
1017 Error::SectionLengthOverflow { .. }
1018 ));
1019 }
1020
1021 #[test]
1022 fn dii_module_info_overflow_rejected() {
1023 let msg = sample_dii();
1024 let mut buf = vec![0u8; msg.serialized_len()];
1025 msg.serialize_into(&mut buf).unwrap();
1026 buf[39] = 0xFF;
1029 assert!(matches!(
1030 UnMessage::parse(&buf).unwrap_err(),
1031 Error::SectionLengthOverflow { .. }
1032 ));
1033 }
1034
1035 #[test]
1036 fn ddb_rejects_un_message_id() {
1037 let msg = sample_dsi();
1038 let mut buf = vec![0u8; msg.serialized_len()];
1039 msg.serialize_into(&mut buf).unwrap();
1040 assert!(matches!(
1041 DownloadDataBlock::parse(&buf).unwrap_err(),
1042 Error::ReservedBitsViolation {
1043 field: "messageId",
1044 ..
1045 }
1046 ));
1047 }
1048
1049 #[test]
1050 fn adaptation_bytes_round_trip() {
1051 let ddb = DownloadDataBlock {
1052 download_id: 1,
1053 adaptation: &[0x01, 0x02, 0x03],
1054 module_id: 9,
1055 module_version: 0,
1056 block_number: 0,
1057 block_data: &[0xAA],
1058 };
1059 let mut buf = vec![0u8; ddb.serialized_len()];
1060 ddb.serialize_into(&mut buf).unwrap();
1061 assert_eq!(buf[9], 3); assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
1063 }
1064
1065 #[cfg(feature = "serde")]
1066 #[test]
1067 fn dii_serializes_to_valid_json() {
1068 let msg = sample_dii();
1069 let j = serde_json::to_string(&msg).unwrap();
1070 assert!(j.contains("\"download_id\":171"));
1071 assert!(j.contains("\"block_size\":4066"));
1072 }
1073
1074 fn sample_gii() -> GroupInfoIndication<'static> {
1093 GroupInfoIndication {
1094 groups: vec![GroupInfo {
1095 group_id: 0x0000_0001,
1096 group_size: 500_000,
1097 group_compatibility: CompatibilityDescriptor {
1098 descriptors: vec![],
1099 },
1100 group_info: &[0xCA, 0xFE],
1101 private_data: &[0xBB],
1102 }],
1103 }
1104 }
1105
1106 #[test]
1107 fn gii_round_trip() {
1108 let gii = sample_gii();
1109 let mut buf = vec![0u8; gii.serialized_len()];
1110 gii.serialize_into(&mut buf).unwrap();
1111 let re = GroupInfoIndication::parse(&buf).unwrap();
1112 assert_eq!(re, gii);
1113 let mut buf2 = vec![0u8; re.serialized_len()];
1115 re.serialize_into(&mut buf2).unwrap();
1116 assert_eq!(buf, buf2, "GII byte-exact re-serialize");
1117 }
1118
1119 #[test]
1123 fn gii_hand_built_byte_anchor() {
1124 #[rustfmt::skip]
1128 let expected: &[u8] = &[
1129 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0xA1, 0x20, 0x00, 0x00, 0x00, 0x02, 0xCA, 0xFE, 0x00, 0x01, 0xBB, ];
1138 let gii = sample_gii();
1139 let mut buf = vec![0u8; gii.serialized_len()];
1140 gii.serialize_into(&mut buf).unwrap();
1141 assert_eq!(buf.as_slice(), expected);
1142 let re = GroupInfoIndication::parse(expected).unwrap();
1143 assert_eq!(re, gii);
1144 }
1145
1146 #[test]
1147 fn gii_empty_groups() {
1148 let gii = GroupInfoIndication { groups: vec![] };
1149 let mut buf = vec![0u8; gii.serialized_len()];
1150 gii.serialize_into(&mut buf).unwrap();
1151 assert_eq!(buf, &[0x00, 0x00]); let re = GroupInfoIndication::parse(&buf).unwrap();
1153 assert!(re.groups.is_empty());
1154 }
1155
1156 #[test]
1157 fn gii_with_compat_round_trip() {
1158 let gii = GroupInfoIndication {
1159 groups: vec![GroupInfo {
1160 group_id: 0xDEAD_BEEF,
1161 group_size: 0x0001_0000,
1162 group_compatibility: nonempty_compat(),
1163 group_info: &[0x01, 0x02, 0x03],
1164 private_data: &[],
1165 }],
1166 };
1167 let mut buf = vec![0u8; gii.serialized_len()];
1168 gii.serialize_into(&mut buf).unwrap();
1169 let re = GroupInfoIndication::parse(&buf).unwrap();
1170 assert_eq!(re, gii);
1171 let mut buf2 = vec![0u8; re.serialized_len()];
1172 re.serialize_into(&mut buf2).unwrap();
1173 assert_eq!(buf, buf2, "GII with compat byte-exact re-serialize");
1174 }
1175
1176 #[test]
1177 fn gii_parse_rejects_short_buffer() {
1178 assert!(matches!(
1179 GroupInfoIndication::parse(&[0x00]).unwrap_err(),
1180 Error::BufferTooShort { .. }
1181 ));
1182 }
1183
1184 #[test]
1185 fn gii_parse_rejects_truncated_group() {
1186 let bytes = &[0x00, 0x01, 0x00, 0x00, 0x00];
1188 assert!(matches!(
1189 GroupInfoIndication::parse(bytes).unwrap_err(),
1190 Error::BufferTooShort { .. }
1191 ));
1192 }
1193
1194 #[cfg(feature = "serde")]
1195 #[test]
1196 fn gii_serde_round_trip() {
1197 let gii = sample_gii();
1198 let json = serde_json::to_string(&gii).unwrap();
1199 assert!(json.contains("\"group_id\":1"));
1200 assert!(json.contains("\"group_size\":500000"));
1201 assert!(json.contains("\"group_info\""));
1202 }
1203}