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