1#![macro_use]
2
3use crate::read::magic_finder::{Backwards, Forward, MagicFinder, OptimisticMagicFinder};
4use crate::read::ArchiveOffset;
5use crate::result::{invalid, ZipError, ZipResult};
6use core::mem;
7use std::io;
8use std::io::prelude::*;
9use std::slice;
10
11#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
16#[repr(transparent)]
17pub(crate) struct Magic(u32);
18
19impl Magic {
20 pub const fn literal(x: u32) -> Self {
21 Self(x)
22 }
23
24 #[inline(always)]
25 #[allow(dead_code)]
26 pub const fn from_le_bytes(bytes: [u8; 4]) -> Self {
27 Self(u32::from_le_bytes(bytes))
28 }
29
30 #[inline(always)]
31 pub const fn to_le_bytes(self) -> [u8; 4] {
32 self.0.to_le_bytes()
33 }
34
35 #[allow(clippy::wrong_self_convention)]
36 #[inline(always)]
37 pub fn from_le(self) -> Self {
38 Self(u32::from_le(self.0))
39 }
40
41 #[allow(clippy::wrong_self_convention)]
42 #[inline(always)]
43 pub fn to_le(self) -> Self {
44 Self(u32::to_le(self.0))
45 }
46
47 pub const LOCAL_FILE_HEADER_SIGNATURE: Self = Self::literal(0x04034b50);
48 pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: Self = Self::literal(0x02014b50);
49 pub const CENTRAL_DIRECTORY_END_SIGNATURE: Self = Self::literal(0x06054b50);
50 pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: Self = Self::literal(0x06064b50);
51 pub const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: Self = Self::literal(0x07064b50);
52}
53
54#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
56#[repr(transparent)]
57pub(crate) struct ExtraFieldMagic(u16);
58
59#[allow(dead_code)]
61impl ExtraFieldMagic {
62 pub const fn literal(x: u16) -> Self {
63 Self(x)
64 }
65
66 #[inline(always)]
67 pub const fn from_le_bytes(bytes: [u8; 2]) -> Self {
68 Self(u16::from_le_bytes(bytes))
69 }
70
71 #[inline(always)]
72 pub const fn to_le_bytes(self) -> [u8; 2] {
73 self.0.to_le_bytes()
74 }
75
76 #[allow(clippy::wrong_self_convention)]
77 #[inline(always)]
78 pub fn from_le(self) -> Self {
79 Self(u16::from_le(self.0))
80 }
81
82 #[allow(clippy::wrong_self_convention)]
83 #[inline(always)]
84 pub fn to_le(self) -> Self {
85 Self(u16::to_le(self.0))
86 }
87
88 pub const ZIP64_EXTRA_FIELD_TAG: Self = Self::literal(0x0001);
89}
90
91pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64;
139pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize;
145
146pub(crate) unsafe trait Pod: Copy + 'static {
154 #[inline]
155 fn zeroed() -> Self {
156 unsafe { mem::zeroed() }
157 }
158
159 #[inline]
160 fn as_bytes(&self) -> &[u8] {
161 unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
162 }
163
164 #[inline]
165 fn as_bytes_mut(&mut self) -> &mut [u8] {
166 unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::<Self>()) }
167 }
168}
169
170pub(crate) trait FixedSizeBlock: Pod {
171 const MAGIC: Magic;
172
173 fn magic(self) -> Magic;
174
175 const WRONG_MAGIC_ERROR: ZipError;
176
177 #[allow(clippy::wrong_self_convention)]
178 fn from_le(self) -> Self;
179
180 fn parse<R: Read>(reader: &mut R) -> ZipResult<Self> {
181 let mut block = Self::zeroed();
182 reader.read_exact(block.as_bytes_mut())?;
183 let block = Self::from_le(block);
184
185 if block.magic() != Self::MAGIC {
186 return Err(Self::WRONG_MAGIC_ERROR);
187 }
188 Ok(block)
189 }
190
191 fn to_le(self) -> Self;
192
193 fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
194 let block = self.to_le();
195 writer.write_all(block.as_bytes())?;
196 Ok(())
197 }
198}
199
200macro_rules! from_le {
202 ($obj:ident, $field:ident, $type:ty) => {
203 $obj.$field = <$type>::from_le($obj.$field);
204 };
205 ($obj:ident, [($field:ident, $type:ty) $(,)?]) => {
206 from_le![$obj, $field, $type];
207 };
208 ($obj:ident, [($field:ident, $type:ty), $($rest:tt),+ $(,)?]) => {
209 from_le![$obj, $field, $type];
210 from_le!($obj, [$($rest),+]);
211 };
212}
213
214macro_rules! to_le {
216 ($obj:ident, $field:ident, $type:ty) => {
217 $obj.$field = <$type>::to_le($obj.$field);
218 };
219 ($obj:ident, [($field:ident, $type:ty) $(,)?]) => {
220 to_le![$obj, $field, $type];
221 };
222 ($obj:ident, [($field:ident, $type:ty), $($rest:tt),+ $(,)?]) => {
223 to_le![$obj, $field, $type];
224 to_le!($obj, [$($rest),+]);
225 };
226}
227
228macro_rules! to_and_from_le {
232 ($($args:tt),+ $(,)?) => {
233 #[inline(always)]
234 fn from_le(mut self) -> Self {
235 from_le![self, [$($args),+]];
236 self
237 }
238 #[inline(always)]
239 fn to_le(mut self) -> Self {
240 to_le![self, [$($args),+]];
241 self
242 }
243 };
244}
245
246#[derive(Copy, Clone, Debug)]
247#[repr(packed, C)]
248pub(crate) struct Zip32CDEBlock {
249 magic: Magic,
250 pub disk_number: u16,
251 pub disk_with_central_directory: u16,
252 pub number_of_files_on_this_disk: u16,
253 pub number_of_files: u16,
254 pub central_directory_size: u32,
255 pub central_directory_offset: u32,
256 pub zip_file_comment_length: u16,
257}
258
259unsafe impl Pod for Zip32CDEBlock {}
260
261impl FixedSizeBlock for Zip32CDEBlock {
262 const MAGIC: Magic = Magic::CENTRAL_DIRECTORY_END_SIGNATURE;
263
264 #[inline(always)]
265 fn magic(self) -> Magic {
266 self.magic
267 }
268
269 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid digital signature header");
270
271 to_and_from_le![
272 (magic, Magic),
273 (disk_number, u16),
274 (disk_with_central_directory, u16),
275 (number_of_files_on_this_disk, u16),
276 (number_of_files, u16),
277 (central_directory_size, u32),
278 (central_directory_offset, u32),
279 (zip_file_comment_length, u16)
280 ];
281}
282
283#[derive(Debug)]
284pub(crate) struct Zip32CentralDirectoryEnd {
285 pub disk_number: u16,
286 pub disk_with_central_directory: u16,
287 pub number_of_files_on_this_disk: u16,
288 pub number_of_files: u16,
289 pub central_directory_size: u32,
290 pub central_directory_offset: u32,
291 pub zip_file_comment: Box<[u8]>,
292}
293
294impl Zip32CentralDirectoryEnd {
295 fn into_block_and_comment(self) -> (Zip32CDEBlock, Box<[u8]>) {
296 let Self {
297 disk_number,
298 disk_with_central_directory,
299 number_of_files_on_this_disk,
300 number_of_files,
301 central_directory_size,
302 central_directory_offset,
303 zip_file_comment,
304 } = self;
305 let block = Zip32CDEBlock {
306 magic: Zip32CDEBlock::MAGIC,
307 disk_number,
308 disk_with_central_directory,
309 number_of_files_on_this_disk,
310 number_of_files,
311 central_directory_size,
312 central_directory_offset,
313 zip_file_comment_length: zip_file_comment.len() as u16,
314 };
315
316 (block, zip_file_comment)
317 }
318
319 pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip32CentralDirectoryEnd> {
320 let Zip32CDEBlock {
321 disk_number,
323 disk_with_central_directory,
324 number_of_files_on_this_disk,
325 number_of_files,
326 central_directory_size,
327 central_directory_offset,
328 zip_file_comment_length,
329 ..
330 } = Zip32CDEBlock::parse(reader)?;
331
332 let mut zip_file_comment = vec![0u8; zip_file_comment_length as usize].into_boxed_slice();
333 if let Err(e) = reader.read_exact(&mut zip_file_comment) {
334 if e.kind() == io::ErrorKind::UnexpectedEof {
335 return Err(invalid!("EOCD comment exceeds file boundary"));
336 }
337
338 return Err(e.into());
339 }
340
341 Ok(Zip32CentralDirectoryEnd {
342 disk_number,
343 disk_with_central_directory,
344 number_of_files_on_this_disk,
345 number_of_files,
346 central_directory_size,
347 central_directory_offset,
348 zip_file_comment,
349 })
350 }
351
352 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
353 let (block, comment) = self.into_block_and_comment();
354
355 if comment.len() > u16::MAX as usize {
356 return Err(invalid!("EOCD comment length exceeds u16::MAX"));
357 }
358
359 block.write(writer)?;
360 writer.write_all(&comment)?;
361 Ok(())
362 }
363
364 pub fn may_be_zip64(&self) -> bool {
365 self.number_of_files == u16::MAX || self.central_directory_offset == u32::MAX
366 }
367}
368
369#[derive(Copy, Clone)]
370#[repr(packed, C)]
371pub(crate) struct Zip64CDELocatorBlock {
372 magic: Magic,
373 pub disk_with_central_directory: u32,
374 pub end_of_central_directory_offset: u64,
375 pub number_of_disks: u32,
376}
377
378unsafe impl Pod for Zip64CDELocatorBlock {}
379
380impl FixedSizeBlock for Zip64CDELocatorBlock {
381 const MAGIC: Magic = Magic::ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE;
382
383 #[inline(always)]
384 fn magic(self) -> Magic {
385 self.magic
386 }
387
388 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid zip64 locator digital signature header");
389
390 to_and_from_le![
391 (magic, Magic),
392 (disk_with_central_directory, u32),
393 (end_of_central_directory_offset, u64),
394 (number_of_disks, u32),
395 ];
396}
397
398pub(crate) struct Zip64CentralDirectoryEndLocator {
399 pub disk_with_central_directory: u32,
400 pub end_of_central_directory_offset: u64,
401 pub number_of_disks: u32,
402}
403
404impl Zip64CentralDirectoryEndLocator {
405 pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> {
406 let Zip64CDELocatorBlock {
407 disk_with_central_directory,
409 end_of_central_directory_offset,
410 number_of_disks,
411 ..
412 } = Zip64CDELocatorBlock::parse(reader)?;
413
414 Ok(Zip64CentralDirectoryEndLocator {
415 disk_with_central_directory,
416 end_of_central_directory_offset,
417 number_of_disks,
418 })
419 }
420
421 pub fn block(self) -> Zip64CDELocatorBlock {
422 let Self {
423 disk_with_central_directory,
424 end_of_central_directory_offset,
425 number_of_disks,
426 } = self;
427 Zip64CDELocatorBlock {
428 magic: Zip64CDELocatorBlock::MAGIC,
429 disk_with_central_directory,
430 end_of_central_directory_offset,
431 number_of_disks,
432 }
433 }
434
435 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
436 self.block().write(writer)
437 }
438}
439
440#[derive(Copy, Clone)]
441#[repr(packed, C)]
442pub(crate) struct Zip64CDEBlock {
443 magic: Magic,
444 pub record_size: u64,
445 pub version_made_by: u16,
446 pub version_needed_to_extract: u16,
447 pub disk_number: u32,
448 pub disk_with_central_directory: u32,
449 pub number_of_files_on_this_disk: u64,
450 pub number_of_files: u64,
451 pub central_directory_size: u64,
452 pub central_directory_offset: u64,
453}
454
455unsafe impl Pod for Zip64CDEBlock {}
456
457impl FixedSizeBlock for Zip64CDEBlock {
458 const MAGIC: Magic = Magic::ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE;
459
460 fn magic(self) -> Magic {
461 self.magic
462 }
463
464 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid digital signature header");
465
466 to_and_from_le![
467 (magic, Magic),
468 (record_size, u64),
469 (version_made_by, u16),
470 (version_needed_to_extract, u16),
471 (disk_number, u32),
472 (disk_with_central_directory, u32),
473 (number_of_files_on_this_disk, u64),
474 (number_of_files, u64),
475 (central_directory_size, u64),
476 (central_directory_offset, u64),
477 ];
478}
479
480pub(crate) struct Zip64CentralDirectoryEnd {
481 pub record_size: u64,
482 pub version_made_by: u16,
483 pub version_needed_to_extract: u16,
484 pub disk_number: u32,
485 pub disk_with_central_directory: u32,
486 pub number_of_files_on_this_disk: u64,
487 pub number_of_files: u64,
488 pub central_directory_size: u64,
489 pub central_directory_offset: u64,
490 pub extensible_data_sector: Box<[u8]>,
491}
492
493impl Zip64CentralDirectoryEnd {
494 pub fn parse<T: Read>(reader: &mut T, max_size: u64) -> ZipResult<Zip64CentralDirectoryEnd> {
495 let Zip64CDEBlock {
496 record_size,
497 version_made_by,
498 version_needed_to_extract,
499 disk_number,
500 disk_with_central_directory,
501 number_of_files_on_this_disk,
502 number_of_files,
503 central_directory_size,
504 central_directory_offset,
505 ..
506 } = Zip64CDEBlock::parse(reader)?;
507
508 if record_size < 44 {
509 return Err(invalid!("Low EOCD64 record size"));
510 } else if record_size.saturating_add(12) > max_size {
511 return Err(invalid!("EOCD64 extends beyond EOCD64 locator"));
512 }
513
514 let mut zip_file_comment = vec![0u8; record_size as usize - 44].into_boxed_slice();
515 reader.read_exact(&mut zip_file_comment)?;
516
517 Ok(Self {
518 record_size,
519 version_made_by,
520 version_needed_to_extract,
521 disk_number,
522 disk_with_central_directory,
523 number_of_files_on_this_disk,
524 number_of_files,
525 central_directory_size,
526 central_directory_offset,
527 extensible_data_sector: zip_file_comment,
528 })
529 }
530
531 pub fn into_block_and_comment(self) -> (Zip64CDEBlock, Box<[u8]>) {
532 let Self {
533 record_size,
534 version_made_by,
535 version_needed_to_extract,
536 disk_number,
537 disk_with_central_directory,
538 number_of_files_on_this_disk,
539 number_of_files,
540 central_directory_size,
541 central_directory_offset,
542 extensible_data_sector,
543 } = self;
544
545 (
546 Zip64CDEBlock {
547 magic: Zip64CDEBlock::MAGIC,
548 record_size,
549 version_made_by,
550 version_needed_to_extract,
551 disk_number,
552 disk_with_central_directory,
553 number_of_files_on_this_disk,
554 number_of_files,
555 central_directory_size,
556 central_directory_offset,
557 },
558 extensible_data_sector,
559 )
560 }
561
562 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
563 let (block, comment) = self.into_block_and_comment();
564 block.write(writer)?;
565 writer.write_all(&comment)?;
566 Ok(())
567 }
568}
569
570pub(crate) struct DataAndPosition<T> {
571 pub data: T,
572 #[allow(dead_code)]
573 pub position: u64,
574}
575
576impl<T> From<(T, u64)> for DataAndPosition<T> {
577 fn from(value: (T, u64)) -> Self {
578 Self {
579 data: value.0,
580 position: value.1,
581 }
582 }
583}
584
585pub(crate) struct CentralDirectoryEndInfo {
586 pub eocd: DataAndPosition<Zip32CentralDirectoryEnd>,
587 pub eocd64: Option<DataAndPosition<Zip64CentralDirectoryEnd>>,
588
589 pub archive_offset: u64,
590}
591
592pub(crate) fn find_central_directory<R: Read + Seek>(
597 reader: &mut R,
598 archive_offset: ArchiveOffset,
599 end_exclusive: u64,
600 file_len: u64,
601) -> ZipResult<CentralDirectoryEndInfo> {
602 const EOCD_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
603 Magic::CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
604
605 const EOCD64_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
606 Magic::ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
607
608 const CDFH_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
609 Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes();
610
611 let mut eocd_finder = MagicFinder::<Backwards<'static>>::new(&EOCD_SIG_BYTES, 0, end_exclusive);
613 let mut subfinder: Option<OptimisticMagicFinder<Forward<'static>>> = None;
614
615 let mut parsing_error = None;
617
618 while let Some(eocd_offset) = eocd_finder.next(reader)? {
619 let eocd = match Zip32CentralDirectoryEnd::parse(reader) {
621 Ok(eocd) => eocd,
622 Err(e) => {
623 if parsing_error.is_none() {
624 parsing_error = Some(e);
625 }
626 continue;
627 }
628 };
629
630 if eocd.zip_file_comment.len() as u64 + eocd_offset + 22 > file_len {
633 parsing_error = Some(invalid!("Invalid EOCD comment length"));
634 continue;
635 }
636
637 let zip64_metadata = if eocd.may_be_zip64() {
638 fn try_read_eocd64_locator(
639 reader: &mut (impl Read + Seek),
640 eocd_offset: u64,
641 ) -> ZipResult<(u64, Zip64CentralDirectoryEndLocator)> {
642 if eocd_offset < mem::size_of::<Zip64CDELocatorBlock>() as u64 {
643 return Err(invalid!("EOCD64 Locator does not fit in file"));
644 }
645
646 let locator64_offset = eocd_offset - mem::size_of::<Zip64CDELocatorBlock>() as u64;
647
648 reader.seek(io::SeekFrom::Start(locator64_offset))?;
649 Ok((
650 locator64_offset,
651 Zip64CentralDirectoryEndLocator::parse(reader)?,
652 ))
653 }
654
655 try_read_eocd64_locator(reader, eocd_offset).ok()
656 } else {
657 None
658 };
659
660 let Some((locator64_offset, locator64)) = zip64_metadata else {
661 let relative_cd_offset = eocd.central_directory_offset as u64;
663
664 if eocd.number_of_files == 0 {
666 return Ok(CentralDirectoryEndInfo {
667 eocd: (eocd, eocd_offset).into(),
668 eocd64: None,
669 archive_offset: eocd_offset.saturating_sub(relative_cd_offset),
670 });
671 }
672
673 if relative_cd_offset >= eocd_offset {
675 parsing_error = Some(invalid!("Invalid CDFH offset in EOCD"));
676 continue;
677 }
678
679 let subfinder = subfinder
681 .get_or_insert_with(OptimisticMagicFinder::new_empty)
682 .repurpose(
683 &CDFH_SIG_BYTES,
684 (relative_cd_offset, eocd_offset),
687 match archive_offset {
688 ArchiveOffset::Known(n) => {
689 Some((relative_cd_offset.saturating_add(n).min(eocd_offset), true))
690 }
691 _ => Some((relative_cd_offset, false)),
692 },
693 );
694
695 if let Some(cd_offset) = subfinder.next(reader)? {
697 let archive_offset = cd_offset - relative_cd_offset;
699
700 return Ok(CentralDirectoryEndInfo {
701 eocd: (eocd, eocd_offset).into(),
702 eocd64: None,
703 archive_offset,
704 });
705 }
706
707 parsing_error = Some(invalid!("No CDFH found"));
708 continue;
709 };
710
711 if locator64.end_of_central_directory_offset >= locator64_offset {
713 parsing_error = Some(invalid!("Invalid EOCD64 Locator CD offset"));
714 continue;
715 }
716
717 if locator64.number_of_disks > 1 {
718 parsing_error = Some(invalid!("Multi-disk ZIP files are not supported"));
719 continue;
720 }
721
722 fn try_read_eocd64<R: Read + Seek>(
725 reader: &mut R,
726 locator64: &Zip64CentralDirectoryEndLocator,
727 expected_length: u64,
728 ) -> ZipResult<Zip64CentralDirectoryEnd> {
729 let z64 = Zip64CentralDirectoryEnd::parse(reader, expected_length)?;
730
731 if z64.disk_with_central_directory != locator64.disk_with_central_directory {
733 return Err(invalid!("Invalid EOCD64: inconsistency with Locator data"));
734 }
735
736 if z64.record_size + 12 != expected_length {
738 return Err(invalid!("Invalid EOCD64: inconsistent length"));
739 }
740
741 Ok(z64)
742 }
743
744 let subfinder = subfinder
746 .get_or_insert_with(OptimisticMagicFinder::new_empty)
747 .repurpose(
748 &EOCD64_SIG_BYTES,
749 (locator64.end_of_central_directory_offset, locator64_offset),
750 match archive_offset {
751 ArchiveOffset::Known(n) => Some((
752 locator64
753 .end_of_central_directory_offset
754 .saturating_add(n)
755 .min(locator64_offset),
756 true,
757 )),
758 _ => Some((locator64.end_of_central_directory_offset, false)),
759 },
760 );
761
762 let mut local_error = None;
764 while let Some(eocd64_offset) = subfinder.next(reader)? {
765 let archive_offset = eocd64_offset - locator64.end_of_central_directory_offset;
766
767 match try_read_eocd64(
768 reader,
769 &locator64,
770 locator64_offset.saturating_sub(eocd64_offset),
771 ) {
772 Ok(eocd64) => {
773 if eocd64_offset
774 < eocd64
775 .number_of_files
776 .saturating_mul(
777 mem::size_of::<crate::types::ZipCentralEntryBlock>() as u64
778 )
779 .saturating_add(eocd64.central_directory_offset)
780 {
781 local_error =
782 Some(invalid!("Invalid EOCD64: inconsistent number of files"));
783 continue;
784 }
785
786 return Ok(CentralDirectoryEndInfo {
787 eocd: (eocd, eocd_offset).into(),
788 eocd64: Some((eocd64, eocd64_offset).into()),
789 archive_offset,
790 });
791 }
792 Err(e) => {
793 local_error = Some(e);
794 }
795 }
796 }
797
798 parsing_error = local_error.or(Some(invalid!("Could not find EOCD64")));
799 }
800
801 Err(parsing_error.unwrap_or(invalid!("Could not find EOCD")))
802}
803
804pub(crate) fn is_dir(filename: &str) -> bool {
805 filename
806 .chars()
807 .next_back()
808 .is_some_and(|c| c == '/' || c == '\\')
809}
810
811#[cfg(test)]
812mod test {
813 use super::*;
814 use std::io::Cursor;
815
816 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
817 #[repr(packed, C)]
818 pub struct TestBlock {
819 magic: Magic,
820 pub file_name_length: u16,
821 }
822
823 unsafe impl Pod for TestBlock {}
824
825 impl FixedSizeBlock for TestBlock {
826 const MAGIC: Magic = Magic::literal(0x01111);
827
828 fn magic(self) -> Magic {
829 self.magic
830 }
831
832 const WRONG_MAGIC_ERROR: ZipError = invalid!("unreachable");
833
834 to_and_from_le![(magic, Magic), (file_name_length, u16)];
835 }
836
837 #[test]
839 fn block_serde() {
840 let block = TestBlock {
841 magic: TestBlock::MAGIC,
842 file_name_length: 3,
843 };
844 let mut c = Cursor::new(Vec::new());
845 block.write(&mut c).unwrap();
846 c.set_position(0);
847 let block2 = TestBlock::parse(&mut c).unwrap();
848 assert_eq!(block, block2);
849 }
850}