use super::{client_cancellation::open_get, fixture::*};
use crate::{
Headers, KnownHeaderName, Method, Status,
h2::{
H2ErrorCode, H2Transport,
frame::{Frame, data as data_frame},
},
headers::hpack::PseudoHeaders,
};
use futures_lite::io::AsyncRead;
use std::{
pin::Pin,
task::{Context, Poll},
};
const BODY: &[u8] = &[b'x'; 5000];
fn window_credit(frames: &[Frame], id: u32) -> u64 {
frames
.iter()
.filter_map(|frame| match frame {
Frame::WindowUpdate {
stream_id,
increment,
} if *stream_id == id => Some(u64::from(*increment)),
_ => None,
})
.sum()
}
fn settle(fx: &mut DriverFixture) -> Vec<Frame> {
let mut frames = Vec::new();
for _ in 0..8 {
let _ = fx.tick();
frames.extend(fx.next_outbound_frames());
}
frames
}
fn peer_padded_data(fx: &mut DriverFixture, stream_id: u32, data: &[u8], padding: u8) {
let prefix_len = data_frame::encoded_prefix_len(padding);
let mut frame = vec![0; prefix_len + data.len() + usize::from(padding)];
let data_len = u32::try_from(data.len()).expect("data fits u32");
data_frame::encode_prefix(stream_id, false, data_len, padding, &mut frame)
.expect("encode DATA prefix");
frame[prefix_len..prefix_len + data.len()].copy_from_slice(data);
fx.peer.write_all(&frame);
}
fn read_body(transport: &mut H2Transport, len: usize) -> usize {
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut buf = vec![0; len];
match Pin::new(transport).poll_read(&mut cx, &mut buf) {
Poll::Ready(Ok(read)) => read,
other => panic!("body read: {other:?}"),
}
}
fn client_with_buffered_body(body: &[u8]) -> (DriverFixture, u32, H2Transport) {
let mut fx = DriverFixture::new_client();
fx.complete_handshake_client();
let (id, transport) = open_get(&mut fx);
fx.peer_response_headers(id, Status::Ok, false);
fx.peer_data(id, body, false);
settle(&mut fx);
(fx, id, transport)
}
#[test]
fn padding_is_credited_without_reads() {
let mut fx = DriverFixture::new_server();
fx.complete_handshake();
fx.peer_open_stream(1, Method::Post, "/", false);
let Poll::Ready(Some(Ok(_conn))) = fx.tick() else {
panic!("expected a Conn");
};
settle(&mut fx);
for _ in 0..16 {
peer_padded_data(&mut fx, 1, &[b'x'; 100], 255);
}
let frames = settle(&mut fx);
assert_eq!(window_credit(&frames, 0), 4096, "{frames:?}");
assert_eq!(window_credit(&frames, 1), 4096, "{frames:?}");
}
#[test]
fn late_data_after_client_cancellation_is_credited() {
let mut fx = DriverFixture::new_client();
fx.complete_handshake_client();
let (id, transport) = open_get(&mut fx);
fx.peer_response_headers(id, Status::Ok, false);
settle(&mut fx);
drop(transport);
settle(&mut fx);
fx.peer_data(id, BODY, true);
let frames = settle(&mut fx);
assert_eq!(window_credit(&frames, 0), 5000, "{frames:?}");
}
#[test]
fn unread_response_body_is_credited_when_client_cancels() {
let (mut fx, _, transport) = client_with_buffered_body(BODY);
drop(transport);
let frames = settle(&mut fx);
assert_eq!(window_credit(&frames, 0), 5000, "{frames:?}");
}
#[test]
fn partial_read_then_cancel_credits_each_byte_once() {
for serviced in [true, false] {
let (mut fx, _, mut transport) = client_with_buffered_body(&[BODY, BODY].concat());
assert_eq!(read_body(&mut transport, BODY.len()), BODY.len());
let mut frames = Vec::new();
if serviced {
frames.extend(settle(&mut fx));
}
drop(transport);
frames.extend(settle(&mut fx));
assert_eq!(
window_credit(&frames, 0),
10_000,
"serviced={serviced}: {frames:?}"
);
}
}
#[test]
fn reads_after_peer_reset_add_no_credit() {
let (mut fx, id, mut transport) = client_with_buffered_body(BODY);
fx.peer_rst_stream(id, H2ErrorCode::Cancel);
let frames = settle(&mut fx);
assert_eq!(window_credit(&frames, 0), 5000, "{frames:?}");
let _ = read_body(&mut transport, BODY.len());
let frames = settle(&mut fx);
assert_eq!(window_credit(&frames, 0), 0, "{frames:?}");
}
#[test]
fn unread_request_body_is_credited_when_handler_drops_conn() {
let mut fx = DriverFixture::new_server();
fx.complete_handshake();
fx.peer_open_stream(1, Method::Post, "/", false);
let Poll::Ready(Some(Ok(conn))) = fx.tick() else {
panic!("expected a Conn");
};
fx.peer_data(1, BODY, false);
settle(&mut fx);
drop(conn);
let frames = settle(&mut fx);
assert_eq!(window_credit(&frames, 0), 5000, "{frames:?}");
}
#[test]
fn data_rejected_on_open_stream_is_credited() {
for content_length_mismatch in [false, true] {
let mut fx = DriverFixture::new_client();
fx.complete_handshake_client();
let (id, _transport) = open_get(&mut fx);
let mut headers = Headers::new();
if content_length_mismatch {
headers.insert(KnownHeaderName::ContentLength, "6000");
}
fx.peer_headers(
id,
PseudoHeaders::default().with_status(Status::Ok),
&headers,
false,
);
fx.peer_data(id, BODY, !content_length_mismatch);
settle(&mut fx);
fx.peer_data(id, BODY, false);
let frames = settle(&mut fx);
assert_eq!(
window_credit(&frames, 0),
10_000,
"content_length_mismatch={content_length_mismatch}: {frames:?}"
);
}
}