use crate::decode::{ReadError, Reader};
use crate::encode::{write_to_uninit, Encode, WriteError, Writer};
use crate::lz4::{compress_bound, CompressStream, DecompressStream};
use core::marker::PhantomData;
use core::ptr::copy_nonoverlapping;
use core::slice::SliceIndex;
use core::num::NonZeroU64;
use alloc::vec::Vec;
use twox_hash::XxHash64;
pub(crate) const REGION_SIZE: usize = 64 * 1024 - 1;
pub const fn max_encoded_size(size: usize) -> usize {
(size / REGION_SIZE) * compress_bound(REGION_SIZE)
+ compress_bound(size - (size / REGION_SIZE) * REGION_SIZE)
+ size.div_ceil(REGION_SIZE) * HEADER_SIZE
}
#[derive(Default, Copy, Clone)]
#[repr(transparent)]
pub struct Seed {
inner: u64,
}
impl Seed {
pub fn new(seed: u64) -> Self {
Self {
inner: seed.saturating_add(1),
}
}
pub fn empty() -> Self {
Self { inner: 0 }
}
}
impl Seed {
pub(crate) fn hash(&self, bytes: &[u8]) -> u64 {
if self.is_empty() {
return 0;
}
XxHash64::oneshot(self.inner, bytes)
}
pub(crate) fn is_empty(&self) -> bool {
self.inner == 0
}
}
impl From<u64> for Seed {
fn from(seed: u64) -> Self {
Self::new(seed)
}
}
impl From<NonZeroU64> for Seed {
fn from(seed: NonZeroU64) -> Self {
Self::new(seed.get())
}
}
pub enum AllocOrd<T = PhantomData<()>>
where
T: Encode,
{
Manual(isize),
Auto(T),
Full,
}
impl Default for AllocOrd {
fn default() -> Self {
AllocOrd::Manual(4 * 1024)
}
}
impl<T: Encode> AllocOrd<T> {
#[inline]
pub(crate) fn cap(&self) -> usize {
match self {
Self::Manual(cap) => (*cap).max(0) as usize,
Self::Auto(hint) => hint.size_of(),
Self::Full => REGION_SIZE * 2,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum RegionError {
#[error("Failed to read data into a region buffer")]
ReadFailure(#[from] ReadError),
#[error("Failed to allocate capacity to write a region buffer")]
WriteFailure(#[from] WriteError),
#[error("Invalid region size hint")]
InvalidSizeTip,
#[error("Malformed region")]
MalformedRegion,
#[error("Hash is not valid for contiguous region")]
InvalidHash,
#[error("Requested region operation is out of bounds of current capacity")]
OutOfBounds,
}
struct DoubleBuffer {
curr: Vec<u8>,
prev: Option<Vec<u8>>,
}
impl DoubleBuffer {
fn new(cap: usize) -> Self {
let curr = Vec::with_capacity(cap.min(REGION_SIZE));
let prev =
(cap > REGION_SIZE).then(|| Vec::with_capacity((cap - REGION_SIZE).min(REGION_SIZE)));
Self { curr, prev }
}
fn swap(&mut self) {
let prev = self.prev.get_or_insert_with(|| {
let predict = if self.curr.len() > 4 * 1024 { self.curr.len() / 4 } else { 256 };
Vec::with_capacity(predict)
});
core::mem::swap(&mut self.curr, prev);
}
fn authorize_pass<S: SwitchAuthority>(&mut self, authority: &mut S) -> Result<(), RegionError> {
authority.pass(&mut self.curr)
}
fn len(&self) -> usize {
self.curr.len()
}
fn get<I>(&self, idx: I) -> Option<&I::Output>
where
I: SliceIndex<[u8]>,
{
self.curr.get(idx)
}
unsafe fn extend_nonoverlapping(&mut self, src: &[u8]) {
self.curr.reserve(src.len());
let dst = self.curr.spare_capacity_mut();
unsafe {
copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr().cast(), src.len());
self.curr.set_len(self.curr.len() + src.len())
}
}
}
pub struct RegionBuffer {
buffer: DoubleBuffer,
cursor: usize,
}
impl RegionBuffer {
pub fn new(cap: usize) -> Self {
Self {
buffer: DoubleBuffer::new(cap),
cursor: 0,
}
}
pub fn remaining_cap(&self) -> usize {
REGION_SIZE - self.buffer.len()
}
pub fn remaining_len(&self) -> usize {
self.buffer.len() - self.cursor
}
pub fn pass<S: SwitchAuthority>(&mut self, authority: &mut S) -> Result<(), RegionError> {
self.buffer.authorize_pass(authority)?;
self.cursor = 0;
Ok(())
}
pub fn swap(&mut self) {
self.buffer.swap();
}
pub fn read(&mut self, n: usize) -> Option<&[u8]> {
let bytes = self.buffer.get(self.cursor..self.cursor + n)?;
self.cursor = self.cursor.saturating_add(n);
Some(bytes)
}
pub unsafe fn write_nonoverlapping(&mut self, src: &[u8]) {
self.buffer.extend_nonoverlapping(src);
}
}
const HEADER_SIZE: usize = 12;
#[derive(Debug)]
struct RegionHeader {
compressed_size: u16,
alloc_size: u16,
hash: u64,
}
impl RegionHeader {
fn from_bytes(bytes: &[u8]) -> Self {
if bytes.len() < HEADER_SIZE {
panic!("Attempt to create a header from slice with length less than HEADER_SIZE");
}
let compressed_size = u16::from_le_bytes(bytes[..2].try_into().unwrap());
let alloc_size = u16::from_le_bytes(bytes[2..4].try_into().unwrap());
let hash = u64::from_le_bytes(bytes[4..12].try_into().unwrap());
Self { compressed_size, alloc_size, hash }
}
fn into_bytes(self) -> [u8; HEADER_SIZE] {
let mut buf = [0u8; HEADER_SIZE];
buf[..2].clone_from_slice(&self.compressed_size.to_le_bytes());
buf[2..4].clone_from_slice(&self.alloc_size.to_le_bytes());
buf[4..12].copy_from_slice(&self.hash.to_le_bytes());
buf
}
}
mod private {
use super::*;
pub trait Sealed {}
impl<W: Writer> Sealed for Push<W> {}
impl<R: Reader> Sealed for Pull<R> {}
}
pub trait SwitchAuthority: private::Sealed {
fn pass(&mut self, buf: &mut Vec<u8>) -> Result<(), RegionError>;
}
pub(crate) struct Pull<R>
where
R: Reader,
{
stream: DecompressStream,
seed: Seed,
source: R,
}
impl<R> Pull<R>
where
R: Reader,
{
pub fn new(src: R, seed: Seed) -> Self {
Self {
stream: DecompressStream::new(),
source: src,
seed,
}
}
pub const fn seed(&self) -> Seed {
self.seed
}
}
impl<R> SwitchAuthority for Pull<R>
where
R: Reader,
{
fn pass(&mut self, buf: &mut Vec<u8>) -> Result<(), RegionError> {
let header = RegionHeader::from_bytes(self.source.read_borrow(HEADER_SIZE)?);
let size = HEADER_SIZE + header.compressed_size as usize;
let bytes = self.source.read_borrow(size).map_err(|e| match e {
ReadError::OutOfBounds => RegionError::InvalidSizeTip,
#[cfg_attr(not(feature = "std"), allow(unreachable_patterns))]
_ => e.into(),
})?;
assert_eq!(
bytes.len(),
size,
"Mismatch between Reader implementation result and actual length requested"
);
unsafe {
buf.set_len(0);
}
buf.reserve(header.alloc_size as usize);
unsafe {
let init = self
.stream
.decompress(&bytes[HEADER_SIZE..], buf.spare_capacity_mut())
.ok_or(RegionError::MalformedRegion)?;
buf.set_len(init);
}
if self.seed.hash(buf) != header.hash {
return Err(RegionError::InvalidHash);
}
self.source.advance(size);
Ok(())
}
}
pub(crate) struct Push<W>
where
W: Writer,
{
stream: CompressStream,
seed: Seed,
writer: W,
}
impl<W> Push<W>
where
W: Writer,
{
pub fn new(dst: W, seed: Seed) -> Self {
Self {
stream: CompressStream::new(),
writer: dst,
seed,
}
}
pub const fn seed(&self) -> Seed {
self.seed
}
}
impl<W> SwitchAuthority for Push<W>
where
W: Writer,
{
fn pass(&mut self, buf: &mut Vec<u8>) -> Result<(), RegionError> {
if buf.is_empty() {
return Ok(());
}
let size = compress_bound(buf.len()) + HEADER_SIZE;
let arena = self.writer.allocate(size)?;
assert_eq!(
arena.len(),
size,
"Mismatch between Writer implementation result and actual length requested"
);
let compressed = unsafe {
self.stream
.compress(buf, &mut arena[HEADER_SIZE..])
.ok_or(RegionError::MalformedRegion)?
};
let header = RegionHeader {
compressed_size: compressed as u16,
alloc_size: buf.len() as u16,
hash: self.seed.hash(buf),
};
write_to_uninit(header.into_bytes().as_slice(), arena);
unsafe {
buf.set_len(0);
self.writer.commit(HEADER_SIZE + compressed);
}
Ok(())
}
}