#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Container {
#[default]
Zlib,
Gzip,
Raw,
}
impl Container {
pub fn header(self, level: u32) -> Vec<u8> {
match self {
Container::Zlib => crate::unit::zlib_header(level).to_vec(),
Container::Gzip => {
let xfl: u8 = match level {
9 => 2,
1 => 4,
_ => 0,
};
vec![
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, xfl, 0x03, ]
}
Container::Raw => Vec::new(),
}
}
pub fn checksum_init(self) -> u32 {
match self {
Container::Zlib => 1,
Container::Gzip => 0,
Container::Raw => 0,
}
}
pub fn checksum(self, data: &[u8]) -> u32 {
match self {
Container::Zlib => crate::unit::adler32(1, data),
Container::Gzip | Container::Raw => crate::crc32::crc32(0, data),
}
}
pub fn checksum_combine(self, acc: u32, next: u32, next_len: u64) -> u32 {
match self {
Container::Zlib => crate::unit::adler32_combine(acc, next, next_len),
Container::Gzip | Container::Raw => crate::crc32::crc32_combine(acc, next, next_len),
}
}
pub fn trailer(self, sum: u32, total: u64) -> Vec<u8> {
match self {
Container::Zlib => sum.to_be_bytes().to_vec(),
Container::Gzip => {
let mut v = Vec::with_capacity(8);
v.extend_from_slice(&sum.to_le_bytes());
v.extend_from_slice(&((total & 0xffff_ffff) as u32).to_le_bytes());
v
}
Container::Raw => Vec::new(),
}
}
pub fn encoder_is_zlib_wrapped(self) -> bool {
matches!(self, Container::Zlib)
}
pub fn wrap(self, level: u32, body: &[u8], src: &[u8]) -> Vec<u8> {
let head = self.header(level);
let tail = self.trailer(self.checksum(src), src.len() as u64);
let mut out = Vec::with_capacity(head.len() + body.len() + tail.len());
out.extend_from_slice(&head);
out.extend_from_slice(body);
out.extend_from_slice(&tail);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gzip_header_matches_a_real_gzip_encoder() {
use std::io::Write;
let expected: [(u32, [u8; 10]); 3] = [
(1, [0x1f, 0x8b, 0x08, 0x00, 0, 0, 0, 0, 0x04, 0x03]),
(6, [0x1f, 0x8b, 0x08, 0x00, 0, 0, 0, 0, 0x00, 0x03]),
(9, [0x1f, 0x8b, 0x08, 0x00, 0, 0, 0, 0, 0x02, 0x03]),
];
for (level, want) in expected {
let ours = Container::Gzip.header(level);
assert_eq!(ours.len(), 10, "gzip header is 10 bytes");
assert_eq!(
ours,
want.to_vec(),
"level {level}: header differs from GNU gzip's"
);
let mut e = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::new(level));
e.write_all(b"payload").unwrap();
let real = e.finish().unwrap();
assert_eq!(&real[..4], &ours[..4], "level {level}: magic/CM/FLG differ");
assert_eq!(real[8], ours[8], "level {level}: XFL differs");
assert_eq!(
real[9], 255,
"flate2 changed its OS byte; re-read the note above"
);
}
}
#[test]
fn trailers_have_the_right_shape_and_endianness() {
assert_eq!(
Container::Zlib.trailer(0x0123_4567, 99),
vec![0x01, 0x23, 0x45, 0x67]
);
assert_eq!(
Container::Gzip.trailer(0x0123_4567, 0x89AB_CDEF),
vec![0x67, 0x45, 0x23, 0x01, 0xEF, 0xCD, 0xAB, 0x89]
);
assert_eq!(
&Container::Gzip.trailer(0, (1u64 << 32) + 5)[4..],
&[5, 0, 0, 0]
);
assert!(Container::Raw.trailer(123, 456).is_empty());
}
#[test]
fn checksum_identity_matches_the_algorithm() {
assert_eq!(Container::Zlib.checksum_init(), 1, "Adler-32 identity is 1");
assert_eq!(Container::Gzip.checksum_init(), 0, "CRC-32 identity is 0");
assert_eq!(
Container::Zlib.checksum(b""),
Container::Zlib.checksum_init()
);
assert_eq!(
Container::Gzip.checksum(b""),
Container::Gzip.checksum_init()
);
assert_ne!(
Container::Zlib.checksum(b"abc"),
Container::Gzip.checksum(b"abc")
);
assert_eq!(Container::Gzip.checksum(b"123456789"), 0xCBF4_3926);
}
}