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_opts::{self, BlockSizeId, EncodeOptions as Lz4EncodeOptions, Lz4Error};
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 opts = Lz4EncodeOptions {
level: level.min(12),
block_size: BlockSizeId::Max64Kb,
block_linked: true,
block_checksum: false,
content_checksum: io.prefs.checksum == 1,
content_size,
..Lz4EncodeOptions::default()
};
let mut enc = match lz4_opts::Encoder::new_plain(&opts) {
Ok(e) => e,
Err(e) => {
let name = lz4_error_name(&e);
return Err(io.exm(35, m::LZ4_COMPRESS_FAILED, &[src, name.as_bytes()]));
}
};
let mut out = Vec::new();
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());
out.clear();
if let Err(e) = enc.update(&rp.loaded()[..n], &mut out) {
let name = lz4_error_name(&e);
return Err(io.exm(35, m::LZ4_COMPRESS_FAILED, &[src, name.as_bytes()]));
}
written += write_out(io, &out)?;
read_progress(io, false, read, file_size, written);
rp.consume(n);
read += io.fill(rp, LZ4_BLOCK)? as u64;
}
out.clear();
if let Err(e) = enc.finish(&mut out) {
let name = lz4_error_name(&e);
return Err(io.exm(38, m::LZ4_END_FAILED, &[src, name.as_bytes()]));
}
written += write_out(io, &out)?;
io.sparse_end()?;
Ok((read, written))
}
fn lz4_error_name(e: &Lz4Error) -> String {
match e {
Lz4Error::Lib(l) => l.name().to_string(),
Lz4Error::Io(_) => m::LZ4F_GENERIC.to_string(),
}
}
fn write_out(io: &mut Io<'_>, out: &[u8]) -> Fio<u64> {
if !out.is_empty() {
io.write_block(out)?;
}
Ok(out.len() as u64)
}
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 Unit {
Header,
Word,
Block(usize),
Trailer(usize),
}
struct Lz4Member {
dec: lz4_opts::Decoder,
unit: Unit,
gathered: Vec<u8>,
block_checksum: bool,
content_checksum: bool,
max_block: usize,
pending: Vec<u8>,
pend_at: usize,
ended: bool,
}
impl Lz4Member {
fn new() -> Self {
Lz4Member {
dec: lz4_opts::Decoder::new(&lz4_opts::DecodeOptions::default())
.expect("LZ4F_createDecompressionContext"),
unit: Unit::Header,
gathered: Vec::new(),
block_checksum: false,
content_checksum: false,
max_block: 0,
pending: Vec::new(),
pend_at: 0,
ended: false,
}
}
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 take(&self, avail: usize) -> usize {
match self.unit {
Unit::Header | Unit::Word => 1,
Unit::Block(left) | Unit::Trailer(left) => left.min(avail),
}
}
fn advance(&mut self, piece: &[u8]) {
match self.unit {
Unit::Header => {
self.gathered.extend_from_slice(piece);
if let Ok(Some(h)) = lz4_opts::parse_frame_header(&self.gathered)
&& h.header_len == self.gathered.len()
{
self.block_checksum = h.block_checksum;
self.content_checksum = h.content_checksum;
self.max_block = h.block_size.bytes();
self.gathered.clear();
self.unit = Unit::Word;
}
}
Unit::Word => {
self.gathered.extend_from_slice(piece);
if self.gathered.len() == 4 {
let word = u32::from_le_bytes([
self.gathered[0],
self.gathered[1],
self.gathered[2],
self.gathered[3],
]);
self.gathered.clear();
let size = (word & 0x7FFF_FFFF) as usize;
let total = size.min(self.max_block) + 4 * usize::from(self.block_checksum);
self.unit = if word == 0 {
Unit::Trailer(if self.content_checksum { 4 } else { 0 })
} else if total == 0 {
Unit::Word
} else {
Unit::Block(total)
};
}
}
Unit::Block(left) => {
self.unit = match left - piece.len() {
0 => Unit::Word,
rest => Unit::Block(rest),
};
}
Unit::Trailer(left) => self.unit = Unit::Trailer(left - piece.len()),
}
}
}
impl Coder for Lz4Member {
fn call(&mut self, input: &[u8], out: &mut [u8], _finish: bool) -> Call {
let (mut used, mut made) = (0, 0);
let call = |used, made, r| Call { used, made, r };
loop {
self.flush(out, &mut made);
if self.pend_at < self.pending.len() {
return call(used, made, Ok(false));
}
if self.ended {
return call(used, made, Ok(true));
}
if used == input.len() {
return call(used, made, Ok(false));
}
let n = self.take(input.len() - used).max(1);
let piece = &input[used..used + n];
let before = self.pending.len();
match self.dec.decompress(piece, &mut self.pending) {
Ok((0, hint)) if hint != 0 && self.pending.len() == before => {
return call(used, made, Err(Fail::Lz4(m::LZ4F_GENERIC)));
}
Ok((taken, hint)) => {
used += taken;
self.ended = hint == 0;
self.advance(&piece[..taken]);
}
Err(e) => return call(used, made, Err(Fail::Lz4(lz4_error_static(&e)))),
}
}
}
}
fn lz4_error_static(e: &Lz4Error) -> &'static str {
match e {
Lz4Error::Lib(l) => l.name(),
Lz4Error::Io(_) => m::LZ4F_GENERIC,
}
}
#[cfg(test)]
mod tests {
use super::*;
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 opts = Lz4EncodeOptions {
block_size: BlockSizeId::Max64Kb,
block_linked: true,
content_size: Some(data.len() as u64),
..Lz4EncodeOptions::default()
};
let mut e = lz4_opts::Encoder::new_plain(&opts).unwrap();
let mut frame = Vec::new();
e.update(&data, &mut frame).unwrap();
e.finish(&mut frame).unwrap();
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");
}
}