use std::task::Poll;
use futures::channel::{mpsc, oneshot};
use futures::executor::LocalPool;
use futures::future::poll_fn;
use futures::stream;
use futures::task::LocalSpawnExt;
use futures::{SinkExt, StreamExt};
use h2ts_client::frames::{serialize_frame, Frame, FrameDecoder, Settings};
use h2ts_client::hpack::{Header, HpackDecoder, HpackEncoder};
use h2ts_client::{connect, ConnectOptions, RequestBody, RequestInit, Transport, TransportError};
const CONNECTION_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
const SPEC_INITIAL_WINDOW: i64 = 65535;
fn mock_transport() -> (
Transport,
mpsc::UnboundedReceiver<Vec<u8>>,
mpsc::UnboundedSender<Vec<u8>>,
) {
let (c2s_tx, c2s_rx) = mpsc::unbounded::<Vec<u8>>(); let (s2c_tx, s2c_rx) = mpsc::unbounded::<Vec<u8>>(); let writer = Box::pin(c2s_tx.sink_map_err(|e| TransportError(e.to_string())));
let reader = Box::pin(s2c_rx);
(Transport::new(reader, writer), c2s_rx, s2c_tx)
}
async fn read_startup(c2s_rx: &mut mpsc::UnboundedReceiver<Vec<u8>>, want: usize) -> Vec<Frame> {
let mut buf: Vec<u8> = Vec::new();
while buf.len() < CONNECTION_PREFACE.len() {
buf.extend(c2s_rx.next().await.expect("client closed before preface"));
}
assert_eq!(&buf[..CONNECTION_PREFACE.len()], CONNECTION_PREFACE);
let mut dec = FrameDecoder::default();
let keep = |raw: Vec<Frame>, frames: &mut Vec<Frame>| {
for f in raw {
if !matches!(f, Frame::WindowUpdate { stream_id: 0, .. }) {
frames.push(f);
}
}
};
let mut frames: Vec<Frame> = Vec::new();
keep(dec.push(&buf[CONNECTION_PREFACE.len()..]).unwrap(), &mut frames);
while frames.len() < want {
let chunk = c2s_rx.next().await.expect("client closed before frames");
keep(dec.push(&chunk).unwrap(), &mut frames);
}
frames
}
struct ServerSide {
rx: mpsc::UnboundedReceiver<Vec<u8>>,
dec: FrameDecoder,
queue: std::collections::VecDeque<Frame>,
skip_conn_wu: bool,
}
impl ServerSide {
fn new(rx: mpsc::UnboundedReceiver<Vec<u8>>) -> Self {
Self {
rx,
dec: FrameDecoder::default(),
queue: std::collections::VecDeque::new(),
skip_conn_wu: true,
}
}
fn take_queued(&mut self) -> Option<Frame> {
while let Some(f) = self.queue.pop_front() {
if self.skip_conn_wu && matches!(f, Frame::WindowUpdate { stream_id: 0, .. }) {
self.skip_conn_wu = false;
continue;
}
return Some(f);
}
None
}
async fn read_preface(&mut self) {
let mut buf: Vec<u8> = Vec::new();
while buf.len() < CONNECTION_PREFACE.len() {
buf.extend(self.rx.next().await.expect("client closed before preface"));
}
assert_eq!(&buf[..CONNECTION_PREFACE.len()], CONNECTION_PREFACE);
for f in self.dec.push(&buf[CONNECTION_PREFACE.len()..]).unwrap() {
self.queue.push_back(f);
}
}
async fn next_frame(&mut self) -> Frame {
loop {
if let Some(f) = self.take_queued() {
return f;
}
let chunk = self.rx.next().await.expect("client closed unexpectedly");
for f in self.dec.push(&chunk).unwrap() {
self.queue.push_back(f);
}
}
}
async fn next_data(&mut self) -> (Vec<u8>, bool) {
loop {
match self.next_frame().await {
Frame::Data {
data, end_stream, ..
} => return (data, end_stream),
Frame::Settings { .. } | Frame::WindowUpdate { .. } | Frame::Ping { .. } => {
continue
}
_ => panic!("unexpected non-DATA frame during upload"),
}
}
}
fn try_next_frame(&mut self) -> Option<Frame> {
while let Ok(chunk) = self.rx.try_recv() {
for f in self.dec.push(&chunk).unwrap() {
self.queue.push_back(f);
}
}
self.take_queued()
}
}
async fn quiesce() {
for _ in 0..64 {
let mut yielded = false;
poll_fn(|cx| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
.await;
}
}
#[test]
fn opens_with_preface_and_settings_not_http1_upgrade() {
let mut pool = LocalPool::new();
let (transport, mut c2s_rx, _s2c_tx) = mock_transport();
let (_conn, driver) = connect(transport, ConnectOptions::default());
pool.spawner().spawn_local(driver).unwrap();
pool.run_until(async move {
let frames = read_startup(&mut c2s_rx, 1).await;
assert!(matches!(frames[0], Frame::Settings { ack: false, .. }));
});
}
#[test]
fn sends_first_request_before_any_server_bytes() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, mut c2s_rx, _s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let conn2 = conn.clone();
sp.spawn_local(async move {
let _ = conn2
.request(RequestInit {
method: Some("GET".into()),
path: Some("/hello".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await;
})
.unwrap();
pool.run_until(async move {
let frames = read_startup(&mut c2s_rx, 2).await;
assert!(matches!(frames[0], Frame::Settings { ack: false, .. }));
assert!(matches!(frames[1], Frame::Headers { stream_id: 1, .. }));
});
}
#[test]
fn completes_a_request_when_the_server_replies_afterwards() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, mut c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let req = conn.request(RequestInit {
method: Some("GET".into()),
path: Some("/hello".into()),
authority: Some("example.com".into()),
..Default::default()
});
let server = async move {
let frames = read_startup(&mut c2s_rx, 2).await;
assert!(matches!(frames[1], Frame::Headers { stream_id: 1, .. }));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"ok".to_vec(),
end_stream: true,
}))
.unwrap();
};
let (res, ()) = futures::join!(req, server);
let mut res = res.unwrap();
assert_eq!(res.status, 200);
assert_eq!(res.text().await.unwrap(), "ok");
});
}
#[test]
fn ping_resolves_with_the_round_trip_time() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let ping = conn.ping();
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
let opaque = loop {
match server.next_frame().await {
Frame::Ping {
ack: false,
opaque_data,
} => break opaque_data,
Frame::Settings { .. } => continue,
_ => panic!("expected a PING frame"),
}
};
s2c_tx
.unbounded_send(serialize_frame(&Frame::Ping {
ack: true,
opaque_data: opaque,
}))
.unwrap();
};
let (rtt, ()) = futures::join!(ping, server);
let rtt = rtt.unwrap();
assert!(
rtt >= 0.0,
"round-trip time should be non-negative, got {rtt}"
);
});
}
#[test]
fn streams_a_request_body_from_a_stream() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let body = RequestBody::stream(stream::iter(vec![
b"aaaa".to_vec(),
b"bbbb".to_vec(),
b"cccc".to_vec(),
]));
let req = conn.request(RequestInit {
method: Some("POST".into()),
path: Some("/upload".into()),
authority: Some("example.com".into()),
body,
..Default::default()
});
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(
server.next_frame().await,
Frame::Settings { ack: false, .. }
));
assert!(matches!(
server.next_frame().await,
Frame::Headers {
stream_id: 1,
end_stream: false,
..
}
));
let mut received = Vec::new();
loop {
let (data, end_stream) = server.next_data().await;
received.extend(data);
if end_stream {
break;
}
}
assert_eq!(received, b"aaaabbbbcccc");
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"ok".to_vec(),
end_stream: true,
}))
.unwrap();
};
let (res, ()) = futures::join!(req, server);
let mut res = res.unwrap();
assert_eq!(res.status, 200);
assert_eq!(res.text().await.unwrap(), "ok");
});
}
#[test]
fn upload_respects_connection_and_stream_flow_control() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let total_len = 70_000usize;
let body = vec![0x61u8; total_len];
sp.spawn_local(async move {
let _ = conn
.request(RequestInit {
method: Some("POST".into()),
path: Some("/upload".into()),
authority: Some("example.com".into()),
body: body.into(),
..Default::default()
})
.await;
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(
server.next_frame().await,
Frame::Settings { ack: false, .. }
));
assert!(matches!(
server.next_frame().await,
Frame::Headers {
stream_id: 1,
end_stream: false,
..
}
));
let mut sent = 0usize;
while sent < SPEC_INITIAL_WINDOW as usize {
match server.next_frame().await {
Frame::Data {
stream_id: 1,
data,
end_stream,
} => {
assert!(!end_stream, "client ran past the flow-control window");
sent += data.len();
}
_ => panic!("expected DATA while uploading"),
}
}
assert_eq!(sent, SPEC_INITIAL_WINDOW as usize);
quiesce().await;
assert!(
server.try_next_frame().is_none(),
"client ignored flow control and kept sending"
);
let rest = (total_len - sent) as u32;
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 0,
window_size_increment: rest,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 1,
window_size_increment: rest,
}))
.unwrap();
let mut done = false;
while !done {
match server.next_frame().await {
Frame::Data {
stream_id: 1,
data,
end_stream,
} => {
sent += data.len();
done = end_stream;
}
_ => panic!("expected DATA after the window update"),
}
}
assert_eq!(sent, total_len);
});
}
#[test]
fn returns_the_response_before_the_upload_finishes() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let (release_tx, release_rx) = oneshot::channel::<()>();
let body = RequestBody::stream(stream::unfold(
(0u8, Some(release_rx)),
|(i, mut gate)| async move {
match i {
0 => Some((b"part1".to_vec(), (1, gate))),
1 => {
if let Some(rx) = gate.take() {
let _ = rx.await;
}
Some((b"part2".to_vec(), (2, gate)))
}
_ => None,
}
},
));
pool.run_until(async move {
let client = async move {
let mut res = conn
.request(RequestInit {
method: Some("POST".into()),
path: Some("/upload".into()),
authority: Some("example.com".into()),
body,
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
release_tx.send(()).unwrap();
assert_eq!(res.text().await.unwrap(), "done");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(
server.next_frame().await,
Frame::Settings { ack: false, .. }
));
assert!(matches!(
server.next_frame().await,
Frame::Headers {
stream_id: 1,
end_stream: false,
..
}
));
let (data, end_stream) = server.next_data().await;
assert_eq!(data, b"part1");
assert!(!end_stream);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
let (data, end_stream) = server.next_data().await;
assert_eq!(data, b"part2");
assert!(!end_stream);
let (data, end_stream) = server.next_data().await;
assert!(data.is_empty());
assert!(end_stream);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"done".to_vec(),
end_stream: true,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn streams_the_response_body_incrementally() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let client = async move {
let res = conn
.request(RequestInit {
method: Some("GET".into()),
path: Some("/stream".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
let mut body = res.into_body();
assert_eq!(body.next().await.unwrap().unwrap(), b"one");
assert_eq!(body.next().await.unwrap().unwrap(), b"two");
assert_eq!(body.next().await.unwrap().unwrap(), b"three");
assert!(body.next().await.is_none());
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(
server.next_frame().await,
Frame::Settings { ack: false, .. }
));
assert!(matches!(
server.next_frame().await,
Frame::Headers {
stream_id: 1,
end_stream: true,
..
}
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
for (chunk, last) in [
(&b"one"[..], false),
(&b"two"[..], false),
(&b"three"[..], true),
] {
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: chunk.to_vec(),
end_stream: last,
}))
.unwrap();
}
};
futures::join!(client, server);
});
}
#[test]
fn finishes_upload_after_an_early_complete_response() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let (release_tx, release_rx) = oneshot::channel::<()>();
let body = RequestBody::stream(stream::unfold(
(0u8, Some(release_rx)),
|(i, mut gate)| async move {
match i {
0 => Some((b"part1".to_vec(), (1, gate))),
1 => {
if let Some(rx) = gate.take() {
let _ = rx.await;
}
Some((b"part2".to_vec(), (2, gate)))
}
_ => None,
}
},
));
pool.run_until(async move {
let client = async move {
let mut res = conn
.request(RequestInit {
method: Some("POST".into()),
path: Some("/echo".into()),
authority: Some("example.com".into()),
body,
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
release_tx.send(()).unwrap();
assert_eq!(res.text().await.unwrap(), "done");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(
server.next_frame().await,
Frame::Settings { ack: false, .. }
));
assert!(matches!(
server.next_frame().await,
Frame::Headers {
stream_id: 1,
end_stream: false,
..
}
));
let (data, end_stream) = server.next_data().await;
assert_eq!(data, b"part1");
assert!(!end_stream);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"done".to_vec(),
end_stream: true,
}))
.unwrap();
let (data, end_stream) = server.next_data().await;
assert_eq!(data, b"part2", "client dropped its body after the early response");
assert!(!end_stream);
let (data, end_stream) = server.next_data().await;
assert!(data.is_empty());
assert!(end_stream, "client never sent END_STREAM for its request body");
};
futures::join!(client, server);
});
}
#[test]
fn finishes_a_flow_limited_upload_after_an_early_complete_response() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let total = 100_000usize;
let body = vec![0x61u8; total];
sp.spawn_local(async move {
let mut res = conn
.request(RequestInit {
method: Some("POST".into()),
path: Some("/upload".into()),
authority: Some("example.com".into()),
body: body.into(),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
let _ = res.bytes().await;
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
let mut sent = 0usize;
while sent < SPEC_INITIAL_WINDOW as usize {
let (d, end) = server.next_data().await;
assert!(!end);
sent += d.len();
}
assert_eq!(sent, SPEC_INITIAL_WINDOW as usize);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: true,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 0,
window_size_increment: 1_000_000,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 1,
window_size_increment: 1_000_000,
}))
.unwrap();
loop {
let (d, end) = server.next_data().await;
sent += d.len();
if end {
break;
}
}
assert_eq!(sent, total, "client dropped its body after the early response");
});
}
#[test]
fn ping_errors_when_the_connection_closes_in_flight() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, _c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let ping = conn.ping();
drop(s2c_tx);
assert!(
ping.await.is_err(),
"ping should error when the connection closes in flight"
);
});
}
#[test]
fn rst_stream_mid_upload_fails_the_request_without_hanging() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let body = RequestBody::stream(stream::once(async { b"part1".to_vec() }).chain(stream::pending()));
pool.run_until(async move {
let client = async move {
let res = conn
.request(RequestInit {
method: Some("POST".into()),
path: Some("/upload".into()),
authority: Some("example.com".into()),
body,
..Default::default()
})
.await;
assert!(res.is_err(), "request should error when the stream is reset");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
let (data, _end) = server.next_data().await;
assert_eq!(data, b"part1");
s2c_tx
.unbounded_send(serialize_frame(&Frame::RstStream {
stream_id: 1,
error_code: 8,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn goaway_with_error_tears_down_and_fails_in_flight_requests() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await;
assert!(res.is_err(), "request should error after a GOAWAY(error)");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Goaway {
last_stream_id: 0,
error_code: 1,
debug_data: Vec::new(),
}))
.unwrap();
};
futures::join!(client, server);
assert!(
conn_probe.is_closed(),
"connection should be closed after a GOAWAY error"
);
});
}
#[test]
fn rst_stream_mid_download_errors_the_response_body() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let client = async move {
let mut res = conn
.request(RequestInit {
path: Some("/download".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
assert!(
res.bytes().await.is_err(),
"a reset mid-download must surface as a body error"
);
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"one".to_vec(),
end_stream: false,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::RstStream {
stream_id: 1,
error_code: 8,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn surfaces_response_trailers() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let client = async move {
let mut res = conn
.request(RequestInit {
path: Some("/rpc".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
assert_eq!(res.bytes().await.unwrap(), b"data");
let trailers = res.trailers().expect("trailers should be present");
assert_eq!(trailers.get("grpc-status").map(String::as_str), Some("0"));
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let head = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: head,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"data".to_vec(),
end_stream: false,
}))
.unwrap();
let trailers = HpackEncoder::new().encode(&[Header::new("grpc-status", "0")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: trailers,
end_stream: true,
end_headers: true,
priority: None,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn honors_a_retroactively_shrunk_send_window() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let total = 65535usize + 1000;
let body = vec![0x61u8; total];
sp.spawn_local(async move {
let _ = conn
.request(RequestInit {
method: Some("POST".into()),
path: Some("/upload".into()),
authority: Some("example.com".into()),
body: body.into(),
..Default::default()
})
.await;
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 0,
window_size_increment: 1_000_000,
}))
.unwrap();
let mut sent = 0usize;
while sent < 65535 {
let (d, end) = server.next_data().await;
assert!(!end);
sent += d.len();
}
assert_eq!(sent, 65535, "client sent past its initial stream window");
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings {
initial_window_size: Some(100),
..Default::default()
},
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 1,
window_size_increment: 65445,
}))
.unwrap();
let (d, end) = server.next_data().await;
assert_eq!(d.len(), 10, "client ignored the retroactively-shrunk window");
assert!(!end);
sent += d.len();
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 1,
window_size_increment: 2000,
}))
.unwrap();
loop {
let (d, end) = server.next_data().await;
sent += d.len();
if end {
break;
}
}
assert_eq!(sent, total);
});
}
#[test]
fn graceful_goaway_fails_higher_streams_but_lets_lower_finish() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let req1 = conn.request(RequestInit {
path: Some("/a".into()),
authority: Some("example.com".into()),
..Default::default()
});
let req3 = conn.request(RequestInit {
path: Some("/b".into()),
authority: Some("example.com".into()),
..Default::default()
});
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 3, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Goaway {
last_stream_id: 1,
error_code: 0,
debug_data: Vec::new(),
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"ok".to_vec(),
end_stream: true,
}))
.unwrap();
};
let (r1, r3, ()) = futures::join!(req1, req3, server);
assert!(r3.is_err(), "stream above lastStreamId must fail");
let mut r1 = r1.expect("stream at/below lastStreamId should complete");
assert_eq!(r1.status, 200);
assert_eq!(r1.text().await.unwrap(), "ok");
});
}
#[test]
fn connection_window_update_zero_tears_down_with_goaway() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await;
assert!(res.is_err(), "request should error on a protocol error");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 0,
window_size_increment: 0,
}))
.unwrap();
assert!(matches!(server.next_frame().await, Frame::Goaway { .. }));
};
futures::join!(client, server);
assert!(conn_probe.is_closed(), "connection should be closed");
});
}
#[test]
fn reassembles_a_header_block_split_across_continuation() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
let mid = block.len() / 2;
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block[..mid].to_vec(),
end_stream: false,
end_headers: false,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Continuation {
stream_id: 1,
header_block_fragment: block[mid..].to_vec(),
end_headers: true,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: Vec::new(),
end_stream: true,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn honors_the_peers_max_concurrent_streams() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let conn1 = conn.clone();
sp.spawn_local(async move {
let mut res = conn1
.request(RequestInit {
path: Some("/a".into()),
authority: Some("e".into()),
..Default::default()
})
.await
.unwrap();
let _ = res.bytes().await;
})
.unwrap();
let (gate_tx, gate_rx) = oneshot::channel::<()>();
let (done_tx, done_rx) = oneshot::channel::<u16>();
let conn2 = conn.clone();
sp.spawn_local(async move {
let _ = gate_rx.await;
let mut res = conn2
.request(RequestInit {
path: Some("/b".into()),
authority: Some("e".into()),
..Default::default()
})
.await
.unwrap();
let _ = res.bytes().await;
let _ = done_tx.send(res.status);
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings {
max_concurrent_streams: Some(1),
..Default::default()
},
}))
.unwrap();
assert!(matches!(
server.next_frame().await,
Frame::Settings { ack: true, .. }
));
gate_tx.send(()).unwrap();
quiesce().await;
assert!(
server.try_next_frame().is_none(),
"client opened a second stream past the peer's limit"
);
let head = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: head,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"a".to_vec(),
end_stream: true,
}))
.unwrap();
loop {
match server.next_frame().await {
Frame::Headers { stream_id: 3, .. } => break,
Frame::WindowUpdate { .. } => continue,
_ => panic!("expected stream 3 HEADERS after the slot freed"),
}
}
let head3 = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 3,
header_block_fragment: head3,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 3,
data: b"b".to_vec(),
end_stream: true,
}))
.unwrap();
assert_eq!(done_rx.await.unwrap(), 200);
});
}
#[test]
fn stream_window_update_zero_resets_only_the_stream() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("e".into()),
..Default::default()
})
.await;
assert!(res.is_err(), "request should error, not hang");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::WindowUpdate {
stream_id: 1,
window_size_increment: 0,
}))
.unwrap();
loop {
if matches!(server.next_frame().await, Frame::RstStream { stream_id: 1, .. }) {
break;
}
}
};
futures::join!(client, server);
assert!(
!conn_probe.is_closed(),
"the connection should survive a single stream reset"
);
});
}
#[test]
fn a_frame_over_max_frame_size_tears_down_with_goaway() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("e".into()),
..Default::default()
})
.await;
assert!(res.is_err());
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: vec![0u8; 20000],
end_stream: false,
}))
.unwrap();
loop {
if matches!(server.next_frame().await, Frame::Goaway { .. }) {
break;
}
}
};
futures::join!(client, server);
assert!(conn_probe.is_closed());
});
}
#[test]
fn strips_padding_from_a_padded_headers_frame() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("e".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
let pad_len = 4usize;
let mut payload = Vec::new();
payload.push(pad_len as u8);
payload.extend_from_slice(&block);
payload.resize(payload.len() + pad_len, 0); let mut frame = Vec::new();
let len = payload.len();
frame.push(((len >> 16) & 0xff) as u8);
frame.push(((len >> 8) & 0xff) as u8);
frame.push((len & 0xff) as u8);
frame.push(0x1); frame.push(0x4 | 0x8); frame.extend_from_slice(&[0, 0, 0, 1]); frame.extend_from_slice(&payload);
s2c_tx.unbounded_send(frame).unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: Vec::new(),
end_stream: true,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn refuses_an_inbound_push_promise() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
sp.spawn_local(async move {
let _ = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("e".into()),
..Default::default()
})
.await;
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
let push = HpackEncoder::new().encode(&[
Header::new(":method", "GET"),
Header::new(":scheme", "http"),
Header::new(":authority", "e"),
Header::new(":path", "/pushed"),
]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::PushPromise {
stream_id: 1,
promised_stream_id: 2,
header_block_fragment: push,
end_headers: true,
}))
.unwrap();
loop {
match server.next_frame().await {
Frame::RstStream {
stream_id: 2,
error_code,
} => {
assert_eq!(error_code, 7); break;
}
_ => continue,
}
}
});
}
#[test]
fn splits_an_oversized_request_header_block_on_send() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, _s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let big = "a".repeat(40000);
let big_send = big.clone();
sp.spawn_local(async move {
let _ = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("e".into()),
headers: vec![("x-big".into(), big_send)],
..Default::default()
})
.await;
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
let mut fragments: Vec<u8> = Vec::new();
match server.next_frame().await {
Frame::Headers {
stream_id: 1,
header_block_fragment,
end_headers,
..
} => {
assert!(!end_headers, "block should not fit one HEADERS frame");
fragments.extend_from_slice(&header_block_fragment);
}
_ => panic!("expected HEADERS"),
}
loop {
match server.next_frame().await {
Frame::Continuation {
header_block_fragment,
end_headers,
..
} => {
fragments.extend_from_slice(&header_block_fragment);
if end_headers {
break;
}
}
_ => panic!("expected CONTINUATION"),
}
}
let decoded = HpackDecoder::new(4096).decode(&fragments).unwrap();
assert!(decoded.iter().any(|h| h.name == "x-big" && h.value == big));
});
}
#[test]
fn replenishes_the_receive_window_only_on_consumption() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
let (gate_tx, gate_rx) = oneshot::channel::<()>();
let (done_tx, done_rx) = oneshot::channel::<Vec<u8>>();
sp.spawn_local(async move {
let res = conn
.request(RequestInit {
path: Some("/download".into()),
authority: Some("e".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
let mut body = res.into_body();
gate_rx.await.unwrap(); let chunk = body.next().await.unwrap().unwrap();
let _ = done_tx.send(chunk);
})
.unwrap();
pool.run_until(async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let head = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: head,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"hello".to_vec(),
end_stream: false,
}))
.unwrap();
quiesce().await;
let mut pre = Vec::new();
while let Some(f) = server.try_next_frame() {
pre.push(f);
}
assert!(
!pre.iter().any(|f| matches!(f, Frame::WindowUpdate { .. })),
"replenished the window before the body was read"
);
gate_tx.send(()).unwrap();
assert_eq!(done_rx.await.unwrap(), b"hello");
let (mut stream_wu, mut conn_wu) = (false, false);
for _ in 0..8 {
match server.next_frame().await {
Frame::WindowUpdate {
stream_id: 1,
window_size_increment,
} => {
assert_eq!(window_size_increment, 5);
stream_wu = true;
}
Frame::WindowUpdate {
stream_id: 0,
window_size_increment,
} => {
assert_eq!(window_size_increment, 5);
conn_wu = true;
}
_ => {}
}
if stream_wu && conn_wu {
break;
}
}
assert!(stream_wu && conn_wu, "consumption must replenish both windows");
});
}
#[test]
fn treats_a_1xx_interim_response_as_non_final() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let client = async move {
let mut res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(res.status, 200);
assert_eq!(res.bytes().await.unwrap(), b"body");
assert!(res.trailers().is_none());
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let early = HpackEncoder::new()
.encode(&[Header::new(":status", "103"), Header::new("link", "</a.css>")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: early,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
let head = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: head,
end_stream: false,
end_headers: true,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"body".to_vec(),
end_stream: true,
}))
.unwrap();
};
futures::join!(client, server);
});
}
#[test]
fn out_of_range_max_frame_size_tears_down_with_goaway() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await;
assert!(res.is_err(), "request should error on an invalid SETTINGS");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings {
max_frame_size: Some(100),
..Default::default()
},
}))
.unwrap();
while !matches!(server.next_frame().await, Frame::Goaway { .. }) {}
};
futures::join!(client, server);
assert!(conn_probe.is_closed(), "connection should be closed");
});
}
#[test]
fn a_header_block_over_the_cap_tears_down_with_goaway() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await;
assert!(res.is_err(), "request should error on a header-block flood");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
s2c_tx
.unbounded_send(serialize_frame(&Frame::Settings {
ack: false,
settings: Settings::default(),
}))
.unwrap();
let frag = vec![0u8; 16000];
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: frag.clone(),
end_stream: false,
end_headers: false,
priority: None,
}))
.unwrap();
for _ in 0..70 {
s2c_tx
.unbounded_send(serialize_frame(&Frame::Continuation {
stream_id: 1,
header_block_fragment: frag.clone(),
end_headers: false,
}))
.unwrap();
}
while !matches!(server.next_frame().await, Frame::Goaway { .. }) {}
};
futures::join!(client, server);
assert!(conn_probe.is_closed(), "connection should be closed");
});
}
#[test]
fn an_unterminated_header_block_interrupted_by_data_is_a_protocol_error() {
let mut pool = LocalPool::new();
let sp = pool.spawner();
let (transport, c2s_rx, s2c_tx) = mock_transport();
let (conn, driver) = connect(transport, ConnectOptions::default());
sp.spawn_local(driver).unwrap();
pool.run_until(async move {
let conn_probe = conn.clone();
let client = async move {
let res = conn
.request(RequestInit {
path: Some("/x".into()),
authority: Some("example.com".into()),
..Default::default()
})
.await;
assert!(res.is_err(), "request should error on a protocol error");
};
let server = async move {
let mut server = ServerSide::new(c2s_rx);
server.read_preface().await;
assert!(matches!(server.next_frame().await, Frame::Settings { .. }));
assert!(matches!(
server.next_frame().await,
Frame::Headers { stream_id: 1, .. }
));
let block = HpackEncoder::new().encode(&[Header::new(":status", "200")]);
s2c_tx
.unbounded_send(serialize_frame(&Frame::Headers {
stream_id: 1,
header_block_fragment: block,
end_stream: false,
end_headers: false,
priority: None,
}))
.unwrap();
s2c_tx
.unbounded_send(serialize_frame(&Frame::Data {
stream_id: 1,
data: b"nope".to_vec(),
end_stream: false,
}))
.unwrap();
assert!(matches!(server.next_frame().await, Frame::Goaway { .. }));
};
futures::join!(client, server);
assert!(conn_probe.is_closed(), "connection should be closed");
});
}