use std::cell::RefCell;
use std::io::{self, Write};
use std::rc::Rc;
#[cfg(not(feature = "zlib-c"))]
use flate2::{Crc, Decompress, FlushDecompress, Status};
use stuffr::lz4_frame::{
BlockMode, BlockSize, FrameEncoder, FrameInfo, decompress_into, decompress_into_with_dict,
};
use stuffr::xz_opts::{self, Check, DecodeOptions, EncodeOptions, Filter, Format, Lzma, XzError};
use super::cli::CType;
use super::fileio::{
CSTREAM_IN_SIZE, DSTREAM_IN_SIZE, DSTREAM_OUT_SIZE, Fio, Foreign, FrameCodec, Io, ReadPool,
};
use super::messages as m;
use super::util::FILESIZE_UNKNOWN;
use crate::compat::host::Host;
pub(crate) struct Formats;
impl FrameCodec for Formats {
fn compress(
&mut self,
io: &mut Io<'_>,
rp: &mut ReadPool,
ctype: CType,
src_name: &[u8],
file_size: u64,
level: i32,
) -> Fio<(u64, u64)> {
match ctype {
CType::Gzip => compress_gz(io, rp, src_name, file_size, level),
CType::Lz4 => compress_lz4(io, rp, src_name, file_size, level),
CType::Lzma => compress_lzma(io, rp, src_name, file_size, level, true),
CType::Xz | CType::Zstd => compress_lzma(io, rp, src_name, file_size, level, false),
}
}
fn decompress(
&mut self,
io: &mut Io<'_>,
rp: &mut ReadPool,
kind: Foreign,
src_name: &[u8],
) -> Fio<Option<u64>> {
match kind {
Foreign::Gzip => match gzip_member() {
Some(mut dec) => decode_frame(io, rp, kind, src_name, dec.as_mut()),
None => Ok(None),
},
Foreign::Lz4 => decode_frame(io, rp, kind, src_name, &mut Lz4Member::new()),
Foreign::Xz | Foreign::Lzma => {
let plain = kind == Foreign::Lzma;
let opts = DecodeOptions {
format: if plain { Format::Lzma } else { Format::Xz },
memlimit: memlimit(io.host),
single_stream: true,
..DecodeOptions::default()
};
match xz_opts::Decoder::new(&opts) {
Ok(dec) => decode_frame(io, rp, kind, src_name, &mut LzmaMember::new(dec)),
Err(e) => {
let what = if plain {
m::LZMA_ALONE_DECODER
} else {
m::LZMA_STREAM_DECODER
};
let code = lzma_ret(&e).to_string();
io.d.at(
1,
io.err,
m::LZMA_DECODER_INIT_ERROR,
&[what.as_bytes(), src_name, code.as_bytes()],
);
Ok(None)
}
}
}
}
}
}
fn memlimit(host: &Host) -> u64 {
match host {
Host::Real => u64::MAX,
Host::Sandbox(_) => 256 << 20,
}
}
#[derive(Clone, Default)]
struct Shared(Rc<RefCell<Vec<u8>>>);
impl Shared {
fn take(&self) -> Vec<u8> {
std::mem::take(&mut *self.0.borrow_mut())
}
}
impl Write for Shared {
fn write(&mut self, b: &[u8]) -> io::Result<usize> {
self.0.borrow_mut().extend_from_slice(b);
Ok(b.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn pct(part: u64, whole: u64) -> String {
let v = part as f64 / whole as f64 * 100.0;
if v.is_nan() {
m::NAN.to_owned()
} else if v.is_infinite() {
m::INF.to_owned()
} else {
format!("{v:.2}")
}
}
fn read_progress(io: &mut Io<'_>, gz: bool, read: u64, file_size: u64, written: u64) {
let mb = ((read >> 20) as u32).to_string();
let r = pct(written, read);
if file_size == FILESIZE_UNKNOWN {
let fmt = if gz { m::GZ_PROGRESS } else { m::READ_PROGRESS };
io.display_update_progress(fmt, &[mb.as_bytes(), r.as_bytes()]);
} else {
let of = ((file_size >> 20) as u32).to_string();
let fmt = if gz {
m::GZ_PROGRESS_OF
} else {
m::READ_PROGRESS_OF
};
io.display_update_progress(fmt, &[mb.as_bytes(), of.as_bytes(), r.as_bytes()]);
}
}
fn drain(io: &mut Io<'_>, sink: &Shared) -> Fio<u64> {
let out = sink.take();
if !out.is_empty() {
io.write_block(&out)?;
}
Ok(out.len() as u64)
}
#[cfg(feature = "zlib-c")]
const CSTREAM_OUT_SIZE: usize = (128 << 10) + (128 << 10 >> 8) + 3 + 4;
#[cfg(feature = "zlib-c")]
struct GzWriter(stuffr::zlib_c::RawDeflate<Shared>);
#[cfg(feature = "zlib-c")]
impl GzWriter {
fn new(sink: Shared, level: u32) -> io::Result<Self> {
stuffr::zlib_c::RawDeflate::with_options(sink, level, 15 + 16, CSTREAM_OUT_SIZE)
.map(GzWriter)
}
fn write(&mut self, b: &[u8]) -> io::Result<()> {
self.0.write_all(b)
}
fn finish(self) -> io::Result<()> {
self.0.finish().map(drop)
}
}
#[cfg(not(feature = "zlib-c"))]
const OS_CODE: u8 = if cfg!(target_vendor = "apple") {
19
} else if cfg!(windows) {
10
} else {
3
};
#[cfg(not(feature = "zlib-c"))]
struct GzWriter {
enc: flate2::write::DeflateEncoder<Shared>,
sink: Shared,
crc: Crc,
}
#[cfg(not(feature = "zlib-c"))]
impl GzWriter {
fn new(sink: Shared, level: u32) -> io::Result<Self> {
let xfl = match level {
9 => 2,
0 | 1 => 4,
_ => 0,
};
sink.0
.borrow_mut()
.extend_from_slice(&[0x1f, 0x8b, 8, 0, 0, 0, 0, 0, xfl, OS_CODE]);
Ok(GzWriter {
enc: flate2::write::DeflateEncoder::new(sink.clone(), flate2::Compression::new(level)),
sink,
crc: Crc::new(),
})
}
fn write(&mut self, b: &[u8]) -> io::Result<()> {
self.crc.update(b);
self.enc.write_all(b)
}
fn finish(self) -> io::Result<()> {
self.enc.finish()?;
let mut out = self.sink.0.borrow_mut();
out.extend_from_slice(&self.crc.sum().to_le_bytes());
out.extend_from_slice(&self.crc.amount().to_le_bytes());
Ok(())
}
}
fn compress_gz(
io: &mut Io<'_>,
rp: &mut ReadPool,
src: &[u8],
file_size: u64,
level: i32,
) -> Fio<(u64, u64)> {
let level = level.min(9);
if level < -1 {
return Err(io.exm(71, m::GZ_DEFLATE_INIT, &[src, b"-2"]));
}
let level = if level == -1 { 6 } else { level as u32 };
let sink = Shared::default();
let Ok(mut gz) = GzWriter::new(sink.clone(), level) else {
return Err(io.exm(71, m::GZ_DEFLATE_INIT, &[src, b"-2"]));
};
let (mut read, mut written) = (0u64, 0u64);
loop {
io.fill(rp, CSTREAM_IN_SIZE)?;
let n = rp.loaded().len();
if n == 0 {
break;
}
read += n as u64;
if gz.write(rp.loaded()).is_err() {
return Err(io.exm(72, m::GZ_DEFLATE_ERROR, &[src, b"-2"]));
}
rp.consume(n);
written += drain(io, &sink)?;
read_progress(io, true, read, file_size, written);
}
if gz.finish().is_err() {
return Err(io.exm(77, m::GZ_DEFLATE_ERROR, &[src, b"-2"]));
}
written += drain(io, &sink)?;
io.sparse_end()?;
Ok((read, written))
}
fn lzma_ret(e: &XzError) -> i32 {
match e {
XzError::UnsupportedCheck(_) => 3,
XzError::Mem { .. } => 5,
XzError::MemLimit { .. } => 6,
XzError::Format => 7,
XzError::Options => 8,
XzError::Data => 9,
XzError::BufError => 10,
XzError::Prog | XzError::Io(_) => 11,
}
}
fn compress_lzma(
io: &mut Io<'_>,
rp: &mut ReadPool,
src: &[u8],
file_size: u64,
level: i32,
plain: bool,
) -> Fio<(u64, u64)> {
let mut level = level.clamp(0, 9) as u32;
if matches!(io.host, Host::Sandbox(_)) {
level = level.min(6);
}
let Some(preset) = Lzma::preset(level, false) else {
return Err(io.exm(81, m::LZMA_PRESET_ERROR, &[src]));
};
let opts = if plain {
EncodeOptions {
format: Format::Lzma,
filters: vec![Filter::Lzma1(preset)],
..EncodeOptions::default()
}
} else {
EncodeOptions {
format: Format::Xz,
filters: vec![Filter::Lzma2(preset)],
check: Check::Crc64,
threads: 0,
..EncodeOptions::default()
}
};
let mut enc = match xz_opts::Encoder::new(&opts) {
Ok(e) => e,
Err(e) => {
let code = lzma_ret(&e).to_string();
let (n, fmt) = if plain {
(82, m::LZMA_ALONE_ENCODER_ERROR)
} else {
(83, m::LZMA_EASY_ENCODER_ERROR)
};
return Err(io.exm(n, fmt, &[src, code.as_bytes()]));
}
};
let (mut read, mut written) = (0u64, 0u64);
let mut out = Vec::new();
loop {
io.fill(rp, CSTREAM_IN_SIZE)?;
let n = rp.loaded().len();
let r = if n == 0 {
enc.finish(&mut out)
} else {
read += n as u64;
enc.feed(rp.loaded(), &mut out)
};
if let Err(e) = r {
let code = lzma_ret(&e).to_string();
return Err(io.exm(84, m::LZMA_ENCODING_ERROR, &[src, code.as_bytes()]));
}
rp.consume(n);
if !out.is_empty() {
io.write_block(&out)?;
written += out.len() as u64;
out.clear();
}
read_progress(io, false, read, file_size, written);
if n == 0 {
break;
}
}
io.sparse_end()?;
Ok((read, written))
}
const LZ4_BLOCK: usize = 64 << 10;
fn compress_lz4(
io: &mut Io<'_>,
rp: &mut ReadPool,
src: &[u8],
file_size: u64,
_level: i32,
) -> Fio<(u64, u64)> {
let content_size = (file_size != FILESIZE_UNKNOWN && file_size != 0).then_some(file_size);
let info = FrameInfo::new()
.block_size(BlockSize::Max64KB)
.block_mode(BlockMode::Linked)
.content_checksum(io.prefs.checksum == 1)
.content_size(content_size);
let sink = Shared::default();
let mut enc = FrameEncoder::with_frame_info(info, sink.clone());
let (mut read, mut written) = (0u64, 0u64);
read += io.fill(rp, LZ4_BLOCK)? as u64;
while !rp.loaded().is_empty() {
let n = LZ4_BLOCK.min(rp.loaded().len());
if enc.write_all(&rp.loaded()[..n]).is_err() {
return Err(io.exm(
35,
m::LZ4_COMPRESS_FAILED,
&[src, m::LZ4F_GENERIC.as_bytes()],
));
}
written += drain(io, &sink)?;
read_progress(io, false, read, file_size, written);
rp.consume(n);
read += io.fill(rp, LZ4_BLOCK)? as u64;
}
if enc.finish().is_err() {
return Err(io.exm(
38,
m::LZ4_END_FAILED,
&[src, m::LZ4F_FRAME_SIZE_WRONG.as_bytes()],
));
}
written += drain(io, &sink)?;
io.sparse_end()?;
Ok((read, written))
}
enum Fail {
BufError,
Code(i32),
Lz4(&'static str),
Unfinished,
}
struct Call {
used: usize,
made: usize,
r: Result<bool, Fail>,
}
trait Coder {
fn call(&mut self, input: &[u8], out: &mut [u8], finish: bool) -> Call;
fn unused(&self) -> usize {
0
}
}
const READ_AHEAD: usize = 64;
fn decode_frame(
io: &mut Io<'_>,
rp: &mut ReadPool,
kind: Foreign,
src: &[u8],
dec: &mut dyn Coder,
) -> Fio<Option<u64>> {
let lz4 = kind == Foreign::Lz4;
let mut buf = vec![0u8; DSTREAM_OUT_SIZE];
let mut total = 0u64;
let mut tail: Vec<u8> = Vec::new();
let keep_tail = |rp: &ReadPool, n: usize, tail: &mut Vec<u8>| {
tail.extend_from_slice(&rp.loaded()[n.saturating_sub(READ_AHEAD)..n]);
if tail.len() > READ_AHEAD {
tail.drain(..tail.len() - READ_AHEAD);
}
};
let mut pos = 0;
let mut finish = false;
let mut full = false;
let failed = loop {
if pos == rp.loaded().len() && !(lz4 && full) {
keep_tail(rp, pos, &mut tail);
rp.consume(pos);
pos = 0;
io.fill(rp, DSTREAM_IN_SIZE)?;
if rp.loaded().is_empty() {
if lz4 {
break Some(Fail::Unfinished);
}
finish = true;
}
}
let c = dec.call(&rp.loaded()[pos..], &mut buf, finish);
pos += c.used;
full = c.made == buf.len();
match c.r {
Err(f) => break Some(f),
Ok(ended) => {
if c.made > 0 {
io.write_block(&buf[..c.made])?;
total += c.made as u64;
if lz4 {
let h = io.human(total).plain();
io.display_update_progress(m::LZ4_PROGRESS, &[h.as_bytes()]);
}
}
if ended {
break None;
}
if !lz4 && c.used == 0 && c.made == 0 && finish {
break Some(Fail::BufError);
}
}
}
};
if failed.is_none() {
let back = dec.unused();
let rewind = back.min(pos);
pos -= rewind;
rp.consume(pos);
let rest = (back - rewind).min(tail.len());
if rest > 0 {
rp.give_back(&tail[tail.len() - rest..]);
}
} else {
rp.consume(pos);
}
io.sparse_end()?;
let Some(f) = failed else {
return Ok(Some(total));
};
let lzma = matches!(kind, Foreign::Xz | Foreign::Lzma);
match f {
Fail::BufError if lzma => io.d.at(1, io.err, m::LZMA_PREMATURE, &[src]),
Fail::BufError => io.d.at(1, io.err, m::GZ_PREMATURE, &[src]),
Fail::Code(c) => {
let fmt = if lzma {
m::LZMA_DECODING_ERROR
} else {
m::GZ_INFLATE_ERROR
};
let c = c.to_string();
io.d.at(1, io.err, fmt, &[src, c.as_bytes()]);
}
Fail::Lz4(name) => io.d.at(
1,
io.err,
m::LZ4_DECOMPRESSION_ERROR,
&[src, name.as_bytes()],
),
Fail::Unfinished => io.d.at(1, io.err, m::LZ4_UNFINISHED, &[src]),
}
Ok(None)
}
#[cfg(not(feature = "zlib-c"))]
const Z_DATA_ERROR: i32 = -3;
#[cfg(feature = "zlib-c")]
struct ZlibMember(stuffr::zlib_c::Inflate);
#[cfg(feature = "zlib-c")]
impl Coder for ZlibMember {
fn call(&mut self, input: &[u8], out: &mut [u8], finish: bool) -> Call {
let (used, made, rc) = self.0.call(input, out, finish);
let r = match rc {
0 => Ok(false),
1 => Ok(true),
-5 => Err(Fail::BufError),
rc => Err(Fail::Code(rc)),
};
Call { used, made, r }
}
}
#[cfg(feature = "zlib-c")]
fn gzip_member() -> Option<Box<dyn Coder>> {
stuffr::zlib_c::Inflate::new(15 + 16)
.ok()
.map(|z| Box::new(ZlibMember(z)) as Box<dyn Coder>)
}
#[cfg(not(feature = "zlib-c"))]
fn gzip_member() -> Option<Box<dyn Coder>> {
Some(Box::new(GzMember::new()))
}
#[cfg(not(feature = "zlib-c"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GzSt {
Head,
XLen,
Extra,
Name,
Comment,
HCrc,
Body,
Trailer,
Done,
}
#[cfg(not(feature = "zlib-c"))]
struct GzMember {
st: GzSt,
at: usize,
flags: u8,
xlen: usize,
field: [u8; 10],
hcrc: Crc,
inflate: Decompress,
crc: Crc,
}
#[cfg(not(feature = "zlib-c"))]
impl GzMember {
fn new() -> Self {
GzMember {
st: GzSt::Head,
at: 0,
flags: 0,
xlen: 0,
field: [0; 10],
hcrc: Crc::new(),
inflate: Decompress::new(false),
crc: Crc::new(),
}
}
fn next_field(&mut self, from: GzSt) {
let order = [
(GzSt::XLen, 0x04),
(GzSt::Name, 0x08),
(GzSt::Comment, 0x10),
(GzSt::HCrc, 0x02),
];
let rank = |s: GzSt| match s {
GzSt::Head => 0,
GzSt::XLen | GzSt::Extra => 1,
GzSt::Name => 2,
GzSt::Comment => 3,
_ => 4,
};
self.at = 0;
self.st = order
.iter()
.enumerate()
.find(|(i, (_, bit))| *i + 1 > rank(from) && self.flags & bit != 0)
.map_or(GzSt::Body, |(_, (s, _))| *s);
}
fn byte(&mut self, b: u8) -> Result<(), Fail> {
match self.st {
GzSt::Head => {
self.hcrc.update(&[b]);
self.field[self.at] = b;
self.at += 1;
if self.at == 4 {
if self.field[2] != 8 || self.field[3] & 0xe0 != 0 {
return Err(Fail::Code(Z_DATA_ERROR));
}
self.flags = self.field[3];
}
if self.at == 10 {
self.next_field(GzSt::Head);
}
}
GzSt::XLen => {
self.hcrc.update(&[b]);
self.field[self.at] = b;
self.at += 1;
if self.at == 2 {
self.xlen = usize::from(u16::from_le_bytes([self.field[0], self.field[1]]));
self.at = 0;
self.st = GzSt::Extra;
if self.xlen == 0 {
self.next_field(GzSt::Extra);
}
}
}
GzSt::Extra => {
self.hcrc.update(&[b]);
self.at += 1;
if self.at == self.xlen {
self.next_field(GzSt::Extra);
}
}
GzSt::Name | GzSt::Comment => {
self.hcrc.update(&[b]);
if b == 0 {
self.next_field(self.st);
}
}
GzSt::HCrc => {
self.field[self.at] = b;
self.at += 1;
if self.at == 2 {
let want = u16::from_le_bytes([self.field[0], self.field[1]]);
if u32::from(want) != self.hcrc.sum() & 0xffff {
return Err(Fail::Code(Z_DATA_ERROR));
}
self.at = 0;
self.st = GzSt::Body;
}
}
GzSt::Trailer => {
self.field[self.at] = b;
self.at += 1;
let word = |f: &[u8; 10], at: usize| {
u32::from_le_bytes([f[at], f[at + 1], f[at + 2], f[at + 3]])
};
if self.at == 4 && word(&self.field, 0) != self.crc.sum() {
return Err(Fail::Code(Z_DATA_ERROR));
}
if self.at == 8 {
if word(&self.field, 4) != self.crc.amount() {
return Err(Fail::Code(Z_DATA_ERROR));
}
self.st = GzSt::Done;
}
}
GzSt::Body | GzSt::Done => {}
}
Ok(())
}
fn body(&mut self, input: &[u8], out: &mut [u8]) -> Result<(usize, usize), Fail> {
let (before_in, before_out) = (self.inflate.total_in(), self.inflate.total_out());
let st = self
.inflate
.decompress(input, out, FlushDecompress::None)
.map_err(|_| Fail::Code(Z_DATA_ERROR))?;
let took = (self.inflate.total_in() - before_in) as usize;
let made = (self.inflate.total_out() - before_out) as usize;
self.crc.update(&out[..made]);
if st == Status::StreamEnd {
self.st = GzSt::Trailer;
self.at = 0;
}
Ok((took, made))
}
fn run(
&mut self,
input: &[u8],
out: &mut [u8],
used: &mut usize,
made: &mut usize,
) -> Result<bool, Fail> {
loop {
match self.st {
GzSt::Done => return Ok(true),
GzSt::Body => {
if *made == out.len() {
return Ok(false);
}
let (took, n) = self.body(&input[*used..], &mut out[*made..])?;
*used += took;
*made += n;
if took == 0 && n == 0 && self.st == GzSt::Body {
if *used == input.len() {
return Ok(false);
}
return Err(Fail::Code(Z_DATA_ERROR));
}
}
_ => {
let Some(&b) = input.get(*used) else {
return Ok(false);
};
self.byte(b)?;
*used += 1;
}
}
}
}
}
#[cfg(not(feature = "zlib-c"))]
impl Coder for GzMember {
fn call(&mut self, input: &[u8], out: &mut [u8], finish: bool) -> Call {
let (mut used, mut made) = (0, 0);
let r = match self.run(input, out, &mut used, &mut made) {
Ok(false) if finish => Err(Fail::BufError),
r => r,
};
Call { used, made, r }
}
}
const LZMA_PIECE: usize = 256;
struct LzmaMember {
dec: xz_opts::Decoder,
fed: u64,
pending: Vec<u8>,
pend_at: usize,
failed: Option<Fail>,
back: usize,
}
impl LzmaMember {
fn new(dec: xz_opts::Decoder) -> Self {
LzmaMember {
dec,
fed: 0,
pending: Vec::new(),
pend_at: 0,
failed: None,
back: 0,
}
}
fn fail(e: &XzError) -> Fail {
match e {
XzError::BufError => Fail::BufError,
e => Fail::Code(lzma_ret(e)),
}
}
fn flush(&mut self, out: &mut [u8], made: &mut usize) {
let n = (self.pending.len() - self.pend_at).min(out.len() - *made);
out[*made..*made + n].copy_from_slice(&self.pending[self.pend_at..self.pend_at + n]);
self.pend_at += n;
*made += n;
if self.pend_at == self.pending.len() {
self.pending.clear();
self.pend_at = 0;
}
}
}
impl Coder for LzmaMember {
fn call(&mut self, input: &[u8], out: &mut [u8], finish: bool) -> Call {
let (mut used, mut made) = (0, 0);
loop {
self.flush(out, &mut made);
if made == out.len() {
return Call {
used,
made,
r: Ok(false),
};
}
if let Some(f) = self.failed.take() {
return Call {
used,
made,
r: Err(f),
};
}
if self.dec.finished() {
return Call {
used,
made,
r: Ok(true),
};
}
let r = if used < input.len() {
let piece = &input[used..(used + LZMA_PIECE).min(input.len())];
let r = self.dec.feed(piece, &mut self.pending);
if self.dec.finished() {
let in_bytes = self.dec.progress().in_bytes;
let call_start = self.fed - used as u64;
if in_bytes >= call_start {
used = (in_bytes - call_start) as usize;
self.back = 0;
} else {
used = 0;
self.back = (call_start - in_bytes) as usize;
}
self.fed = in_bytes;
} else {
used += piece.len();
self.fed += piece.len() as u64;
}
r
} else if finish {
let r = self.dec.finish(&mut self.pending);
if r.is_ok() && !self.dec.finished() {
Err(XzError::BufError)
} else {
if self.dec.finished() {
self.back = self.fed.saturating_sub(self.dec.progress().in_bytes) as usize;
}
r
}
} else {
return Call {
used,
made,
r: Ok(false),
};
};
if let Err(e) = r {
self.failed = Some(Self::fail(&e));
}
}
}
fn unused(&self) -> usize {
self.back
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum L4 {
Header,
BlockHeader,
Block,
Copy,
BlockCrc,
Suffix,
Done,
}
struct Lz4Member {
st: L4,
buf: Vec<u8>,
fhs: usize,
block_checksum: bool,
content_checksum: bool,
linked: bool,
max_block: usize,
content_size: u64,
decoded: u64,
size: usize,
left: usize,
content_hash: Xxh32,
block_hash: Xxh32,
dict: Vec<u8>,
pending: Vec<u8>,
pend_at: usize,
}
impl Lz4Member {
fn new() -> Self {
Lz4Member {
st: L4::Header,
buf: Vec::new(),
fhs: 0,
block_checksum: false,
content_checksum: false,
linked: false,
max_block: 0,
content_size: 0,
decoded: 0,
size: 0,
left: 0,
content_hash: Xxh32::new(0),
block_hash: Xxh32::new(0),
dict: Vec::new(),
pending: Vec::new(),
pend_at: 0,
}
}
fn gather(&mut self, input: &[u8], i: &mut usize, need: usize) -> bool {
let take = need.saturating_sub(self.buf.len()).min(input.len() - *i);
self.buf.extend_from_slice(&input[*i..*i + take]);
*i += take;
self.buf.len() >= need
}
fn produced(&mut self, data: &[u8]) {
self.decoded += data.len() as u64;
if self.content_checksum {
self.content_hash.update(data);
}
if self.linked {
self.dict.extend_from_slice(data);
if self.dict.len() > 2 * LZ4_BLOCK {
self.dict.drain(..self.dict.len() - LZ4_BLOCK);
}
}
}
fn flush(&mut self, out: &mut [u8], made: &mut usize) {
let n = (self.pending.len() - self.pend_at).min(out.len() - *made);
out[*made..*made + n].copy_from_slice(&self.pending[self.pend_at..self.pend_at + n]);
self.pend_at += n;
*made += n;
if self.pend_at == self.pending.len() {
self.pending.clear();
self.pend_at = 0;
}
}
fn header(&mut self) -> Result<(), Fail> {
let flg = self.buf[4];
if self.fhs == 0 {
if (flg >> 1) & 1 != 0 {
return Err(Fail::Lz4(m::LZ4F_RESERVED_FLAG_SET));
}
if (flg >> 6) & 3 != 1 {
return Err(Fail::Lz4(m::LZ4F_HEADER_VERSION_WRONG));
}
self.fhs =
7 + if flg & 0x08 != 0 { 8 } else { 0 } + if flg & 0x01 != 0 { 4 } else { 0 };
return Ok(());
}
let bd = self.buf[5];
if bd >> 7 != 0 {
return Err(Fail::Lz4(m::LZ4F_RESERVED_FLAG_SET));
}
let id = (bd >> 4) & 7;
if id < 4 {
return Err(Fail::Lz4(m::LZ4F_MAX_BLOCK_SIZE_INVALID));
}
if bd & 0x0f != 0 {
return Err(Fail::Lz4(m::LZ4F_RESERVED_FLAG_SET));
}
let hc = (xxh32(&self.buf[4..self.fhs - 1], 0) >> 8) as u8;
if hc != self.buf[self.fhs - 1] {
return Err(Fail::Lz4(m::LZ4F_HEADER_CHECKSUM_INVALID));
}
self.linked = (flg >> 5) & 1 == 0;
self.block_checksum = (flg >> 4) & 1 != 0;
self.content_checksum = (flg >> 2) & 1 != 0;
self.content_size = if flg & 0x08 != 0 {
u64::from_le_bytes(self.buf[6..14].try_into().expect("eight bytes"))
} else {
0
};
self.max_block = 1 << (8 + 2 * usize::from(id));
Ok(())
}
fn end_mark(&mut self) -> Result<(), Fail> {
if self.content_size != 0 && self.decoded != self.content_size {
return Err(Fail::Lz4(m::LZ4F_FRAME_SIZE_WRONG));
}
self.st = if self.content_checksum {
L4::Suffix
} else {
L4::Done
};
Ok(())
}
fn run(
&mut self,
input: &[u8],
out: &mut [u8],
i: &mut usize,
made: &mut usize,
) -> Result<bool, Fail> {
loop {
self.flush(out, made);
if self.pend_at < self.pending.len() {
return Ok(false);
}
match self.st {
L4::Done => return Ok(true),
L4::Header => {
let need = if self.fhs == 0 { 7 } else { self.fhs };
if !self.gather(input, i, need) {
return Ok(false);
}
let had = self.fhs;
self.header()?;
if had != 0 {
self.buf.clear();
self.st = L4::BlockHeader;
}
}
L4::BlockHeader => {
if !self.gather(input, i, 4) {
return Ok(false);
}
let word = u32::from_le_bytes(self.buf[..4].try_into().expect("four"));
self.buf.clear();
let size = (word & 0x7FFF_FFFF) as usize;
if size == 0 {
self.end_mark()?;
continue;
}
if size > self.max_block {
return Err(Fail::Lz4(m::LZ4F_MAX_BLOCK_SIZE_INVALID));
}
self.size = size;
if word & 0x8000_0000 != 0 {
self.left = size;
self.block_hash = Xxh32::new(0);
self.st = L4::Copy;
} else {
self.st = L4::Block;
}
}
L4::Copy => {
let take = self.left.min(input.len() - *i).min(out.len() - *made);
if take == 0 {
return Ok(false);
}
let data = &input[*i..*i + take];
if self.block_checksum {
self.block_hash.update(data);
}
out[*made..*made + take].copy_from_slice(data);
self.produced(data);
*i += take;
*made += take;
self.left -= take;
if self.left == 0 {
self.st = if self.block_checksum {
L4::BlockCrc
} else {
L4::BlockHeader
};
}
}
L4::BlockCrc => {
if !self.gather(input, i, 4) {
return Ok(false);
}
let want = u32::from_le_bytes(self.buf[..4].try_into().expect("four"));
self.buf.clear();
if want != self.block_hash.digest() {
return Err(Fail::Lz4(m::LZ4F_BLOCK_CHECKSUM_INVALID));
}
self.st = L4::BlockHeader;
}
L4::Block => {
let need = self.size + if self.block_checksum { 4 } else { 0 };
if !self.gather(input, i, need) {
return Ok(false);
}
let data = std::mem::take(&mut self.buf);
let block = &data[..self.size];
if self.block_checksum {
let want = u32::from_le_bytes(data[self.size..].try_into().expect("four"));
if want != xxh32(block, 0) {
return Err(Fail::Lz4(m::LZ4F_BLOCK_CHECKSUM_INVALID));
}
}
let mut dec = std::mem::take(&mut self.pending);
dec.resize(self.max_block, 0);
let r = if self.linked {
let at = self.dict.len().saturating_sub(LZ4_BLOCK);
decompress_into_with_dict(block, &mut dec, &self.dict[at..])
} else {
decompress_into(block, &mut dec)
};
let n = r.map_err(|_| Fail::Lz4(m::LZ4F_DECOMPRESSION_FAILED))?;
dec.truncate(n);
self.produced(&dec);
self.pending = dec;
self.pend_at = 0;
self.buf = data;
self.buf.clear();
self.st = L4::BlockHeader;
}
L4::Suffix => {
if !self.gather(input, i, 4) {
return Ok(false);
}
let want = u32::from_le_bytes(self.buf[..4].try_into().expect("four"));
self.buf.clear();
if want != self.content_hash.digest() {
return Err(Fail::Lz4(m::LZ4F_CONTENT_CHECKSUM_INVALID));
}
self.st = L4::Done;
}
}
}
}
}
impl Coder for Lz4Member {
fn call(&mut self, input: &[u8], out: &mut [u8], _finish: bool) -> Call {
let (mut used, mut made) = (0, 0);
let r = self.run(input, out, &mut used, &mut made);
Call { used, made, r }
}
}
const P1: u32 = 0x9E37_79B1;
const P2: u32 = 0x85EB_CA77;
const P3: u32 = 0xC2B2_AE3D;
const P4: u32 = 0x27D4_EB2F;
const P5: u32 = 0x1656_67B1;
#[derive(Clone)]
struct Xxh32 {
acc: [u32; 4],
mem: [u8; 16],
len: usize,
total: u64,
seed: u32,
}
fn xxh_round(acc: u32, lane: u32) -> u32 {
acc.wrapping_add(lane.wrapping_mul(P2))
.rotate_left(13)
.wrapping_mul(P1)
}
fn lane(b: &[u8]) -> u32 {
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
}
impl Xxh32 {
fn new(seed: u32) -> Self {
Xxh32 {
acc: [
seed.wrapping_add(P1).wrapping_add(P2),
seed.wrapping_add(P2),
seed,
seed.wrapping_sub(P1),
],
mem: [0; 16],
len: 0,
total: 0,
seed,
}
}
fn stripe(&mut self, s: &[u8]) {
for (k, acc) in self.acc.iter_mut().enumerate() {
*acc = xxh_round(*acc, lane(&s[4 * k..]));
}
}
fn update(&mut self, mut data: &[u8]) {
self.total += data.len() as u64;
if self.len > 0 {
let take = (16 - self.len).min(data.len());
self.mem[self.len..self.len + take].copy_from_slice(&data[..take]);
self.len += take;
data = &data[take..];
if self.len < 16 {
return;
}
let m = self.mem;
self.stripe(&m);
self.len = 0;
}
let (chunks, rest) = data.as_chunks::<16>();
for c in chunks {
self.stripe(c);
}
self.mem[..rest.len()].copy_from_slice(rest);
self.len = rest.len();
}
fn digest(&self) -> u32 {
let mut h = if self.total >= 16 {
let [a, b, c, d] = self.acc;
a.rotate_left(1)
.wrapping_add(b.rotate_left(7))
.wrapping_add(c.rotate_left(12))
.wrapping_add(d.rotate_left(18))
} else {
self.seed.wrapping_add(P5)
};
h = h.wrapping_add(self.total as u32);
let mut rest = &self.mem[..self.len];
while rest.len() >= 4 {
h = h
.wrapping_add(lane(rest).wrapping_mul(P3))
.rotate_left(17)
.wrapping_mul(P4);
rest = &rest[4..];
}
for &b in rest {
h = h
.wrapping_add(u32::from(b).wrapping_mul(P5))
.rotate_left(11)
.wrapping_mul(P1);
}
h ^= h >> 15;
h = h.wrapping_mul(P2);
h ^= h >> 13;
h = h.wrapping_mul(P3);
h ^ (h >> 16)
}
}
fn xxh32(data: &[u8], seed: u32) -> u32 {
let mut h = Xxh32::new(seed);
h.update(data);
h.digest()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xxh32_matches_the_reference_vectors() {
assert_eq!(xxh32(b"", 0), 0x02CC_5D05);
assert_eq!(xxh32(b"a", 0), 0x550D_7456);
assert_eq!(xxh32(b"abc", 0), 0x32D1_53FF);
assert_eq!(
xxh32(b"Nobody inspects the spammish repetition", 0),
0xE229_3B2F
);
let data: Vec<u8> = (0..1000u32).map(|i| (i * 7 + 3) as u8).collect();
let mut h = Xxh32::new(0);
for piece in data.chunks(13) {
h.update(piece);
}
assert_eq!(h.digest(), xxh32(&data, 0));
}
#[test]
fn the_lz4_header_checksum_is_liblz4s() {
assert_eq!((xxh32(&[0x44, 0x40], 0) >> 8) as u8, 0x5e);
}
fn drive(dec: &mut dyn Coder, input: &[u8], piece: usize, room: usize) -> (usize, Vec<u8>) {
let mut out = Vec::new();
let mut buf = vec![0u8; room];
let (mut at, mut end) = (0, piece.min(input.len()));
loop {
let c = dec.call(&input[at..end], &mut buf, false);
out.extend_from_slice(&buf[..c.made]);
at += c.used;
match c.r {
Err(_) => panic!("piece {piece}, room {room}: failed"),
Ok(true) => return (at, out),
Ok(false) => {}
}
if at == end && c.made < room {
assert!(end < input.len(), "piece {piece}, room {room}: ran out");
end = (end + piece).min(input.len());
}
}
}
#[test]
fn lz4_frames_decode_in_any_pieces() {
let data: Vec<u8> = (0..300_000u32)
.map(|i| if i % 7 == 0 { (i / 7) as u8 } else { b'q' })
.collect();
let sink = Shared::default();
let info = FrameInfo::new()
.block_size(BlockSize::Max64KB)
.block_mode(BlockMode::Linked)
.content_checksum(true)
.content_size(Some(data.len() as u64));
let mut e = FrameEncoder::with_frame_info(info, sink.clone());
e.write_all(&data).unwrap();
e.finish().unwrap();
let mut frame = sink.take();
let len = frame.len();
frame.extend_from_slice(b"tail");
for (piece, room) in [(1, 1000), (7, 70_000), (4096, 128 << 10), (1 << 20, 3)] {
let (used, out) = drive(&mut Lz4Member::new(), &frame, piece, room);
assert_eq!(used, len, "piece {piece}");
assert!(out == data, "piece {piece}: output differs");
}
}
#[test]
fn gzip_members_decode_in_any_pieces() {
let data = b"hello hello hello gzip\n".repeat(500);
let mut gz = flate2::GzBuilder::new()
.filename("n")
.comment("c")
.extra(vec![1, 2, 3])
.write(Vec::new(), flate2::Compression::default());
gz.write_all(&data).unwrap();
let mut v = gz.finish().unwrap();
let len = v.len();
v.extend_from_slice(b"next");
for (piece, room) in [(1, 100), (3, 7), (64, 128 << 10), (1 << 20, 1)] {
let mut dec = gzip_member().expect("inflateInit2");
let (used, out) = drive(dec.as_mut(), &v, piece, room);
assert_eq!(used, len, "piece {piece}");
assert_eq!(out, data, "piece {piece}");
}
}
#[test]
fn lzma_output_waits_in_bounded_pieces() {
let data = vec![0u8; 8 << 20];
let mut enc = xz_opts::Encoder::new(&EncodeOptions::default()).unwrap();
let mut xz = Vec::new();
enc.feed(&data, &mut xz).unwrap();
enc.finish(&mut xz).unwrap();
let opts = DecodeOptions {
single_stream: true,
..DecodeOptions::default()
};
let mut dec = LzmaMember::new(xz_opts::Decoder::new(&opts).unwrap());
let mut buf = vec![0u8; DSTREAM_OUT_SIZE];
let (mut at, mut total, mut most) = (0, 0, 0);
loop {
let c = dec.call(&xz[at..], &mut buf, false);
at += c.used;
total += c.made;
most = most.max(dec.pending.capacity());
assert!(buf[..c.made].iter().all(|&b| b == 0));
if matches!(c.r, Ok(true)) {
break;
}
assert!(c.r.is_ok());
}
assert_eq!((at, total), (xz.len(), data.len()));
assert!(most < 8 << 20, "pending grew to {most} bytes");
}
}