mod close;
mod closed_streams;
mod constants;
mod handler_signals;
mod inflow;
mod outbound;
mod recv;
mod send;
#[cfg(test)]
mod tests;
mod types;
use super::{H2Error, connection::H2Connection, role::Role, transport::H2Transport};
use crate::{
Conn, Priority,
headers::hpack::{HpackDecoder, HpackEncoder},
};
use closed_streams::{ClosedReason, ClosedStreams};
use constants::{
INITIAL_CONNECTION_RECV_WINDOW, MAX_BUFFER_SIZE, MAX_DATA_CHUNK_SIZE, MAX_FLOW_CONTROL_WINDOW,
READ_WINDOW,
};
use futures_lite::io::{AsyncRead, AsyncWrite};
use hashbrown::HashMap;
use inflow::Inflow;
use recv::PendingHeaders;
use send::ScheduleEntry;
use std::{
collections::BTreeMap,
future::Future,
io,
pin::Pin,
sync::Arc,
task::{Context, Poll, ready},
time::Instant,
};
use swansong::ShuttingDown;
use types::{
AcceptorConfig, Action, CloseOutcome, DriverState, Next, ReadPhase, StreamEntry, frame_slice,
};
#[derive(Debug)]
pub struct H2Driver<T> {
connection: Arc<H2Connection>,
transport: T,
role: Role,
state: DriverState,
shutting_down: ShuttingDown,
read_buf: Vec<u8>,
read_start: usize,
read_filled: usize,
read_phase: ReadPhase,
write_buf: Vec<u8>,
write_cursor: usize,
write_flush_pending: bool,
hpack: HpackDecoder,
hpack_encoder: HpackEncoder,
streams: BTreeMap<u32, StreamEntry>,
last_peer_stream_id: u32,
stream_priorities: HashMap<u32, Priority>,
send_schedule: Vec<ScheduleEntry>,
sending_streams: Vec<u32>,
signaled_scratch: Vec<u32>,
pending_headers: Option<PendingHeaders>,
close_outcome: Option<CloseOutcome>,
finished: bool,
body_scratch: Vec<u8>,
headers_scratch: Vec<u8>,
connection_send_window: i64,
connection_inflow: Inflow,
closed_streams: ClosedStreams,
pub(super) config: AcceptorConfig,
burst_start: Option<Instant>,
rounds_since_pending: usize,
}
impl<T> H2Driver<T>
where
T: AsyncRead + AsyncWrite + Unpin + Send,
{
pub(super) fn new(connection: Arc<H2Connection>, transport: T, role: Role) -> Self {
let shutting_down = connection.swansong().shutting_down();
let context = connection.context();
let config = AcceptorConfig::from_http_config(context.config());
let hpack_encoder = HpackEncoder::new(
context.observer.clone(),
context.config.dynamic_table_capacity(),
context.config.recent_pairs_size(),
context.config.recent_pairs_auto(),
);
Self {
connection,
transport,
role,
state: DriverState::AwaitingPreface,
shutting_down,
read_buf: Vec::new(),
read_start: 0,
read_filled: 0,
read_phase: ReadPhase::NeedHeader,
write_buf: Vec::new(),
write_cursor: 0,
write_flush_pending: false,
hpack: HpackDecoder::new(config.hpack_table_capacity()),
hpack_encoder,
streams: BTreeMap::new(),
last_peer_stream_id: 0,
stream_priorities: HashMap::new(),
send_schedule: Vec::new(),
sending_streams: Vec::new(),
signaled_scratch: Vec::new(),
pending_headers: None,
close_outcome: None,
finished: false,
body_scratch: vec![0u8; MAX_DATA_CHUNK_SIZE as usize],
headers_scratch: Vec::new(),
connection_send_window: INITIAL_CONNECTION_RECV_WINDOW,
connection_inflow: Inflow::new(INITIAL_CONNECTION_RECV_WINDOW),
closed_streams: ClosedStreams::default(),
config,
burst_start: None,
rounds_since_pending: 0,
}
}
pub fn connection(&self) -> &Arc<H2Connection> {
&self.connection
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Next<'_, T> {
Next { driver: self }
}
pub(super) fn drive(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Conn<H2Transport>, H2Error>>> {
let poll = self.drive_rounds(cx);
if poll.is_pending() {
self.rounds_since_pending = 0;
}
poll
}
#[allow(
clippy::too_many_lines,
reason = "state-machine orchestration; splitting muddies the read-as-a-recipe shape"
)]
fn drive_rounds(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Conn<H2Transport>, H2Error>>> {
if self.finished {
return Poll::Ready(None);
}
while self.rounds_since_pending < self.config.copy_loops_per_yield() {
self.rounds_since_pending += 1;
log::trace!("h2 drive loop number: {}", self.rounds_since_pending);
self.service_handler_signals();
self.advance_outbound_sends(cx);
match self.poll_flush_outbound(cx) {
Poll::Ready(Ok(())) => {}
Poll::Ready(Err(e)) => {
if self.close_outcome.is_none() {
self.close_outcome = Some(CloseOutcome::Io(e));
}
return Poll::Ready(self.finish_with_current_outcome());
}
Poll::Pending => return Poll::Pending,
}
if self.state == DriverState::Closing {
if matches!(self.close_outcome, Some(CloseOutcome::Io(_))) {
return Poll::Ready(self.finish_with_current_outcome());
}
if self.has_active_send_cursors()
|| self.has_open_send_half()
|| self.has_pending_recv()
{
self.log_closing_blockers();
} else {
self.set_state(
DriverState::Drained,
"outbound drained, no in-flight streams",
);
}
}
if self.state == DriverState::Running
&& Pin::new(&mut self.shutting_down).poll(cx).is_ready()
{
self.begin_close(CloseOutcome::Graceful);
continue;
}
match self.state {
DriverState::AwaitingPreface => {
let poll = match self.role {
Role::Server => self.poll_read_preface(cx),
Role::Client => {
self.queue_client_preface();
Poll::Ready(Ok(()))
}
};
match poll {
Poll::Ready(Ok(())) => {
self.set_state(DriverState::NeedsServerSettings, "preface complete");
}
Poll::Ready(Err(e)) => {
self.close_outcome = Some(e);
return Poll::Ready(self.finish_with_current_outcome());
}
Poll::Pending => {
if self.park(cx) {
return Poll::Pending;
}
}
}
}
DriverState::NeedsServerSettings => {
self.queue_settings();
let raise = self
.connection_inflow
.raise_target(i64::from(self.config.initial_connection_window_size()));
if raise > 0 {
self.queue_window_update(0, u32::try_from(raise).unwrap_or(u32::MAX));
}
self.set_state(DriverState::Running, "initial SETTINGS queued");
}
DriverState::Running | DriverState::Closing => match self.poll_advance_read(cx) {
Poll::Ready(Ok(Action::Continue)) => {}
Poll::Ready(Ok(Action::Emit(conn))) => {
if self.state == DriverState::Running {
return Poll::Ready(Some(Ok(conn)));
}
}
Poll::Ready(Ok(Action::Close(outcome))) => {
self.begin_close(outcome);
}
Poll::Ready(Err(e)) => {
if self.state == DriverState::Closing {
self.close_outcome.get_or_insert(e);
return Poll::Ready(self.finish_with_current_outcome());
}
self.begin_close(e);
}
Poll::Pending => {
if self.park(cx) {
return Poll::Pending;
}
}
},
DriverState::Drained => match self.poll_drain_peer(cx) {
Poll::Ready(()) => {
return Poll::Ready(self.finish_with_current_outcome());
}
Poll::Pending => return Poll::Pending,
},
}
}
cx.waker().wake_by_ref();
Poll::Pending
}
fn park(&mut self, cx: &mut Context<'_>) -> bool {
self.connection.outbound_waker().register(cx.waker());
!self.has_pending_handler_signals() && !self.has_pending_outbound_progress()
}
fn set_state(&mut self, new: DriverState, reason: &'static str) {
if self.state == new {
return;
}
log::trace!(
"h2 driver: state {old:?} → {new:?} ({reason})",
old = self.state,
);
self.state = new;
}
fn poll_fill_to(&mut self, target: usize, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
while self.read_filled - self.read_start < target {
if self.read_start > 0 {
self.read_buf
.copy_within(self.read_start..self.read_filled, 0);
self.read_filled -= self.read_start;
self.read_start = 0;
}
let len = target.max(READ_WINDOW);
if self.read_buf.len() < len {
self.read_buf.resize(len, 0);
}
let n = ready!(
Pin::new(&mut self.transport).poll_read(cx, &mut self.read_buf[self.read_filled..])
)?;
if n == 0 {
return Poll::Ready(Err(io::Error::from(io::ErrorKind::UnexpectedEof)));
}
self.read_filled += n;
self.burst_start = None;
}
Poll::Ready(Ok(()))
}
fn consume_read(&mut self, len: usize) {
self.read_start += len;
self.read_phase = ReadPhase::NeedHeader;
if self.read_start == self.read_filled {
self.read_start = 0;
self.read_filled = 0;
if self.read_buf.capacity() > MAX_BUFFER_SIZE / 16 {
self.read_buf = Vec::new();
}
}
}
pub(super) fn closed_reason(&self, stream_id: u32) -> Option<ClosedReason> {
self.closed_streams.reason(stream_id)
}
}