#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
use thunder::wire::config::{ErrorConvention, Handshake, HelloStyle, PushPolicy, TlsPolicy};
use thunder::wire::{read_request_with_limit, Request};
use thunder::{Client, ClientError, Config};
struct Counting;
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
System.alloc(layout)
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
System.alloc_zeroed(layout)
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOCATED.fetch_add(new_size.saturating_sub(layout.size()), Ordering::Relaxed);
System.realloc(ptr, layout, new_size)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
}
}
#[global_allocator]
static ALLOC: Counting = Counting;
const CAP: usize = 64;
const DECLARED_BODY: u32 = 512 * 1024 * 1024;
fn probe_profile() -> Config {
Config {
scheme: "test",
default_port: 0,
handshake: Handshake::None,
hello_style: HelloStyle::NotUsed,
push: PushPolicy::Reserved,
max_frame_bytes: CAP,
max_in_flight: 64,
error_codes: ErrorConvention::None,
tls: TlsPolicy::Off,
}
}
#[tokio::test]
async fn oversized_frame_is_refused_without_allocating_the_body() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = format!("127.0.0.1:{}", listener.local_addr().unwrap().port());
let server = tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let (read_half, mut write_half) = stream.into_split();
let mut reader = tokio::io::BufReader::new(read_half);
let _req: Request = read_request_with_limit(&mut reader, 1024 * 1024)
.await
.unwrap()
.0;
write_half
.write_all(&DECLARED_BODY.to_le_bytes())
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
});
let client = Client::connect(&addr, probe_profile()).await.unwrap();
let before = ALLOCATED.load(Ordering::Relaxed);
let err = client.call("GET", vec![]).await.unwrap_err();
let grew_by = ALLOCATED.load(Ordering::Relaxed) - before;
assert!(
matches!(err, ClientError::FrameTooLarge { .. }),
"expected the frame-too-large class, got {err:?}"
);
const CEILING: usize = 8 * 1024 * 1024;
assert!(
grew_by < CEILING,
"refusing a frame declaring {DECLARED_BODY} bytes allocated {grew_by} bytes \
(ceiling {CEILING}) — the body was sized from the prefix before the cap was checked \
(WIRE-020)"
);
server.abort();
}