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