use crate::varint::{self, VarInt};
pub const MAX_STREAMS_LIMIT: u64 = 1u64 << 60;
pub const H3_DATAGRAM_ERROR: u64 = 0x33;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapsuleDecodeError {
Malformed,
}
fn decode_exact_varint(payload: &[u8]) -> Result<u64, CapsuleDecodeError> {
let (value, consumed) = decode_varint(payload).ok_or(CapsuleDecodeError::Malformed)?;
if consumed != payload.len() {
return Err(CapsuleDecodeError::Malformed);
}
Ok(value)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapsuleValidationError {
MaxStreamsExceedsLimit,
MaxStreamsDecreased,
MaxDataExceedsLimit,
MaxDataDecreased,
}
pub const PROHIBITED_WT_MAX_STREAM_DATA_CAPSULE_TYPE: u64 = 0x190B4D3E;
pub const PROHIBITED_WT_STREAM_DATA_BLOCKED_CAPSULE_TYPE: u64 = 0x190B4D42;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u64)]
pub enum CapsuleType {
CloseSession = 0x2843,
DrainSession = 0x78ae,
MaxData = 0x190B4D3D,
MaxStreamsBidi = 0x190B4D3F,
MaxStreamsUni = 0x190B4D40,
DataBlocked = 0x190B4D41,
StreamsBlockedBidi = 0x190B4D43,
StreamsBlockedUni = 0x190B4D44,
}
impl CapsuleType {
pub fn from_type(t: u64) -> Option<Self> {
match t {
0x2843 => Some(Self::CloseSession),
0x78ae => Some(Self::DrainSession),
0x190B4D3D => Some(Self::MaxData),
0x190B4D3F => Some(Self::MaxStreamsBidi),
0x190B4D40 => Some(Self::MaxStreamsUni),
0x190B4D41 => Some(Self::DataBlocked),
0x190B4D43 => Some(Self::StreamsBlockedBidi),
0x190B4D44 => Some(Self::StreamsBlockedUni),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Capsule {
CloseSession {
error_code: u32,
message: String,
},
DrainSession,
MaxData {
maximum: u64,
},
MaxStreams {
bidirectional: bool,
maximum: u64,
},
DataBlocked {
maximum: u64,
},
StreamsBlocked {
bidirectional: bool,
maximum: u64,
},
Unknown {
capsule_type: u64,
payload: Vec<u8>,
},
}
fn encode_varint(buf: &mut Vec<u8>, value: u64) {
let value = VarInt::new(value).expect("capsule field value fits in VarInt");
varint::encode_into_vec(buf, value);
}
fn varint_encoded_len(value: u64) -> usize {
VarInt::new(value)
.expect("capsule field value fits in VarInt")
.encoded_len()
}
fn decode_varint(buf: &[u8]) -> Option<(u64, usize)> {
varint::decode(buf).ok().map(|(v, n)| (v.get(), n))
}
impl Capsule {
pub fn is_prohibited_in_http3_type(capsule_type: u64) -> bool {
matches!(
capsule_type,
PROHIBITED_WT_MAX_STREAM_DATA_CAPSULE_TYPE
| PROHIBITED_WT_STREAM_DATA_BLOCKED_CAPSULE_TYPE
)
}
pub fn is_prohibited_in_http3(&self) -> bool {
Self::is_prohibited_in_http3_type(self.capsule_type())
}
pub fn encode_as_data_frame(&self, buf: &mut Vec<u8>) {
let mut capsule_bytes = Vec::new();
self.encode(&mut capsule_bytes);
varint::encode_into_vec(buf, VarInt::ZERO);
encode_varint(buf, capsule_bytes.len() as u64);
buf.extend_from_slice(&capsule_bytes);
}
pub fn encode(&self, buf: &mut Vec<u8>) {
match self {
Self::CloseSession {
error_code,
message,
} => {
Self::encode_capsule_header(
buf,
CapsuleType::CloseSession as u64,
4 + message.len(),
);
buf.extend_from_slice(&error_code.to_be_bytes());
buf.extend_from_slice(message.as_bytes());
}
Self::DrainSession => {
Self::encode_capsule_header(buf, CapsuleType::DrainSession as u64, 0);
}
Self::MaxData { maximum } => {
let payload_len = varint_encoded_len(*maximum);
Self::encode_capsule_header(buf, CapsuleType::MaxData as u64, payload_len);
encode_varint(buf, *maximum);
}
Self::MaxStreams {
bidirectional,
maximum,
} => {
let capsule_type = if *bidirectional {
CapsuleType::MaxStreamsBidi as u64
} else {
CapsuleType::MaxStreamsUni as u64
};
let payload_len = varint_encoded_len(*maximum);
Self::encode_capsule_header(buf, capsule_type, payload_len);
encode_varint(buf, *maximum);
}
Self::DataBlocked { maximum } => {
let payload_len = varint_encoded_len(*maximum);
Self::encode_capsule_header(buf, CapsuleType::DataBlocked as u64, payload_len);
encode_varint(buf, *maximum);
}
Self::StreamsBlocked {
bidirectional,
maximum,
} => {
let capsule_type = if *bidirectional {
CapsuleType::StreamsBlockedBidi as u64
} else {
CapsuleType::StreamsBlockedUni as u64
};
let payload_len = varint_encoded_len(*maximum);
Self::encode_capsule_header(buf, capsule_type, payload_len);
encode_varint(buf, *maximum);
}
Self::Unknown {
capsule_type,
payload,
} => {
Self::encode_capsule_header(buf, *capsule_type, payload.len());
buf.extend_from_slice(payload);
}
}
}
fn encode_capsule_header(buf: &mut Vec<u8>, capsule_type: u64, length: usize) {
encode_varint(buf, capsule_type);
encode_varint(buf, length as u64);
}
pub fn decode(buf: &[u8]) -> Result<Option<(Self, usize)>, CapsuleDecodeError> {
let mut offset = 0;
let Some((capsule_type, len)) = decode_varint(&buf[offset..]) else {
return Ok(None);
};
offset += len;
let Some((length, len)) = decode_varint(&buf[offset..]) else {
return Ok(None);
};
offset += len;
let length = length as usize;
if buf.len() < offset + length {
return Ok(None);
}
let payload = &buf[offset..offset + length];
let capsule = Self::decode_payload(capsule_type, payload)?;
Ok(Some((capsule, offset + length)))
}
fn decode_payload(capsule_type: u64, payload: &[u8]) -> Result<Self, CapsuleDecodeError> {
match CapsuleType::from_type(capsule_type) {
Some(CapsuleType::CloseSession) => {
if payload.len() < 4 {
return Err(CapsuleDecodeError::Malformed);
}
let error_code = u32::from_be_bytes(
payload[..4]
.try_into()
.map_err(|_| CapsuleDecodeError::Malformed)?,
);
let message_bytes = &payload[4..];
if message_bytes.len() > 1024 {
return Err(CapsuleDecodeError::Malformed);
}
let message = String::from_utf8(message_bytes.to_vec())
.map_err(|_| CapsuleDecodeError::Malformed)?;
Ok(Self::CloseSession {
error_code,
message,
})
}
Some(CapsuleType::DrainSession) => {
if !payload.is_empty() {
return Err(CapsuleDecodeError::Malformed);
}
Ok(Self::DrainSession)
}
Some(CapsuleType::MaxData) => {
let maximum = decode_exact_varint(payload)?;
Ok(Self::MaxData { maximum })
}
Some(CapsuleType::MaxStreamsBidi) => {
let maximum = decode_exact_varint(payload)?;
Ok(Self::MaxStreams {
bidirectional: true,
maximum,
})
}
Some(CapsuleType::MaxStreamsUni) => {
let maximum = decode_exact_varint(payload)?;
Ok(Self::MaxStreams {
bidirectional: false,
maximum,
})
}
Some(CapsuleType::DataBlocked) => {
let maximum = decode_exact_varint(payload)?;
Ok(Self::DataBlocked { maximum })
}
Some(CapsuleType::StreamsBlockedBidi) => {
let maximum = decode_exact_varint(payload)?;
Ok(Self::StreamsBlocked {
bidirectional: true,
maximum,
})
}
Some(CapsuleType::StreamsBlockedUni) => {
let maximum = decode_exact_varint(payload)?;
Ok(Self::StreamsBlocked {
bidirectional: false,
maximum,
})
}
None => Ok(Self::Unknown {
capsule_type,
payload: payload.to_vec(),
}),
}
}
pub fn capsule_type(&self) -> u64 {
match self {
Self::CloseSession { .. } => CapsuleType::CloseSession as u64,
Self::DrainSession => CapsuleType::DrainSession as u64,
Self::MaxData { .. } => CapsuleType::MaxData as u64,
Self::MaxStreams { bidirectional, .. } => {
if *bidirectional {
CapsuleType::MaxStreamsBidi as u64
} else {
CapsuleType::MaxStreamsUni as u64
}
}
Self::DataBlocked { .. } => CapsuleType::DataBlocked as u64,
Self::StreamsBlocked { bidirectional, .. } => {
if *bidirectional {
CapsuleType::StreamsBlockedBidi as u64
} else {
CapsuleType::StreamsBlockedUni as u64
}
}
Self::Unknown { capsule_type, .. } => *capsule_type,
}
}
pub fn validate_max_streams(
maximum: u64,
current_max: u64,
) -> Result<(), CapsuleValidationError> {
if maximum > MAX_STREAMS_LIMIT {
return Err(CapsuleValidationError::MaxStreamsExceedsLimit);
}
if maximum < current_max {
return Err(CapsuleValidationError::MaxStreamsDecreased);
}
Ok(())
}
pub fn validate_max_data(maximum: u64, current_max: u64) -> Result<(), CapsuleValidationError> {
if maximum > crate::VarInt::MAX.get() {
return Err(CapsuleValidationError::MaxDataExceedsLimit);
}
if maximum < current_max {
return Err(CapsuleValidationError::MaxDataDecreased);
}
Ok(())
}
pub fn is_flow_control(&self) -> bool {
matches!(
self,
Self::MaxData { .. }
| Self::MaxStreams { .. }
| Self::DataBlocked { .. }
| Self::StreamsBlocked { .. }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_capsule_type_from_type() {
assert_eq!(
CapsuleType::from_type(0x2843),
Some(CapsuleType::CloseSession)
);
assert_eq!(
CapsuleType::from_type(0x78ae),
Some(CapsuleType::DrainSession)
);
assert_eq!(
CapsuleType::from_type(0x190B4D3D),
Some(CapsuleType::MaxData)
);
assert_eq!(
CapsuleType::from_type(0x190B4D3F),
Some(CapsuleType::MaxStreamsBidi)
);
assert_eq!(
CapsuleType::from_type(0x190B4D40),
Some(CapsuleType::MaxStreamsUni)
);
assert_eq!(CapsuleType::from_type(0x99), None);
}
#[test]
fn test_is_flow_control() {
assert!(
!Capsule::CloseSession {
error_code: 0,
message: String::new()
}
.is_flow_control()
);
assert!(!Capsule::DrainSession.is_flow_control());
assert!(Capsule::MaxData { maximum: 0 }.is_flow_control());
assert!(
Capsule::MaxStreams {
bidirectional: true,
maximum: 0
}
.is_flow_control()
);
assert!(Capsule::DataBlocked { maximum: 0 }.is_flow_control());
assert!(
Capsule::StreamsBlocked {
bidirectional: true,
maximum: 0
}
.is_flow_control()
);
}
#[test]
fn test_is_prohibited_in_http3_type() {
assert!(Capsule::is_prohibited_in_http3_type(
PROHIBITED_WT_MAX_STREAM_DATA_CAPSULE_TYPE
));
assert!(Capsule::is_prohibited_in_http3_type(
PROHIBITED_WT_STREAM_DATA_BLOCKED_CAPSULE_TYPE
));
assert!(!Capsule::is_prohibited_in_http3_type(
CapsuleType::MaxData as u64
));
}
#[test]
fn test_validate_max_streams() {
assert!(Capsule::validate_max_streams(10, 5).is_ok());
assert!(Capsule::validate_max_streams(10, 10).is_ok());
assert!(Capsule::validate_max_streams(MAX_STREAMS_LIMIT, 0).is_ok());
assert_eq!(
Capsule::validate_max_streams(MAX_STREAMS_LIMIT + 1, 0),
Err(CapsuleValidationError::MaxStreamsExceedsLimit)
);
assert_eq!(
Capsule::validate_max_streams(5, 10),
Err(CapsuleValidationError::MaxStreamsDecreased)
);
}
#[test]
fn test_validate_max_data() {
assert!(Capsule::validate_max_data(100, 50).is_ok());
assert!(Capsule::validate_max_data(100, 100).is_ok());
assert!(Capsule::validate_max_data(crate::VarInt::MAX.get(), 0).is_ok());
assert_eq!(
Capsule::validate_max_data(50, 100),
Err(CapsuleValidationError::MaxDataDecreased)
);
assert_eq!(
Capsule::validate_max_data(crate::VarInt::MAX.get() + 1, 0),
Err(CapsuleValidationError::MaxDataExceedsLimit)
);
}
#[test]
fn test_is_prohibited_in_http3() {
let c = Capsule::Unknown {
capsule_type: PROHIBITED_WT_MAX_STREAM_DATA_CAPSULE_TYPE,
payload: vec![],
};
assert!(c.is_prohibited_in_http3());
}
}