use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, TcpStream};
pub struct CaptureUpstream {
pub port: u16,
pub received_body: Arc<Mutex<Option<Vec<u8>>>>,
pub received_headers: Arc<Mutex<Option<String>>>,
pub received_request_line: Arc<Mutex<Option<String>>>,
}
impl CaptureUpstream {
pub fn header(&self, name: &str) -> Option<String> {
let headers = self.received_headers.lock().unwrap().clone()?;
headers.lines().find_map(|l| {
l.split_once(':')
.filter(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.trim().to_string())
})
}
}
async fn read_request(stream: &mut TcpStream) -> std::io::Result<(String, Vec<u8>)> {
let mut reader = BufReader::new(stream);
let mut header_block = String::new();
loop {
let mut line = String::new();
let n = reader.read_line(&mut line).await?;
if n == 0 {
break;
}
let blank = line == "\r\n" || line == "\n";
header_block.push_str(&line);
if blank {
break;
}
}
let body = if let Some(cl) = parse_content_length(&header_block) {
let mut buf = vec![0u8; cl];
let _ = reader.read_exact(&mut buf).await;
buf
} else if is_chunked(&header_block) {
read_chunked(&mut reader).await?
} else {
Vec::new()
};
Ok((header_block, body))
}
async fn read_chunked(reader: &mut BufReader<&mut TcpStream>) -> std::io::Result<Vec<u8>> {
let mut body = Vec::new();
loop {
let mut size_line = String::new();
if reader.read_line(&mut size_line).await? == 0 {
break;
}
let size = usize::from_str_radix(size_line.trim(), 16).unwrap_or(0);
if size == 0 {
let mut trailer = String::new();
let _ = reader.read_line(&mut trailer).await;
break;
}
let mut chunk = vec![0u8; size];
reader.read_exact(&mut chunk).await?;
body.extend_from_slice(&chunk);
let mut crlf = [0u8; 2];
let _ = reader.read_exact(&mut crlf).await;
}
Ok(body)
}
fn parse_content_length(headers: &str) -> Option<usize> {
headers
.lines()
.find_map(|l| {
l.split_once(':')
.filter(|(k, _)| k.eq_ignore_ascii_case("content-length"))
})
.and_then(|(_, v)| v.trim().parse().ok())
}
fn is_chunked(headers: &str) -> bool {
headers.lines().any(|l| {
l.split_once(':').is_some_and(|(k, v)| {
k.eq_ignore_ascii_case("transfer-encoding")
&& v.to_ascii_lowercase().contains("chunked")
})
})
}
pub async fn spawn_capture_200() -> CaptureUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let received_body = Arc::new(Mutex::new(None));
let received_headers = Arc::new(Mutex::new(None));
let received_request_line = Arc::new(Mutex::new(None));
let body_sink = received_body.clone();
let hdr_sink = received_headers.clone();
let line_sink = received_request_line.clone();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
if let Ok((headers, body)) = read_request(&mut stream).await {
*line_sink.lock().unwrap() = headers.lines().next().map(str::to_string);
*hdr_sink.lock().unwrap() = Some(headers);
*body_sink.lock().unwrap() = Some(body);
}
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok")
.await;
let _ = stream.flush().await;
}
});
CaptureUpstream {
port,
received_body,
received_headers,
received_request_line,
}
}
pub async fn spawn_always_200() -> u16 {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
tokio::spawn(async move {
while let Ok((mut stream, _)) = listener.accept().await {
tokio::spawn(async move {
if read_request(&mut stream).await.is_ok() {
let _ = stream
.write_all(
b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok",
)
.await;
let _ = stream.flush().await;
}
});
}
});
port
}
pub struct TrickleUpstream {
pub port: u16,
pub final_written_at: Arc<Mutex<Option<Instant>>>,
}
pub async fn spawn_trickle_sse(chunk_count: usize, gap: Duration) -> TrickleUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let final_written_at = Arc::new(Mutex::new(None));
let stamp = final_written_at.clone();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
let _ = read_request(&mut stream).await;
let head = "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\
Transfer-Encoding: chunked\r\n\r\n";
let _ = stream.write_all(head.as_bytes()).await;
let _ = stream.flush().await;
for i in 0..chunk_count {
let payload = format!("data: chunk-{i}\n\n");
let framed = format!("{:x}\r\n{}\r\n", payload.len(), payload);
let _ = stream.write_all(framed.as_bytes()).await;
let _ = stream.flush().await;
if i + 1 < chunk_count {
tokio::time::sleep(gap).await;
}
}
let _ = stream.write_all(b"0\r\n\r\n").await;
let _ = stream.flush().await;
*stamp.lock().unwrap() = Some(Instant::now());
}
});
TrickleUpstream {
port,
final_written_at,
}
}
#[allow(clippy::too_many_arguments)]
pub async fn spawn_capture_usage_sse(
input_tokens: u64,
cache_read: u64,
cache_write: u64,
eph_5m: u64,
eph_1h: u64,
output_tokens: u64,
) -> CaptureUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let received_body = Arc::new(Mutex::new(None));
let received_headers = Arc::new(Mutex::new(None));
let received_request_line = Arc::new(Mutex::new(None));
let body_sink = received_body.clone();
let hdr_sink = received_headers.clone();
let line_sink = received_request_line.clone();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
if let Ok((headers, body)) = read_request(&mut stream).await {
*line_sink.lock().unwrap() = headers.lines().next().map(str::to_string);
*hdr_sink.lock().unwrap() = Some(headers);
*body_sink.lock().unwrap() = Some(body);
}
let sse = format!(
"event: message_start\n\
data: {{\"type\":\"message_start\",\"message\":{{\"usage\":{{\"input_tokens\":{input_tokens},\"cache_read_input_tokens\":{cache_read},\"cache_creation_input_tokens\":{cache_write},\"cache_creation\":{{\"ephemeral_5m_input_tokens\":{eph_5m},\"ephemeral_1h_input_tokens\":{eph_1h}}},\"output_tokens\":1}}}}}}\n\n\
event: content_block_delta\n\
data: {{\"type\":\"content_block_delta\",\"delta\":{{\"text\":\"hello\"}}}}\n\n\
event: message_delta\n\
data: {{\"type\":\"message_delta\",\"usage\":{{\"output_tokens\":{output_tokens}}}}}\n\n"
);
let head = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\
Content-Length: {}\r\nConnection: close\r\n\r\n",
sse.len()
);
let _ = stream.write_all(head.as_bytes()).await;
let _ = stream.write_all(sse.as_bytes()).await;
let _ = stream.flush().await;
}
});
CaptureUpstream {
port,
received_body,
received_headers,
received_request_line,
}
}
fn responses_completed_frame(
input_tokens: u64,
cached_tokens: u64,
cache_write_tokens: u64,
output_tokens: u64,
) -> String {
let total = input_tokens + output_tokens;
let usage = format!(
"\"usage\":{{\"input_tokens\":{input_tokens},\
\"input_tokens_details\":{{\"cache_write_tokens\":{cache_write_tokens},\"cached_tokens\":{cached_tokens}}},\
\"output_tokens\":{output_tokens},\
\"output_tokens_details\":{{\"reasoning_tokens\":0}},\
\"total_tokens\":{total}}}"
);
let body = super::capture::RESPONSES_COMPLETED_FIXTURE
.replace(super::capture::RESPONSES_COMPLETED_FIXTURE_USAGE, &usage);
assert!(
body.contains(&usage),
"the fixture's usage object must be substitutable — the two consts have drifted"
);
format!("event: response.completed\ndata: {body}\n\n")
}
fn sse_pieces(lead: &str, frame: &str, trail: &str) -> Vec<String> {
let json_at = frame
.find("data: ")
.expect("the frame carries one data: line")
+ "data: ".len();
let a = json_at + 40;
let b = json_at + (frame.len() - json_at) / 2;
let mut pieces = vec![
lead.to_string(),
frame[..a].to_string(),
frame[a..b].to_string(),
frame[b..].to_string(),
];
assert!(
!pieces[2].contains('\n'),
"the middle piece must be newline-free — it is the case D-15 exists for"
);
if !trail.is_empty() {
pieces.push(trail.to_string());
}
pieces
}
const RESPONSES_DELTA_EVENT: &str = "event: response.output_text.delta\n\
data: {\"type\":\"response.output_text.delta\",\"delta\":\"hi\"}\n\n";
const CHAT_COMPLETIONS_DELTA_EVENTS: &str = concat!(
r#"data: {"id":"chatcmpl-398","object":"chat.completion.chunk","created":1789410009,"model":"qwen2.5-coder:14b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"hi"},"finish_reason":null}]}"#,
"\n\n",
r#"data: {"id":"chatcmpl-398","object":"chat.completion.chunk","created":1789410009,"model":"qwen2.5-coder:14b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}"#,
"\n\n",
);
const CHAT_COMPLETIONS_DONE: &str = "data: [DONE]\n\n";
fn chat_completions_usage_chunk(
prompt_tokens: u64,
cached_tokens: u64,
completion_tokens: u64,
) -> String {
let total = prompt_tokens + completion_tokens;
let usage = format!(
"\"usage\":{{\"prompt_tokens\":{prompt_tokens},\
\"prompt_tokens_details\":{{\"cached_tokens\":{cached_tokens}}},\
\"completion_tokens\":{completion_tokens},\
\"total_tokens\":{total}}}"
);
let body = super::capture::CHAT_COMPLETIONS_CHUNK_FIXTURE
.replace(super::capture::CHAT_COMPLETIONS_CHUNK_FIXTURE_USAGE, &usage);
assert!(
body.contains(&usage),
"the fixture's usage object must be substitutable — the two consts have drifted"
);
format!("data: {body}\n\n")
}
pub async fn spawn_capture_responses_sse(
input_tokens: u64,
cached_tokens: u64,
cache_write_tokens: u64,
output_tokens: u64,
) -> CaptureUpstream {
spawn_capture_sse_pieces(sse_pieces(
RESPONSES_DELTA_EVENT,
&responses_completed_frame(
input_tokens,
cached_tokens,
cache_write_tokens,
output_tokens,
),
"",
))
.await
}
pub async fn spawn_capture_chat_completions_sse(
prompt_tokens: u64,
cached_tokens: u64,
completion_tokens: u64,
) -> CaptureUpstream {
spawn_capture_sse_pieces(sse_pieces(
CHAT_COMPLETIONS_DELTA_EVENTS,
&chat_completions_usage_chunk(prompt_tokens, cached_tokens, completion_tokens),
CHAT_COMPLETIONS_DONE,
))
.await
}
fn google_usage_chunk(
prompt_tokens: u64,
cached_tokens: u64,
candidates_tokens: u64,
thoughts_tokens: u64,
) -> String {
let total = prompt_tokens + candidates_tokens + thoughts_tokens;
let usage = format!(
"\"usageMetadata\":{{\"promptTokenCount\":{prompt_tokens},\
\"cachedContentTokenCount\":{cached_tokens},\
\"candidatesTokenCount\":{candidates_tokens},\
\"thoughtsTokenCount\":{thoughts_tokens},\
\"toolUsePromptTokenCount\":0,\
\"totalTokenCount\":{total}}}"
);
let body = super::capture::GOOGLE_GENERATE_CONTENT_FIXTURE.replace(
super::capture::GOOGLE_GENERATE_CONTENT_FIXTURE_USAGE,
&usage,
);
assert!(
body.contains(&usage),
"the fixture's usageMetadata object must be substitutable — the two consts have drifted"
);
format!("data: {body}\n\n")
}
const GOOGLE_DELTA_EVENT: &str = concat!(
r#"data: {"candidates":[{"content":{"parts":[{"text":"The parser "}],"role":"model"},"index":0}],"#,
r#""usageMetadata":{"promptTokenCount":1024,"cachedContentTokenCount":256,"candidatesTokenCount":9,"thoughtsTokenCount":4,"toolUsePromptTokenCount":0,"totalTokenCount":1037},"#,
r#""modelVersion":"gemini-2.5-pro","responseId":"y8nHaPeaDvqfmecPmLXzsAo"}"#,
"\n\n",
);
pub async fn spawn_capture_google_sse(
prompt_tokens: u64,
cached_tokens: u64,
candidates_tokens: u64,
thoughts_tokens: u64,
) -> CaptureUpstream {
let pieces = sse_pieces(
GOOGLE_DELTA_EVENT,
&google_usage_chunk(
prompt_tokens,
cached_tokens,
candidates_tokens,
thoughts_tokens,
),
"",
);
assert!(
!pieces[1..]
.iter()
.any(|p| p.contains("\"usageMetadata\"") && p.contains("\"totalTokenCount\"")),
"the terminal usageMetadata must straddle a chunk boundary"
);
spawn_capture_sse_pieces(pieces).await
}
pub async fn spawn_capture_chat_completions_sse_without_usage() -> CaptureUpstream {
let stream = format!("{CHAT_COMPLETIONS_DELTA_EVENTS}{CHAT_COMPLETIONS_DONE}");
assert!(
!stream.contains("usage"),
"the no-usage branch must carry no usage object — otherwise it tests the other branch"
);
spawn_capture_sse_pieces(stream.split_inclusive("\n\n").map(str::to_string).collect()).await
}
pub async fn spawn_capture_ollama_native_stream(
prompt_eval_count: u64,
prompt_eval_cached_count: u64,
eval_count: u64,
) -> CaptureUpstream {
let content = |text: &str| {
format!(
"{{\"model\":\"qwen2.5-coder:7b\",\"message\":{{\"role\":\"assistant\",\"content\":\"{text}\"}},\"done\":false}}\n"
)
};
let done = format!(
"{{\"model\":\"qwen2.5-coder:7b\",\"message\":{{\"role\":\"assistant\",\"content\":\"\"}},\
\"done\":true,\"done_reason\":\"stop\",\"prompt_eval_count\":{prompt_eval_count},\
\"prompt_eval_cached_count\":{prompt_eval_cached_count},\"eval_count\":{eval_count}}}\n"
);
spawn_capture_sse_pieces(vec![content("p"), format!("{}{done}", content("ong"))]).await
}
async fn spawn_capture_sse_pieces(pieces: Vec<String>) -> CaptureUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let received_body = Arc::new(Mutex::new(None));
let received_headers = Arc::new(Mutex::new(None));
let received_request_line = Arc::new(Mutex::new(None));
let body_sink = received_body.clone();
let hdr_sink = received_headers.clone();
let line_sink = received_request_line.clone();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
if let Ok((headers, body)) = read_request(&mut stream).await {
*line_sink.lock().unwrap() = headers.lines().next().map(str::to_string);
*hdr_sink.lock().unwrap() = Some(headers);
*body_sink.lock().unwrap() = Some(body);
}
let head = "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\
Transfer-Encoding: chunked\r\n\r\n";
let _ = stream.write_all(head.as_bytes()).await;
let _ = stream.flush().await;
for piece in &pieces {
let framed = format!("{:x}\r\n{}\r\n", piece.len(), piece);
let _ = stream.write_all(framed.as_bytes()).await;
let _ = stream.flush().await;
tokio::time::sleep(Duration::from_millis(5)).await;
}
let _ = stream.write_all(b"0\r\n\r\n").await;
let _ = stream.flush().await;
}
});
CaptureUpstream {
port,
received_body,
received_headers,
received_request_line,
}
}
pub struct MultiCaptureUpstream {
pub port: u16,
pub bodies: Arc<Mutex<Vec<Vec<u8>>>>,
}
impl MultiCaptureUpstream {
pub async fn wait_for(&self, n: usize) -> Vec<Vec<u8>> {
for _ in 0..300 {
{
let got = self.bodies.lock().unwrap();
if got.len() >= n {
return got.clone();
}
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
self.bodies.lock().unwrap().clone()
}
}
pub async fn spawn_multi_capture_usage_sse(
input_tokens: u64,
output_tokens: u64,
) -> MultiCaptureUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let bodies = Arc::new(Mutex::new(Vec::new()));
let sink = bodies.clone();
tokio::spawn(async move {
while let Ok((mut stream, _)) = listener.accept().await {
if let Ok((_headers, body)) = read_request(&mut stream).await {
sink.lock().unwrap().push(body);
}
let sse = format!(
"event: message_start\n\
data: {{\"type\":\"message_start\",\"message\":{{\"usage\":{{\"input_tokens\":{input_tokens},\"cache_read_input_tokens\":0,\"cache_creation_input_tokens\":0,\"output_tokens\":1}}}}}}\n\n\
event: message_delta\n\
data: {{\"type\":\"message_delta\",\"usage\":{{\"output_tokens\":{output_tokens}}}}}\n\n"
);
let head = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\
Content-Length: {}\r\nConnection: close\r\n\r\n",
sse.len()
);
let _ = stream.write_all(head.as_bytes()).await;
let _ = stream.write_all(sse.as_bytes()).await;
let _ = stream.flush().await;
}
});
MultiCaptureUpstream { port, bodies }
}
pub async fn spawn_reject_then_capture_usage_sse() -> MultiCaptureUpstream {
spawn_error_then_capture_usage_sse("400 Bad Request").await
}
pub async fn spawn_error_then_capture_usage_sse(status: &'static str) -> MultiCaptureUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let bodies = Arc::new(Mutex::new(Vec::new()));
let sink = bodies.clone();
tokio::spawn(async move {
let mut count = 0usize;
while let Ok((mut stream, _)) = listener.accept().await {
if let Ok((_headers, body)) = read_request(&mut stream).await {
sink.lock().unwrap().push(body);
}
count += 1;
let response = if count == 1 {
format!("HTTP/1.1 {status}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
} else {
let sse = "event: message_delta\ndata: {\"type\":\"message_delta\",\"usage\":{\"output_tokens\":1}}\n\n";
format!("HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{sse}", sse.len())
};
let _ = stream.write_all(response.as_bytes()).await;
let _ = stream.flush().await;
if count >= 2 {
break;
}
}
});
MultiCaptureUpstream { port, bodies }
}
pub async fn spawn_message_start_then_hangup() -> CaptureUpstream {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
let received_body = Arc::new(Mutex::new(None));
let received_headers = Arc::new(Mutex::new(None));
let received_request_line = Arc::new(Mutex::new(None));
let body_sink = received_body.clone();
let hdr_sink = received_headers.clone();
let line_sink = received_request_line.clone();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
if let Ok((headers, body)) = read_request(&mut stream).await {
*line_sink.lock().unwrap() = headers.lines().next().map(str::to_string);
*hdr_sink.lock().unwrap() = Some(headers);
*body_sink.lock().unwrap() = Some(body);
}
let start = "event: message_start\n\
data: {\"type\":\"message_start\",\"message\":{\"usage\":{\"input_tokens\":40,\"cache_read_input_tokens\":0,\"cache_creation_input_tokens\":0,\"output_tokens\":1}}}\n\n";
let head =
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nConnection: close\r\n\r\n";
let _ = stream.write_all(head.as_bytes()).await;
let _ = stream.write_all(start.as_bytes()).await;
let _ = stream.flush().await;
drop(stream);
}
});
CaptureUpstream {
port,
received_body,
received_headers,
received_request_line,
}
}
pub async fn closed_port() -> u16 {
use std::net::{SocketAddr, TcpStream as StdTcpStream};
use std::sync::atomic::{AtomicU16, Ordering};
static NEXT: AtomicU16 = AtomicU16::new(20000);
loop {
let port = NEXT.fetch_add(1, Ordering::Relaxed);
assert!(port < 21000, "exhausted 20000..21000 dead ports");
let addr = SocketAddr::from(([127, 0, 0, 1], port));
if StdTcpStream::connect_timeout(&addr, Duration::from_millis(50)).is_err() {
return port;
}
}
}
pub async fn spawn_hang_after_accept() -> u16 {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let port = listener.local_addr().unwrap().port();
tokio::spawn(async move {
if let Ok((mut stream, _)) = listener.accept().await {
let _ = read_request(&mut stream).await;
tokio::time::sleep(Duration::from_secs(30)).await;
drop(stream);
}
});
port
}