use stuffr::lz4_opts::{
DecodeOptions, Decoder, LEGACY_MAGIC, LegacyDecoder, Lz4Error, Lz4fError, MAGIC,
is_skippable_magic,
};
use super::display::show_decode_progress;
use super::io::{Fail, Run, Sink, Src};
use super::messages as m;
const FEED: usize = 4 << 10;
pub(super) struct Decoded {
pub read: u64,
pub written: u64,
pub undecodable: bool,
}
fn le32(b: &[u8]) -> u32 {
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
}
pub(super) fn stream(
run: &mut Run,
src: &mut Src,
sink: &mut Sink,
passthrough: bool,
) -> Result<Decoded, Fail> {
let mut d = Decoded {
read: 0,
written: 0,
undecodable: false,
};
let mut shown = None;
let mut first = true;
loop {
let head = src.fill(4);
if head.is_empty() {
break;
}
if head.len() < 4 {
return Err(run.exm(40, m::E40_MAGIC_UNREADABLE, &[]));
}
let magic_bytes = [head[0], head[1], head[2], head[3]];
let magic = le32(&magic_bytes);
src.consume(4);
if is_skippable_magic(magic) {
run.say(4, m::SKIPPABLE_DETECTED, &[]);
let size = src.fill(4);
if size.len() < 4 {
return Err(run.exm(42, m::E42_SKIPPABLE_SIZE, &[]));
}
let n = le32(size);
src.consume(4);
if !src.skip(u64::from(n)) {
return Err(run.exm(43, m::E43_SKIP, &[]));
}
} else if magic == LEGACY_MAGIC {
run.say(4, m::LEGACY_DETECTED, &[]);
legacy(run, src, sink, &mut d)?;
} else if magic == MAGIC {
modern(run, src, sink, &magic_bytes, &mut d, &mut shown)?;
break;
} else if first && passthrough {
sink.write(run, &magic_bytes)?;
d.written += 4;
loop {
let rest = src.fill(1);
if rest.is_empty() {
break;
}
let n = rest.len();
sink.write(run, rest)?;
src.consume(n);
d.written += n as u64;
}
break;
} else if first {
return Err(run.exm(44, m::E44_UNRECOGNIZED, &[]));
} else {
run.say(2, m::UNDECODABLE, &[]);
if src.seekable {
let at = src.taken().to_string();
run.say(2, m::AT_POSITION, &[at.as_bytes()]);
}
run.say(2, m::NEWLINE, &[]);
d.undecodable = true;
break;
}
first = false;
}
d.read = src.taken();
Ok(d)
}
fn modern(
run: &mut Run,
src: &mut Src,
sink: &mut Sink,
magic: &[u8; 4],
d: &mut Decoded,
shown: &mut Option<u64>,
) -> Result<(), Fail> {
let opts = DecodeOptions {
dictionary: run.dict.clone(),
};
let Ok(mut dec) = Decoder::new(&opts) else {
let name = Lz4fError::AllocationFailed.name();
return Err(run.exm(34, m::E34_DECOMPRESSION, &[name.as_bytes()]));
};
let mut out = Vec::new();
let mut hint = match dec.decompress(magic, &mut out) {
Ok((_, hint)) => hint,
Err(e) => return Err(failed(run, &e)),
};
loop {
let avail = src.fill(1);
if avail.is_empty() {
break;
}
let take = avail.len().min(FEED);
let r = dec.decompress(&avail[..take], &mut out);
let consumed = r.as_ref().map_or(0, |&(n, _)| n);
src.consume(consumed);
sink.write(run, &out)?;
d.written += out.len() as u64;
out.clear();
show_decode_progress(run, d.written, true, shown);
match r {
Ok((0, _)) => return Err(run.exm(26, m::E26_INCOMPLETE, &[])),
Ok((_, h)) => hint = h,
Err(e) => return Err(failed(run, &e)),
}
}
if hint != 0 {
return Err(run.exm(26, m::E26_INCOMPLETE, &[]));
}
Ok(())
}
fn failed(run: &mut Run, e: &Lz4Error) -> Fail {
let name = match e {
Lz4Error::Lib(e) => e.name(),
Lz4Error::Io(_) => Lz4fError::IoRead.name(),
};
run.exm(34, m::E34_DECOMPRESSION, &[name.as_bytes()])
}
fn legacy(run: &mut Run, src: &mut Src, sink: &mut Sink, d: &mut Decoded) -> Result<(), Fail> {
let mut dec = LegacyDecoder::new();
let mut out = Vec::new();
if dec
.decompress(&LEGACY_MAGIC.to_le_bytes(), &mut out)
.is_err()
{
return Err(run.exm(64, m::E64_LEGACY_DECODE, &[]));
}
loop {
let avail = src.fill(4);
if avail.is_empty() {
if dec.at_block_boundary() {
return Ok(());
}
return Err(run.exm(63, m::E63_LEGACY_BLOCK, &[]));
}
let take = avail.len().min(FEED);
let r = dec.decompress(&avail[..take], &mut out);
let (consumed, hint) = match r {
Ok(v) => v,
Err(_) => return Err(run.exm(64, m::E64_LEGACY_DECODE, &[])),
};
src.consume(consumed);
if !out.is_empty() {
sink.write(run, &out)?;
d.written += out.len() as u64;
out.clear();
show_decode_progress(run, d.written, false, &mut None);
}
if hint == 0 {
return Ok(());
}
if consumed == 0 && take < 4 {
return Err(run.exm(61, m::E61_LEGACY_SIZE, &[]));
}
}
}