use std::fs;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::Path;
use crossbeam_channel::{bounded, Receiver, Sender};
use crate::io::prefs::{Prefs, LZ4_MAX_DICT_SIZE, MB};
pub const INBUFF_SIZE: usize = 4 * MB;
pub const OUTBUFF_SIZE: usize = INBUFF_SIZE;
pub const PBUFFERS_NB: usize = 3;
pub const LZ4IO_D_BUFFER_SIZE: usize = 64 * 1024;
pub fn load_dict_file(dict_path: &Path) -> io::Result<Vec<u8>> {
let mut file = fs::File::open(dict_path)?;
let _ = file.seek(SeekFrom::End(-(LZ4_MAX_DICT_SIZE as i64)));
let mut circular: Vec<u8> = vec![0u8; LZ4_MAX_DICT_SIZE];
let mut dict_end: usize = 0;
let mut dict_len: usize = 0;
loop {
let cap = LZ4_MAX_DICT_SIZE - dict_end;
let n = file.read(&mut circular[dict_end..dict_end + cap])?;
if n == 0 {
break;
}
dict_end = (dict_end + n) % LZ4_MAX_DICT_SIZE;
dict_len += n;
}
if dict_len > LZ4_MAX_DICT_SIZE {
dict_len = LZ4_MAX_DICT_SIZE;
}
let dict_start = (LZ4_MAX_DICT_SIZE + dict_end - dict_len) % LZ4_MAX_DICT_SIZE;
let mut out = Vec::with_capacity(dict_len);
if dict_start + dict_len <= LZ4_MAX_DICT_SIZE {
out.extend_from_slice(&circular[dict_start..dict_start + dict_len]);
} else {
out.extend_from_slice(&circular[dict_start..]);
out.extend_from_slice(&circular[..dict_end]);
}
Ok(out)
}
pub struct DecompressResources {
pub src_buffer: Vec<u8>,
pub dst_buffer: Vec<u8>,
pub dict_buffer: Option<Vec<u8>>,
}
impl DecompressResources {
pub fn new(_prefs: &Prefs) -> io::Result<Self> {
Ok(DecompressResources {
src_buffer: vec![0u8; LZ4IO_D_BUFFER_SIZE],
dst_buffer: vec![0u8; LZ4IO_D_BUFFER_SIZE],
dict_buffer: None,
})
}
pub fn with_dict(_prefs: &Prefs, dict_path: &Path) -> io::Result<Self> {
let dict = load_dict_file(dict_path)?;
Ok(DecompressResources {
src_buffer: vec![0u8; LZ4IO_D_BUFFER_SIZE],
dst_buffer: vec![0u8; LZ4IO_D_BUFFER_SIZE],
dict_buffer: Some(dict),
})
}
pub fn from_prefs(prefs: &Prefs) -> io::Result<Self> {
if prefs.use_dictionary {
let path = prefs.dictionary_filename.as_deref().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Dictionary error: no filename provided",
)
})?;
Self::with_dict(prefs, Path::new(path))
} else {
Self::new(prefs)
}
}
#[inline]
pub fn dict(&self) -> Option<&[u8]> {
self.dict_buffer.as_deref()
}
}
#[derive(Debug)]
pub struct Buffer {
pub data: Vec<u8>,
pub size: usize,
}
impl Buffer {
fn new(capacity: usize) -> Self {
Buffer {
data: vec![0u8; capacity],
size: 0,
}
}
#[inline]
pub fn capacity(&self) -> usize {
self.data.capacity()
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.data[..self.size]
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [u8] {
let cap = self.data.capacity();
&mut self.data[..cap]
}
}
pub struct BufferPool {
sender: Sender<Buffer>,
receiver: Receiver<Buffer>,
}
impl BufferPool {
pub fn new(buf_size: usize, count: usize) -> Self {
assert!(count > 0, "BufferPool count must be > 0");
let (sender, receiver) = bounded(count);
for _ in 0..count {
sender
.send(Buffer::new(buf_size))
.expect("channel capacity matches loop count");
}
BufferPool { sender, receiver }
}
pub fn acquire(&self) -> Buffer {
self.receiver
.recv()
.expect("BufferPool channel closed unexpectedly")
}
pub fn release(&self, buf: Buffer) {
self.sender
.send(buf)
.expect("BufferPool channel closed unexpectedly");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buffer_new_zeroed() {
let b = Buffer::new(1024);
assert_eq!(b.capacity(), 1024);
assert_eq!(b.size, 0);
assert_eq!(b.as_slice().len(), 0);
}
#[test]
fn buffer_pool_acquire_release() {
let pool = BufferPool::new(4096, PBUFFERS_NB);
let mut b = pool.acquire();
assert_eq!(b.capacity(), 4096);
b.size = 0;
pool.release(b);
let b2 = pool.acquire();
assert_eq!(b2.capacity(), 4096);
pool.release(b2);
}
#[test]
fn buffer_pool_exhaustion_and_restore() {
let pool = BufferPool::new(256, 2);
let b1 = pool.acquire();
let b2 = pool.acquire();
pool.release(b1);
pool.release(b2);
let b3 = pool.acquire();
pool.release(b3);
}
#[test]
fn buffer_pool_concurrent_acquire_release() {
use std::sync::Arc;
use std::thread;
let pool = Arc::new(BufferPool::new(1024, PBUFFERS_NB));
let mut handles = Vec::new();
for _ in 0..8 {
let p = Arc::clone(&pool);
handles.push(thread::spawn(move || {
let mut buf = p.acquire();
buf.size = 10;
buf.size = 0;
p.release(buf);
}));
}
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn decompress_resources_new() {
let prefs = Prefs::default();
let res = DecompressResources::new(&prefs).expect("should not fail");
assert_eq!(res.src_buffer.len(), LZ4IO_D_BUFFER_SIZE);
assert_eq!(res.dst_buffer.len(), LZ4IO_D_BUFFER_SIZE);
assert!(res.dict_buffer.is_none());
}
#[test]
fn decompress_resources_from_prefs_no_dict() {
let prefs = Prefs::default();
let res = DecompressResources::from_prefs(&prefs).expect("no dict, should succeed");
assert!(res.dict().is_none());
}
#[test]
fn load_dict_file_small() {
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().unwrap();
let data = b"hello world dictionary content";
tmp.write_all(data).unwrap();
let dict = load_dict_file(tmp.path()).expect("load should succeed");
assert_eq!(dict, data.as_ref());
}
#[test]
fn load_dict_file_large_truncated_to_64k() {
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().unwrap();
let data: Vec<u8> = (0u8..=255).cycle().take(128 * 1024).collect();
tmp.write_all(&data).unwrap();
let dict = load_dict_file(tmp.path()).expect("load should succeed");
assert_eq!(dict.len(), LZ4_MAX_DICT_SIZE);
assert_eq!(dict, &data[64 * 1024..]);
}
}