1use crate::compatibility::CompatibilityDescriptor;
13use crate::error::{Error, Result};
14use dvb_common::{Parse, Serialize};
15
16pub const PROTOCOL_DISCRIMINATOR: u8 = 0x11;
18pub const DSMCC_TYPE_UN_DOWNLOAD: u8 = 0x03;
20pub const MESSAGE_ID_DII: u16 = 0x1002;
22pub const MESSAGE_ID_DDB: u16 = 0x1003;
24pub const MESSAGE_ID_DSI: u16 = 0x1006;
26
27const MESSAGE_HEADER_LEN: usize = 12;
31const SERVER_ID_LEN: usize = 20;
33const PRIVATE_LEN_FIELD: usize = 2;
35const DII_FIXED_LEN: usize = 16;
39const MODULE_HEADER_LEN: usize = 8;
42const DDB_FIXED_LEN: usize = 6;
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49pub struct Dsi<'a> {
50 pub transaction_id: u32,
53 pub adaptation: &'a [u8],
55 pub server_id: [u8; SERVER_ID_LEN],
57 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
59 pub private_data: &'a [u8],
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67pub struct DiiModule<'a> {
68 pub module_id: u16,
70 pub module_size: u32,
72 pub module_version: u8,
74 pub module_info: &'a [u8],
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize))]
82pub struct Dii<'a> {
83 pub transaction_id: u32,
85 pub adaptation: &'a [u8],
87 pub download_id: u32,
89 pub block_size: u16,
91 pub window_size: u8,
93 pub ack_period: u8,
95 pub t_c_download_window: u32,
97 pub t_c_download_scenario: u32,
99 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
101 pub modules: Vec<DiiModule<'a>>,
103 pub private_data: &'a [u8],
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111#[non_exhaustive]
112pub enum UnMessage<'a> {
113 Dsi(Dsi<'a>),
115 Dii(Dii<'a>),
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize))]
123pub struct DownloadDataBlock<'a> {
124 pub download_id: u32,
126 pub adaptation: &'a [u8],
128 pub module_id: u16,
130 pub module_version: u8,
132 pub block_number: u16,
134 pub block_data: &'a [u8],
136}
137
138fn parse_header<'a>(bytes: &'a [u8], what: &'static str) -> Result<(u16, u32, &'a [u8], &'a [u8])> {
142 if bytes.len() < MESSAGE_HEADER_LEN {
143 return Err(Error::BufferTooShort {
144 need: MESSAGE_HEADER_LEN,
145 have: bytes.len(),
146 what,
147 });
148 }
149 if bytes[0] != PROTOCOL_DISCRIMINATOR {
150 return Err(Error::ReservedBitsViolation {
151 field: "protocolDiscriminator",
152 reason: "must be 0x11 (ISO/IEC 13818-6 §7.2)",
153 });
154 }
155 if bytes[1] != DSMCC_TYPE_UN_DOWNLOAD {
156 return Err(Error::ReservedBitsViolation {
157 field: "dsmccType",
158 reason: "must be 0x03 — U-N download (ISO/IEC 13818-6 §7.2)",
159 });
160 }
161 let message_id = u16::from_be_bytes([bytes[2], bytes[3]]);
162 let id = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
163 let adaptation_length = bytes[9] as usize;
164 let message_length = u16::from_be_bytes([bytes[10], bytes[11]]) as usize;
165 let total = MESSAGE_HEADER_LEN + message_length;
166 if bytes.len() < total {
167 return Err(Error::SectionLengthOverflow {
168 declared: message_length,
169 available: bytes.len() - MESSAGE_HEADER_LEN,
170 });
171 }
172 if adaptation_length > message_length {
173 return Err(Error::SectionLengthOverflow {
174 declared: adaptation_length,
175 available: message_length,
176 });
177 }
178 let adaptation = &bytes[MESSAGE_HEADER_LEN..MESSAGE_HEADER_LEN + adaptation_length];
179 let payload = &bytes[MESSAGE_HEADER_LEN + adaptation_length..total];
180 Ok((message_id, id, adaptation, payload))
181}
182
183fn serialize_header(
186 buf: &mut [u8],
187 message_id: u16,
188 id: u32,
189 adaptation: &[u8],
190 payload_len: usize,
191) -> Result<usize> {
192 let message_length = adaptation.len() + payload_len;
193 if adaptation.len() > u8::MAX as usize {
194 return Err(Error::SectionLengthOverflow {
195 declared: adaptation.len(),
196 available: u8::MAX as usize,
197 });
198 }
199 if message_length > u16::MAX as usize {
200 return Err(Error::SectionLengthOverflow {
201 declared: message_length,
202 available: u16::MAX as usize,
203 });
204 }
205 buf[0] = PROTOCOL_DISCRIMINATOR;
206 buf[1] = DSMCC_TYPE_UN_DOWNLOAD;
207 buf[2..4].copy_from_slice(&message_id.to_be_bytes());
208 buf[4..8].copy_from_slice(&id.to_be_bytes());
209 buf[8] = 0xFF; buf[9] = adaptation.len() as u8;
211 buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
212 buf[MESSAGE_HEADER_LEN..MESSAGE_HEADER_LEN + adaptation.len()].copy_from_slice(adaptation);
213 Ok(MESSAGE_HEADER_LEN + adaptation.len())
214}
215
216fn length_prefixed(bytes: &[u8], pos: usize, end: usize) -> Result<(&[u8], usize)> {
218 if pos + 2 > end {
219 return Err(Error::BufferTooShort {
220 need: pos + 2,
221 have: end,
222 what: "DSM-CC 16-bit length field",
223 });
224 }
225 let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
226 let start = pos + 2;
227 if start + len > end {
228 return Err(Error::SectionLengthOverflow {
229 declared: len,
230 available: end - start,
231 });
232 }
233 Ok((&bytes[start..start + len], start + len))
234}
235
236fn parse_compat_block<'a>(
239 payload: &'a [u8],
240 offset: usize,
241 end: usize,
242) -> Result<(CompatibilityDescriptor<'a>, usize)> {
243 use crate::compatibility::COMPAT_DESC_LEN_FIELD;
244 if offset + COMPAT_DESC_LEN_FIELD > end {
245 return Err(Error::BufferTooShort {
246 need: offset + COMPAT_DESC_LEN_FIELD,
247 have: end,
248 what: "compatibilityDescriptor in DSM-CC message",
249 });
250 }
251 let compat_desc_len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
252 let compat_end = offset + COMPAT_DESC_LEN_FIELD + compat_desc_len;
253 if compat_end > end {
254 return Err(Error::SectionLengthOverflow {
255 declared: compat_desc_len,
256 available: end - offset - COMPAT_DESC_LEN_FIELD,
257 });
258 }
259 let cd = CompatibilityDescriptor::parse(&payload[offset..compat_end])?;
260 Ok((cd, compat_end))
261}
262
263impl<'a> Parse<'a> for UnMessage<'a> {
264 type Error = crate::error::Error;
265
266 fn parse(bytes: &'a [u8]) -> Result<Self> {
267 let (message_id, transaction_id, adaptation, payload) =
268 parse_header(bytes, "UnMessage header")?;
269 let end = payload.len();
270 match message_id {
271 MESSAGE_ID_DSI => {
272 if end < SERVER_ID_LEN {
273 return Err(Error::BufferTooShort {
274 need: SERVER_ID_LEN,
275 have: end,
276 what: "Dsi body",
277 });
278 }
279 let mut server_id = [0u8; SERVER_ID_LEN];
280 server_id.copy_from_slice(&payload[..SERVER_ID_LEN]);
281 let (compatibility_descriptor, pos) =
282 parse_compat_block(payload, SERVER_ID_LEN, end)?;
283 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
284 Ok(UnMessage::Dsi(Dsi {
285 transaction_id,
286 adaptation,
287 server_id,
288 compatibility_descriptor,
289 private_data,
290 }))
291 }
292 MESSAGE_ID_DII => {
293 if end < DII_FIXED_LEN {
294 return Err(Error::BufferTooShort {
295 need: DII_FIXED_LEN,
296 have: end,
297 what: "Dii body",
298 });
299 }
300 let download_id =
301 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
302 let block_size = u16::from_be_bytes([payload[4], payload[5]]);
303 let window_size = payload[6];
304 let ack_period = payload[7];
305 let t_c_download_window =
306 u32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
307 let t_c_download_scenario =
308 u32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
309 let (compatibility_descriptor, mut pos) =
310 parse_compat_block(payload, DII_FIXED_LEN, end)?;
311 if pos + 2 > end {
312 return Err(Error::BufferTooShort {
313 need: pos + 2,
314 have: end,
315 what: "Dii numberOfModules",
316 });
317 }
318 let number_of_modules =
319 u16::from_be_bytes([payload[pos], payload[pos + 1]]) as usize;
320 pos += 2;
321 let mut modules = Vec::with_capacity(number_of_modules.min(256));
322 for _ in 0..number_of_modules {
323 if pos + MODULE_HEADER_LEN > end {
324 return Err(Error::BufferTooShort {
325 need: pos + MODULE_HEADER_LEN,
326 have: end,
327 what: "Dii module entry",
328 });
329 }
330 let module_id = u16::from_be_bytes([payload[pos], payload[pos + 1]]);
331 let module_size = u32::from_be_bytes([
332 payload[pos + 2],
333 payload[pos + 3],
334 payload[pos + 4],
335 payload[pos + 5],
336 ]);
337 let module_version = payload[pos + 6];
338 let module_info_length = payload[pos + 7] as usize;
339 let info_start = pos + MODULE_HEADER_LEN;
340 if info_start + module_info_length > end {
341 return Err(Error::SectionLengthOverflow {
342 declared: module_info_length,
343 available: end - info_start,
344 });
345 }
346 modules.push(DiiModule {
347 module_id,
348 module_size,
349 module_version,
350 module_info: &payload[info_start..info_start + module_info_length],
351 });
352 pos = info_start + module_info_length;
353 }
354 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
355 Ok(UnMessage::Dii(Dii {
356 transaction_id,
357 adaptation,
358 download_id,
359 block_size,
360 window_size,
361 ack_period,
362 t_c_download_window,
363 t_c_download_scenario,
364 compatibility_descriptor,
365 modules,
366 private_data,
367 }))
368 }
369 _ => Err(Error::ReservedBitsViolation {
370 field: "messageId",
371 reason: "expected 0x1002 (DII) or 0x1006 (DSI) on table_id 0x3B \
372 (ISO/IEC 13818-6 §7.3)",
373 }),
374 }
375 }
376}
377
378impl Serialize for UnMessage<'_> {
379 type Error = crate::error::Error;
380
381 fn serialized_len(&self) -> usize {
382 match self {
383 UnMessage::Dsi(dsi) => {
384 MESSAGE_HEADER_LEN
385 + dsi.adaptation.len()
386 + SERVER_ID_LEN
387 + dsi.compatibility_descriptor.serialized_len()
388 + PRIVATE_LEN_FIELD
389 + dsi.private_data.len()
390 }
391 UnMessage::Dii(dii) => {
392 MESSAGE_HEADER_LEN
393 + dii.adaptation.len()
394 + DII_FIXED_LEN
395 + dii.compatibility_descriptor.serialized_len()
396 + 2 + dii
398 .modules
399 .iter()
400 .map(|m| MODULE_HEADER_LEN + m.module_info.len())
401 .sum::<usize>()
402 + PRIVATE_LEN_FIELD
403 + dii.private_data.len()
404 }
405 }
406 }
407
408 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
409 let len = self.serialized_len();
410 if buf.len() < len {
411 return Err(Error::OutputBufferTooSmall {
412 need: len,
413 have: buf.len(),
414 });
415 }
416 match self {
417 UnMessage::Dsi(dsi) => {
418 let payload_len = len - MESSAGE_HEADER_LEN - dsi.adaptation.len();
419 let mut pos = serialize_header(
420 buf,
421 MESSAGE_ID_DSI,
422 dsi.transaction_id,
423 dsi.adaptation,
424 payload_len,
425 )?;
426 buf[pos..pos + SERVER_ID_LEN].copy_from_slice(&dsi.server_id);
427 pos += SERVER_ID_LEN;
428 let written = dsi
429 .compatibility_descriptor
430 .serialize_into(&mut buf[pos..])?;
431 pos += written;
432 put_length_prefixed(buf, pos, dsi.private_data)?;
433 }
434 UnMessage::Dii(dii) => {
435 let payload_len = len - MESSAGE_HEADER_LEN - dii.adaptation.len();
436 let mut pos = serialize_header(
437 buf,
438 MESSAGE_ID_DII,
439 dii.transaction_id,
440 dii.adaptation,
441 payload_len,
442 )?;
443 buf[pos..pos + 4].copy_from_slice(&dii.download_id.to_be_bytes());
444 buf[pos + 4..pos + 6].copy_from_slice(&dii.block_size.to_be_bytes());
445 buf[pos + 6] = dii.window_size;
446 buf[pos + 7] = dii.ack_period;
447 buf[pos + 8..pos + 12].copy_from_slice(&dii.t_c_download_window.to_be_bytes());
448 buf[pos + 12..pos + 16].copy_from_slice(&dii.t_c_download_scenario.to_be_bytes());
449 pos += DII_FIXED_LEN;
450 let written = dii
451 .compatibility_descriptor
452 .serialize_into(&mut buf[pos..])?;
453 pos += written;
454 if dii.modules.len() > u16::MAX as usize {
455 return Err(Error::SectionLengthOverflow {
456 declared: dii.modules.len(),
457 available: u16::MAX as usize,
458 });
459 }
460 buf[pos..pos + 2].copy_from_slice(&(dii.modules.len() as u16).to_be_bytes());
461 pos += 2;
462 for m in &dii.modules {
463 if m.module_info.len() > u8::MAX as usize {
464 return Err(Error::SectionLengthOverflow {
465 declared: m.module_info.len(),
466 available: u8::MAX as usize,
467 });
468 }
469 buf[pos..pos + 2].copy_from_slice(&m.module_id.to_be_bytes());
470 buf[pos + 2..pos + 6].copy_from_slice(&m.module_size.to_be_bytes());
471 buf[pos + 6] = m.module_version;
472 buf[pos + 7] = m.module_info.len() as u8;
473 pos += MODULE_HEADER_LEN;
474 buf[pos..pos + m.module_info.len()].copy_from_slice(m.module_info);
475 pos += m.module_info.len();
476 }
477 put_length_prefixed(buf, pos, dii.private_data)?;
478 }
479 }
480 Ok(len)
481 }
482}
483
484fn put_length_prefixed(buf: &mut [u8], pos: usize, data: &[u8]) -> Result<usize> {
486 if data.len() > u16::MAX as usize {
487 return Err(Error::SectionLengthOverflow {
488 declared: data.len(),
489 available: u16::MAX as usize,
490 });
491 }
492 buf[pos..pos + 2].copy_from_slice(&(data.len() as u16).to_be_bytes());
493 buf[pos + 2..pos + 2 + data.len()].copy_from_slice(data);
494 Ok(pos + 2 + data.len())
495}
496
497impl<'a> Parse<'a> for DownloadDataBlock<'a> {
498 type Error = crate::error::Error;
499
500 fn parse(bytes: &'a [u8]) -> Result<Self> {
501 let (message_id, download_id, adaptation, payload) =
502 parse_header(bytes, "DownloadDataBlock header")?;
503 if message_id != MESSAGE_ID_DDB {
504 return Err(Error::ReservedBitsViolation {
505 field: "messageId",
506 reason: "expected 0x1003 (DDB) on table_id 0x3C (ISO/IEC 13818-6 §7.3.7)",
507 });
508 }
509 if payload.len() < DDB_FIXED_LEN {
510 return Err(Error::BufferTooShort {
511 need: DDB_FIXED_LEN,
512 have: payload.len(),
513 what: "DownloadDataBlock body",
514 });
515 }
516 Ok(DownloadDataBlock {
517 download_id,
518 adaptation,
519 module_id: u16::from_be_bytes([payload[0], payload[1]]),
520 module_version: payload[2],
521 block_number: u16::from_be_bytes([payload[4], payload[5]]),
522 block_data: &payload[DDB_FIXED_LEN..],
523 })
524 }
525}
526
527impl Serialize for DownloadDataBlock<'_> {
528 type Error = crate::error::Error;
529
530 fn serialized_len(&self) -> usize {
531 MESSAGE_HEADER_LEN + self.adaptation.len() + DDB_FIXED_LEN + self.block_data.len()
532 }
533
534 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
535 let len = self.serialized_len();
536 if buf.len() < len {
537 return Err(Error::OutputBufferTooSmall {
538 need: len,
539 have: buf.len(),
540 });
541 }
542 let payload_len = DDB_FIXED_LEN + self.block_data.len();
543 let pos = serialize_header(
544 buf,
545 MESSAGE_ID_DDB,
546 self.download_id,
547 self.adaptation,
548 payload_len,
549 )?;
550 buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
551 buf[pos + 2] = self.module_version;
552 buf[pos + 3] = 0xFF; buf[pos + 4..pos + 6].copy_from_slice(&self.block_number.to_be_bytes());
554 buf[pos + DDB_FIXED_LEN..pos + DDB_FIXED_LEN + self.block_data.len()]
555 .copy_from_slice(self.block_data);
556 Ok(len)
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 fn sample_dsi() -> UnMessage<'static> {
565 UnMessage::Dsi(Dsi {
566 transaction_id: 0x8000_0000,
567 adaptation: &[],
568 server_id: [0xFF; 20],
569 compatibility_descriptor: CompatibilityDescriptor {
570 descriptors: vec![],
571 },
572 private_data: &[0x0A, 0x0B],
573 })
574 }
575
576 fn sample_dii() -> UnMessage<'static> {
577 UnMessage::Dii(Dii {
578 transaction_id: 0x8002_0002,
579 adaptation: &[],
580 download_id: 0x0000_00AB,
581 block_size: 4066,
582 window_size: 0,
583 ack_period: 0,
584 t_c_download_window: 0,
585 t_c_download_scenario: 0,
586 compatibility_descriptor: CompatibilityDescriptor {
587 descriptors: vec![],
588 },
589 modules: vec![
590 DiiModule {
591 module_id: 1,
592 module_size: 8000,
593 module_version: 3,
594 module_info: &[0xDE, 0xAD],
595 },
596 DiiModule {
597 module_id: 2,
598 module_size: 100,
599 module_version: 1,
600 module_info: &[],
601 },
602 ],
603 private_data: &[],
604 })
605 }
606
607 #[test]
608 fn dsi_round_trip() {
609 let msg = sample_dsi();
610 let mut buf = vec![0u8; msg.serialized_len()];
611 msg.serialize_into(&mut buf).unwrap();
612 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
613 }
614
615 #[test]
616 fn dii_round_trip() {
617 let msg = sample_dii();
618 let mut buf = vec![0u8; msg.serialized_len()];
619 msg.serialize_into(&mut buf).unwrap();
620 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
621 }
622
623 fn nonempty_compat() -> CompatibilityDescriptor<'static> {
627 CompatibilityDescriptor {
628 descriptors: vec![crate::compatibility::CompatibilityDescriptorEntry {
629 descriptor_type: crate::compatibility::DescriptorType::SystemHardware,
630 specifier_type: crate::compatibility::SpecifierType::IeeeOui,
631 specifier_data: [0x00, 0x15, 0x0A],
632 model: 0x1234,
633 version: 0x0001,
634 sub_descriptors: vec![crate::compatibility::SubDescriptor {
635 sub_descriptor_type: crate::compatibility::SubDescriptorType::Unallocated(0x05),
636 data: &[0xAA, 0xBB],
637 }],
638 }],
639 }
640 }
641
642 #[test]
643 fn dsi_with_compat_round_trip() {
644 let msg = UnMessage::Dsi(Dsi {
645 transaction_id: 0x8000_0000,
646 adaptation: &[],
647 server_id: [0xFF; 20],
648 compatibility_descriptor: nonempty_compat(),
649 private_data: &[0x0A, 0x0B],
650 });
651 let mut buf = vec![0u8; msg.serialized_len()];
652 msg.serialize_into(&mut buf).unwrap();
653 let re = UnMessage::parse(&buf).unwrap();
654 assert_eq!(re, msg);
655 let mut buf2 = vec![0u8; re.serialized_len()];
656 re.serialize_into(&mut buf2).unwrap();
657 assert_eq!(buf, buf2, "byte-exact re-serialize");
658 }
659
660 #[test]
661 fn dii_with_compat_round_trip() {
662 let msg = UnMessage::Dii(Dii {
663 transaction_id: 0x8002_0002,
664 adaptation: &[],
665 download_id: 0x0000_00AB,
666 block_size: 4066,
667 window_size: 0,
668 ack_period: 0,
669 t_c_download_window: 0,
670 t_c_download_scenario: 0,
671 compatibility_descriptor: nonempty_compat(),
672 modules: vec![DiiModule {
673 module_id: 1,
674 module_size: 8000,
675 module_version: 3,
676 module_info: &[0xDE, 0xAD],
677 }],
678 private_data: &[],
679 });
680 let mut buf = vec![0u8; msg.serialized_len()];
681 msg.serialize_into(&mut buf).unwrap();
682 let re = UnMessage::parse(&buf).unwrap();
683 assert_eq!(re, msg);
684 let mut buf2 = vec![0u8; re.serialized_len()];
685 re.serialize_into(&mut buf2).unwrap();
686 assert_eq!(buf, buf2, "byte-exact re-serialize");
687 }
688
689 #[test]
690 fn ddb_round_trip() {
691 let ddb = DownloadDataBlock {
692 download_id: 0xAB,
693 adaptation: &[],
694 module_id: 1,
695 module_version: 3,
696 block_number: 2,
697 block_data: &[0x55; 64],
698 };
699 let mut buf = vec![0u8; ddb.serialized_len()];
700 ddb.serialize_into(&mut buf).unwrap();
701 assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
702 }
703
704 #[test]
705 fn header_fields_on_wire() {
706 let msg = sample_dsi();
707 let mut buf = vec![0u8; msg.serialized_len()];
708 msg.serialize_into(&mut buf).unwrap();
709 assert_eq!(buf[0], 0x11); assert_eq!(buf[1], 0x03); assert_eq!(u16::from_be_bytes([buf[2], buf[3]]), MESSAGE_ID_DSI);
712 assert_eq!(buf[8], 0xFF); let ml = u16::from_be_bytes([buf[10], buf[11]]) as usize;
715 assert_eq!(ml, buf.len() - 12);
716 }
717
718 #[test]
719 fn parse_rejects_wrong_protocol_discriminator() {
720 let msg = sample_dsi();
721 let mut buf = vec![0u8; msg.serialized_len()];
722 msg.serialize_into(&mut buf).unwrap();
723 buf[0] = 0x12;
724 assert!(matches!(
725 UnMessage::parse(&buf).unwrap_err(),
726 Error::ReservedBitsViolation {
727 field: "protocolDiscriminator",
728 ..
729 }
730 ));
731 }
732
733 #[test]
734 fn parse_rejects_unknown_message_id() {
735 let msg = sample_dsi();
736 let mut buf = vec![0u8; msg.serialized_len()];
737 msg.serialize_into(&mut buf).unwrap();
738 buf[2] = 0x10;
739 buf[3] = 0x01; assert!(matches!(
741 UnMessage::parse(&buf).unwrap_err(),
742 Error::ReservedBitsViolation {
743 field: "messageId",
744 ..
745 }
746 ));
747 }
748
749 #[test]
750 fn parse_rejects_short_buffer() {
751 assert!(matches!(
752 UnMessage::parse(&[0x11, 0x03]).unwrap_err(),
753 Error::BufferTooShort { .. }
754 ));
755 }
756
757 #[test]
758 fn parse_rejects_message_length_overflow() {
759 let msg = sample_dsi();
760 let mut buf = vec![0u8; msg.serialized_len()];
761 msg.serialize_into(&mut buf).unwrap();
762 buf[10] = 0xFF;
763 buf[11] = 0xFF; assert!(matches!(
765 UnMessage::parse(&buf).unwrap_err(),
766 Error::SectionLengthOverflow { .. }
767 ));
768 }
769
770 #[test]
771 fn dii_module_info_overflow_rejected() {
772 let msg = sample_dii();
773 let mut buf = vec![0u8; msg.serialized_len()];
774 msg.serialize_into(&mut buf).unwrap();
775 buf[39] = 0xFF;
778 assert!(matches!(
779 UnMessage::parse(&buf).unwrap_err(),
780 Error::SectionLengthOverflow { .. }
781 ));
782 }
783
784 #[test]
785 fn ddb_rejects_un_message_id() {
786 let msg = sample_dsi();
787 let mut buf = vec![0u8; msg.serialized_len()];
788 msg.serialize_into(&mut buf).unwrap();
789 assert!(matches!(
790 DownloadDataBlock::parse(&buf).unwrap_err(),
791 Error::ReservedBitsViolation {
792 field: "messageId",
793 ..
794 }
795 ));
796 }
797
798 #[test]
799 fn adaptation_bytes_round_trip() {
800 let ddb = DownloadDataBlock {
801 download_id: 1,
802 adaptation: &[0x01, 0x02, 0x03],
803 module_id: 9,
804 module_version: 0,
805 block_number: 0,
806 block_data: &[0xAA],
807 };
808 let mut buf = vec![0u8; ddb.serialized_len()];
809 ddb.serialize_into(&mut buf).unwrap();
810 assert_eq!(buf[9], 3); assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
812 }
813
814 #[cfg(feature = "serde")]
815 #[test]
816 fn dii_serializes_to_valid_json() {
817 let msg = sample_dii();
818 let j = serde_json::to_string(&msg).unwrap();
819 assert!(j.contains("\"download_id\":171"));
820 assert!(j.contains("\"block_size\":4066"));
821 }
822}