use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
thread_local! {
static REQUESTED: Cell<usize> = const { Cell::new(0) };
static RESERVE_FIRST: Cell<bool> = const { Cell::new(false) };
}
struct CountingAllocator;
fn record(bytes: usize) {
let _ = REQUESTED.try_with(|c| c.set(c.get().saturating_add(bytes)));
}
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
record(layout.size());
unsafe { System.alloc(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
record(layout.size());
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
record(new_size);
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
pub(crate) fn reserve_first_control(compressed_data: &[u8], original_size: u32) {
if RESERVE_FIRST.with(Cell::get) {
let _ = lz4_flex::decompress(compressed_data, original_size as usize);
}
}
#[cfg(feature = "messagepack")]
mod tests {
use super::*;
use crate::byte_storage::{ByteStorage, ByteStorageError};
const FIXTURE: &str = include_str!("../tests/vectors/wire-format.json");
fn bytes_requested<R>(f: impl FnOnce() -> R) -> (R, usize) {
let before = REQUESTED.with(Cell::get);
let result = f();
(result, REQUESTED.with(Cell::get) - before)
}
fn reject_vector(name: &str) -> (Vec<u8>, usize) {
let fixture: serde_json::Value =
serde_json::from_str(FIXTURE).expect("wire-format.json fixture must parse");
let vector = fixture["reject_vectors"]
.as_array()
.expect("wire-format.json has no reject_vectors group")
.iter()
.find(|v| v["name"] == name)
.unwrap_or_else(|| panic!("reject_vectors has no {name:?} vector"));
let envelope = hex::decode(vector["envelope_hex"].as_str().expect("envelope_hex"))
.expect("envelope_hex must decode");
let original_size = vector["original_size"].as_u64().expect("original_size");
(envelope, original_size as usize)
}
fn retrieve_counted(envelope: &[u8]) -> (Result<(Vec<u8>, String), ByteStorageError>, usize) {
let storage = ByteStorage::new(None);
bytes_requested(|| storage.retrieve(envelope))
}
#[test]
fn size_cap_and_ratio_reads_allocate_less_than_original_size() {
for (name, expected) in [
(
"reject_original_size_over_cap",
ByteStorageError::InputTooLarge,
),
("reject_ratio_bomb", ByteStorageError::DecompressionBomb),
] {
let (envelope, original_size) = reject_vector(name);
let (result, requested) = retrieve_counted(&envelope);
assert_eq!(result.err(), Some(expected), "[{name}] wrong rejection");
assert!(requested > 0, "[{name}] probe saw no allocation at all");
assert!(
requested < original_size,
"[{name}] the read requested {requested} B from the allocator, \
reaching original_size {original_size} B"
);
}
}
#[test]
fn probe_catches_a_reserve_first_reader() {
let (envelope, original_size) = reject_vector("reject_ratio_bomb");
RESERVE_FIRST.with(|c| c.set(true));
let (result, requested) = retrieve_counted(&envelope);
RESERVE_FIRST.with(|c| c.set(false));
assert_eq!(result.err(), Some(ByteStorageError::DecompressionBomb));
assert!(
requested >= original_size,
"probe missed the {original_size} B reserve-first buffer: counted {requested} B"
);
}
}