mirror of
https://github.com/Noratrieb/zwergli.git
synced 2026-07-12 09:07:48 +02:00
static work
This commit is contained in:
parent
8e1d90e0ce
commit
00ec110aac
2 changed files with 160 additions and 45 deletions
162
src/lib.rs
162
src/lib.rs
|
|
@ -1,3 +1,5 @@
|
|||
use std::ffi::CStr;
|
||||
|
||||
struct Bitstream<'a> {
|
||||
data: &'a [u8],
|
||||
pos_bit: usize,
|
||||
|
|
@ -9,15 +11,18 @@ impl Bitstream<'_> {
|
|||
|
||||
let mut result = 0;
|
||||
|
||||
let mut read = 0;
|
||||
|
||||
while len > 0 {
|
||||
if self.data.is_empty() {
|
||||
return Err(Error::Incomplete);
|
||||
}
|
||||
|
||||
let to_read_from_current_byte = std::cmp::min(len % 8, 8 - self.pos_bit);
|
||||
result <<= to_read_from_current_byte;
|
||||
result |=
|
||||
((self.data[0] >> self.pos_bit) & ((1 << to_read_from_current_byte) - 1)) as u16;
|
||||
result |= (((self.data[0] >> self.pos_bit) & ((1 << to_read_from_current_byte) - 1))
|
||||
as u16)
|
||||
<< read;
|
||||
read += to_read_from_current_byte;
|
||||
len -= to_read_from_current_byte;
|
||||
self.pos_bit += to_read_from_current_byte;
|
||||
|
||||
|
|
@ -235,11 +240,78 @@ impl HuffmanTree {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GzipError {
|
||||
InvalidHeader,
|
||||
UnexpectedHeaderValue {
|
||||
value: u8,
|
||||
expected: u8,
|
||||
kind: &'static str,
|
||||
},
|
||||
Deflate(Error),
|
||||
}
|
||||
|
||||
pub fn inflate_gzip(data: &[u8], out: &mut Vec<u8>) -> Result<(), GzipError> {
|
||||
if data.len() < 10 {
|
||||
return Err(GzipError::InvalidHeader);
|
||||
}
|
||||
if data[0] != 31 {
|
||||
return Err(GzipError::UnexpectedHeaderValue {
|
||||
value: data[0],
|
||||
expected: 31,
|
||||
kind: "ID",
|
||||
});
|
||||
}
|
||||
if data[1] != 139 {
|
||||
return Err(GzipError::UnexpectedHeaderValue {
|
||||
value: data[1],
|
||||
expected: 31,
|
||||
kind: "ID",
|
||||
});
|
||||
}
|
||||
if data[2] != 8 {
|
||||
return Err(GzipError::UnexpectedHeaderValue {
|
||||
value: data[2],
|
||||
expected: 8,
|
||||
kind: "compression method",
|
||||
});
|
||||
}
|
||||
|
||||
let flg = data[3];
|
||||
|
||||
if flg != 8 && flg != 0 {
|
||||
return Err(GzipError::UnexpectedHeaderValue {
|
||||
value: flg,
|
||||
expected: 8,
|
||||
kind: "FLG",
|
||||
});
|
||||
}
|
||||
|
||||
let mut data_start = 10;
|
||||
|
||||
if flg & 0b1000 != 0 {
|
||||
let fname =
|
||||
CStr::from_bytes_until_nul(&data[10..]).map_err(|_| GzipError::InvalidHeader)?;
|
||||
data_start += fname.count_bytes() + 1;
|
||||
}
|
||||
|
||||
let blocks = &data.get((data_start)..).ok_or(GzipError::InvalidHeader)?;
|
||||
let blocks = &blocks
|
||||
.get(..(blocks.len() - 8))
|
||||
.ok_or(GzipError::InvalidHeader)?; // crc32 and isize
|
||||
|
||||
inflate(blocks, out).map_err(GzipError::Deflate)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ReservedBlockType,
|
||||
Incomplete,
|
||||
InvalidCode,
|
||||
InvalidDistance,
|
||||
InvalidLength,
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc1951
|
||||
|
|
@ -347,10 +419,6 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
|
|||
let mut compute_value =
|
||||
|first_code, extra_bits_amount, value_start| -> Result<u16, Error> {
|
||||
assert_eq!(extra_bits_amount, 3);
|
||||
dbg!(data.read_bits(1).unwrap());
|
||||
dbg!(data.read_bits(1).unwrap());
|
||||
dbg!(data.read_bits(1).unwrap());
|
||||
std::process::exit(1);
|
||||
|
||||
// TODO: read_bits is broken (i suspect the byte parts are reversed)
|
||||
|
||||
|
|
@ -399,7 +467,19 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
|
|||
_ => return Err(Error::InvalidCode),
|
||||
}?;
|
||||
|
||||
dbg!((length, dist_value, distance));
|
||||
let base = out
|
||||
.len()
|
||||
.checked_sub(distance as usize)
|
||||
.ok_or(Error::InvalidDistance)?;
|
||||
|
||||
let length = length as usize;
|
||||
let end = base + length;
|
||||
|
||||
if end >= out.len() {
|
||||
return Err(Error::InvalidLength);
|
||||
}
|
||||
|
||||
out.extend_from_within(base..end);
|
||||
}
|
||||
286.. => return Err(Error::InvalidCode),
|
||||
}
|
||||
|
|
@ -416,20 +496,42 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod helpers {
|
||||
use crate::HuffmanTree;
|
||||
|
||||
#[test]
|
||||
fn bitstream() {
|
||||
let bytes = [0b110_010_01_u8, 0b010_111_01];
|
||||
let mut stream = super::Bitstream {
|
||||
fn bitstream_single() {
|
||||
let bytes = [0b10010_u8];
|
||||
let mut stream = crate::Bitstream {
|
||||
data: &bytes,
|
||||
pos_bit: 0,
|
||||
};
|
||||
assert_eq!(stream.read_bits(2).unwrap(), 0b01);
|
||||
assert_eq!(stream.read_bits(3).unwrap(), 0b010);
|
||||
assert_eq!(stream.read_bits(5).unwrap(), 0b11001);
|
||||
assert_eq!(stream.read_bits(3).unwrap(), 0b111);
|
||||
assert_eq!(stream.read_bits(3).unwrap(), 0b010);
|
||||
assert_eq!(stream.read_bits(1).unwrap(), 0);
|
||||
assert_eq!(stream.read_bits(1).unwrap(), 1);
|
||||
assert_eq!(stream.read_bits(1).unwrap(), 0);
|
||||
assert_eq!(stream.read_bits(1).unwrap(), 0);
|
||||
assert_eq!(stream.read_bits(1).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bitstream_extra_bits() {
|
||||
let bytes = [0b1110_u8];
|
||||
let mut stream = crate::Bitstream {
|
||||
data: &bytes,
|
||||
pos_bit: 0,
|
||||
};
|
||||
assert_eq!(stream.read_bits(4).unwrap(), 0b1110);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bitstream_extra_bits_byte_overlap() {
|
||||
let bytes = [0b10_000000, 0b000000_11];
|
||||
let mut stream = crate::Bitstream {
|
||||
data: &bytes,
|
||||
pos_bit: 0,
|
||||
};
|
||||
stream.read_bits(6).unwrap();
|
||||
assert_eq!(stream.read_bits(4).unwrap(), 0b1110);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -438,3 +540,31 @@ mod tests {
|
|||
HuffmanTree::from_lengths(&lengths);
|
||||
}
|
||||
}
|
||||
|
||||
mod decode {
|
||||
#[test]
|
||||
fn cargo_toml_gzip() {
|
||||
let data = [
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x7f, 0x27, 0xb4, 0x68, 0x00, 0x03, 0x43, 0x61,
|
||||
0x72, 0x67, 0x6f, 0x2e, 0x74, 0x6f, 0x6d, 0x6c, 0x00, 0x8b, 0x2e, 0x48,
|
||||
0x4c, 0xce, 0x4e, 0x4c, 0x4f, 0x8d, 0xe5, 0xca, 0x4b, 0xcc, 0x4d, 0x55,
|
||||
0xb0, 0x55, 0x50, 0xaa, 0x2a, 0x4f, 0x2d, 0x4a, 0xcf, 0xc9, 0x54, 0xe2,
|
||||
0x2a, 0x4b, 0x2d, 0x2a, 0xce, 0xcc, 0xcf, 0x03, 0x89, 0x19, 0xe8, 0x19,
|
||||
0xea, 0x19, 0x28, 0x71, 0xa5, 0xa6, 0x64, 0x96, 0x40, 0x45, 0x8c, 0x0c,
|
||||
0x8c, 0x4c, 0x94, 0xb8, 0xb8, 0xa2, 0x53, 0x52, 0x0b, 0x52, 0xf3, 0x52,
|
||||
0x52, 0xf3, 0x92, 0x33, 0x53, 0x8b, 0x63, 0xb9, 0x00, 0x5f, 0x81, 0x28,
|
||||
0x59, 0x4e, 0x00, 0x00, 0x00
|
||||
];
|
||||
let mut out = Vec::new();
|
||||
crate::inflate_gzip(&data, &mut out).unwrap();
|
||||
let out = String::from_utf8(out).unwrap();
|
||||
assert_eq!(out, r#"[package]
|
||||
name = "zwergli"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
"#)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
src/main.rs
25
src/main.rs
|
|
@ -1,31 +1,16 @@
|
|||
use std::ffi::CStr;
|
||||
|
||||
fn main() {
|
||||
let gz = std::env::args().nth(1).expect("must provide a cli argument");
|
||||
let gz = std::env::args()
|
||||
.nth(1)
|
||||
.expect("must provide a cli argument");
|
||||
let gz = std::fs::read(gz).unwrap();
|
||||
|
||||
assert_eq!(gz[0], 31, "ID");
|
||||
assert_eq!(gz[1], 139, "ID");
|
||||
assert_eq!(gz[2], 8, "compression method");
|
||||
|
||||
let flg = gz[3];
|
||||
let mut out: Vec<u8> = Vec::new();
|
||||
|
||||
assert!(flg == 8 || flg == 0); // only FLG.FNAME
|
||||
|
||||
let mut data_start = 10;
|
||||
|
||||
if flg & 0b1000 != 0 {
|
||||
let fname = CStr::from_bytes_until_nul(&gz[10..]).unwrap();
|
||||
dbg!(fname);
|
||||
data_start += fname.count_bytes() + 1;
|
||||
}
|
||||
|
||||
let blocks = &gz[(data_start)..];
|
||||
let blocks = &blocks[..(blocks.len() - 8)]; // crc32 and isize
|
||||
|
||||
let mut out = Vec::new();
|
||||
|
||||
zwergli::inflate(blocks, &mut out);
|
||||
zwergli::inflate_gzip(&gz, &mut out).unwrap();
|
||||
|
||||
dbg!(String::from_utf8(out)).ok();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue