use flate2::{Compression, Crc, write::DeflateEncoder};
use std::io::Write;
const MEMBER_NAME: &[u8] = b"main.voxj";
const ZIP64_SENTINEL: u32 = 0xFFFF_FFFF;
const ZIP64_VERSION: u16 = 45;
const BASE_VERSION: u16 = 20;
const DOS_TIME: u16 = 0x0000;
const DOS_DATE: u16 = 0x0021;
pub fn wrap_voxjz(member: &[u8]) -> Vec<u8> {
wrap_voxjz_with(member, u64::from(ZIP64_SENTINEL))
}
fn wrap_voxjz_with(member: &[u8], zip64_at: u64) -> Vec<u8> {
let mut crc = Crc::new();
crc.update(member);
let crc = crc.sum();
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best());
encoder
.write_all(member)
.expect("write to Vec is infallible");
let compressed = encoder.finish().expect("flush to Vec is infallible");
let uncompressed_size = member.len() as u64;
let compressed_size = compressed.len() as u64;
let name_len = MEMBER_NAME.len() as u16;
let entry_zip64 = uncompressed_size >= zip64_at || compressed_size >= zip64_at;
let version_needed = if entry_zip64 {
ZIP64_VERSION
} else {
BASE_VERSION
};
let (stored_csize, stored_usize) = if entry_zip64 {
(ZIP64_SENTINEL, ZIP64_SENTINEL)
} else {
(compressed_size as u32, uncompressed_size as u32)
};
let extra = if entry_zip64 {
zip64_size_extra(uncompressed_size, compressed_size)
} else {
Vec::new()
};
let extra_len = extra.len() as u16;
let mut out = Vec::new();
out.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); out.extend_from_slice(&version_needed.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&8u16.to_le_bytes()); out.extend_from_slice(&DOS_TIME.to_le_bytes()); out.extend_from_slice(&DOS_DATE.to_le_bytes()); out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&stored_csize.to_le_bytes());
out.extend_from_slice(&stored_usize.to_le_bytes());
out.extend_from_slice(&name_len.to_le_bytes());
out.extend_from_slice(&extra_len.to_le_bytes()); out.extend_from_slice(MEMBER_NAME);
out.extend_from_slice(&extra);
out.extend_from_slice(&compressed);
let cd_offset = out.len() as u64;
out.extend_from_slice(&0x0201_4b50u32.to_le_bytes()); out.extend_from_slice(&version_needed.to_le_bytes()); out.extend_from_slice(&version_needed.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&8u16.to_le_bytes()); out.extend_from_slice(&DOS_TIME.to_le_bytes()); out.extend_from_slice(&DOS_DATE.to_le_bytes()); out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&stored_csize.to_le_bytes());
out.extend_from_slice(&stored_usize.to_le_bytes());
out.extend_from_slice(&name_len.to_le_bytes());
out.extend_from_slice(&extra_len.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(MEMBER_NAME);
out.extend_from_slice(&extra);
let cd_size = out.len() as u64 - cd_offset;
if entry_zip64 || cd_offset >= zip64_at || cd_size >= zip64_at {
let zip64_eocd_offset = out.len() as u64;
out.extend_from_slice(&0x0606_4b50u32.to_le_bytes()); out.extend_from_slice(&44u64.to_le_bytes()); out.extend_from_slice(&ZIP64_VERSION.to_le_bytes()); out.extend_from_slice(&ZIP64_VERSION.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&1u64.to_le_bytes()); out.extend_from_slice(&1u64.to_le_bytes()); out.extend_from_slice(&cd_size.to_le_bytes()); out.extend_from_slice(&cd_offset.to_le_bytes());
out.extend_from_slice(&0x0706_4b50u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&zip64_eocd_offset.to_le_bytes()); out.extend_from_slice(&1u32.to_le_bytes()); }
let eocd_cd_size = if cd_size >= zip64_at {
ZIP64_SENTINEL
} else {
cd_size as u32
};
let eocd_cd_offset = if cd_offset >= zip64_at {
ZIP64_SENTINEL
} else {
cd_offset as u32
};
out.extend_from_slice(&0x0605_4b50u32.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&1u16.to_le_bytes()); out.extend_from_slice(&1u16.to_le_bytes()); out.extend_from_slice(&eocd_cd_size.to_le_bytes());
out.extend_from_slice(&eocd_cd_offset.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out
}
fn zip64_size_extra(uncompressed_size: u64, compressed_size: u64) -> Vec<u8> {
let mut extra = Vec::with_capacity(20);
extra.extend_from_slice(&0x0001u16.to_le_bytes()); extra.extend_from_slice(&16u16.to_le_bytes()); extra.extend_from_slice(&uncompressed_size.to_le_bytes());
extra.extend_from_slice(&compressed_size.to_le_bytes());
extra
}
#[cfg(test)]
mod tests {
use super::{
DOS_DATE, MEMBER_NAME, ZIP64_SENTINEL, ZIP64_VERSION, wrap_voxjz, wrap_voxjz_with,
};
use crate::unwrap_voxjz;
const EOCD_SIG: [u8; 4] = [0x50, 0x4b, 0x05, 0x06];
const ZIP64_EOCD_SIG: [u8; 4] = [0x50, 0x4b, 0x06, 0x06];
const ZIP64_LOCATOR_SIG: [u8; 4] = [0x50, 0x4b, 0x06, 0x07];
const ZIP64_EXTRA_ID: [u8; 2] = [0x01, 0x00];
fn u16_at(bytes: &[u8], at: usize) -> u16 {
u16::from_le_bytes([bytes[at], bytes[at + 1]])
}
fn u32_at(bytes: &[u8], at: usize) -> u32 {
u32::from_le_bytes(bytes[at..at + 4].try_into().unwrap())
}
#[test]
fn classic_archive_round_trips_without_zip64() {
let member = br#"{"version":1,"main":{}}"#;
let bytes = wrap_voxjz(member);
let eocd = bytes.len() - 22;
assert_eq!(bytes[eocd..eocd + 4], EOCD_SIG);
assert_ne!(bytes[eocd - 20..eocd - 16], ZIP64_LOCATOR_SIG);
assert_eq!(u16_at(&bytes, 28), 0); assert_eq!(u16_at(&bytes, 12), DOS_DATE);
assert_eq!(unwrap_voxjz(&bytes).unwrap(), member);
}
#[test]
fn zip64_archive_round_trips_and_is_well_formed() {
let member = br#"{"version":1,"main":{"note":"forced zip64"}}"#;
let bytes = wrap_voxjz_with(member, 1);
assert_eq!(u16_at(&bytes, 4), ZIP64_VERSION); assert_eq!(u32_at(&bytes, 18), ZIP64_SENTINEL); assert_eq!(u32_at(&bytes, 22), ZIP64_SENTINEL); let name_len = u16_at(&bytes, 26) as usize;
let extra_len = u16_at(&bytes, 28) as usize;
assert_eq!(name_len, MEMBER_NAME.len());
let extra = &bytes[30 + name_len..30 + name_len + extra_len];
assert_eq!(extra[0..2], ZIP64_EXTRA_ID);
assert_eq!(u16_at(extra, 2), 16);
let eocd = bytes.len() - 22;
let locator = eocd - 20;
let zip64_eocd = locator - 56;
assert_eq!(bytes[eocd..eocd + 4], EOCD_SIG);
assert_eq!(bytes[locator..locator + 4], ZIP64_LOCATOR_SIG);
assert_eq!(bytes[zip64_eocd..zip64_eocd + 4], ZIP64_EOCD_SIG);
assert_eq!(u32_at(&bytes, eocd + 16), ZIP64_SENTINEL);
assert_eq!(unwrap_voxjz(&bytes).unwrap(), member);
}
}