use std::{
alloc::{GlobalAlloc, Layout, System},
cell::Cell,
};
use serde::{Deserialize, Serialize};
use postbag::{from_full_slice, from_slim_slice};
thread_local! {
static LIVE: Cell<usize> = const { Cell::new(0) };
static PEAK: Cell<usize> = const { Cell::new(0) };
static COUNT: Cell<usize> = const { Cell::new(0) };
}
struct Tracking;
unsafe impl GlobalAlloc for Tracking {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let _ = LIVE.try_with(|live| {
let now = live.get() + layout.size();
live.set(now);
let _ = PEAK.try_with(|peak| peak.set(peak.get().max(now)));
let _ = COUNT.try_with(|count| count.set(count.get() + 1));
});
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let _ = LIVE.try_with(|live| live.set(live.get().saturating_sub(layout.size())));
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: Tracking = Tracking;
fn peak_allocation(mut f: impl FnMut()) -> usize {
f();
let before = LIVE.get();
PEAK.set(before);
f();
PEAK.get().saturating_sub(before)
}
fn allocation_count(mut f: impl FnMut()) -> usize {
f();
let before = COUNT.get();
f();
COUNT.get() - before
}
fn varint(mut value: u64) -> Vec<u8> {
let mut out = Vec::new();
while value >= 0x80 {
out.push((value as u8) | 0x80);
value >>= 7;
}
out.push(value as u8);
out
}
const ALLOWED: usize = 1 << 20;
#[test]
fn a_claimed_string_length_is_not_reserved() {
let hostile = varint(1 << 34);
let peak = peak_allocation(|| {
let result = from_full_slice::<String>(&hostile);
assert!(result.is_err(), "16 GB of string arrived in {} bytes", hostile.len());
});
assert!(peak < ALLOWED, "reserved {peak} bytes for {} bytes of input", hostile.len());
}
#[test]
fn a_claimed_byte_array_length_is_not_reserved() {
let hostile = varint(1 << 34);
let peak = peak_allocation(|| {
assert!(from_full_slice::<serde_bytes::ByteBuf>(&hostile).is_err());
});
assert!(peak < ALLOWED, "reserved {peak} bytes for {} bytes of input", hostile.len());
}
#[test]
fn a_claimed_length_inside_a_block_is_not_reserved() {
let mut hostile = vec![0x01, 0x41];
let inner = varint(1 << 34);
hostile.push(inner.len() as u8);
hostile.extend(&inner);
#[derive(Deserialize)]
struct One {
#[serde(rename = "_0")]
_v: String,
}
let peak = peak_allocation(|| {
assert!(from_full_slice::<One>(&hostile).is_err());
});
assert!(peak < ALLOWED, "reserved {peak} bytes for {} bytes of input", hostile.len());
}
#[test]
fn a_claimed_sequence_length_is_not_reserved() {
const SERDE_PREALLOC_CAP: usize = (1 << 20) + 1024;
let hostile = varint(1 << 34);
let peak = peak_allocation(|| {
assert!(from_slim_slice::<Vec<u64>>(&hostile).is_err());
});
assert!(peak < SERDE_PREALLOC_CAP, "reserved {peak} bytes for {} bytes of input", hostile.len());
}
#[test]
fn a_claimed_identifier_length_is_not_reserved() {
let mut hostile = vec![0x01, 0x40];
hostile.extend(varint(1 << 34));
#[derive(Deserialize)]
struct One {
#[serde(rename = "_0")]
_v: u8,
}
let peak = peak_allocation(|| {
assert!(from_full_slice::<One>(&hostile).is_err());
});
assert!(peak < ALLOWED, "reserved {peak} bytes for {} bytes of input", hostile.len());
}
#[test]
fn reading_a_struct_of_numbered_fields() {
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Reading {
#[serde(rename = "_0")]
a: u32,
#[serde(rename = "_1")]
b: u32,
#[serde(rename = "_2")]
c: bool,
}
let value = Reading { a: 300, b: 7, c: true };
let bytes = postbag::to_full_vec(&value).unwrap();
let count = allocation_count(|| {
assert_eq!(from_full_slice::<Reading>(&bytes).unwrap(), value);
});
println!("3 fields, {} bytes: {count} allocations", bytes.len());
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Wider {
#[serde(rename = "_0")]
a: u32,
#[serde(rename = "_1")]
b: u32,
#[serde(rename = "_2")]
c: bool,
#[serde(rename = "_3")]
d: u32,
#[serde(rename = "_4")]
e: u32,
#[serde(rename = "_5")]
f: bool,
}
let wide = Wider { a: 300, b: 7, c: true, d: 1, e: 2, f: false };
let wide_bytes = postbag::to_full_vec(&wide).unwrap();
let wide_count = allocation_count(|| {
assert_eq!(from_full_slice::<Wider>(&wide_bytes).unwrap(), wide);
});
println!("6 fields, {} bytes: {wide_count} allocations", wide_bytes.len());
if cfg!(postbag_fast_compile) {
assert!(count <= 5 * 3, "reading three fields took {count} allocations");
assert!(wide_count <= 5 * 6, "reading six fields took {wide_count} allocations");
} else {
assert_eq!(count, 3, "reading three fields");
assert_eq!(wide_count, 6, "reading six fields");
}
}