use core::fmt;
use super::error::Http2ParseError;
use super::frame::Http2Frame;
use super::headers::{Http2Continuation, Http2Headers, Http2PushPromise};
use super::types::Http2FrameType;
const END_HEADERS: u8 = 0x04;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Http2HeaderBlockSequence {
locked_stream_id: Option<u32>,
}
impl Http2HeaderBlockSequence {
pub const fn new() -> Self {
Self {
locked_stream_id: None,
}
}
pub const fn is_idle(&self) -> bool {
self.locked_stream_id.is_none()
}
pub const fn is_open(&self) -> bool {
self.locked_stream_id.is_some()
}
pub const fn locked_stream_id(&self) -> Option<u32> {
self.locked_stream_id
}
pub fn observe<'a>(
&mut self,
frame: Http2Frame<'a>,
) -> Result<Option<Http2HeaderBlockFragment<'a>>, Http2HeaderBlockSequenceError> {
if let Some(expected_stream_id) = self.locked_stream_id {
return self.observe_open(frame, expected_stream_id);
}
match frame.frame_type() {
Http2FrameType::HEADERS => {
let headers = Http2Headers::from_frame(frame)
.map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
let end_headers = headers.frame().flags() & END_HEADERS != 0;
let stream_id = headers.frame().stream_id().value();
let fragment = Http2HeaderBlockFragment::Headers(headers);
if !end_headers {
self.locked_stream_id = Some(stream_id);
}
Ok(Some(fragment))
}
Http2FrameType::PUSH_PROMISE => {
let push_promise = Http2PushPromise::from_frame(frame)
.map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
let end_headers = push_promise.frame().flags() & END_HEADERS != 0;
let stream_id = push_promise.frame().stream_id().value();
let fragment = Http2HeaderBlockFragment::PushPromise(push_promise);
if !end_headers {
self.locked_stream_id = Some(stream_id);
}
Ok(Some(fragment))
}
Http2FrameType::CONTINUATION => {
let continuation = Http2Continuation::from_frame(frame)
.map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
Err(Http2HeaderBlockSequenceError::UnexpectedContinuation {
actual_stream_id: continuation.frame().stream_id().value(),
})
}
_ => Ok(None),
}
}
fn observe_open<'a>(
&mut self,
frame: Http2Frame<'a>,
expected_stream_id: u32,
) -> Result<Option<Http2HeaderBlockFragment<'a>>, Http2HeaderBlockSequenceError> {
if frame.frame_type() != Http2FrameType::CONTINUATION {
return Err(Http2HeaderBlockSequenceError::InterleavedFrame {
expected_stream_id,
actual_stream_id: frame.stream_id().value(),
actual_frame_type: frame.frame_type(),
});
}
let continuation = Http2Continuation::from_frame(frame)
.map_err(Http2HeaderBlockSequenceError::MalformedFragment)?;
let actual_stream_id = continuation.frame().stream_id().value();
if actual_stream_id != expected_stream_id {
return Err(Http2HeaderBlockSequenceError::WrongContinuationStream {
expected_stream_id,
actual_stream_id,
});
}
let end_headers = continuation.frame().flags() & END_HEADERS != 0;
let fragment = Http2HeaderBlockFragment::Continuation(continuation);
if end_headers {
self.locked_stream_id = None;
}
Ok(Some(fragment))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http2HeaderBlockFragment<'a> {
Headers(Http2Headers<'a>),
PushPromise(Http2PushPromise<'a>),
Continuation(Http2Continuation<'a>),
}
impl<'a> Http2HeaderBlockFragment<'a> {
pub const fn frame(&self) -> Http2Frame<'a> {
match self {
Self::Headers(fragment) => fragment.frame(),
Self::PushPromise(fragment) => fragment.frame(),
Self::Continuation(fragment) => fragment.frame(),
}
}
pub fn field_block_fragment(&self) -> &'a [u8] {
match self {
Self::Headers(fragment) => fragment.field_block_fragment(),
Self::PushPromise(fragment) => fragment.field_block_fragment(),
Self::Continuation(fragment) => fragment.field_block_fragment(),
}
}
pub fn is_end_headers(&self) -> bool {
self.frame().flags() & END_HEADERS != 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http2HeaderBlockSequenceError {
MalformedFragment(Http2ParseError),
UnexpectedContinuation {
actual_stream_id: u32,
},
WrongContinuationStream {
expected_stream_id: u32,
actual_stream_id: u32,
},
InterleavedFrame {
expected_stream_id: u32,
actual_stream_id: u32,
actual_frame_type: Http2FrameType,
},
}
impl fmt::Display for Http2HeaderBlockSequenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MalformedFragment(error) => {
write!(f, "malformed HTTP/2 header-block fragment: {error}")
}
Self::UnexpectedContinuation { actual_stream_id } => write!(
f,
"unexpected HTTP/2 CONTINUATION on stream {actual_stream_id}"
),
Self::WrongContinuationStream {
expected_stream_id,
actual_stream_id,
} => write!(
f,
"HTTP/2 CONTINUATION stream mismatch: expected {expected_stream_id}, got {actual_stream_id}"
),
Self::InterleavedFrame {
expected_stream_id,
actual_stream_id,
actual_frame_type,
} => write!(
f,
"HTTP/2 header block on stream {expected_stream_id} was interrupted by frame type {} on stream {actual_stream_id}",
actual_frame_type.raw()
),
}
}
}