use bytes::Bytes;
use super::{Http2Config, Http2Error, Http2Event, Http2Parser};
use crate::{FlowSide, ParserKind, Timestamp};
#[derive(Debug, Clone)]
pub struct Http2Session {
inner: Http2Parser,
}
impl Default for Http2Session {
fn default() -> Self {
Self::new()
}
}
impl Http2Session {
pub fn new() -> Self {
Self::with_config(Http2Config::default().with_require_preface(false))
}
pub fn with_config(config: Http2Config) -> Self {
Self {
inner: Http2Parser::with_config(config),
}
}
pub fn parser(&self) -> &Http2Parser {
&self.inner
}
fn feed(&mut self, dir: FlowSide, bytes: &[u8], out: &mut Vec<Http2Event>) {
if bytes.is_empty() {
return;
}
let mut data = Bytes::copy_from_slice(bytes);
loop {
let accepted = self.inner.push(dir, &data);
while let Some(ev) = self.inner.next_event() {
out.push(ev);
}
if accepted == data.len() {
break;
}
if accepted == 0 {
debug_assert!(
self.inner.is_failed() || self.inner.is_finished(dir),
"push refused bytes without reporting a reason",
);
break;
}
data = data.slice(accepted..);
}
}
fn rebuild(&mut self) {
self.inner = Http2Parser::with_config(self.inner.config().clone());
}
}
impl crate::SessionParser for Http2Session {
type Message = Http2Event;
fn feed_initiator(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<Http2Event>) {
self.feed(FlowSide::Initiator, bytes, out);
}
fn feed_responder(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<Http2Event>) {
self.feed(FlowSide::Responder, bytes, out);
}
fn fin_initiator(&mut self, out: &mut Vec<Http2Event>) {
self.inner.fin(FlowSide::Initiator);
while let Some(ev) = self.inner.next_event() {
out.push(ev);
}
}
fn fin_responder(&mut self, out: &mut Vec<Http2Event>) {
self.inner.fin(FlowSide::Responder);
while let Some(ev) = self.inner.next_event() {
out.push(ev);
}
}
fn rst_initiator(&mut self) {
self.rebuild();
}
fn rst_responder(&mut self) {
self.rebuild();
}
fn parser_kind(&self) -> ParserKind {
ParserKind::Http2
}
fn is_poisoned(&self) -> bool {
self.inner.is_failed()
}
fn poison_reason(&self) -> Option<&str> {
self.inner.error().map(Http2Error::as_str)
}
fn is_done(&self) -> bool {
self.inner.is_done()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SessionParser as _;
use crate::http2::PREFACE;
const HEADERS: u8 = 0x1;
const DATA: u8 = 0x0;
const END_HEADERS: u8 = 0x4;
fn frame(kind: u8, flags: u8, stream: u32, payload: &[u8]) -> Vec<u8> {
let mut v = Vec::new();
let len = payload.len() as u32;
v.extend_from_slice(&[(len >> 16) as u8, (len >> 8) as u8, len as u8]);
v.push(kind);
v.push(flags);
v.extend_from_slice(&stream.to_be_bytes());
v.extend_from_slice(payload);
v
}
fn literal(name: &str, value: &str) -> Vec<u8> {
let mut v = vec![0x40, name.len() as u8];
v.extend_from_slice(name.as_bytes());
v.push(value.len() as u8);
v.extend_from_slice(value.as_bytes());
v
}
fn feed(s: &mut Http2Session, dir: FlowSide, bytes: &[u8]) -> Vec<Http2Event> {
let mut out = Vec::new();
match dir {
FlowSide::Initiator => s.feed_initiator(bytes, Timestamp::default(), &mut out),
FlowSide::Responder => s.feed_responder(bytes, Timestamp::default(), &mut out),
}
out
}
#[test]
fn adapter_emits_stream_events_through_the_trait() {
let mut s = Http2Session::new();
let mut block = vec![0x82, 0x87]; block.extend(literal(":authority", "api.example"));
block.extend(literal(":path", "/v1/things"));
let mut wire = PREFACE.to_vec();
wire.extend(frame(HEADERS, END_HEADERS, 1, &block));
let out = feed(&mut s, FlowSide::Initiator, &wire);
let head = out
.iter()
.find_map(|e| match e {
Http2Event::Head(h) => Some(h),
_ => None,
})
.expect("a head");
assert_eq!(head.authority(), Some("api.example"));
assert_eq!(head.path(), Some("/v1/things"));
assert_eq!(s.parser_kind(), ParserKind::Http2);
}
#[test]
fn adapter_joins_a_connection_with_no_preface() {
let mut tolerant = Http2Session::new();
let bare = frame(HEADERS, END_HEADERS, 3, &[0x82]);
let out = feed(&mut tolerant, FlowSide::Initiator, &bare);
assert!(!tolerant.is_poisoned(), "{:?}", tolerant.poison_reason());
assert!(matches!(out.first(), Some(Http2Event::Head(h)) if h.stream_id == 3));
let mut strict = Http2Session::with_config(Http2Config::default());
feed(&mut strict, FlowSide::Initiator, &bare);
assert_eq!(strict.poison_reason(), Some("bad-preface"));
}
#[test]
fn adapter_reports_a_framing_failure_as_poison() {
let mut s = Http2Session::new();
let mut wire = frame(HEADERS, 0, 1, &[0x82]);
wire.extend(frame(DATA, 0, 1, b"x"));
feed(&mut s, FlowSide::Initiator, &wire);
assert!(s.is_poisoned());
assert_eq!(s.poison_reason(), Some("interleaved-continuation"));
}
#[test]
fn adapter_survives_a_feed_larger_than_the_buffer_cap() {
let mut s = Http2Session::with_config(
Http2Config::default()
.with_require_preface(false)
.with_max_buffered_bytes(8 * 1024)
.with_max_frame_size(4 * 1024),
);
let mut wire = frame(HEADERS, END_HEADERS, 1, &[0x82]);
let chunk = vec![b'x'; 1024];
for _ in 0..64 {
wire.extend(frame(DATA, 0, 1, &chunk)); }
let out = feed(&mut s, FlowSide::Initiator, &wire);
let body: usize = out
.iter()
.filter_map(|e| match e {
Http2Event::Body { data, .. } => Some(data.len()),
_ => None,
})
.sum();
assert_eq!(body, 64 * 1024, "no body bytes may be lost");
assert!(!s.is_poisoned());
}
#[test]
fn a_frame_too_large_for_the_buffer_is_reported_not_swallowed() {
let mut s = Http2Session::with_config(
Http2Config::default()
.with_require_preface(false)
.with_max_buffered_bytes(4096),
);
let out = feed(
&mut s,
FlowSide::Initiator,
&frame(DATA, 0, 1, &vec![0u8; 16 * 1024]),
);
assert!(
s.is_poisoned(),
"a parser that can never progress must say so"
);
assert_eq!(s.poison_reason(), Some("frame-too-large"));
assert!(out.is_empty());
}
#[test]
fn adapter_reset_starts_a_clean_connection_and_keeps_its_policy() {
let mut s = Http2Session::new();
let mut bad = frame(HEADERS, 0, 1, &[0x82]);
bad.extend(frame(DATA, 0, 1, b"x"));
feed(&mut s, FlowSide::Initiator, &bad);
assert!(s.is_poisoned());
s.rst_initiator();
assert!(!s.is_poisoned(), "a reset starts a fresh connection");
let out = feed(
&mut s,
FlowSide::Initiator,
&frame(HEADERS, END_HEADERS, 5, &[0x82]),
);
assert!(matches!(out.first(), Some(Http2Event::Head(h)) if h.stream_id == 5));
}
#[test]
fn fin_finishes_the_session_without_poisoning() {
let mut s = Http2Session::new();
feed(
&mut s,
FlowSide::Initiator,
&frame(HEADERS, END_HEADERS, 1, &[0x82]),
);
let mut out = Vec::new();
s.fin_initiator(&mut out);
assert!(!s.is_done(), "the responder side is still live");
s.fin_responder(&mut out);
assert!(s.is_done());
assert!(!s.is_poisoned(), "a clean close is not a parse error");
}
}