use crate::BaleError;
use zerocopy::byteorder::little_endian::{U16, U32, U64};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(C)]
pub struct Zip64Eocd {
pub signature: U32,
pub record_size: U64,
pub version_made_by: U16,
pub version_needed: U16,
pub disk_number: U32,
pub cd_start_disk: U32,
pub cd_entries_disk: U64,
pub cd_entries_total: U64,
pub cd_size: U64,
pub cd_offset: U64,
}
impl Zip64Eocd {
pub const SIGNATURE: u32 = 0x06064b50;
pub const SIZE: usize = 56;
const RECORD_DATA_SIZE: u64 = 44;
const VERSION_MADE_BY_UNIX: u16 = (3 << 8) | 45;
const VERSION_NEEDED_ZIP64: u16 = 45;
#[must_use]
pub fn new(entry_count: u64, cd_size: u64, cd_offset: u64) -> Self {
Self {
signature: U32::new(Self::SIGNATURE),
record_size: U64::new(Self::RECORD_DATA_SIZE),
version_made_by: U16::new(Self::VERSION_MADE_BY_UNIX),
version_needed: U16::new(Self::VERSION_NEEDED_ZIP64),
disk_number: U32::new(0),
cd_start_disk: U32::new(0),
cd_entries_disk: U64::new(entry_count),
cd_entries_total: U64::new(entry_count),
cd_size: U64::new(cd_size),
cd_offset: U64::new(cd_offset),
}
}
#[must_use]
pub const fn cd_entries(&self) -> u64 {
self.cd_entries_total.get()
}
#[must_use]
pub const fn cd_size(&self) -> u64 {
self.cd_size.get()
}
#[must_use]
pub const fn cd_offset(&self) -> u64 {
self.cd_offset.get()
}
#[must_use]
pub const fn is_valid(&self) -> bool {
self.signature.get() == Self::SIGNATURE && self.record_size.get() >= Self::RECORD_DATA_SIZE
}
pub fn validated(&self) -> Result<&Self, BaleError> {
if self.is_valid() {
Ok(self)
} else {
Err(BaleError::InvalidSignature {
expected: Self::SIGNATURE,
found: self.signature.get(),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_is_56_bytes() {
assert_eq!(std::mem::size_of::<Zip64Eocd>(), Zip64Eocd::SIZE);
assert_eq!(Zip64Eocd::SIZE, 56);
}
#[test]
fn new_has_correct_signature() {
let eocd = Zip64Eocd::new(0, 0, 0);
assert!(eocd.is_valid());
assert_eq!(eocd.signature.get(), Zip64Eocd::SIGNATURE);
assert_eq!(eocd.record_size.get(), 44);
}
#[test]
fn roundtrip_with_values() {
let eocd = Zip64Eocd::new(100_000, 1_000_000_000, 2_000_000_000);
let bytes = eocd.as_bytes();
assert_eq!(bytes.len(), Zip64Eocd::SIZE);
let restored = Zip64Eocd::ref_from_bytes(bytes).unwrap();
assert!(restored.is_valid());
assert_eq!(restored.cd_entries(), 100_000);
assert_eq!(restored.cd_size(), 1_000_000_000);
assert_eq!(restored.cd_offset(), 2_000_000_000);
}
#[test]
fn accessors() {
let eocd = Zip64Eocd::new(42, 1000, 2000);
assert_eq!(eocd.cd_entries(), 42);
assert_eq!(eocd.cd_size(), 1000);
assert_eq!(eocd.cd_offset(), 2000);
}
#[test]
fn invalid_signature_fails_validation() {
let mut eocd = Zip64Eocd::new(0, 0, 0);
eocd.signature = U32::new(0x12345678);
assert!(!eocd.is_valid());
}
#[test]
fn invalid_record_size_fails_validation() {
let mut eocd = Zip64Eocd::new(0, 0, 0);
eocd.record_size = U64::new(43); assert!(!eocd.is_valid());
}
#[test]
fn larger_record_size_passes_validation() {
let mut eocd = Zip64Eocd::new(0, 0, 0);
eocd.record_size = U64::new(100); assert!(eocd.is_valid());
}
#[test]
fn validated_returns_ok_for_valid() {
let eocd = Zip64Eocd::new(0, 0, 0);
assert!(eocd.validated().is_ok());
}
#[test]
fn validated_returns_err_for_invalid() {
let mut eocd = Zip64Eocd::new(0, 0, 0);
eocd.signature = U32::new(0xDEADBEEF);
let err = eocd.validated().unwrap_err();
assert!(matches!(
err,
BaleError::InvalidSignature {
expected: 0x06064b50,
found: 0xDEADBEEF
}
));
}
#[test]
fn large_values() {
let large_offset: u64 = 5_000_000_000; let large_entries: u64 = 100_000;
let large_size: u64 = 10_000_000_000;
let eocd = Zip64Eocd::new(large_entries, large_size, large_offset);
assert_eq!(eocd.cd_entries(), large_entries);
assert_eq!(eocd.cd_size(), large_size);
assert_eq!(eocd.cd_offset(), large_offset);
}
}