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
180
src/lib.rs
180
src/lib.rs
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
struct Bitstream<'a> {
|
struct Bitstream<'a> {
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
pos_bit: usize,
|
pos_bit: usize,
|
||||||
|
|
@ -9,15 +11,18 @@ impl Bitstream<'_> {
|
||||||
|
|
||||||
let mut result = 0;
|
let mut result = 0;
|
||||||
|
|
||||||
|
let mut read = 0;
|
||||||
|
|
||||||
while len > 0 {
|
while len > 0 {
|
||||||
if self.data.is_empty() {
|
if self.data.is_empty() {
|
||||||
return Err(Error::Incomplete);
|
return Err(Error::Incomplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
let to_read_from_current_byte = std::cmp::min(len % 8, 8 - self.pos_bit);
|
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))
|
||||||
result |=
|
as u16)
|
||||||
((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;
|
len -= to_read_from_current_byte;
|
||||||
self.pos_bit += 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)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ReservedBlockType,
|
ReservedBlockType,
|
||||||
Incomplete,
|
Incomplete,
|
||||||
InvalidCode,
|
InvalidCode,
|
||||||
|
InvalidDistance,
|
||||||
|
InvalidLength,
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc1951
|
// 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 =
|
let mut compute_value =
|
||||||
|first_code, extra_bits_amount, value_start| -> Result<u16, Error> {
|
|first_code, extra_bits_amount, value_start| -> Result<u16, Error> {
|
||||||
assert_eq!(extra_bits_amount, 3);
|
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)
|
// 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),
|
_ => 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),
|
286.. => return Err(Error::InvalidCode),
|
||||||
}
|
}
|
||||||
|
|
@ -416,25 +496,75 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::HuffmanTree;
|
mod helpers {
|
||||||
|
use crate::HuffmanTree;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitstream() {
|
fn bitstream_single() {
|
||||||
let bytes = [0b110_010_01_u8, 0b010_111_01];
|
let bytes = [0b10010_u8];
|
||||||
let mut stream = super::Bitstream {
|
let mut stream = crate::Bitstream {
|
||||||
data: &bytes,
|
data: &bytes,
|
||||||
pos_bit: 0,
|
pos_bit: 0,
|
||||||
};
|
};
|
||||||
assert_eq!(stream.read_bits(2).unwrap(), 0b01);
|
assert_eq!(stream.read_bits(1).unwrap(), 0);
|
||||||
assert_eq!(stream.read_bits(3).unwrap(), 0b010);
|
assert_eq!(stream.read_bits(1).unwrap(), 1);
|
||||||
assert_eq!(stream.read_bits(5).unwrap(), 0b11001);
|
assert_eq!(stream.read_bits(1).unwrap(), 0);
|
||||||
assert_eq!(stream.read_bits(3).unwrap(), 0b111);
|
assert_eq!(stream.read_bits(1).unwrap(), 0);
|
||||||
assert_eq!(stream.read_bits(3).unwrap(), 0b010);
|
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]
|
||||||
|
fn decode() {
|
||||||
|
let lengths = HuffmanTree::fixed_lit_len_code_lengths();
|
||||||
|
HuffmanTree::from_lengths(&lengths);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
mod decode {
|
||||||
fn decode() {
|
#[test]
|
||||||
let lengths = HuffmanTree::fixed_lit_len_code_lengths();
|
fn cargo_toml_gzip() {
|
||||||
HuffmanTree::from_lengths(&lengths);
|
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() {
|
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();
|
let gz = std::fs::read(gz).unwrap();
|
||||||
|
|
||||||
assert_eq!(gz[0], 31, "ID");
|
assert_eq!(gz[0], 31, "ID");
|
||||||
assert_eq!(gz[1], 139, "ID");
|
assert_eq!(gz[1], 139, "ID");
|
||||||
assert_eq!(gz[2], 8, "compression method");
|
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
|
zwergli::inflate_gzip(&gz, &mut out).unwrap();
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
dbg!(String::from_utf8(out)).ok();
|
dbg!(String::from_utf8(out)).ok();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue