use zerocopy::byteorder::little_endian::{U16, U32};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
use crate::{DosDateTime, EntryKind};
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(C)]
pub struct CentralDirectoryHeader {
pub signature: U32,
pub version_made_by: U16,
pub version_needed: U16,
pub flags: U16,
pub compression: U16,
pub mod_time: U16,
pub mod_date: U16,
pub crc32: U32,
pub compressed_size: U32,
pub uncompressed_size: U32,
pub filename_length: U16,
pub extra_length: U16,
pub comment_length: U16,
pub disk_start: U16,
pub internal_attrs: U16,
pub external_attrs: U32,
pub local_header_offset: U32,
}
impl CentralDirectoryHeader {
pub const SIGNATURE: u32 = 0x02014b50;
pub const SIZE: usize = 46;
pub const EXTRA_SIZE: u16 = 8;
const VERSION_MADE_BY_UNIX: u16 = (3 << 8) | 10;
const VERSION_STORE: u16 = 10;
const COMPRESSION_STORE: u16 = 0;
#[must_use]
pub const fn stride(path_size: usize) -> usize {
Self::SIZE
.saturating_add(path_size)
.saturating_add(Self::EXTRA_SIZE as usize)
}
#[must_use]
pub fn kind(&self) -> EntryKind {
let mode = self.external_attrs.get() >> 16;
EntryKind::from_mode(mode)
}
#[must_use]
pub fn new(
size: u32,
crc32: u32,
mtime: DosDateTime,
local_offset: u32,
unix_mode: u32,
path_size: u16,
) -> Self {
let external_attrs = (unix_mode & 0xFFFF) << 16;
Self {
signature: U32::new(Self::SIGNATURE),
version_made_by: U16::new(Self::VERSION_MADE_BY_UNIX),
version_needed: U16::new(Self::VERSION_STORE),
flags: U16::new(0),
compression: U16::new(Self::COMPRESSION_STORE),
mod_time: U16::new(mtime.time),
mod_date: U16::new(mtime.date),
crc32: U32::new(crc32),
compressed_size: U32::new(size),
uncompressed_size: U32::new(size),
filename_length: U16::new(path_size),
extra_length: U16::new(Self::EXTRA_SIZE),
comment_length: U16::new(0),
disk_start: U16::new(0),
internal_attrs: U16::new(0),
external_attrs: U32::new(external_attrs),
local_header_offset: U32::new(local_offset),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BaleEocd;
const TEST_PATH_SIZE: u16 = 128;
const UNIX_MODE_FILE: u32 = 0o644;
const UNIX_MODE_EXEC: u32 = 0o755;
#[test]
fn header_size_is_46_bytes() {
assert_eq!(
std::mem::size_of::<CentralDirectoryHeader>(),
CentralDirectoryHeader::SIZE
);
}
#[test]
fn stride_is_header_plus_path_plus_extra() {
let default_path = BaleEocd::DEFAULT_PATH_SIZE as usize;
let extra = CentralDirectoryHeader::EXTRA_SIZE as usize;
let test_path = TEST_PATH_SIZE as usize;
assert_eq!(
CentralDirectoryHeader::stride(default_path),
CentralDirectoryHeader::SIZE + default_path + extra
);
assert_eq!(
CentralDirectoryHeader::stride(test_path),
CentralDirectoryHeader::SIZE + test_path + extra
);
}
#[test]
fn new_header_has_correct_signature() {
let header = CentralDirectoryHeader::new(
100,
0x12345678,
DosDateTime::default(),
0,
UNIX_MODE_FILE,
BaleEocd::DEFAULT_PATH_SIZE,
);
assert_eq!(header.signature.get(), CentralDirectoryHeader::SIGNATURE);
}
#[test]
fn unix_mode_in_external_attrs() {
let header = CentralDirectoryHeader::new(
100,
0,
DosDateTime::default(),
0,
UNIX_MODE_EXEC,
BaleEocd::DEFAULT_PATH_SIZE,
);
assert_eq!(header.external_attrs.get(), UNIX_MODE_EXEC << 16);
}
#[test]
fn kind_returns_file() {
let header = CentralDirectoryHeader::new(
100,
0,
DosDateTime::default(),
0,
0o100644, BaleEocd::DEFAULT_PATH_SIZE,
);
assert_eq!(header.kind(), crate::EntryKind::File);
}
#[test]
fn kind_returns_directory() {
let header = CentralDirectoryHeader::new(
0,
0,
DosDateTime::default(),
0,
0o040755, BaleEocd::DEFAULT_PATH_SIZE,
);
assert_eq!(header.kind(), crate::EntryKind::Directory);
}
#[test]
fn roundtrip() {
const FILE_SIZE: u32 = 1024;
const TEST_CRC: u32 = 0xD87F7E0C; const LOCAL_OFFSET: u32 = 4096;
const TEST_DATE: u16 = 0x58CF; const TEST_TIME: u16 = 0x6955;
let mtime = DosDateTime::from_date_time_parts(TEST_DATE, TEST_TIME);
let header = CentralDirectoryHeader::new(
FILE_SIZE,
TEST_CRC,
mtime,
LOCAL_OFFSET,
UNIX_MODE_FILE,
BaleEocd::DEFAULT_PATH_SIZE,
);
let bytes = header.as_bytes();
assert_eq!(bytes.len(), CentralDirectoryHeader::SIZE);
let restored = CentralDirectoryHeader::ref_from_bytes(bytes).unwrap();
assert_eq!(restored.signature.get(), CentralDirectoryHeader::SIGNATURE);
assert_eq!(restored.uncompressed_size.get(), FILE_SIZE);
assert_eq!(restored.crc32.get(), TEST_CRC);
assert_eq!(restored.local_header_offset.get(), LOCAL_OFFSET);
assert_eq!(restored.filename_length.get(), BaleEocd::DEFAULT_PATH_SIZE);
}
}