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