1#![macro_use]
2
3use crate::read::magic_finder::{Backwards, Forward, MagicFinder, OptimisticMagicFinder};
4use crate::read::ArchiveOffset;
5use crate::result::{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 =
270 ZipError::InvalidArchive("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(ZipError::InvalidArchive(
337 "EOCD comment exceeds file boundary",
338 ));
339 }
340
341 return Err(e.into());
342 }
343
344 Ok(Zip32CentralDirectoryEnd {
345 disk_number,
346 disk_with_central_directory,
347 number_of_files_on_this_disk,
348 number_of_files,
349 central_directory_size,
350 central_directory_offset,
351 zip_file_comment,
352 })
353 }
354
355 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
356 let (block, comment) = self.into_block_and_comment();
357
358 if comment.len() > u16::MAX as usize {
359 return Err(ZipError::InvalidArchive(
360 "EOCD comment length exceeds u16::MAX",
361 ));
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 =
394 ZipError::InvalidArchive("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 =
471 ZipError::InvalidArchive("Invalid digital signature header");
472
473 to_and_from_le![
474 (magic, Magic),
475 (record_size, u64),
476 (version_made_by, u16),
477 (version_needed_to_extract, u16),
478 (disk_number, u32),
479 (disk_with_central_directory, u32),
480 (number_of_files_on_this_disk, u64),
481 (number_of_files, u64),
482 (central_directory_size, u64),
483 (central_directory_offset, u64),
484 ];
485}
486
487pub(crate) struct Zip64CentralDirectoryEnd {
488 pub record_size: u64,
489 pub version_made_by: u16,
490 pub version_needed_to_extract: u16,
491 pub disk_number: u32,
492 pub disk_with_central_directory: u32,
493 pub number_of_files_on_this_disk: u64,
494 pub number_of_files: u64,
495 pub central_directory_size: u64,
496 pub central_directory_offset: u64,
497 pub extensible_data_sector: Box<[u8]>,
498}
499
500impl Zip64CentralDirectoryEnd {
501 pub fn parse<T: Read>(reader: &mut T, max_size: u64) -> ZipResult<Zip64CentralDirectoryEnd> {
502 let Zip64CDEBlock {
503 record_size,
504 version_made_by,
505 version_needed_to_extract,
506 disk_number,
507 disk_with_central_directory,
508 number_of_files_on_this_disk,
509 number_of_files,
510 central_directory_size,
511 central_directory_offset,
512 ..
513 } = Zip64CDEBlock::parse(reader)?;
514
515 if record_size < 44 {
516 return Err(ZipError::InvalidArchive("Low EOCD64 record size"));
517 } else if record_size.saturating_add(12) > max_size {
518 return Err(ZipError::InvalidArchive(
519 "EOCD64 extends beyond EOCD64 locator",
520 ));
521 }
522
523 let mut zip_file_comment = vec![0u8; record_size as usize - 44].into_boxed_slice();
524 reader.read_exact(&mut zip_file_comment)?;
525
526 Ok(Self {
527 record_size,
528 version_made_by,
529 version_needed_to_extract,
530 disk_number,
531 disk_with_central_directory,
532 number_of_files_on_this_disk,
533 number_of_files,
534 central_directory_size,
535 central_directory_offset,
536 extensible_data_sector: zip_file_comment,
537 })
538 }
539
540 pub fn into_block_and_comment(self) -> (Zip64CDEBlock, Box<[u8]>) {
541 let Self {
542 record_size,
543 version_made_by,
544 version_needed_to_extract,
545 disk_number,
546 disk_with_central_directory,
547 number_of_files_on_this_disk,
548 number_of_files,
549 central_directory_size,
550 central_directory_offset,
551 extensible_data_sector,
552 } = self;
553
554 (
555 Zip64CDEBlock {
556 magic: Zip64CDEBlock::MAGIC,
557 record_size,
558 version_made_by,
559 version_needed_to_extract,
560 disk_number,
561 disk_with_central_directory,
562 number_of_files_on_this_disk,
563 number_of_files,
564 central_directory_size,
565 central_directory_offset,
566 },
567 extensible_data_sector,
568 )
569 }
570
571 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
572 let (block, comment) = self.into_block_and_comment();
573 block.write(writer)?;
574 writer.write_all(&comment)?;
575 Ok(())
576 }
577}
578
579pub(crate) struct DataAndPosition<T> {
580 pub data: T,
581 #[allow(dead_code)]
582 pub position: u64,
583}
584
585impl<T> From<(T, u64)> for DataAndPosition<T> {
586 fn from(value: (T, u64)) -> Self {
587 Self {
588 data: value.0,
589 position: value.1,
590 }
591 }
592}
593
594pub(crate) struct CentralDirectoryEndInfo {
595 pub eocd: DataAndPosition<Zip32CentralDirectoryEnd>,
596 pub eocd64: Option<DataAndPosition<Zip64CentralDirectoryEnd>>,
597
598 pub archive_offset: u64,
599}
600
601pub(crate) fn find_central_directory<R: Read + Seek>(
606 reader: &mut R,
607 archive_offset: ArchiveOffset,
608 end_exclusive: u64,
609 file_len: u64,
610) -> ZipResult<CentralDirectoryEndInfo> {
611 const EOCD_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
612 Magic::CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
613
614 const EOCD64_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
615 Magic::ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
616
617 const CDFH_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
618 Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes();
619
620 let mut eocd_finder = MagicFinder::<Backwards<'static>>::new(&EOCD_SIG_BYTES, 0, end_exclusive);
622 let mut subfinder: Option<OptimisticMagicFinder<Forward<'static>>> = None;
623
624 let mut parsing_error = None;
626
627 while let Some(eocd_offset) = eocd_finder.next(reader)? {
628 let eocd = match Zip32CentralDirectoryEnd::parse(reader) {
630 Ok(eocd) => eocd,
631 Err(e) => {
632 if parsing_error.is_none() {
633 parsing_error = Some(e);
634 }
635 continue;
636 }
637 };
638
639 if eocd.zip_file_comment.len() as u64 + eocd_offset + 22 > file_len {
642 parsing_error = Some(ZipError::InvalidArchive("Invalid EOCD comment length"));
643 continue;
644 }
645
646 let zip64_metadata = if eocd.may_be_zip64() {
647 fn try_read_eocd64_locator(
648 reader: &mut (impl Read + Seek),
649 eocd_offset: u64,
650 ) -> ZipResult<(u64, Zip64CentralDirectoryEndLocator)> {
651 if eocd_offset < mem::size_of::<Zip64CDELocatorBlock>() as u64 {
652 return Err(ZipError::InvalidArchive(
653 "EOCD64 Locator does not fit in file",
654 ));
655 }
656
657 let locator64_offset = eocd_offset - mem::size_of::<Zip64CDELocatorBlock>() as u64;
658
659 reader.seek(io::SeekFrom::Start(locator64_offset))?;
660 Ok((
661 locator64_offset,
662 Zip64CentralDirectoryEndLocator::parse(reader)?,
663 ))
664 }
665
666 try_read_eocd64_locator(reader, eocd_offset).ok()
667 } else {
668 None
669 };
670
671 let Some((locator64_offset, locator64)) = zip64_metadata else {
672 let relative_cd_offset = eocd.central_directory_offset as u64;
674
675 if eocd.number_of_files == 0 {
677 return Ok(CentralDirectoryEndInfo {
678 eocd: (eocd, eocd_offset).into(),
679 eocd64: None,
680 archive_offset: eocd_offset.saturating_sub(relative_cd_offset),
681 });
682 }
683
684 if relative_cd_offset >= eocd_offset {
686 parsing_error = Some(ZipError::InvalidArchive("Invalid CDFH offset in EOCD"));
687 continue;
688 }
689
690 let subfinder = subfinder
692 .get_or_insert_with(OptimisticMagicFinder::new_empty)
693 .repurpose(
694 &CDFH_SIG_BYTES,
695 (relative_cd_offset, eocd_offset),
698 match archive_offset {
699 ArchiveOffset::Known(n) => {
700 Some((relative_cd_offset.saturating_add(n).min(eocd_offset), true))
701 }
702 _ => Some((relative_cd_offset, false)),
703 },
704 );
705
706 if let Some(cd_offset) = subfinder.next(reader)? {
708 let archive_offset = cd_offset - relative_cd_offset;
710
711 return Ok(CentralDirectoryEndInfo {
712 eocd: (eocd, eocd_offset).into(),
713 eocd64: None,
714 archive_offset,
715 });
716 }
717
718 parsing_error = Some(ZipError::InvalidArchive("No CDFH found"));
719 continue;
720 };
721
722 if locator64.end_of_central_directory_offset >= locator64_offset {
724 parsing_error = Some(ZipError::InvalidArchive("Invalid EOCD64 Locator CD offset"));
725 continue;
726 }
727
728 if locator64.number_of_disks > 1 {
729 parsing_error = Some(ZipError::InvalidArchive(
730 "Multi-disk ZIP files are not supported",
731 ));
732 continue;
733 }
734
735 fn try_read_eocd64<R: Read + Seek>(
738 reader: &mut R,
739 locator64: &Zip64CentralDirectoryEndLocator,
740 expected_length: u64,
741 ) -> ZipResult<Zip64CentralDirectoryEnd> {
742 let z64 = Zip64CentralDirectoryEnd::parse(reader, expected_length)?;
743
744 if z64.disk_with_central_directory != locator64.disk_with_central_directory {
746 return Err(ZipError::InvalidArchive(
747 "Invalid EOCD64: inconsistency with Locator data",
748 ));
749 }
750
751 if z64.record_size + 12 != expected_length {
753 return Err(ZipError::InvalidArchive(
754 "Invalid EOCD64: inconsistent length",
755 ));
756 }
757
758 Ok(z64)
759 }
760
761 let subfinder = subfinder
763 .get_or_insert_with(OptimisticMagicFinder::new_empty)
764 .repurpose(
765 &EOCD64_SIG_BYTES,
766 (locator64.end_of_central_directory_offset, locator64_offset),
767 match archive_offset {
768 ArchiveOffset::Known(n) => Some((
769 locator64
770 .end_of_central_directory_offset
771 .saturating_add(n)
772 .min(locator64_offset),
773 true,
774 )),
775 _ => Some((locator64.end_of_central_directory_offset, false)),
776 },
777 );
778
779 let mut local_error = None;
781 while let Some(eocd64_offset) = subfinder.next(reader)? {
782 let archive_offset = eocd64_offset - locator64.end_of_central_directory_offset;
783
784 match try_read_eocd64(
785 reader,
786 &locator64,
787 locator64_offset.saturating_sub(eocd64_offset),
788 ) {
789 Ok(eocd64) => {
790 if eocd64_offset
791 < eocd64
792 .number_of_files
793 .saturating_mul(
794 mem::size_of::<crate::types::ZipCentralEntryBlock>() as u64
795 )
796 .saturating_add(eocd64.central_directory_offset)
797 {
798 local_error = Some(ZipError::InvalidArchive(
799 "Invalid EOCD64: inconsistent number of files",
800 ));
801 continue;
802 }
803
804 return Ok(CentralDirectoryEndInfo {
805 eocd: (eocd, eocd_offset).into(),
806 eocd64: Some((eocd64, eocd64_offset).into()),
807 archive_offset,
808 });
809 }
810 Err(e) => {
811 local_error = Some(e);
812 }
813 }
814 }
815
816 parsing_error = local_error.or(Some(ZipError::InvalidArchive("Could not find EOCD64")));
817 }
818
819 Err(parsing_error.unwrap_or(ZipError::InvalidArchive("Could not find EOCD")))
820}
821
822pub(crate) fn is_dir(filename: &str) -> bool {
823 filename
824 .chars()
825 .next_back()
826 .is_some_and(|c| c == '/' || c == '\\')
827}
828
829#[cfg(test)]
830mod test {
831 use super::*;
832 use std::io::Cursor;
833
834 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
835 #[repr(packed, C)]
836 pub struct TestBlock {
837 magic: Magic,
838 pub file_name_length: u16,
839 }
840
841 unsafe impl Pod for TestBlock {}
842
843 impl FixedSizeBlock for TestBlock {
844 const MAGIC: Magic = Magic::literal(0x01111);
845
846 fn magic(self) -> Magic {
847 self.magic
848 }
849
850 const WRONG_MAGIC_ERROR: ZipError = ZipError::InvalidArchive("unreachable");
851
852 to_and_from_le![(magic, Magic), (file_name_length, u16)];
853 }
854
855 #[test]
857 fn block_serde() {
858 let block = TestBlock {
859 magic: TestBlock::MAGIC,
860 file_name_length: 3,
861 };
862 let mut c = Cursor::new(Vec::new());
863 block.write(&mut c).unwrap();
864 c.set_position(0);
865 let block2 = TestBlock::parse(&mut c).unwrap();
866 assert_eq!(block, block2);
867 }
868}