use super::{
CodingId,
buffer::{BufferError, Cursor},
};
use crate::media::CodingRecord;
mod encode;
pub use encode::Lz4Encoder;
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Lz4;
impl Lz4 {
pub const fn new() -> Self {
Self
}
pub const TABLE_LEN: usize = 1024;
pub fn encoder(self, table: &mut [u32]) -> Result<Lz4Encoder<'_>, Lz4Error> {
Lz4Encoder::new(table)
}
pub fn encoded_bound(self, bytes: usize) -> Result<usize, Lz4Error> {
self.validate_len(bytes)?;
bytes
.checked_add(bytes / 255)
.and_then(|n| n.checked_add(16))
.ok_or(Lz4Error::SizeOverflow)
}
fn validate_len(self, bytes: usize) -> Result<(), Lz4Error> {
u32::try_from(bytes)
.map(|_| ())
.map_err(|_| Lz4Error::InputTooLarge { bytes })
}
pub const fn record(self) -> CodingRecord<'static> {
CodingRecord::new(CodingId::LZ4, 1, &[])
}
pub fn from_record(record: CodingRecord<'_>) -> Result<Self, Lz4Error> {
if record.id() != CodingId::LZ4 {
return Err(Lz4Error::UnexpectedCoding(record.id()));
}
if record.revision() != 1 {
return Err(Lz4Error::UnsupportedRevision(record.revision()));
}
if !record.params().is_empty() {
return Err(Lz4Error::UnexpectedParameters);
}
Ok(Self)
}
pub fn plan(self, input: &[u8], decoded_len: usize) -> Result<Lz4DecodePlan<'_>, Lz4Error> {
let mut cursor = Cursor::new(input);
let mut remaining = decoded_len;
let mut last_match_remaining = None;
loop {
let sequence = Sequence::read(&mut cursor)?;
remaining =
remaining
.checked_sub(sequence.literals.len())
.ok_or(Lz4Error::OutputOverflow {
remaining,
count: sequence.literals.len(),
})?;
if let Some((distance, len)) = sequence.match_ {
let produced = decoded_len - remaining;
if distance == 0 || usize::from(distance) > produced {
return Err(Lz4Error::InvalidOffset { distance, produced });
}
last_match_remaining = Some(remaining);
remaining = remaining.checked_sub(len).ok_or(Lz4Error::OutputOverflow {
remaining,
count: len,
})?;
} else {
if remaining != 0 {
return Err(Lz4Error::DecodedSizeMismatch {
expected: decoded_len,
actual: decoded_len - remaining,
});
}
if last_match_remaining.is_some_and(|len| len < 12 || sequence.literals.len() < 5) {
return Err(Lz4Error::InvalidBlockEnd);
}
break;
}
}
Ok(Lz4DecodePlan { input, decoded_len })
}
}
struct Sequence<'a> {
literals: &'a [u8],
match_: Option<(u16, usize)>,
}
impl<'a> Sequence<'a> {
fn read(cursor: &mut Cursor<'a>) -> Result<Self, Lz4Error> {
let token = cursor.byte()?;
let literal_len = Self::length(cursor, token >> 4)?;
let literals = cursor.take(literal_len)?;
let match_ = if cursor.is_empty() {
if token & 15 != 0 {
return Err(Lz4Error::InvalidBlockEnd);
}
None
} else {
let bytes = cursor.take(2)?;
let distance = u16::from_le_bytes([bytes[0], bytes[1]]);
let len = Self::length(cursor, token & 15)?
.checked_add(4)
.ok_or(Lz4Error::SizeOverflow)?;
Some((distance, len))
};
Ok(Self { literals, match_ })
}
fn length(cursor: &mut Cursor<'_>, nibble: u8) -> Result<usize, Lz4Error> {
let mut len = usize::from(nibble);
if nibble == 15 {
loop {
let byte = cursor.byte()?;
len = len
.checked_add(usize::from(byte))
.ok_or(Lz4Error::SizeOverflow)?;
if byte != 255 {
break;
}
}
}
Ok(len)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Lz4DecodePlan<'a> {
input: &'a [u8],
decoded_len: usize,
}
impl<'a> Lz4DecodePlan<'a> {
pub const fn codec(self) -> Lz4 {
Lz4
}
pub const fn input(self) -> &'a [u8] {
self.input
}
pub const fn decoded_len(self) -> usize {
self.decoded_len
}
pub fn decode_into(self, output: &mut [u8]) -> Result<usize, Lz4Error> {
if output.len() < self.decoded_len {
return Err(Lz4Error::OutputTooSmall {
needed: self.decoded_len,
available: output.len(),
});
}
let mut position = 0;
self.for_each_sequence(|literals, match_| {
let end = position + literals.len();
output[position..end].copy_from_slice(literals);
position = end;
if let Some((distance, len)) = match_ {
let source = position - usize::from(distance);
let end = position + len;
while position < end {
let count = (position - source).min(end - position);
output.copy_within(source..source + count, position);
position += count;
}
}
});
Ok(self.decoded_len)
}
pub(crate) fn for_each_sequence(self, mut consume: impl FnMut(&[u8], Option<(u16, usize)>)) {
let mut cursor = Cursor::new(self.input);
while !cursor.is_empty() {
let sequence = Sequence::read(&mut cursor).expect("validated LZ4 block");
consume(sequence.literals, sequence.match_);
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Lz4Error {
InvalidWorkspace { entries: usize },
InputTooLarge { bytes: usize },
UnexpectedCoding(CodingId),
UnsupportedRevision(u16),
UnexpectedParameters,
SizeOverflow,
Truncated { offset: usize },
InvalidOffset { distance: u16, produced: usize },
InvalidBlockEnd,
OutputOverflow { remaining: usize, count: usize },
DecodedSizeMismatch { expected: usize, actual: usize },
OutputTooSmall { needed: usize, available: usize },
}
impl From<BufferError> for Lz4Error {
fn from(error: BufferError) -> Self {
match error {
BufferError::SizeOverflow => Self::SizeOverflow,
BufferError::Truncated { offset } => Self::Truncated { offset },
}
}
}