use std::io::Cursor;
use binrw::{binrw, BinRead, BinWrite};
use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
#[cfg(feature = "tracing")]
use tracing::{instrument, trace};
use crate::{error::Result, spec::KnownSize};
#[binrw]
#[brw(little)]
#[brw(repr = u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Signature {
LFH = 0x04034b50,
CDRH = 0x02014b50,
EOCDRH = 0x06054b50,
EOCDR64H = 0x06064b50,
EOCDL64H = 0x07064b50,
DD = 0x08074b50,
}
impl From<Signature> for u32 {
fn from(sig: Signature) -> Self {
sig as u32
}
}
impl KnownSize for Signature {
const SIZE: usize = 4;
}
#[binrw]
#[brw(little)]
#[brw(repr = u16)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Compression {
Stored = 0,
Shrunk = 1,
Reduced1 = 2,
Reduced2 = 3,
Reduced3 = 4,
Reduced4 = 5,
Imploded = 6,
Tokenizing = 7,
Deflate = 8,
Deflate64 = 9,
PKImploding = 10,
Reserved1 = 11,
Bz = 12,
Reserved2 = 13,
Lzma = 14,
Reserved3 = 15,
ZosCmpsc = 16,
Reserved4 = 17,
IbtTerse = 18,
IbtLz77 = 19,
Deprecated = 20,
Zstd = 93,
Mp3 = 94,
Xz = 95,
Jpeg = 96,
WavPack = 97,
Ppmd = 98,
Aex = 99,
}
#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct LFH {
pub version: u16,
pub gpf: GPF,
pub compression: Compression,
pub mod_time: u16,
pub mod_date: u16,
pub crc: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub file_name_length: u16,
pub extra_field_length: u16,
}
impl LFH {
pub fn claimed_length_in_bytes(&self) -> usize {
LFH::SIZE + self.file_name_length as usize + self.extra_field_length as usize
}
}
impl KnownSize for LFH {
const SIZE: usize = 26;
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct CDRH {
pub v_made_by: u16,
pub v_needed: u16,
pub gpf: GPF,
pub compression: Compression,
pub mod_time: u16,
pub mod_date: u16,
pub crc: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub file_name_length: u16,
pub extra_field_length: u16,
pub file_comment_length: u16,
pub disk_start: u16,
pub inter_attr: u16,
pub exter_attr: u32,
pub lfh_offset: u32,
}
impl KnownSize for CDRH {
const SIZE: usize = 42;
}
#[binrw]
#[brw(little)]
#[derive(Debug, Clone)]
pub struct EOCDRH {
pub(crate) disk_num: u16,
pub(crate) disk_num_start_of_cd: u16,
pub(crate) num_of_entries_this_disk: u16,
pub(crate) num_of_entries: u16,
pub(crate) cd_size: u32,
pub(crate) cd_offset: u32,
pub(crate) comment_length: u16,
}
impl KnownSize for EOCDRH {
const SIZE: usize = 18;
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug, PartialEq)]
pub struct GPF(u16);
impl GPF {
pub fn data_descriptor(&self) -> bool {
self.0 & 0x08 != 0
}
pub fn language_encoding_flag(&self) -> bool {
self.0 & 0x800 != 0
}
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct EOCDR64H {
pub size_of_eocdr64: u64,
pub v_made_by: u16,
pub v_needed: u16,
pub disk_num: u32,
pub disk_num_start_of_cd: u32,
pub num_entries_this_disk: u64,
pub num_entries: u64,
pub cd_size: u64,
pub cd_offset: u64,
}
impl KnownSize for EOCDR64H {
const SIZE: usize = 56;
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct EOCDL64H {
pub num_disk_with_eocdr64: u32,
pub relative_offset_of_eocdr64: u64,
pub total_num_of_disks: u32,
}
impl KnownSize for EOCDL64H {
const SIZE: usize = 16;
}
#[cfg_attr(feature = "tracing", instrument(skip(reader), level = "trace"))]
pub(crate) async fn read<T, R>(reader: &mut R) -> Result<T>
where
T: BinRead + KnownSize + std::fmt::Debug,
for<'a> T::Args<'a>: Default,
R: AsyncRead + Unpin,
{
#[cfg(feature = "tracing")]
trace!("reading header of size {:02X?}", T::SIZE);
let mut buffer = vec![0; T::SIZE];
reader.read_exact(&mut buffer).await?;
#[cfg(feature = "tracing")]
trace!("read buffer: {:02X?}", buffer);
let header = T::read_le(&mut Cursor::new(buffer))?;
#[cfg(feature = "tracing")]
trace!("parsed header: {:?}", header);
Ok(header)
}
#[cfg_attr(feature = "tracing", instrument(skip(reader, tail_len), level = "trace"))]
pub(crate) async fn read_record<H, T, R>(reader: &mut R, tail_len: impl FnOnce(&H) -> Result<usize>) -> Result<T>
where
H: BinRead + KnownSize + std::fmt::Debug,
for<'a> H::Args<'a>: Default,
T: BinRead + std::fmt::Debug,
for<'a> T::Args<'a>: Default,
R: AsyncRead + Unpin,
{
let mut buffer = vec![0; H::SIZE];
reader.read_exact(&mut buffer).await?;
let header = H::read_le(&mut Cursor::new(&buffer))?;
#[cfg(feature = "tracing")]
trace!("parsed header: {:?}", header);
let offset = buffer.len();
buffer.resize(offset + tail_len(&header)?, 0);
reader.read_exact(&mut buffer[offset..]).await?;
#[cfg(feature = "tracing")]
trace!("read record buffer: {:02X?}", buffer);
let record = T::read_le(&mut Cursor::new(buffer))?;
#[cfg(feature = "tracing")]
trace!("parsed record: {:?}", record);
Ok(record)
}
pub(crate) async fn write<T, W>(writer: &mut W, value: &T) -> Result<()>
where
T: BinWrite,
for<'a> T::Args<'a>: Default,
W: AsyncWrite + Unpin,
{
let mut buffer = Vec::new();
value.write_le(&mut Cursor::new(&mut buffer))?;
writer.write_all(&buffer).await?;
Ok(())
}