use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
const PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
const FRAME_TYPE_SETTINGS: u8 = 0x04;
const FRAME_TYPE_WINDOW_UPDATE: u8 = 0x08;
const SETTING_HEADER_TABLE_SIZE: u16 = 0x0001;
const SETTING_ENABLE_PUSH: u16 = 0x0002;
const SETTING_INITIAL_WINDOW_SIZE: u16 = 0x0004;
const SETTING_MAX_HEADER_LIST_SIZE: u16 = 0x0006;
const EXPECTED_HEADER_TABLE_SIZE: u32 = 65_536;
const EXPECTED_ENABLE_PUSH: u32 = 0;
const EXPECTED_INITIAL_WINDOW_SIZE: u32 = 6_291_456;
const EXPECTED_MAX_HEADER_LIST_SIZE: u32 = 262_144;
const EXPECTED_CONNECTION_WINDOW_DELTA: u32 = 15_663_105;
#[tokio::test]
async fn h2_handshake_writes_chrome_146_settings_and_window_update() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut sock, _) = listener.accept().await.unwrap();
let mut buf = vec![0u8; 256];
let mut total = 0;
let deadline = tokio::time::sleep(Duration::from_secs(2));
tokio::pin!(deadline);
while total < 24 {
tokio::select! {
_ = &mut deadline => break,
r = sock.read(&mut buf[total..]) => {
match r {
Ok(0) => break,
Ok(n) => total += n,
Err(_) => break,
}
}
}
}
let server_settings: [u8; 9] = [0, 0, 0, 0x04, 0, 0, 0, 0, 0];
let _ = sock.write_all(&server_settings).await;
let _ = sock.flush().await;
while total < 70 {
tokio::select! {
_ = &mut deadline => break,
r = sock.read(&mut buf[total..]) => {
match r {
Ok(0) => break,
Ok(n) => total += n,
Err(_) => break,
}
}
}
}
buf.truncate(total);
buf
});
let _client = tokio::spawn(async move {
let tcp = TcpStream::connect(addr).await.unwrap();
let profile = browser_oxide::stealth::presets::chrome_148_macos();
if let Ok((_sender, conn)) = browser_oxide::net::h2_client::handshake(tcp, &profile).await {
let _ = conn.await;
}
});
let bytes = server.await.unwrap();
assert!(
bytes.len() >= 70,
"captured fewer than 70 bytes ({}), can't validate frames: {:02x?}",
bytes.len(),
&bytes[..bytes.len().min(40)]
);
assert_eq!(&bytes[..24], PREFACE, "HTTP/2 connection preface mismatch");
let settings_hdr = &bytes[24..33];
let settings_len =
u32::from_be_bytes([0, settings_hdr[0], settings_hdr[1], settings_hdr[2]]) as usize;
assert_eq!(
settings_hdr[3], FRAME_TYPE_SETTINGS,
"first frame is not SETTINGS, got type 0x{:02x}",
settings_hdr[3]
);
assert_eq!(
settings_hdr[4], 0,
"SETTINGS frame must have flags=0 (not ACK)"
);
assert_eq!(
u32::from_be_bytes([
settings_hdr[5],
settings_hdr[6],
settings_hdr[7],
settings_hdr[8]
]) & 0x7FFF_FFFF,
0,
"SETTINGS frame must use stream_id=0"
);
assert_eq!(
settings_len, 24,
"Chrome SETTINGS frame must contain exactly 4 settings (24 bytes), got {settings_len}"
);
let settings_payload = &bytes[33..33 + settings_len];
let settings: Vec<(u16, u32)> = settings_payload
.chunks_exact(6)
.map(|s| {
let id = u16::from_be_bytes([s[0], s[1]]);
let v = u32::from_be_bytes([s[2], s[3], s[4], s[5]]);
(id, v)
})
.collect();
assert_eq!(
settings.len(),
4,
"expected 4 settings, got {}",
settings.len()
);
assert_eq!(
settings[0],
(SETTING_HEADER_TABLE_SIZE, EXPECTED_HEADER_TABLE_SIZE),
"SETTINGS[0] must be HEADER_TABLE_SIZE=65536"
);
assert_eq!(
settings[1],
(SETTING_ENABLE_PUSH, EXPECTED_ENABLE_PUSH),
"SETTINGS[1] must be ENABLE_PUSH=0"
);
assert_eq!(
settings[2],
(SETTING_INITIAL_WINDOW_SIZE, EXPECTED_INITIAL_WINDOW_SIZE),
"SETTINGS[2] must be INITIAL_WINDOW_SIZE=6291456"
);
assert_eq!(
settings[3],
(SETTING_MAX_HEADER_LIST_SIZE, EXPECTED_MAX_HEADER_LIST_SIZE),
"SETTINGS[3] must be MAX_HEADER_LIST_SIZE=262144"
);
let wu_hdr = &bytes[57..66];
let wu_len = u32::from_be_bytes([0, wu_hdr[0], wu_hdr[1], wu_hdr[2]]) as usize;
assert_eq!(
wu_hdr[3], FRAME_TYPE_WINDOW_UPDATE,
"second frame is not WINDOW_UPDATE, got type 0x{:02x}",
wu_hdr[3]
);
assert_eq!(wu_len, 4, "WINDOW_UPDATE payload must be 4 bytes");
assert_eq!(
u32::from_be_bytes([wu_hdr[5], wu_hdr[6], wu_hdr[7], wu_hdr[8]]) & 0x7FFF_FFFF,
0,
"Connection-level WINDOW_UPDATE must use stream_id=0"
);
let wu_delta = u32::from_be_bytes([bytes[66], bytes[67], bytes[68], bytes[69]]) & 0x7FFF_FFFF;
assert_eq!(
wu_delta, EXPECTED_CONNECTION_WINDOW_DELTA,
"Connection WINDOW_UPDATE delta must be 15_663_105 (Chrome 146)"
);
}