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