use std::sync::Mutex;
use no_std_io2::io::Read;
pub use crate::traits::CompressionAction;
pub use crate::traits::types::Compressor;
use tracing::trace;
#[derive(Debug, Clone, Copy)]
struct LzmaParams {
lc: u32,
lp: u32,
pb: u32,
dict_size: u32,
offset: usize,
}
static LZMA_CACHE: Mutex<Option<LzmaParams>> = Mutex::new(None);
const LZMA_MAX_LC: u32 = 4;
const LZMA_MAX_LP: u32 = 4;
const LZMA_MAX_PB: u32 = 4;
const LZMA_MAX_OFFSET: usize = 10;
#[derive(Copy, Clone)]
pub struct LzmaAdaptiveCompressor;
impl CompressionAction for LzmaAdaptiveCompressor {
type Error = crate::error::BackhandError;
type Compressor = Option<Compressor>;
type FilesystemCompressor = crate::v3::compressor::FilesystemCompressor;
type SuperBlock = crate::v3::squashfs::SuperBlock;
fn decompress(
&self,
bytes: &[u8],
out: &mut Vec<u8>,
_compressor: Self::Compressor,
) -> Result<(), Self::Error> {
trace!("v3_lzma decompress");
if bytes.is_empty() {
return Ok(());
}
if let Ok(cache) = LZMA_CACHE.lock() {
if let Some(params) = *cache {
drop(cache); if let Ok(result) = self.try_lzma_with_params(bytes, params) {
tracing::trace!("LZMA decompression successful with cached parameters");
out.extend_from_slice(&result);
return Ok(());
}
tracing::trace!("Cached parameters failed, falling back to brute force");
}
}
if let Ok(result) = self.brute_force_lzma_params(bytes) {
tracing::trace!("LZMA decompression successful after brute force discovery");
out.extend_from_slice(&result);
return Ok(());
}
trace!("Adaptive LZMA failed, trying standard LZMA");
if let Ok(mut reader) = lzma_rust2::LzmaReader::new_mem_limit(bytes, u32::MAX, None) {
if reader.read_to_end(out).is_ok() {
trace!("Standard LZMA decompression successful: {} bytes", out.len());
return Ok(());
}
out.clear();
}
Err(crate::BackhandError::UnsupportedCompression(
"Failed to decompress LZMA adaptive data".to_string(),
))
}
fn compress(
&self,
_bytes: &[u8],
_fc: Self::FilesystemCompressor,
_block_size: u32,
) -> Result<Vec<u8>, Self::Error> {
unimplemented!();
}
}
impl LzmaAdaptiveCompressor {
fn try_lzma_with_params(
&self,
bytes: &[u8],
params: LzmaParams,
) -> Result<Vec<u8>, crate::BackhandError> {
if params.offset >= bytes.len() {
return Err(crate::BackhandError::UnsupportedCompression("Invalid offset".to_string()));
}
let data = &bytes[params.offset..];
tracing::trace!(
"Processing {} bytes from offset {}: {:02x?}",
data.len(),
params.offset,
&data[..core::cmp::min(16, data.len())]
);
tracing::trace!("Attempting lzma-adaptive-sys decompression");
let estimated_output_size = core::cmp::max(data.len() * 10, 8192);
let dict_size = if params.dict_size == 0xFFFFFFFF || params.dict_size == 0 {
1u32 << 23 } else {
params.dict_size
};
tracing::trace!(
"Calling lzma_adaptive_sys::decompress_lzma with lc={}, lp={}, pb={}, dict_size=0x{:X}, offset={}",
params.lc,
params.lp,
params.pb,
dict_size,
params.offset
);
match lzma_adaptive_sys::decompress_lzma(
bytes,
params.lc,
params.lp,
params.pb,
dict_size,
params.offset,
estimated_output_size,
) {
Ok(result) => {
tracing::trace!("lzma_adaptive_sys SUCCESS: decompressed {} bytes", result.len());
return Ok(result);
}
Err(error_code) => {
tracing::trace!("lzma_adaptive_sys failed with error code: {}", error_code);
}
}
Err(crate::BackhandError::UnsupportedCompression("LZMA decompression failed".to_string()))
}
fn brute_force_lzma_params(&self, bytes: &[u8]) -> Result<Vec<u8>, crate::BackhandError> {
tracing::trace!("Starting LZMA brute force parameter discovery");
for offset in 0..=LZMA_MAX_OFFSET {
if offset >= bytes.len() {
continue;
}
for lc in 0..=LZMA_MAX_LC {
for lp in 0..=LZMA_MAX_LP {
for pb in 0..=LZMA_MAX_PB {
for &dict_size in &[0xFFFFFFFF, 0x800000, 0x100000, 0x400000] {
let params = LzmaParams { lc, lp, pb, dict_size, offset };
tracing::trace!(
"Trying LZMA params: lc={}, lp={}, pb={}, dict_size=0x{:X}, offset={}",
lc,
lp,
pb,
dict_size,
offset
);
if let Ok(result) = self.try_lzma_with_params(bytes, params) {
tracing::trace!(
"SUCCESS: Found working LZMA params: lc={}, lp={}, pb={}, dict_size=0x{:X}, offset={}, decompressed {} bytes",
lc,
lp,
pb,
dict_size,
offset,
result.len()
);
if let Ok(mut cache) = LZMA_CACHE.lock() {
*cache = Some(params);
}
return Ok(result);
}
}
}
}
}
}
Err(crate::BackhandError::UnsupportedCompression(
"Failed to find working LZMA parameters".to_string(),
))
}
}