use std::convert::From;
use kawa::repr::Slice;
use nom::{
Err, IResult,
bytes::complete::{tag, take},
combinator::{complete, map},
error::{ErrorKind, ParseError},
multi::many0,
number::complete::{be_u8, be_u16, be_u24, be_u32},
sequence::tuple,
};
use sozu_command::logging::ansi_palette;
macro_rules! log_module_context {
() => {{
let (open, reset, _, _, _) = ansi_palette();
format!("{open}MUX-PARSER{reset}\t >>>", open = open, reset = reset)
}};
}
pub const FRAME_HEADER_SIZE: usize = 9;
pub const STREAM_ID_MASK: u32 = 0x7FFFFFFF;
pub const FLAG_END_STREAM: u8 = 0x1;
pub const FLAG_END_HEADERS: u8 = 0x4;
pub const FLAG_PADDED: u8 = 0x8;
pub const FLAG_PRIORITY: u8 = 0x20;
pub const FLAG_ACK: u8 = 0x1;
pub const PRIORITY_PAYLOAD_SIZE: u32 = 5;
pub const RST_STREAM_PAYLOAD_SIZE: u32 = 4;
pub const SETTINGS_ENTRY_SIZE: u32 = 6;
pub const PING_PAYLOAD_SIZE: u32 = 8;
pub const WINDOW_UPDATE_PAYLOAD_SIZE: u32 = 4;
pub const GOAWAY_PAYLOAD_SIZE: u32 = 8;
pub const SETTINGS_HEADER_TABLE_SIZE: u16 = 1;
pub const SETTINGS_ENABLE_PUSH: u16 = 2;
pub const SETTINGS_MAX_CONCURRENT_STREAMS: u16 = 3;
pub const SETTINGS_INITIAL_WINDOW_SIZE: u16 = 4;
pub const SETTINGS_MAX_FRAME_SIZE: u16 = 5;
pub const SETTINGS_MAX_HEADER_LIST_SIZE: u16 = 6;
pub const SETTINGS_ENABLE_CONNECT_PROTOCOL: u16 = 8;
pub const SETTINGS_NO_RFC7540_PRIORITIES: u16 = 9;
pub const SETTINGS_COUNT: u32 = 8;
#[derive(Clone, Debug, PartialEq)]
pub struct FrameHeader {
pub payload_len: u32,
pub frame_type: FrameType,
pub flags: u8,
pub stream_id: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub enum FrameType {
Data,
Headers,
Priority,
RstStream,
Settings,
PushPromise,
Ping,
GoAway,
WindowUpdate,
Continuation,
PriorityUpdate,
Unknown(u8),
}
impl std::str::FromStr for H2Error {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
"NO_ERROR" => Ok(H2Error::NoError),
"PROTOCOL_ERROR" => Ok(H2Error::ProtocolError),
"INTERNAL_ERROR" => Ok(H2Error::InternalError),
"FLOW_CONTROL_ERROR" => Ok(H2Error::FlowControlError),
"SETTINGS_TIMEOUT" => Ok(H2Error::SettingsTimeout),
"STREAM_CLOSED" => Ok(H2Error::StreamClosed),
"FRAME_SIZE_ERROR" => Ok(H2Error::FrameSizeError),
"REFUSED_STREAM" => Ok(H2Error::RefusedStream),
"CANCEL" => Ok(H2Error::Cancel),
"COMPRESSION_ERROR" => Ok(H2Error::CompressionError),
"CONNECT_ERROR" => Ok(H2Error::ConnectError),
"ENHANCE_YOUR_CALM" => Ok(H2Error::EnhanceYourCalm),
"INADEQUATE_SECURITY" => Ok(H2Error::InadequateSecurity),
"HTTP_1_1_REQUIRED" => Ok(H2Error::HTTP11Required),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ParserError<'a> {
pub input: &'a [u8],
pub kind: ParserErrorKind,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParserErrorKind {
Nom(ErrorKind),
H2(H2Error),
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u32)]
pub enum H2Error {
NoError = 0x0,
ProtocolError = 0x1,
InternalError = 0x2,
FlowControlError = 0x3,
SettingsTimeout = 0x4,
StreamClosed = 0x5,
FrameSizeError = 0x6,
RefusedStream = 0x7,
Cancel = 0x8,
CompressionError = 0x9,
ConnectError = 0xa,
EnhanceYourCalm = 0xb,
InadequateSecurity = 0xc,
HTTP11Required = 0xd,
}
impl TryFrom<u32> for H2Error {
type Error = u32;
fn try_from(code: u32) -> Result<Self, u32> {
match code {
0x0 => Ok(H2Error::NoError),
0x1 => Ok(H2Error::ProtocolError),
0x2 => Ok(H2Error::InternalError),
0x3 => Ok(H2Error::FlowControlError),
0x4 => Ok(H2Error::SettingsTimeout),
0x5 => Ok(H2Error::StreamClosed),
0x6 => Ok(H2Error::FrameSizeError),
0x7 => Ok(H2Error::RefusedStream),
0x8 => Ok(H2Error::Cancel),
0x9 => Ok(H2Error::CompressionError),
0xa => Ok(H2Error::ConnectError),
0xb => Ok(H2Error::EnhanceYourCalm),
0xc => Ok(H2Error::InadequateSecurity),
0xd => Ok(H2Error::HTTP11Required),
other => Err(other),
}
}
}
impl H2Error {
pub const fn as_str(&self) -> &'static str {
match self {
H2Error::NoError => "NO_ERROR",
H2Error::ProtocolError => "PROTOCOL_ERROR",
H2Error::InternalError => "INTERNAL_ERROR",
H2Error::FlowControlError => "FLOW_CONTROL_ERROR",
H2Error::SettingsTimeout => "SETTINGS_TIMEOUT",
H2Error::StreamClosed => "STREAM_CLOSED",
H2Error::FrameSizeError => "FRAME_SIZE_ERROR",
H2Error::RefusedStream => "REFUSED_STREAM",
H2Error::Cancel => "CANCEL",
H2Error::CompressionError => "COMPRESSION_ERROR",
H2Error::ConnectError => "CONNECT_ERROR",
H2Error::EnhanceYourCalm => "ENHANCE_YOUR_CALM",
H2Error::InadequateSecurity => "INADEQUATE_SECURITY",
H2Error::HTTP11Required => "HTTP_1_1_REQUIRED",
}
}
}
impl std::fmt::Display for H2Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl<'a> ParserError<'a> {
pub fn new(input: &'a [u8], error: ParserErrorKind) -> ParserError<'a> {
ParserError { input, kind: error }
}
pub fn new_h2(input: &'a [u8], error: H2Error) -> ParserError<'a> {
ParserError {
input,
kind: ParserErrorKind::H2(error),
}
}
}
impl<'a> ParseError<&'a [u8]> for ParserError<'a> {
fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self {
ParserError {
input,
kind: ParserErrorKind::Nom(kind),
}
}
fn append(input: &'a [u8], kind: ErrorKind, _other: Self) -> Self {
ParserError {
input,
kind: ParserErrorKind::Nom(kind),
}
}
}
impl<'a> From<(&'a [u8], ErrorKind)> for ParserError<'a> {
fn from((input, kind): (&'a [u8], ErrorKind)) -> Self {
ParserError {
input,
kind: ParserErrorKind::Nom(kind),
}
}
}
pub fn preface(i: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")(i)
}
macro_rules! ensure_frame_size {
($input:expr, $cond:expr) => {
if !($cond) {
return Err(Err::Failure(ParserError::new_h2(
$input,
H2Error::FrameSizeError,
)));
}
};
}
pub fn frame_header(input: &[u8], max_frame_size: u32) -> IResult<&[u8], FrameHeader, ParserError> {
let in_len = input.len();
let (i, payload_len) = be_u24(input)?;
if payload_len > max_frame_size {
return Err(Err::Failure(ParserError::new_h2(
i,
H2Error::FrameSizeError,
)));
}
let (i, t) = be_u8(i)?;
let frame_type = convert_frame_type(t);
let (i, flags) = be_u8(i)?;
let (i, raw_stream_id) = be_u32(i)?;
let stream_id = raw_stream_id & STREAM_ID_MASK;
debug_assert!(i.len() <= in_len, "parser must not grow its input");
debug_assert_eq!(
i.len(),
in_len - FRAME_HEADER_SIZE,
"frame_header must consume exactly the 9-byte header"
);
debug_assert!(
payload_len <= max_frame_size,
"frame_header must enforce the size bound it was given"
);
debug_assert_eq!(
stream_id & !STREAM_ID_MASK,
0,
"reserved high bit must be masked off the stream id"
);
let valid_stream_id = match frame_type {
FrameType::Data
| FrameType::Headers
| FrameType::Priority
| FrameType::RstStream
| FrameType::PushPromise
| FrameType::Continuation => stream_id != 0,
FrameType::Settings | FrameType::Ping | FrameType::GoAway | FrameType::PriorityUpdate => {
stream_id == 0
}
FrameType::WindowUpdate | FrameType::Unknown(_) => true,
};
if !valid_stream_id {
error!("{} invalid stream_id: {}", log_module_context!(), stream_id);
return Err(Err::Failure(ParserError::new_h2(i, H2Error::ProtocolError)));
}
Ok((
i,
FrameHeader {
payload_len,
frame_type,
flags,
stream_id,
},
))
}
fn convert_frame_type(t: u8) -> FrameType {
trace!("{} got frame type: {}", log_module_context!(), t);
match t {
0 => FrameType::Data,
1 => FrameType::Headers,
2 => FrameType::Priority,
3 => FrameType::RstStream,
4 => FrameType::Settings,
5 => FrameType::PushPromise,
6 => FrameType::Ping,
7 => FrameType::GoAway,
8 => FrameType::WindowUpdate,
9 => FrameType::Continuation,
0x10 => FrameType::PriorityUpdate,
other => FrameType::Unknown(other),
}
}
#[derive(Clone, Debug)]
pub enum Frame {
Data(Data),
Headers(Headers),
Priority(Priority),
RstStream(RstStream),
Settings(Settings),
PushPromise(PushPromise),
Ping(Ping),
GoAway(GoAway),
WindowUpdate(WindowUpdate),
Continuation(Continuation),
PriorityUpdate(PriorityUpdate),
Unknown(u8),
}
#[derive(Clone, Debug, PartialEq)]
pub struct PriorityUpdate {
pub prioritized_stream_id: u32,
pub priority_field_value: Vec<u8>,
}
pub fn frame_body<'a>(
i: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = i.len();
let f = match header.frame_type {
FrameType::Data => data_frame(i, header)?,
FrameType::Headers => headers_frame(i, header)?,
FrameType::Priority => {
ensure_frame_size!(i, header.payload_len == PRIORITY_PAYLOAD_SIZE);
priority_frame(i, header)?
}
FrameType::RstStream => {
ensure_frame_size!(i, header.payload_len == RST_STREAM_PAYLOAD_SIZE);
rst_stream_frame(i, header)?
}
FrameType::PushPromise => push_promise_frame(i, header)?,
FrameType::Continuation => continuation_frame(i, header)?,
FrameType::Settings => {
ensure_frame_size!(
i,
!(header.flags & FLAG_ACK != 0 && header.payload_len != 0)
);
ensure_frame_size!(i, header.payload_len % SETTINGS_ENTRY_SIZE == 0);
settings_frame(i, header)?
}
FrameType::Ping => {
ensure_frame_size!(i, header.payload_len == PING_PAYLOAD_SIZE);
ping_frame(i, header)?
}
FrameType::GoAway => {
ensure_frame_size!(i, header.payload_len >= GOAWAY_PAYLOAD_SIZE);
goaway_frame(i, header)?
}
FrameType::WindowUpdate => {
ensure_frame_size!(i, header.payload_len == WINDOW_UPDATE_PAYLOAD_SIZE);
window_update_frame(i, header)?
}
FrameType::PriorityUpdate => priority_update_frame(i, header)?,
FrameType::Unknown(_) => unknown_frame(i, header)?,
};
debug_assert!(
f.0.len() <= in_len,
"frame body parser must not grow its input"
);
debug_assert!(
f.0.len() <= in_len.saturating_sub(header.payload_len as usize),
"frame body must consume at least the declared payload_len bytes"
);
Ok(f)
}
#[derive(Clone, Debug)]
pub struct Data {
pub stream_id: u32,
pub payload: Slice,
pub end_stream: bool,
}
fn strip_padding<'a>(
i: &'a [u8],
flags: u8,
error_input: &'a [u8],
) -> IResult<&'a [u8], u8, ParserError<'a>> {
let in_len = i.len();
let (i, pad_length) = if flags & FLAG_PADDED != 0 {
let (i, pad_length) = be_u8(i)?;
(i, pad_length)
} else {
(i, 0)
};
debug_assert_eq!(
i.len(),
in_len - (flags & FLAG_PADDED != 0) as usize,
"strip_padding consumes the pad-length byte iff PADDED is set"
);
if (pad_length as usize) > i.len() {
return Err(Err::Failure(ParserError::new_h2(
error_input,
H2Error::ProtocolError,
)));
}
debug_assert!(
pad_length as usize <= i.len(),
"strip_padding must reject pad_length larger than the remaining payload"
);
Ok((i, pad_length))
}
fn unpad<'a>(
i: &'a [u8],
pad_length: u8,
error_input: &'a [u8],
) -> Result<&'a [u8], Err<ParserError<'a>>> {
let in_len = i.len();
let content_len = i
.len()
.checked_sub(pad_length as usize)
.ok_or_else(|| Err::Failure(ParserError::new_h2(error_input, H2Error::ProtocolError)))?;
debug_assert!(
content_len <= in_len,
"unpad must not grow the content slice"
);
debug_assert_eq!(
content_len + pad_length as usize,
in_len,
"content and padding must exactly partition the input"
);
Ok(&i[..content_len])
}
pub fn data_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (remaining, i) = take(header.payload_len)(input)?;
let (i, pad_length) = strip_padding(i, header.flags, input)?;
let payload = unpad(i, pad_length, input)?;
debug_assert_eq!(
remaining.len(),
in_len - header.payload_len as usize,
"data_frame must consume exactly payload_len bytes"
);
debug_assert!(
payload.len() <= header.payload_len as usize,
"data payload must fit within the declared frame payload"
);
debug_assert!(
pad_length as usize <= header.payload_len as usize,
"padding must not exceed the frame payload"
);
Ok((
remaining,
Frame::Data(Data {
stream_id: header.stream_id,
payload: Slice::new(input, payload),
end_stream: header.flags & FLAG_END_STREAM != 0,
}),
))
}
#[derive(Clone, Debug)]
pub struct Headers {
pub stream_id: u32,
pub priority: Option<PriorityPart>,
pub header_block_fragment: Slice,
pub end_stream: bool,
pub end_headers: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct StreamDependency {
pub exclusive: bool,
pub stream_id: u32,
}
fn stream_dependency(i: &[u8]) -> IResult<&[u8], StreamDependency, ParserError<'_>> {
let (i, stream) = map(be_u32, |i| StreamDependency {
exclusive: i & 0x80000000 != 0,
stream_id: i & STREAM_ID_MASK,
})(i)?;
Ok((i, stream))
}
pub fn headers_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (remaining, i) = take(header.payload_len)(input)?;
let (i, pad_length) = strip_padding(i, header.flags, input)?;
let (i, priority) = if header.flags & FLAG_PRIORITY != 0 {
let (i, stream_dependency) = stream_dependency(i)?;
let (i, weight) = be_u8(i)?;
(
i,
Some(PriorityPart::Rfc7540 {
stream_dependency,
weight,
}),
)
} else {
(i, None)
};
let header_block_fragment = unpad(i, pad_length, input)?;
debug_assert_eq!(
remaining.len(),
in_len - header.payload_len as usize,
"headers_frame must consume exactly payload_len bytes"
);
debug_assert!(
header_block_fragment.len() <= header.payload_len as usize,
"header block fragment must fit within the declared frame payload"
);
Ok((
remaining,
Frame::Headers(Headers {
stream_id: header.stream_id,
priority,
header_block_fragment: Slice::new(input, header_block_fragment),
end_stream: header.flags & FLAG_END_STREAM != 0,
end_headers: header.flags & FLAG_END_HEADERS != 0,
}),
))
}
#[derive(Clone, Debug, PartialEq)]
pub enum PriorityPart {
Rfc7540 {
stream_dependency: StreamDependency,
weight: u8,
},
Rfc9218 {
urgency: u8, incremental: bool,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct Priority {
pub stream_id: u32,
pub inner: PriorityPart,
}
pub fn priority_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (i, data) = take(header.payload_len)(input)?;
debug_assert_eq!(
i.len(),
in_len - header.payload_len as usize,
"priority_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
data.len(),
PRIORITY_PAYLOAD_SIZE as usize,
"PRIORITY payload must be exactly 5 bytes"
);
let (_, (stream_dependency, weight)) = tuple((stream_dependency, be_u8))(data)?;
Ok((
i,
Frame::Priority(Priority {
stream_id: header.stream_id,
inner: PriorityPart::Rfc7540 {
stream_dependency,
weight,
},
}),
))
}
#[derive(Clone, Debug, PartialEq)]
pub struct RstStream {
pub stream_id: u32,
pub error_code: u32,
}
pub fn rst_stream_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (i, data) = take(header.payload_len)(input)?;
debug_assert_eq!(
i.len(),
in_len - header.payload_len as usize,
"rst_stream_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
data.len(),
RST_STREAM_PAYLOAD_SIZE as usize,
"RST_STREAM payload must be exactly 4 bytes"
);
let (_, error_code) = be_u32(data)?;
Ok((
i,
Frame::RstStream(RstStream {
stream_id: header.stream_id,
error_code,
}),
))
}
#[derive(Clone, Debug, PartialEq)]
pub struct Settings {
pub settings: Vec<Setting>,
pub ack: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Setting {
pub identifier: u16,
pub value: u32,
}
pub const MAX_SETTINGS_ENTRIES: usize = 64;
pub fn settings_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
if header.payload_len / SETTINGS_ENTRY_SIZE > MAX_SETTINGS_ENTRIES as u32 {
return Err(Err::Failure(ParserError::new_h2(
input,
H2Error::FrameSizeError,
)));
}
let in_len = input.len();
let (i, data) = take(header.payload_len)(input)?;
let (_, settings) = many0(map(
complete(tuple((be_u16, be_u32))),
|(identifier, value)| Setting { identifier, value },
))(data)?;
debug_assert_eq!(
i.len(),
in_len - header.payload_len as usize,
"settings_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
settings.len(),
header.payload_len as usize / SETTINGS_ENTRY_SIZE as usize,
"one SETTINGS entry decodes per 6 payload bytes"
);
debug_assert!(
settings.len() <= MAX_SETTINGS_ENTRIES,
"settings_frame must honour the MAX_SETTINGS_ENTRIES allocation cap"
);
Ok((
i,
Frame::Settings(Settings {
settings,
ack: header.flags & FLAG_ACK != 0,
}),
))
}
#[derive(Clone, Debug)]
pub struct PushPromise;
pub fn push_promise_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let (_remaining, _payload) = take(header.payload_len)(input)?;
Err(Err::Failure(ParserError::new_h2(
input,
H2Error::ProtocolError,
)))
}
#[derive(Clone, Debug, PartialEq)]
pub struct Ping {
pub payload: [u8; 8],
pub ack: bool,
}
pub fn ping_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (i, data) = take(header.payload_len)(input)?;
debug_assert_eq!(
i.len(),
in_len - header.payload_len as usize,
"ping_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
data.len(),
PING_PAYLOAD_SIZE as usize,
"PING payload must be exactly 8 bytes for the opaque-data copy"
);
let mut p = Ping {
payload: [0; 8],
ack: header.flags & FLAG_ACK != 0,
};
p.payload[..8].copy_from_slice(&data[..8]);
Ok((i, Frame::Ping(p)))
}
#[derive(Clone, Debug)]
pub struct GoAway {
pub last_stream_id: u32,
pub error_code: u32,
pub additional_debug_data: Slice,
}
pub fn goaway_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (remaining, i) = take(header.payload_len)(input)?;
let (i, raw_last_stream_id) = be_u32(i)?;
let last_stream_id = raw_last_stream_id & STREAM_ID_MASK;
let (additional_debug_data, error_code) = be_u32(i)?;
debug_assert_eq!(
remaining.len(),
in_len - header.payload_len as usize,
"goaway_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
additional_debug_data.len(),
header.payload_len as usize - GOAWAY_PAYLOAD_SIZE as usize,
"GOAWAY debug data is payload_len minus the 8-byte fixed prefix"
);
debug_assert_eq!(
last_stream_id & !STREAM_ID_MASK,
0,
"GOAWAY last_stream_id reserved high bit must be masked"
);
Ok((
remaining,
Frame::GoAway(GoAway {
last_stream_id,
error_code,
additional_debug_data: Slice::new(input, additional_debug_data),
}),
))
}
#[derive(Clone, Debug, PartialEq)]
pub struct WindowUpdate {
pub stream_id: u32,
pub increment: u32,
}
pub fn window_update_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (i, data) = take(header.payload_len)(input)?;
debug_assert_eq!(
i.len(),
in_len - header.payload_len as usize,
"window_update_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
data.len(),
WINDOW_UPDATE_PAYLOAD_SIZE as usize,
"WINDOW_UPDATE payload must be exactly 4 bytes"
);
let (_, increment) = be_u32(data)?;
let increment = increment & STREAM_ID_MASK;
debug_assert!(
increment <= STREAM_ID_MASK,
"window-size increment must be a 31-bit value"
);
Ok((
i,
Frame::WindowUpdate(WindowUpdate {
stream_id: header.stream_id,
increment,
}),
))
}
#[derive(Clone, Debug)]
pub struct Continuation;
pub fn continuation_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (remaining, _) = take(header.payload_len)(input)?;
debug_assert_eq!(
remaining.len(),
in_len - header.payload_len as usize,
"continuation_frame must consume exactly payload_len bytes"
);
Ok((remaining, Frame::Continuation(Continuation)))
}
pub fn unknown_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
let in_len = input.len();
let (remaining, _payload) = take(header.payload_len)(input)?;
debug_assert_eq!(
remaining.len(),
in_len - header.payload_len as usize,
"unknown_frame must consume exactly payload_len bytes"
);
let raw = match header.frame_type {
FrameType::Unknown(t) => t,
_ => 0,
};
Ok((remaining, Frame::Unknown(raw)))
}
pub fn priority_update_frame<'a>(
input: &'a [u8],
header: &FrameHeader,
) -> IResult<&'a [u8], Frame, ParserError<'a>> {
if header.payload_len < PRIORITY_UPDATE_MIN_PAYLOAD {
return Err(Err::Failure(ParserError::new_h2(
input,
H2Error::FrameSizeError,
)));
}
let value_len = (header.payload_len - PRIORITY_UPDATE_MIN_PAYLOAD) as usize;
if value_len > PRIORITY_UPDATE_MAX_VALUE {
return Err(Err::Failure(ParserError::new_h2(
input,
H2Error::ProtocolError,
)));
}
let in_len = input.len();
let (remaining, payload) = take(header.payload_len)(input)?;
let (raw_id_bytes, value_bytes) = payload.split_at(PRIORITY_UPDATE_MIN_PAYLOAD as usize);
debug_assert_eq!(
remaining.len(),
in_len - header.payload_len as usize,
"priority_update_frame must consume exactly payload_len bytes"
);
debug_assert_eq!(
raw_id_bytes.len(),
PRIORITY_UPDATE_MIN_PAYLOAD as usize,
"PRIORITY_UPDATE stream-id prefix must be exactly 4 bytes"
);
debug_assert!(
value_bytes.len() <= PRIORITY_UPDATE_MAX_VALUE,
"priority field value must respect the PRIORITY_UPDATE_MAX_VALUE cap"
);
let prioritized_stream_id = u32::from_be_bytes([
raw_id_bytes[0],
raw_id_bytes[1],
raw_id_bytes[2],
raw_id_bytes[3],
]) & STREAM_ID_MASK;
debug_assert_eq!(
prioritized_stream_id & !STREAM_ID_MASK,
0,
"prioritized_stream_id reserved high bit must be masked"
);
Ok((
remaining,
Frame::PriorityUpdate(PriorityUpdate {
prioritized_stream_id,
priority_field_value: value_bytes.to_vec(),
}),
))
}
pub const PRIORITY_UPDATE_MIN_PAYLOAD: u32 = 4;
pub const PRIORITY_UPDATE_MAX_VALUE: usize = 1024;
#[cfg(test)]
mod tests {
use super::*;
const DEFAULT_MAX_FRAME_SIZE: u32 = 1 << 14;
#[test]
fn test_settings_ack_with_payload_rejected() {
let input = [
0x00, 0x00, 0x06, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x64,
];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::Settings);
assert_eq!(header.flags & FLAG_ACK, FLAG_ACK);
assert_eq!(header.payload_len, 6);
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"SETTINGS ACK with non-empty payload must be rejected"
);
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected Failure(FrameSizeError), got {other:?}"),
}
}
#[test]
fn test_settings_ack_empty_accepted() {
let input = [
0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::Settings);
assert_eq!(header.flags & FLAG_ACK, FLAG_ACK);
let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
match frame {
Frame::Settings(settings) => {
assert!(settings.ack, "ACK flag must be set");
assert!(
settings.settings.is_empty(),
"should have no settings entries"
);
}
other => panic!("expected Frame::Settings, got {other:?}"),
}
}
#[test]
fn test_window_update_max_increment() {
let input = [
0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7F, 0xFF, 0xFF, 0xFF, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::WindowUpdate);
assert_eq!(header.stream_id, 1);
let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
match frame {
Frame::WindowUpdate(wu) => {
assert_eq!(wu.increment, 0x7FFFFFFF);
assert_eq!(wu.stream_id, 1);
}
other => panic!("expected Frame::WindowUpdate, got {other:?}"),
}
}
#[test]
fn test_window_update_zero_increment_parsed() {
let input = [
0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::WindowUpdate);
let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
match frame {
Frame::WindowUpdate(wu) => {
assert_eq!(wu.stream_id, 0);
assert_eq!(wu.increment, 0);
}
other => panic!("expected Frame::WindowUpdate, got {other:?}"),
}
let input2 = [
0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, ];
let (remaining2, header2) =
frame_header(&input2, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, frame2) =
frame_body(remaining2, &header2).expect("second frame body parses cleanly");
match frame2 {
Frame::WindowUpdate(wu) => {
assert_eq!(wu.stream_id, 3);
assert_eq!(wu.increment, 0);
}
other => panic!("expected Frame::WindowUpdate, got {other:?}"),
}
}
#[test]
fn test_unknown_frame_type_is_ignored() {
let input = [
0x00, 0x00, 0x04, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, ];
let (remaining, header) = frame_header(&input, DEFAULT_MAX_FRAME_SIZE)
.expect("unknown frame header must parse cleanly");
assert!(matches!(header.frame_type, FrameType::Unknown(0xFF)));
assert_eq!(header.payload_len, 4);
let (after, frame) =
frame_body(remaining, &header).expect("unknown frame body must be consumed");
assert!(after.is_empty(), "payload bytes must be consumed");
match frame {
Frame::Unknown(0xFF) => {}
other => panic!("expected Frame::Unknown(0xFF), got {other:?}"),
}
}
#[test]
fn test_priority_update_empty_field_parses() {
let input = [
0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let (remaining, header) = frame_header(&input, DEFAULT_MAX_FRAME_SIZE)
.expect("PRIORITY_UPDATE header must parse");
assert!(matches!(header.frame_type, FrameType::PriorityUpdate));
let (_, frame) = frame_body(remaining, &header).expect("body must be consumed");
match frame {
Frame::PriorityUpdate(PriorityUpdate {
prioritized_stream_id,
ref priority_field_value,
}) => {
assert_eq!(prioritized_stream_id, 1);
assert!(priority_field_value.is_empty());
}
other => panic!("expected Frame::PriorityUpdate, got {other:?}"),
}
}
#[test]
fn test_priority_update_with_priority_field_parses() {
let value = b"u=0, i";
let mut input = vec![
0x00,
0x00,
0x04 + value.len() as u8, 0x10, 0x00, 0x00,
0x00,
0x00,
0x00, 0x00,
0x00,
0x00,
0x05, ];
input.extend_from_slice(value);
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("header parses");
assert!(matches!(header.frame_type, FrameType::PriorityUpdate));
let (_, frame) = frame_body(remaining, &header).expect("body parses");
match frame {
Frame::PriorityUpdate(PriorityUpdate {
prioritized_stream_id,
ref priority_field_value,
}) => {
assert_eq!(prioritized_stream_id, 5);
assert_eq!(priority_field_value.as_slice(), value);
}
other => panic!("expected Frame::PriorityUpdate, got {other:?}"),
}
}
#[test]
fn test_priority_update_payload_below_min_is_frame_size_error() {
let input = [
0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("header parses");
let result = frame_body(remaining, &header);
match result {
Err(Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected FRAME_SIZE_ERROR, got {other:?}"),
}
}
#[test]
fn test_priority_update_oversized_value_is_protocol_error() {
let payload_len = (PRIORITY_UPDATE_MIN_PAYLOAD as usize) + PRIORITY_UPDATE_MAX_VALUE + 1;
let mut input = Vec::with_capacity(9 + payload_len);
input.push(((payload_len >> 16) & 0xff) as u8);
input.push(((payload_len >> 8) & 0xff) as u8);
input.push((payload_len & 0xff) as u8);
input.push(0x10); input.push(0x00); input.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); input.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); input.extend(std::iter::repeat_n(b'a', PRIORITY_UPDATE_MAX_VALUE + 1));
let (remaining, header) =
frame_header(&input, payload_len as u32 + 1).expect("header parses");
match frame_body(remaining, &header) {
Err(Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
}
other => {
panic!("expected PROTOCOL_ERROR for oversized PRIORITY_UPDATE value, got {other:?}")
}
}
}
#[test]
fn test_priority_update_at_max_value_accepted() {
let payload_len = (PRIORITY_UPDATE_MIN_PAYLOAD as usize) + PRIORITY_UPDATE_MAX_VALUE;
let mut input = Vec::with_capacity(9 + payload_len);
input.push(((payload_len >> 16) & 0xff) as u8);
input.push(((payload_len >> 8) & 0xff) as u8);
input.push((payload_len & 0xff) as u8);
input.push(0x10);
input.push(0x00);
input.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
input.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
input.extend(std::iter::repeat_n(b'a', PRIORITY_UPDATE_MAX_VALUE));
let (remaining, header) =
frame_header(&input, payload_len as u32 + 1).expect("header parses");
match frame_body(remaining, &header) {
Ok((_, Frame::PriorityUpdate(pu))) => {
assert_eq!(pu.priority_field_value.len(), PRIORITY_UPDATE_MAX_VALUE);
}
other => panic!("expected Frame::PriorityUpdate at the cap boundary, got {other:?}"),
}
}
#[test]
fn test_priority_update_on_non_zero_stream_is_protocol_error() {
let input = [
0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, ];
match frame_header(&input, DEFAULT_MAX_FRAME_SIZE) {
Err(Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
}
other => panic!("expected PROTOCOL_ERROR, got {other:?}"),
}
}
#[test]
fn test_settings_payload_not_multiple_of_6_rejected() {
let input = [
0x00, 0x00, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::Settings);
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"odd-size SETTINGS payload must be rejected"
);
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected Failure(FrameSizeError), got {other:?}"),
}
}
#[test]
fn test_settings_frame_over_cap_rejected() {
let n_entries: u16 = (MAX_SETTINGS_ENTRIES as u16) + 1;
let payload_len = u32::from(n_entries) * SETTINGS_ENTRY_SIZE;
let mut input = Vec::with_capacity(9 + payload_len as usize);
input.extend_from_slice(&payload_len.to_be_bytes()[1..]); input.push(0x04); input.push(0x00); input.extend_from_slice(&[0, 0, 0, 0]); for i in 0..n_entries {
input.extend_from_slice(&i.to_be_bytes()); input.extend_from_slice(&0u32.to_be_bytes()); }
let (remaining, header) =
frame_header(&input, 16_777_215).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::Settings);
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"SETTINGS frame over MAX_SETTINGS_ENTRIES must be rejected"
);
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected Failure(FrameSizeError), got {other:?}"),
}
}
#[test]
fn test_settings_frame_at_cap_accepted() {
let n_entries: u16 = MAX_SETTINGS_ENTRIES as u16;
let payload_len = u32::from(n_entries) * SETTINGS_ENTRY_SIZE;
let mut input = Vec::with_capacity(9 + payload_len as usize);
input.extend_from_slice(&payload_len.to_be_bytes()[1..]);
input.push(0x04);
input.push(0x00);
input.extend_from_slice(&[0, 0, 0, 0]);
for _ in 0..n_entries {
input.extend_from_slice(&0u16.to_be_bytes());
input.extend_from_slice(&0u32.to_be_bytes());
}
let (remaining, header) =
frame_header(&input, 16_777_215).expect("frame header parses cleanly");
let result = frame_body(remaining, &header);
assert!(result.is_ok(), "exactly MAX_SETTINGS_ENTRIES must parse");
}
#[test]
fn test_rst_stream_wrong_payload_size_rejected() {
let input = [
0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::RstStream);
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"wrong RST_STREAM payload size must be rejected"
);
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected Failure(FrameSizeError), got {other:?}"),
}
}
#[test]
fn test_ping_wrong_payload_size_rejected() {
let input = [
0x00, 0x00, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::Ping);
let result = frame_body(remaining, &header);
assert!(result.is_err(), "wrong PING payload size must be rejected");
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected Failure(FrameSizeError), got {other:?}"),
}
}
#[test]
fn test_frame_exceeding_max_frame_size_rejected() {
let input = [
0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
let result = frame_header(&input, DEFAULT_MAX_FRAME_SIZE);
assert!(result.is_err(), "oversized frame must be rejected");
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected Failure(FrameSizeError), got {other:?}"),
}
}
#[test]
fn test_settings_on_nonzero_stream_rejected() {
let input = [
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, ];
let result = frame_header(&input, DEFAULT_MAX_FRAME_SIZE);
assert!(
result.is_err(),
"SETTINGS on non-zero stream must be rejected"
);
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
}
other => panic!("expected Failure(ProtocolError), got {other:?}"),
}
}
#[test]
fn test_data_on_stream_zero_rejected() {
let input = [
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCA, 0xFE,
];
let result = frame_header(&input, DEFAULT_MAX_FRAME_SIZE);
assert!(result.is_err(), "DATA on stream 0 must be rejected");
match result {
Err(nom::Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::ProtocolError));
}
other => panic!("expected Failure(ProtocolError), got {other:?}"),
}
}
#[test]
fn test_window_update_reserved_bit_masked() {
let input = [
0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, frame) = frame_body(remaining, &header).expect("frame body parses cleanly");
match frame {
Frame::WindowUpdate(wu) => {
assert_eq!(
wu.increment, 1,
"reserved bit must be masked to yield increment=1"
);
}
other => panic!("expected Frame::WindowUpdate, got {other:?}"),
}
}
fn build_frame_header(payload_len: u32, frame_type: u8, flags: u8, stream_id: u32) -> Vec<u8> {
vec![
((payload_len >> 16) & 0xFF) as u8,
((payload_len >> 8) & 0xFF) as u8,
(payload_len & 0xFF) as u8,
frame_type,
flags,
((stream_id >> 24) & 0xFF) as u8,
((stream_id >> 16) & 0xFF) as u8,
((stream_id >> 8) & 0xFF) as u8,
(stream_id & 0xFF) as u8,
]
}
fn build_raw_frame(
payload_len: u32,
frame_type: u8,
flags: u8,
stream_id: u32,
payload: &[u8],
) -> Vec<u8> {
let mut raw = build_frame_header(payload_len, frame_type, flags, stream_id);
raw.extend_from_slice(payload);
raw
}
#[test]
fn test_preface_valid() {
let input = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
let (remaining, matched) = preface(input).expect("should parse preface");
assert!(remaining.is_empty());
assert_eq!(matched, input.as_slice());
}
#[test]
fn test_preface_invalid() {
let input = b"GET / HTTP/1.1\r\n";
let result = preface(input);
assert!(result.is_err(), "invalid preface should fail");
}
#[test]
fn test_preface_with_trailing_data() {
let mut input = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".to_vec();
input.extend_from_slice(b"extra stuff");
let (remaining, _) = preface(&input).expect("should parse preface");
assert_eq!(remaining, b"extra stuff");
}
#[test]
fn test_frame_header_settings_basic() {
let raw = build_frame_header(0, 4, 0, 0);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert!(remaining.is_empty());
assert_eq!(header.payload_len, 0);
assert_eq!(header.frame_type, FrameType::Settings);
assert_eq!(header.flags, 0);
assert_eq!(header.stream_id, 0);
}
#[test]
fn test_frame_header_data_basic() {
let raw = build_frame_header(100, 0, 1, 1);
let (_, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.payload_len, 100);
assert_eq!(header.frame_type, FrameType::Data);
assert_eq!(header.flags, 1);
assert_eq!(header.stream_id, 1);
}
#[test]
fn test_frame_header_stream_id_reserved_bit_masked() {
let raw = build_frame_header(5, 0, 0, 0x80000001);
let (_, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.stream_id, 1, "reserved MSB must be masked off");
}
#[test]
fn test_parse_data_frame_end_stream() {
let payload = b"hello";
let raw = build_raw_frame(payload.len() as u32, 0, 0x01, 1, payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Data(d) => {
assert_eq!(d.stream_id, 1);
assert!(d.end_stream);
}
other => panic!("expected Data, got {other:?}"),
}
}
#[test]
fn test_parse_data_frame_no_end_stream() {
let payload = b"data";
let raw = build_raw_frame(payload.len() as u32, 0, 0x00, 3, payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Data(d) => {
assert_eq!(d.stream_id, 3);
assert!(!d.end_stream);
}
other => panic!("expected Data, got {other:?}"),
}
}
#[test]
fn test_parse_data_frame_with_padding() {
let pad_length: u8 = 2;
let actual_data = b"hello";
let total_payload = 1 + actual_data.len() + pad_length as usize;
let mut payload = Vec::new();
payload.push(pad_length);
payload.extend_from_slice(actual_data);
payload.extend_from_slice(&[0x00; 2]);
let raw = build_raw_frame(total_payload as u32, 0, 0x08, 1, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Data(d) => {
assert_eq!(d.stream_id, 1);
assert_eq!(d.payload.len, actual_data.len() as u32);
}
other => panic!("expected Data, got {other:?}"),
}
}
#[test]
fn test_parse_data_frame_padding_exceeds_payload() {
let mut payload = Vec::new();
payload.push(10); payload.extend_from_slice(b"hello");
let raw = build_raw_frame(payload.len() as u32, 0, 0x08, 1, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"padding exceeding payload should be a protocol error"
);
}
#[test]
fn test_parse_headers_frame_basic() {
let hblock = b"\x82\x86";
let raw = build_raw_frame(hblock.len() as u32, 1, 0x04, 1, hblock);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Headers(h) => {
assert_eq!(h.stream_id, 1);
assert!(!h.end_stream);
assert!(h.end_headers);
assert!(h.priority.is_none());
}
other => panic!("expected Headers, got {other:?}"),
}
}
#[test]
fn test_parse_headers_frame_end_stream_and_headers() {
let hblock = b"\x82";
let raw = build_raw_frame(hblock.len() as u32, 1, 0x05, 1, hblock);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Headers(h) => {
assert!(h.end_stream);
assert!(h.end_headers);
}
other => panic!("expected Headers, got {other:?}"),
}
}
#[test]
fn test_parse_headers_frame_with_priority() {
let hblock = b"\x82";
let payload_len = 5 + hblock.len(); let mut payload = Vec::new();
payload.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
payload.push(15); payload.extend_from_slice(hblock);
let raw = build_raw_frame(payload_len as u32, 1, 0x24, 3, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Headers(h) => {
assert_eq!(h.stream_id, 3);
let priority = h.priority.expect("should have priority");
match priority {
PriorityPart::Rfc7540 {
stream_dependency,
weight,
} => {
assert!(!stream_dependency.exclusive);
assert_eq!(stream_dependency.stream_id, 1);
assert_eq!(weight, 15);
}
other => panic!("expected Rfc7540, got {other:?}"),
}
}
other => panic!("expected Headers, got {other:?}"),
}
}
#[test]
fn test_parse_headers_frame_with_exclusive_priority() {
let hblock = b"\x82\x86";
let mut payload = Vec::new();
let dep = 0x80000005u32;
payload.extend_from_slice(&dep.to_be_bytes());
payload.push(255); payload.extend_from_slice(hblock);
let payload_len = payload.len();
let raw = build_raw_frame(payload_len as u32, 1, 0x24, 3, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Headers(h) => {
let priority = h.priority.expect("should have priority");
match priority {
PriorityPart::Rfc7540 {
stream_dependency,
weight,
} => {
assert!(stream_dependency.exclusive, "exclusive bit should be set");
assert_eq!(stream_dependency.stream_id, 5);
assert_eq!(weight, 255);
}
other => panic!("expected Rfc7540, got {other:?}"),
}
}
other => panic!("expected Headers, got {other:?}"),
}
}
#[test]
fn test_parse_headers_stream_id_zero_rejected() {
let raw = build_raw_frame(2, 1, 0x04, 0, b"\x82\x86");
let result = frame_header(&raw, DEFAULT_MAX_FRAME_SIZE);
assert!(
result.is_err(),
"HEADERS with stream_id=0 should be rejected"
);
}
#[test]
fn test_parse_rst_stream() {
let error_code = 0x00000008u32; let raw = build_raw_frame(4, 3, 0, 1, &error_code.to_be_bytes());
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::RstStream(rst) => {
assert_eq!(rst.stream_id, 1);
assert_eq!(rst.error_code, 0x08);
}
other => panic!("expected RstStream, got {other:?}"),
}
}
#[test]
fn test_parse_rst_stream_stream_id_zero_rejected() {
let raw = build_raw_frame(4, 3, 0, 0, &[0u8; 4]);
let result = frame_header(&raw, DEFAULT_MAX_FRAME_SIZE);
assert!(
result.is_err(),
"RST_STREAM with stream_id=0 should be rejected"
);
}
#[test]
fn test_parse_settings_frame_with_values() {
let mut payload = Vec::new();
payload.extend_from_slice(&0x0001u16.to_be_bytes());
payload.extend_from_slice(&4096u32.to_be_bytes());
payload.extend_from_slice(&0x0003u16.to_be_bytes());
payload.extend_from_slice(&100u32.to_be_bytes());
payload.extend_from_slice(&0x0004u16.to_be_bytes());
payload.extend_from_slice(&65535u32.to_be_bytes());
let raw = build_raw_frame(payload.len() as u32, 4, 0x0, 0, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Settings(s) => {
assert!(!s.ack);
assert_eq!(s.settings.len(), 3);
assert_eq!(s.settings[0].identifier, 0x0001);
assert_eq!(s.settings[0].value, 4096);
assert_eq!(s.settings[1].identifier, 0x0003);
assert_eq!(s.settings[1].value, 100);
assert_eq!(s.settings[2].identifier, 0x0004);
assert_eq!(s.settings[2].value, 65535);
}
other => panic!("expected Settings, got {other:?}"),
}
}
#[test]
fn test_parse_ping_frame() {
let ping_payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
let raw = build_raw_frame(8, 6, 0, 0, &ping_payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Ping(p) => {
assert_eq!(p.payload, ping_payload);
assert!(!p.ack);
}
other => panic!("expected Ping, got {other:?}"),
}
}
#[test]
fn test_parse_ping_ack_preserves_payload() {
let ping_payload = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE];
let raw = build_raw_frame(8, 6, 0x01, 0, &ping_payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Ping(p) => {
assert_eq!(
p.payload, ping_payload,
"PING ACK must echo the exact payload"
);
assert!(p.ack);
}
other => panic!("expected Ping, got {other:?}"),
}
}
#[test]
fn test_parse_ping_stream_id_nonzero_rejected() {
let raw = build_raw_frame(8, 6, 0, 1, &[0u8; 8]);
let result = frame_header(&raw, DEFAULT_MAX_FRAME_SIZE);
assert!(result.is_err(), "PING with stream_id!=0 should be rejected");
}
#[test]
fn test_parse_window_update_connection_level() {
let increment = 1000u32;
let raw = build_raw_frame(4, 8, 0, 0, &increment.to_be_bytes());
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::WindowUpdate(w) => {
assert_eq!(w.stream_id, 0);
assert_eq!(w.increment, 1000);
}
other => panic!("expected WindowUpdate, got {other:?}"),
}
}
#[test]
fn test_parse_window_update_stream_level() {
let increment = 65535u32;
let raw = build_raw_frame(4, 8, 0, 5, &increment.to_be_bytes());
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::WindowUpdate(w) => {
assert_eq!(w.stream_id, 5);
assert_eq!(w.increment, 65535);
}
other => panic!("expected WindowUpdate, got {other:?}"),
}
}
#[test]
fn test_parse_window_update_wrong_size() {
let raw = build_raw_frame(3, 8, 0, 0, &[0u8; 3]);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"WINDOW_UPDATE with payload != 4 should fail"
);
match result {
Err(Err::Failure(e)) => {
assert_eq!(e.kind, ParserErrorKind::H2(H2Error::FrameSizeError));
}
other => panic!("expected FrameSizeError, got {other:?}"),
}
}
#[test]
fn test_parse_frame_at_max_frame_size() {
let payload = vec![0u8; DEFAULT_MAX_FRAME_SIZE as usize];
let raw = build_raw_frame(DEFAULT_MAX_FRAME_SIZE, 0, 0x0, 1, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Data(d) => {
assert_eq!(d.payload.len, DEFAULT_MAX_FRAME_SIZE);
}
other => panic!("expected Data, got {other:?}"),
}
}
#[test]
fn test_parse_frame_with_custom_max_frame_size() {
let custom_max = 32768u32;
let payload = vec![0u8; 20000];
let raw = build_raw_frame(20000, 0, 0x0, 1, &payload);
let (remaining, header) =
frame_header(&raw, custom_max).expect("frame header parses with custom max");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Data(d) => {
assert_eq!(d.payload.len, 20000);
}
other => panic!("expected Data, got {other:?}"),
}
}
#[test]
fn test_h2_error_try_from_valid() {
assert_eq!(H2Error::try_from(0x0), Ok(H2Error::NoError));
assert_eq!(H2Error::try_from(0x1), Ok(H2Error::ProtocolError));
assert_eq!(H2Error::try_from(0x6), Ok(H2Error::FrameSizeError));
assert_eq!(H2Error::try_from(0xd), Ok(H2Error::HTTP11Required));
}
#[test]
fn test_h2_error_try_from_invalid() {
assert_eq!(H2Error::try_from(0x0e), Err(0x0e));
assert_eq!(H2Error::try_from(0xFF), Err(0xFF));
}
#[test]
fn test_h2_error_from_str() {
assert_eq!("NO_ERROR".parse::<H2Error>(), Ok(H2Error::NoError));
assert_eq!(
"PROTOCOL_ERROR".parse::<H2Error>(),
Ok(H2Error::ProtocolError)
);
assert_eq!(
"ENHANCE_YOUR_CALM".parse::<H2Error>(),
Ok(H2Error::EnhanceYourCalm)
);
assert!("INVALID_ERROR".parse::<H2Error>().is_err());
}
#[test]
fn test_h2_error_as_str_roundtrip() {
let errors = [
H2Error::NoError,
H2Error::ProtocolError,
H2Error::InternalError,
H2Error::FlowControlError,
H2Error::SettingsTimeout,
H2Error::StreamClosed,
H2Error::FrameSizeError,
H2Error::RefusedStream,
H2Error::Cancel,
H2Error::CompressionError,
H2Error::ConnectError,
H2Error::EnhanceYourCalm,
H2Error::InadequateSecurity,
H2Error::HTTP11Required,
];
for error in &errors {
let s = error.as_str();
let parsed: H2Error = s.parse().unwrap_or_else(|_| panic!("failed to parse {s}"));
assert_eq!(*error, parsed, "roundtrip failed for {s}");
}
}
#[test]
fn test_parse_priority_frame() {
let mut payload = Vec::new();
payload.extend_from_slice(&0x00000001u32.to_be_bytes());
payload.push(15); assert_eq!(payload.len(), 5);
let raw = build_raw_frame(5, 2, 0, 3, &payload);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, f) = frame_body(remaining, &header).expect("frame body parses cleanly");
match f {
Frame::Priority(p) => {
assert_eq!(p.stream_id, 3);
match p.inner {
PriorityPart::Rfc7540 {
stream_dependency,
weight,
} => {
assert!(!stream_dependency.exclusive);
assert_eq!(stream_dependency.stream_id, 1);
assert_eq!(weight, 15);
}
other => panic!("expected Rfc7540, got {other:?}"),
}
}
other => panic!("expected Priority, got {other:?}"),
}
}
#[test]
fn test_parse_priority_wrong_size_rejected() {
let raw = build_raw_frame(4, 2, 0, 1, &[0u8; 4]);
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"PRIORITY with wrong payload size should fail"
);
}
#[test]
fn test_headers_padded_priority_underflow_rejected() {
let raw: [u8; 16] = [
0x00, 0x00, 0x06, 0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x64, 0x6d, 0x6d,
0x6d, 0x6d,
];
let (remaining, header) =
frame_header(&raw, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let result = frame_body(remaining, &header);
assert!(
result.is_err(),
"PADDED+PRIORITY HEADERS with oversized pad_length must error, not panic"
);
}
#[test]
fn test_strip_padding_all_padding_accepted() {
let input = [
0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, frame) =
frame_body(remaining, &header).expect("DATA with all-padding body must parse");
match frame {
Frame::Data(data) => {
assert_eq!(data.stream_id, 1);
assert_eq!(data.payload.len, 0);
}
other => panic!("expected Frame::Data, got {other:?}"),
}
}
#[test]
fn test_strip_padding_pad_length_equals_payload_len_rejected() {
let input = [
0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x01, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let result = frame_body(remaining, &header);
assert!(
matches!(
result,
Err(nom::Err::Failure(ParserError {
kind: ParserErrorKind::H2(H2Error::ProtocolError),
..
}))
),
"pad_length > remaining body must yield PROTOCOL_ERROR, got {result:?}"
);
}
#[test]
fn test_goaway_short_payload_rejected() {
let input = [
0x00, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::GoAway);
let result = frame_body(remaining, &header);
assert!(
matches!(
result,
Err(nom::Err::Failure(ParserError {
kind: ParserErrorKind::H2(H2Error::FrameSizeError),
..
}))
),
"GOAWAY with payload_len < 8 must yield FRAME_SIZE_ERROR, got {result:?}"
);
}
#[test]
fn test_goaway_minimum_payload_accepted() {
let input = [
0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
let (_, frame) = frame_body(remaining, &header).expect("minimal GOAWAY must parse cleanly");
match frame {
Frame::GoAway(goaway) => {
assert_eq!(goaway.last_stream_id, 10);
assert_eq!(goaway.error_code, 0);
}
other => panic!("expected Frame::GoAway, got {other:?}"),
}
}
#[test]
fn test_push_promise_rejected_at_wire_layer() {
let input = [
0x00, 0x00, 0x08, 0x05, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, ];
let (remaining, header) =
frame_header(&input, DEFAULT_MAX_FRAME_SIZE).expect("frame header parses cleanly");
assert_eq!(header.frame_type, FrameType::PushPromise);
let result = frame_body(remaining, &header);
assert!(
matches!(
result,
Err(nom::Err::Failure(ParserError {
kind: ParserErrorKind::H2(H2Error::ProtocolError),
..
}))
),
"PUSH_PROMISE must be rejected at wire layer with PROTOCOL_ERROR, got {result:?}"
);
}
}