use crate::courierust_error::{Error, Result};
use alloc::string::ToString;
use alloc::vec::Vec;
const MAX_SETTINGS_ENTRIES: usize = 256;
pub const STREAM_TYPE_CONTROL: u64 = 0x00;
pub const STREAM_TYPE_PUSH: u64 = 0x01;
pub const STREAM_TYPE_QPACK_ENCODER: u64 = 0x02;
pub const STREAM_TYPE_QPACK_DECODER: u64 = 0x03;
pub const SETTINGS_QPACK_MAX_TABLE_CAPACITY: u64 = 0x1;
pub const SETTINGS_MAX_FIELD_SECTION_SIZE: u64 = 0x6;
pub const SETTINGS_QPACK_BLOCKED_STREAMS: u64 = 0x7;
pub const SETTINGS_ENABLE_CONNECT_PROTOCOL: u64 = 0x8;
pub const SETTINGS_H3_DATAGRAM: u64 = 0x33;
pub const SETTINGS_ENABLE_WEBTRANSPORT: u64 = 0x2b603742;
pub const SETTINGS_WEBTRANSPORT_MAX_SESSIONS: u64 = 0x2b603743;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Frame {
Data(Vec<u8>),
Headers(Vec<u8>),
CancelPush(u64),
Settings(Vec<(u64, u64)>),
PushPromise {
push_id: u64,
headers: Vec<u8>,
},
GoAway(u64),
MaxPushId(u64),
Unknown {
frame_type: u64,
payload: Vec<u8>,
},
}
impl Frame {
pub fn frame_type(&self) -> u64 {
match self {
Frame::Data(_) => 0x00,
Frame::Headers(_) => 0x01,
Frame::CancelPush(_) => 0x03,
Frame::Settings(_) => 0x04,
Frame::PushPromise { .. } => 0x05,
Frame::GoAway(_) => 0x07,
Frame::MaxPushId(_) => 0x0d,
Frame::Unknown { frame_type, .. } => *frame_type,
}
}
pub fn encode(&self, out: &mut Vec<u8>) {
let mut payload = Vec::new();
match self {
Frame::Data(d) => payload.extend_from_slice(d),
Frame::Headers(h) => payload.extend_from_slice(h),
Frame::CancelPush(id) => {
payload.extend_from_slice(&crate::courierust_quic::varint::encode(*id));
}
Frame::Settings(settings) => {
for (id, value) in settings {
payload.extend_from_slice(&crate::courierust_quic::varint::encode(*id));
payload.extend_from_slice(&crate::courierust_quic::varint::encode(*value));
}
}
Frame::PushPromise { push_id, headers } => {
payload.extend_from_slice(&crate::courierust_quic::varint::encode(*push_id));
payload.extend_from_slice(headers);
}
Frame::GoAway(id) => {
payload.extend_from_slice(&crate::courierust_quic::varint::encode(*id));
}
Frame::MaxPushId(id) => {
payload.extend_from_slice(&crate::courierust_quic::varint::encode(*id));
}
Frame::Unknown { payload: data, .. } => payload.extend_from_slice(data),
}
out.extend_from_slice(&crate::courierust_quic::varint::encode(self.frame_type()));
out.extend_from_slice(&crate::courierust_quic::varint::encode(payload.len() as u64));
out.extend_from_slice(&payload);
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
self.encode(&mut out);
out
}
pub fn decode(buf: &[u8], pos: &mut usize) -> Result<Option<Frame>> {
if *pos > buf.len() {
return Err(Error::protocol("HTTP/3 frame position is outside buffer"));
}
if *pos == buf.len() {
return Ok(None);
}
let (frame_type, used) = match crate::courierust_quic::varint::decode(&buf[*pos..]) {
Ok(value) => value,
Err(error) if error.kind == crate::courierust_error::ErrorKind::UnexpectedEof => {
return Ok(None)
}
Err(error) => return Err(error),
};
let mut p = (*pos)
.checked_add(used)
.ok_or_else(|| Error::overflow("HTTP/3 frame type offset overflow"))?;
let (length, used) = match crate::courierust_quic::varint::decode(&buf[p..]) {
Ok(value) => value,
Err(error) if error.kind == crate::courierust_error::ErrorKind::UnexpectedEof => {
return Ok(None)
}
Err(error) => return Err(error),
};
p = p
.checked_add(used)
.ok_or_else(|| Error::overflow("HTTP/3 frame payload offset overflow"))?;
let length = usize::try_from(length)
.map_err(|_| Error::overflow("HTTP/3 frame length does not fit usize"))?;
let end = p
.checked_add(length)
.ok_or_else(|| Error::overflow("HTTP/3 frame length overflow"))?;
if buf.len() < end {
return Ok(None);
}
let payload = &buf[p..end];
let frame = match frame_type {
0x00 => Frame::Data(payload.to_vec()),
0x01 => Frame::Headers(payload.to_vec()),
0x03 => {
let mut q = 0;
let (id, used) = crate::courierust_quic::varint::decode(payload)
.map_err(|e| Error::protocol(e.to_string()))?;
q += used;
if q != payload.len() {
return Err(Error::protocol("trailing bytes in CANCEL_PUSH"));
}
Frame::CancelPush(id)
}
0x04 => {
let mut q = 0;
let mut settings = Vec::new();
while q < payload.len() {
if settings.len() >= MAX_SETTINGS_ENTRIES {
return Err(Error::overflow("HTTP/3 SETTINGS entry count exceeds limit"));
}
let (id, used) = crate::courierust_quic::varint::decode(&payload[q..])
.map_err(|e| Error::protocol(e.to_string()))?;
q = q
.checked_add(used)
.ok_or_else(|| Error::overflow("HTTP/3 SETTINGS id offset overflow"))?;
let (value, used) = crate::courierust_quic::varint::decode(&payload[q..])
.map_err(|e| Error::protocol(e.to_string()))?;
q = q
.checked_add(used)
.ok_or_else(|| Error::overflow("HTTP/3 SETTINGS value offset overflow"))?;
settings.push((id, value));
}
Frame::Settings(settings)
}
0x05 => {
let (push_id, used) = crate::courierust_quic::varint::decode(payload)
.map_err(|e| Error::protocol(e.to_string()))?;
Frame::PushPromise {
push_id,
headers: payload[used..].to_vec(),
}
}
0x07 => {
let mut q = 0;
let (id, used) = crate::courierust_quic::varint::decode(payload)
.map_err(|e| Error::protocol(e.to_string()))?;
q += used;
if q != payload.len() {
return Err(Error::protocol("trailing bytes in GOAWAY"));
}
Frame::GoAway(id)
}
0x0d => {
let mut q = 0;
let (id, used) = crate::courierust_quic::varint::decode(payload)
.map_err(|e| Error::protocol(e.to_string()))?;
q += used;
if q != payload.len() {
return Err(Error::protocol("trailing bytes in MAX_PUSH_ID"));
}
Frame::MaxPushId(id)
}
other => Frame::Unknown {
frame_type: other,
payload: payload.to_vec(),
},
};
*pos = end;
Ok(Some(frame))
}
}
pub fn encode_stream_type(stream_type: u64) -> Vec<u8> {
crate::courierust_quic::varint::encode(stream_type)
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(frame: Frame) {
let wire = frame.to_bytes();
let mut pos = 0;
let decoded = Frame::decode(&wire, &mut pos)
.unwrap()
.expect("complete frame");
assert_eq!(decoded, frame, "round trip for {frame:?}");
assert_eq!(pos, wire.len());
}
#[test]
fn frames_round_trip() {
round_trip(Frame::Data(b"payload".to_vec()));
round_trip(Frame::Headers(vec![0x40 | 8, 0x00]));
round_trip(Frame::CancelPush(3));
round_trip(Frame::CancelPush(1000));
round_trip(Frame::Settings(vec![
(SETTINGS_QPACK_MAX_TABLE_CAPACITY, 4096),
(SETTINGS_MAX_FIELD_SECTION_SIZE, 16384),
]));
round_trip(Frame::PushPromise {
push_id: 7,
headers: vec![0x40 | 8],
});
round_trip(Frame::GoAway(1000));
round_trip(Frame::GoAway((1u64 << 62) - 4));
round_trip(Frame::MaxPushId(0));
round_trip(Frame::MaxPushId(u64::from(u32::MAX)));
}
#[test]
fn id_frames_use_quic_varints() {
assert_eq!(Frame::GoAway(0).to_bytes(), vec![0x07, 0x01, 0x00]);
assert_eq!(Frame::GoAway(63).to_bytes(), vec![0x07, 0x01, 0x3f]);
assert_eq!(Frame::GoAway(64).to_bytes(), vec![0x07, 0x02, 0x40, 0x40]);
assert_eq!(
Frame::GoAway((1u64 << 62) - 4).to_bytes(),
vec![0x07, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc],
);
assert_eq!(Frame::MaxPushId(1).to_bytes(), vec![0x0d, 0x01, 0x01]);
assert_eq!(
Frame::CancelPush(1000).to_bytes(),
vec![0x03, 0x02, 0x43, 0xe8]
);
assert_eq!(
Frame::PushPromise {
push_id: 300,
headers: vec![0x00],
}
.to_bytes(),
vec![0x05, 0x03, 0x41, 0x2c, 0x00],
);
let wire = Frame::GoAway((1u64 << 62) - 4).to_bytes();
let mut pos = 0;
assert_eq!(
Frame::decode(&wire, &mut pos).unwrap(),
Some(Frame::GoAway((1u64 << 62) - 4))
);
assert_eq!(pos, wire.len());
}
#[test]
fn partial_frame_returns_none() {
let wire = Frame::Data(vec![1, 2, 3]).to_bytes();
let mut pos = 0;
assert!(Frame::decode(&wire[..wire.len() - 2], &mut pos)
.unwrap()
.is_none());
}
}