pub const DOS_DATE: u16 = 0x2198;
pub const DOS_TIME: u16 = 0xBC00;
pub const GENERAL_PURPOSE_FLAG: u16 = 0x0002;
pub const VERSION_NEEDED: u16 = 20;
pub const VERSION_MADE_BY: u16 = 0;
pub const COMPRESSION_METHOD_DEFLATE: u16 = 8;
pub const COMPRESSION_METHOD_STORED: u16 = 0;
pub const LOCAL_FILE_HEADER_SIG: u32 = 0x04034b50;
pub const CENTRAL_DIR_HEADER_SIG: u32 = 0x02014b50;
pub const END_OF_CENTRAL_DIR_SIG: u32 = 0x06054b50;
pub const TORRENTZIP_COMMENT_PREFIX: &[u8] = b"TORRENTZIPPED-";
pub const TORRENTZIP_COMMENT_LEN: u16 = 22;
#[derive(Debug, Clone, Copy)]
pub struct LocalFileHeader {
pub signature: u32,
pub version_needed: u16,
pub general_flag: u16,
pub compression_method: u16,
pub last_mod_time: u16,
pub last_mod_date: u16,
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub filename_len: u16,
pub extra_len: u16,
}
impl LocalFileHeader {
pub const SIZE: usize = 30;
pub fn new_torrentzip(filename_len: u16) -> Self {
Self {
signature: LOCAL_FILE_HEADER_SIG,
version_needed: VERSION_NEEDED,
general_flag: GENERAL_PURPOSE_FLAG,
compression_method: COMPRESSION_METHOD_DEFLATE,
last_mod_time: DOS_TIME,
last_mod_date: DOS_DATE,
crc32: 0, compressed_size: 0, uncompressed_size: 0, filename_len,
extra_len: 0,
}
}
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut buf = [0u8; Self::SIZE];
buf[0..4].copy_from_slice(&self.signature.to_le_bytes());
buf[4..6].copy_from_slice(&self.version_needed.to_le_bytes());
buf[6..8].copy_from_slice(&self.general_flag.to_le_bytes());
buf[8..10].copy_from_slice(&self.compression_method.to_le_bytes());
buf[10..12].copy_from_slice(&self.last_mod_time.to_le_bytes());
buf[12..14].copy_from_slice(&self.last_mod_date.to_le_bytes());
buf[14..18].copy_from_slice(&self.crc32.to_le_bytes());
buf[18..22].copy_from_slice(&self.compressed_size.to_le_bytes());
buf[22..26].copy_from_slice(&self.uncompressed_size.to_le_bytes());
buf[26..28].copy_from_slice(&self.filename_len.to_le_bytes());
buf[28..30].copy_from_slice(&self.extra_len.to_le_bytes());
buf
}
}
#[derive(Debug, Clone, Copy)]
pub struct CentralDirHeader {
pub signature: u32,
pub version_made_by: u16,
pub version_needed: u16,
pub general_flag: u16,
pub compression_method: u16,
pub last_mod_time: u16,
pub last_mod_date: u16,
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub filename_len: u16,
pub extra_len: u16,
pub comment_len: u16,
pub disk_start: u16,
pub internal_attr: u16,
pub external_attr: u32,
pub local_header_offset: u32,
}
impl CentralDirHeader {
pub const SIZE: usize = 46;
pub fn new_torrentzip(filename_len: u16, local_header_offset: u32) -> Self {
Self {
signature: CENTRAL_DIR_HEADER_SIG,
version_made_by: VERSION_MADE_BY,
version_needed: VERSION_NEEDED,
general_flag: GENERAL_PURPOSE_FLAG,
compression_method: COMPRESSION_METHOD_DEFLATE,
last_mod_time: DOS_TIME,
last_mod_date: DOS_DATE,
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
filename_len,
extra_len: 0,
comment_len: 0,
disk_start: 0,
internal_attr: 0,
external_attr: 0,
local_header_offset,
}
}
pub fn with_sizes(mut self, crc32: u32, compressed_size: u32, uncompressed_size: u32) -> Self {
self.crc32 = crc32;
self.compressed_size = compressed_size;
self.uncompressed_size = uncompressed_size;
self
}
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut buf = [0u8; Self::SIZE];
buf[0..4].copy_from_slice(&self.signature.to_le_bytes());
buf[4..6].copy_from_slice(&self.version_made_by.to_le_bytes());
buf[6..8].copy_from_slice(&self.version_needed.to_le_bytes());
buf[8..10].copy_from_slice(&self.general_flag.to_le_bytes());
buf[10..12].copy_from_slice(&self.compression_method.to_le_bytes());
buf[12..14].copy_from_slice(&self.last_mod_time.to_le_bytes());
buf[14..16].copy_from_slice(&self.last_mod_date.to_le_bytes());
buf[16..20].copy_from_slice(&self.crc32.to_le_bytes());
buf[20..24].copy_from_slice(&self.compressed_size.to_le_bytes());
buf[24..28].copy_from_slice(&self.uncompressed_size.to_le_bytes());
buf[28..30].copy_from_slice(&self.filename_len.to_le_bytes());
buf[30..32].copy_from_slice(&self.extra_len.to_le_bytes());
buf[32..34].copy_from_slice(&self.comment_len.to_le_bytes());
buf[34..36].copy_from_slice(&self.disk_start.to_le_bytes());
buf[36..38].copy_from_slice(&self.internal_attr.to_le_bytes());
buf[38..42].copy_from_slice(&self.external_attr.to_le_bytes());
buf[42..46].copy_from_slice(&self.local_header_offset.to_le_bytes());
buf
}
}
#[derive(Debug, Clone, Copy)]
pub struct EndOfCentralDir {
pub signature: u32,
pub disk_num: u16,
pub disk_cd_start: u16,
pub disk_entries: u16,
pub total_entries: u16,
pub cd_size: u32,
pub cd_offset: u32,
pub comment_len: u16,
}
impl EndOfCentralDir {
pub const SIZE: usize = 22;
pub fn new_torrentzip(entries: u16, cd_size: u32, cd_offset: u32) -> Self {
Self {
signature: END_OF_CENTRAL_DIR_SIG,
disk_num: 0,
disk_cd_start: 0,
disk_entries: entries,
total_entries: entries,
cd_size,
cd_offset,
comment_len: TORRENTZIP_COMMENT_LEN,
}
}
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut buf = [0u8; Self::SIZE];
buf[0..4].copy_from_slice(&self.signature.to_le_bytes());
buf[4..6].copy_from_slice(&self.disk_num.to_le_bytes());
buf[6..8].copy_from_slice(&self.disk_cd_start.to_le_bytes());
buf[8..10].copy_from_slice(&self.disk_entries.to_le_bytes());
buf[10..12].copy_from_slice(&self.total_entries.to_le_bytes());
buf[12..16].copy_from_slice(&self.cd_size.to_le_bytes());
buf[16..20].copy_from_slice(&self.cd_offset.to_le_bytes());
buf[20..22].copy_from_slice(&self.comment_len.to_le_bytes());
buf
}
}
pub fn make_torrentzip_comment(crc32: u32) -> Vec<u8> {
format!("TORRENTZIPPED-{:08X}", crc32).into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dos_date() {
assert_eq!(DOS_DATE, 0x2198);
}
#[test]
fn test_dos_time() {
assert_eq!(DOS_TIME, 0xBC00);
}
#[test]
fn test_torrentzip_comment() {
let comment = make_torrentzip_comment(0xF175FDED);
assert_eq!(&comment, b"TORRENTZIPPED-F175FDED");
assert_eq!(comment.len(), 22);
}
}