1use crate::error::{Error, Result};
13use dvb_common::{Parse, Serialize};
14
15pub const PROTOCOL_DISCRIMINATOR: u8 = 0x11;
17pub const DSMCC_TYPE_UN_DOWNLOAD: u8 = 0x03;
19pub const MESSAGE_ID_DII: u16 = 0x1002;
21pub const MESSAGE_ID_DDB: u16 = 0x1003;
23pub const MESSAGE_ID_DSI: u16 = 0x1006;
25
26const MESSAGE_HEADER_LEN: usize = 12;
30const SERVER_ID_LEN: usize = 20;
32const COMPAT_LEN_FIELD: usize = 2;
34const PRIVATE_LEN_FIELD: usize = 2;
36const DII_FIXED_LEN: usize = 16;
40const MODULE_HEADER_LEN: usize = 8;
43const DDB_FIXED_LEN: usize = 6;
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize))]
50pub struct Dsi<'a> {
51 pub transaction_id: u32,
54 pub adaptation: &'a [u8],
56 pub server_id: [u8; SERVER_ID_LEN],
58 pub compatibility_descriptor: &'a [u8],
61 pub private_data: &'a [u8],
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize))]
69pub struct DiiModule<'a> {
70 pub module_id: u16,
72 pub module_size: u32,
74 pub module_version: u8,
76 pub module_info: &'a [u8],
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize))]
84pub struct Dii<'a> {
85 pub transaction_id: u32,
87 pub adaptation: &'a [u8],
89 pub download_id: u32,
91 pub block_size: u16,
93 pub window_size: u8,
95 pub ack_period: u8,
97 pub t_c_download_window: u32,
99 pub t_c_download_scenario: u32,
101 pub compatibility_descriptor: &'a [u8],
103 pub modules: Vec<DiiModule<'a>>,
105 pub private_data: &'a [u8],
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113pub enum UnMessage<'a> {
114 Dsi(Dsi<'a>),
116 Dii(Dii<'a>),
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize))]
124pub struct DownloadDataBlock<'a> {
125 pub download_id: u32,
127 pub adaptation: &'a [u8],
129 pub module_id: u16,
131 pub module_version: u8,
133 pub block_number: u16,
135 pub block_data: &'a [u8],
137}
138
139fn parse_header<'a>(bytes: &'a [u8], what: &'static str) -> Result<(u16, u32, &'a [u8], &'a [u8])> {
143 if bytes.len() < MESSAGE_HEADER_LEN {
144 return Err(Error::BufferTooShort {
145 need: MESSAGE_HEADER_LEN,
146 have: bytes.len(),
147 what,
148 });
149 }
150 if bytes[0] != PROTOCOL_DISCRIMINATOR {
151 return Err(Error::ReservedBitsViolation {
152 field: "protocolDiscriminator",
153 reason: "must be 0x11 (ISO/IEC 13818-6 §7.2)",
154 });
155 }
156 if bytes[1] != DSMCC_TYPE_UN_DOWNLOAD {
157 return Err(Error::ReservedBitsViolation {
158 field: "dsmccType",
159 reason: "must be 0x03 — U-N download (ISO/IEC 13818-6 §7.2)",
160 });
161 }
162 let message_id = u16::from_be_bytes([bytes[2], bytes[3]]);
163 let id = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
164 let adaptation_length = bytes[9] as usize;
165 let message_length = u16::from_be_bytes([bytes[10], bytes[11]]) as usize;
166 let total = MESSAGE_HEADER_LEN + message_length;
167 if bytes.len() < total {
168 return Err(Error::SectionLengthOverflow {
169 declared: message_length,
170 available: bytes.len() - MESSAGE_HEADER_LEN,
171 });
172 }
173 if adaptation_length > message_length {
174 return Err(Error::SectionLengthOverflow {
175 declared: adaptation_length,
176 available: message_length,
177 });
178 }
179 let adaptation = &bytes[MESSAGE_HEADER_LEN..MESSAGE_HEADER_LEN + adaptation_length];
180 let payload = &bytes[MESSAGE_HEADER_LEN + adaptation_length..total];
181 Ok((message_id, id, adaptation, payload))
182}
183
184fn serialize_header(
187 buf: &mut [u8],
188 message_id: u16,
189 id: u32,
190 adaptation: &[u8],
191 payload_len: usize,
192) -> Result<usize> {
193 let message_length = adaptation.len() + payload_len;
194 if adaptation.len() > u8::MAX as usize {
195 return Err(Error::SectionLengthOverflow {
196 declared: adaptation.len(),
197 available: u8::MAX as usize,
198 });
199 }
200 if message_length > u16::MAX as usize {
201 return Err(Error::SectionLengthOverflow {
202 declared: message_length,
203 available: u16::MAX as usize,
204 });
205 }
206 buf[0] = PROTOCOL_DISCRIMINATOR;
207 buf[1] = DSMCC_TYPE_UN_DOWNLOAD;
208 buf[2..4].copy_from_slice(&message_id.to_be_bytes());
209 buf[4..8].copy_from_slice(&id.to_be_bytes());
210 buf[8] = 0xFF; buf[9] = adaptation.len() as u8;
212 buf[10..12].copy_from_slice(&(message_length as u16).to_be_bytes());
213 buf[MESSAGE_HEADER_LEN..MESSAGE_HEADER_LEN + adaptation.len()].copy_from_slice(adaptation);
214 Ok(MESSAGE_HEADER_LEN + adaptation.len())
215}
216
217fn length_prefixed(bytes: &[u8], pos: usize, end: usize) -> Result<(&[u8], usize)> {
219 if pos + 2 > end {
220 return Err(Error::BufferTooShort {
221 need: pos + 2,
222 have: end,
223 what: "DSM-CC 16-bit length field",
224 });
225 }
226 let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
227 let start = pos + 2;
228 if start + len > end {
229 return Err(Error::SectionLengthOverflow {
230 declared: len,
231 available: end - start,
232 });
233 }
234 Ok((&bytes[start..start + len], start + len))
235}
236
237impl<'a> Parse<'a> for UnMessage<'a> {
238 type Error = crate::error::Error;
239
240 fn parse(bytes: &'a [u8]) -> Result<Self> {
241 let (message_id, transaction_id, adaptation, payload) =
242 parse_header(bytes, "UnMessage header")?;
243 let end = payload.len();
244 match message_id {
245 MESSAGE_ID_DSI => {
246 if end < SERVER_ID_LEN + COMPAT_LEN_FIELD + PRIVATE_LEN_FIELD {
247 return Err(Error::BufferTooShort {
248 need: SERVER_ID_LEN + COMPAT_LEN_FIELD + PRIVATE_LEN_FIELD,
249 have: end,
250 what: "Dsi body",
251 });
252 }
253 let mut server_id = [0u8; SERVER_ID_LEN];
254 server_id.copy_from_slice(&payload[..SERVER_ID_LEN]);
255 let (compatibility_descriptor, pos) = length_prefixed(payload, SERVER_ID_LEN, end)?;
256 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
257 Ok(UnMessage::Dsi(Dsi {
258 transaction_id,
259 adaptation,
260 server_id,
261 compatibility_descriptor,
262 private_data,
263 }))
264 }
265 MESSAGE_ID_DII => {
266 if end < DII_FIXED_LEN + COMPAT_LEN_FIELD {
267 return Err(Error::BufferTooShort {
268 need: DII_FIXED_LEN + COMPAT_LEN_FIELD,
269 have: end,
270 what: "Dii body",
271 });
272 }
273 let download_id =
274 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
275 let block_size = u16::from_be_bytes([payload[4], payload[5]]);
276 let window_size = payload[6];
277 let ack_period = payload[7];
278 let t_c_download_window =
279 u32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
280 let t_c_download_scenario =
281 u32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
282 let (compatibility_descriptor, mut pos) =
283 length_prefixed(payload, DII_FIXED_LEN, end)?;
284 if pos + 2 > end {
285 return Err(Error::BufferTooShort {
286 need: pos + 2,
287 have: end,
288 what: "Dii numberOfModules",
289 });
290 }
291 let number_of_modules =
292 u16::from_be_bytes([payload[pos], payload[pos + 1]]) as usize;
293 pos += 2;
294 let mut modules = Vec::with_capacity(number_of_modules.min(256));
295 for _ in 0..number_of_modules {
296 if pos + MODULE_HEADER_LEN > end {
297 return Err(Error::BufferTooShort {
298 need: pos + MODULE_HEADER_LEN,
299 have: end,
300 what: "Dii module entry",
301 });
302 }
303 let module_id = u16::from_be_bytes([payload[pos], payload[pos + 1]]);
304 let module_size = u32::from_be_bytes([
305 payload[pos + 2],
306 payload[pos + 3],
307 payload[pos + 4],
308 payload[pos + 5],
309 ]);
310 let module_version = payload[pos + 6];
311 let module_info_length = payload[pos + 7] as usize;
312 let info_start = pos + MODULE_HEADER_LEN;
313 if info_start + module_info_length > end {
314 return Err(Error::SectionLengthOverflow {
315 declared: module_info_length,
316 available: end - info_start,
317 });
318 }
319 modules.push(DiiModule {
320 module_id,
321 module_size,
322 module_version,
323 module_info: &payload[info_start..info_start + module_info_length],
324 });
325 pos = info_start + module_info_length;
326 }
327 let (private_data, _pos) = length_prefixed(payload, pos, end)?;
328 Ok(UnMessage::Dii(Dii {
329 transaction_id,
330 adaptation,
331 download_id,
332 block_size,
333 window_size,
334 ack_period,
335 t_c_download_window,
336 t_c_download_scenario,
337 compatibility_descriptor,
338 modules,
339 private_data,
340 }))
341 }
342 _ => Err(Error::ReservedBitsViolation {
343 field: "messageId",
344 reason: "expected 0x1002 (DII) or 0x1006 (DSI) on table_id 0x3B \
345 (ISO/IEC 13818-6 §7.3)",
346 }),
347 }
348 }
349}
350
351impl Serialize for UnMessage<'_> {
352 type Error = crate::error::Error;
353
354 fn serialized_len(&self) -> usize {
355 match self {
356 UnMessage::Dsi(dsi) => {
357 MESSAGE_HEADER_LEN
358 + dsi.adaptation.len()
359 + SERVER_ID_LEN
360 + COMPAT_LEN_FIELD
361 + dsi.compatibility_descriptor.len()
362 + PRIVATE_LEN_FIELD
363 + dsi.private_data.len()
364 }
365 UnMessage::Dii(dii) => {
366 MESSAGE_HEADER_LEN
367 + dii.adaptation.len()
368 + DII_FIXED_LEN
369 + COMPAT_LEN_FIELD
370 + dii.compatibility_descriptor.len()
371 + 2 + dii
373 .modules
374 .iter()
375 .map(|m| MODULE_HEADER_LEN + m.module_info.len())
376 .sum::<usize>()
377 + PRIVATE_LEN_FIELD
378 + dii.private_data.len()
379 }
380 }
381 }
382
383 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
384 let len = self.serialized_len();
385 if buf.len() < len {
386 return Err(Error::OutputBufferTooSmall {
387 need: len,
388 have: buf.len(),
389 });
390 }
391 match self {
392 UnMessage::Dsi(dsi) => {
393 let payload_len = len - MESSAGE_HEADER_LEN - dsi.adaptation.len();
394 let mut pos = serialize_header(
395 buf,
396 MESSAGE_ID_DSI,
397 dsi.transaction_id,
398 dsi.adaptation,
399 payload_len,
400 )?;
401 buf[pos..pos + SERVER_ID_LEN].copy_from_slice(&dsi.server_id);
402 pos += SERVER_ID_LEN;
403 pos = put_length_prefixed(buf, pos, dsi.compatibility_descriptor)?;
404 put_length_prefixed(buf, pos, dsi.private_data)?;
405 }
406 UnMessage::Dii(dii) => {
407 let payload_len = len - MESSAGE_HEADER_LEN - dii.adaptation.len();
408 let mut pos = serialize_header(
409 buf,
410 MESSAGE_ID_DII,
411 dii.transaction_id,
412 dii.adaptation,
413 payload_len,
414 )?;
415 buf[pos..pos + 4].copy_from_slice(&dii.download_id.to_be_bytes());
416 buf[pos + 4..pos + 6].copy_from_slice(&dii.block_size.to_be_bytes());
417 buf[pos + 6] = dii.window_size;
418 buf[pos + 7] = dii.ack_period;
419 buf[pos + 8..pos + 12].copy_from_slice(&dii.t_c_download_window.to_be_bytes());
420 buf[pos + 12..pos + 16].copy_from_slice(&dii.t_c_download_scenario.to_be_bytes());
421 pos += DII_FIXED_LEN;
422 pos = put_length_prefixed(buf, pos, dii.compatibility_descriptor)?;
423 if dii.modules.len() > u16::MAX as usize {
424 return Err(Error::SectionLengthOverflow {
425 declared: dii.modules.len(),
426 available: u16::MAX as usize,
427 });
428 }
429 buf[pos..pos + 2].copy_from_slice(&(dii.modules.len() as u16).to_be_bytes());
430 pos += 2;
431 for m in &dii.modules {
432 if m.module_info.len() > u8::MAX as usize {
433 return Err(Error::SectionLengthOverflow {
434 declared: m.module_info.len(),
435 available: u8::MAX as usize,
436 });
437 }
438 buf[pos..pos + 2].copy_from_slice(&m.module_id.to_be_bytes());
439 buf[pos + 2..pos + 6].copy_from_slice(&m.module_size.to_be_bytes());
440 buf[pos + 6] = m.module_version;
441 buf[pos + 7] = m.module_info.len() as u8;
442 pos += MODULE_HEADER_LEN;
443 buf[pos..pos + m.module_info.len()].copy_from_slice(m.module_info);
444 pos += m.module_info.len();
445 }
446 put_length_prefixed(buf, pos, dii.private_data)?;
447 }
448 }
449 Ok(len)
450 }
451}
452
453fn put_length_prefixed(buf: &mut [u8], pos: usize, data: &[u8]) -> Result<usize> {
455 if data.len() > u16::MAX as usize {
456 return Err(Error::SectionLengthOverflow {
457 declared: data.len(),
458 available: u16::MAX as usize,
459 });
460 }
461 buf[pos..pos + 2].copy_from_slice(&(data.len() as u16).to_be_bytes());
462 buf[pos + 2..pos + 2 + data.len()].copy_from_slice(data);
463 Ok(pos + 2 + data.len())
464}
465
466impl<'a> Parse<'a> for DownloadDataBlock<'a> {
467 type Error = crate::error::Error;
468
469 fn parse(bytes: &'a [u8]) -> Result<Self> {
470 let (message_id, download_id, adaptation, payload) =
471 parse_header(bytes, "DownloadDataBlock header")?;
472 if message_id != MESSAGE_ID_DDB {
473 return Err(Error::ReservedBitsViolation {
474 field: "messageId",
475 reason: "expected 0x1003 (DDB) on table_id 0x3C (ISO/IEC 13818-6 §7.3.7)",
476 });
477 }
478 if payload.len() < DDB_FIXED_LEN {
479 return Err(Error::BufferTooShort {
480 need: DDB_FIXED_LEN,
481 have: payload.len(),
482 what: "DownloadDataBlock body",
483 });
484 }
485 Ok(DownloadDataBlock {
486 download_id,
487 adaptation,
488 module_id: u16::from_be_bytes([payload[0], payload[1]]),
489 module_version: payload[2],
490 block_number: u16::from_be_bytes([payload[4], payload[5]]),
491 block_data: &payload[DDB_FIXED_LEN..],
492 })
493 }
494}
495
496impl Serialize for DownloadDataBlock<'_> {
497 type Error = crate::error::Error;
498
499 fn serialized_len(&self) -> usize {
500 MESSAGE_HEADER_LEN + self.adaptation.len() + DDB_FIXED_LEN + self.block_data.len()
501 }
502
503 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
504 let len = self.serialized_len();
505 if buf.len() < len {
506 return Err(Error::OutputBufferTooSmall {
507 need: len,
508 have: buf.len(),
509 });
510 }
511 let payload_len = DDB_FIXED_LEN + self.block_data.len();
512 let pos = serialize_header(
513 buf,
514 MESSAGE_ID_DDB,
515 self.download_id,
516 self.adaptation,
517 payload_len,
518 )?;
519 buf[pos..pos + 2].copy_from_slice(&self.module_id.to_be_bytes());
520 buf[pos + 2] = self.module_version;
521 buf[pos + 3] = 0xFF; buf[pos + 4..pos + 6].copy_from_slice(&self.block_number.to_be_bytes());
523 buf[pos + DDB_FIXED_LEN..pos + DDB_FIXED_LEN + self.block_data.len()]
524 .copy_from_slice(self.block_data);
525 Ok(len)
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 fn sample_dsi() -> UnMessage<'static> {
534 UnMessage::Dsi(Dsi {
535 transaction_id: 0x8000_0000,
536 adaptation: &[],
537 server_id: [0xFF; 20],
538 compatibility_descriptor: &[],
539 private_data: &[0x0A, 0x0B],
540 })
541 }
542
543 fn sample_dii() -> UnMessage<'static> {
544 UnMessage::Dii(Dii {
545 transaction_id: 0x8002_0002,
546 adaptation: &[],
547 download_id: 0x0000_00AB,
548 block_size: 4066,
549 window_size: 0,
550 ack_period: 0,
551 t_c_download_window: 0,
552 t_c_download_scenario: 0,
553 compatibility_descriptor: &[],
554 modules: vec![
555 DiiModule {
556 module_id: 1,
557 module_size: 8000,
558 module_version: 3,
559 module_info: &[0xDE, 0xAD],
560 },
561 DiiModule {
562 module_id: 2,
563 module_size: 100,
564 module_version: 1,
565 module_info: &[],
566 },
567 ],
568 private_data: &[],
569 })
570 }
571
572 #[test]
573 fn dsi_round_trip() {
574 let msg = sample_dsi();
575 let mut buf = vec![0u8; msg.serialized_len()];
576 msg.serialize_into(&mut buf).unwrap();
577 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
578 }
579
580 #[test]
581 fn dii_round_trip() {
582 let msg = sample_dii();
583 let mut buf = vec![0u8; msg.serialized_len()];
584 msg.serialize_into(&mut buf).unwrap();
585 assert_eq!(UnMessage::parse(&buf).unwrap(), msg);
586 }
587
588 #[test]
589 fn ddb_round_trip() {
590 let ddb = DownloadDataBlock {
591 download_id: 0xAB,
592 adaptation: &[],
593 module_id: 1,
594 module_version: 3,
595 block_number: 2,
596 block_data: &[0x55; 64],
597 };
598 let mut buf = vec![0u8; ddb.serialized_len()];
599 ddb.serialize_into(&mut buf).unwrap();
600 assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
601 }
602
603 #[test]
604 fn header_fields_on_wire() {
605 let msg = sample_dsi();
606 let mut buf = vec![0u8; msg.serialized_len()];
607 msg.serialize_into(&mut buf).unwrap();
608 assert_eq!(buf[0], 0x11); assert_eq!(buf[1], 0x03); assert_eq!(u16::from_be_bytes([buf[2], buf[3]]), MESSAGE_ID_DSI);
611 assert_eq!(buf[8], 0xFF); let ml = u16::from_be_bytes([buf[10], buf[11]]) as usize;
614 assert_eq!(ml, buf.len() - 12);
615 }
616
617 #[test]
618 fn parse_rejects_wrong_protocol_discriminator() {
619 let msg = sample_dsi();
620 let mut buf = vec![0u8; msg.serialized_len()];
621 msg.serialize_into(&mut buf).unwrap();
622 buf[0] = 0x12;
623 assert!(matches!(
624 UnMessage::parse(&buf).unwrap_err(),
625 Error::ReservedBitsViolation {
626 field: "protocolDiscriminator",
627 ..
628 }
629 ));
630 }
631
632 #[test]
633 fn parse_rejects_unknown_message_id() {
634 let msg = sample_dsi();
635 let mut buf = vec![0u8; msg.serialized_len()];
636 msg.serialize_into(&mut buf).unwrap();
637 buf[2] = 0x10;
638 buf[3] = 0x01; assert!(matches!(
640 UnMessage::parse(&buf).unwrap_err(),
641 Error::ReservedBitsViolation {
642 field: "messageId",
643 ..
644 }
645 ));
646 }
647
648 #[test]
649 fn parse_rejects_short_buffer() {
650 assert!(matches!(
651 UnMessage::parse(&[0x11, 0x03]).unwrap_err(),
652 Error::BufferTooShort { .. }
653 ));
654 }
655
656 #[test]
657 fn parse_rejects_message_length_overflow() {
658 let msg = sample_dsi();
659 let mut buf = vec![0u8; msg.serialized_len()];
660 msg.serialize_into(&mut buf).unwrap();
661 buf[10] = 0xFF;
662 buf[11] = 0xFF; assert!(matches!(
664 UnMessage::parse(&buf).unwrap_err(),
665 Error::SectionLengthOverflow { .. }
666 ));
667 }
668
669 #[test]
670 fn dii_module_info_overflow_rejected() {
671 let msg = sample_dii();
672 let mut buf = vec![0u8; msg.serialized_len()];
673 msg.serialize_into(&mut buf).unwrap();
674 buf[39] = 0xFF;
677 assert!(matches!(
678 UnMessage::parse(&buf).unwrap_err(),
679 Error::SectionLengthOverflow { .. }
680 ));
681 }
682
683 #[test]
684 fn ddb_rejects_un_message_id() {
685 let msg = sample_dsi();
686 let mut buf = vec![0u8; msg.serialized_len()];
687 msg.serialize_into(&mut buf).unwrap();
688 assert!(matches!(
689 DownloadDataBlock::parse(&buf).unwrap_err(),
690 Error::ReservedBitsViolation {
691 field: "messageId",
692 ..
693 }
694 ));
695 }
696
697 #[test]
698 fn adaptation_bytes_round_trip() {
699 let ddb = DownloadDataBlock {
700 download_id: 1,
701 adaptation: &[0x01, 0x02, 0x03],
702 module_id: 9,
703 module_version: 0,
704 block_number: 0,
705 block_data: &[0xAA],
706 };
707 let mut buf = vec![0u8; ddb.serialized_len()];
708 ddb.serialize_into(&mut buf).unwrap();
709 assert_eq!(buf[9], 3); assert_eq!(DownloadDataBlock::parse(&buf).unwrap(), ddb);
711 }
712
713 #[cfg(feature = "serde")]
714 #[test]
715 fn dii_serializes_to_valid_json() {
716 let msg = sample_dii();
717 let j = serde_json::to_string(&msg).unwrap();
718 assert!(j.contains("\"download_id\":171"));
719 assert!(j.contains("\"block_size\":4066"));
720 }
721}