use crate::bytes::{Bytes, BytesMut};
use crate::net::quic_core::{decode_varint, encode_varint};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::net::Ipv6Addr;
use super::h2::hpack::{
decode_huffman as hpack_decode_huffman, encode_huffman_to_buffer as hpack_encode_huffman,
huffman_encoded_size as hpack_huffman_encoded_size,
};
const H3_FRAME_DATA: u64 = 0x0;
const H3_FRAME_HEADERS: u64 = 0x1;
const H3_FRAME_CANCEL_PUSH: u64 = 0x3;
const H3_FRAME_SETTINGS: u64 = 0x4;
const H3_FRAME_PUSH_PROMISE: u64 = 0x5;
const H3_FRAME_GOAWAY: u64 = 0x7;
const H3_FRAME_MAX_PUSH_ID: u64 = 0xD;
const H3_FRAME_DATAGRAM: u64 = 0x30;
const H3_STREAM_TYPE_CONTROL: u64 = 0x00;
const H3_STREAM_TYPE_PUSH: u64 = 0x01;
const H3_STREAM_TYPE_QPACK_ENCODER: u64 = 0x02;
const H3_STREAM_TYPE_QPACK_DECODER: u64 = 0x03;
pub const H3_SETTING_QPACK_MAX_TABLE_CAPACITY: u64 = 0x01;
pub const H3_SETTING_MAX_FIELD_SECTION_SIZE: u64 = 0x06;
pub const H3_SETTING_QPACK_BLOCKED_STREAMS: u64 = 0x07;
pub const H3_SETTING_ENABLE_CONNECT_PROTOCOL: u64 = 0x08;
pub const H3_SETTING_H3_DATAGRAM: u64 = 0x33;
const QPACK_MAX_DECODED_HEADERS: usize = 1000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum H3NativeError {
UnexpectedEof,
InvalidFrame(&'static str),
FrameTooLarge {
payload_size: usize,
max_size: usize,
},
DuplicateSetting(u64),
InvalidSettingValue(u64),
ControlProtocol(&'static str),
StreamProtocol(&'static str),
QpackPolicy(&'static str),
InvalidRequestPseudoHeader(&'static str),
InvalidResponsePseudoHeader(&'static str),
ConcurrentStreamLimitExceeded {
active: u64,
limit: u64,
},
}
impl fmt::Display for H3NativeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEof => write!(f, "unexpected EOF"),
Self::InvalidFrame(msg) => write!(f, "invalid frame: {msg}"),
Self::FrameTooLarge {
payload_size,
max_size,
} => write!(
f,
"frame payload too large: {payload_size} bytes exceeds limit of {max_size} bytes"
),
Self::DuplicateSetting(id) => write!(f, "duplicate setting: 0x{id:x}"),
Self::InvalidSettingValue(id) => write!(f, "invalid setting value: 0x{id:x}"),
Self::ControlProtocol(msg) => write!(f, "control stream protocol violation: {msg}"),
Self::StreamProtocol(msg) => write!(f, "stream protocol violation: {msg}"),
Self::QpackPolicy(msg) => write!(f, "qpack policy violation: {msg}"),
Self::InvalidRequestPseudoHeader(msg) => {
write!(f, "invalid request pseudo-header set: {msg}")
}
Self::InvalidResponsePseudoHeader(msg) => {
write!(f, "invalid response pseudo-header set: {msg}")
}
Self::ConcurrentStreamLimitExceeded { active, limit } => write!(
f,
"concurrent request stream limit exceeded: {active} active, limit {limit}"
),
}
}
}
impl std::error::Error for H3NativeError {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum H3QpackMode {
#[default]
StaticOnly,
DynamicTableAllowed,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum H3EndpointRole {
#[default]
Client,
Server,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H3ConnectionConfig {
pub qpack_mode: H3QpackMode,
pub endpoint_role: H3EndpointRole,
pub max_frame_payload_size: usize,
pub max_concurrent_request_streams: Option<u64>,
}
impl Default for H3ConnectionConfig {
fn default() -> Self {
Self {
qpack_mode: H3QpackMode::StaticOnly,
endpoint_role: H3EndpointRole::Client,
max_frame_payload_size: 1024 * 1024,
max_concurrent_request_streams: None,
}
}
}
impl H3ConnectionConfig {
#[must_use]
pub fn with_dynamic_qpack(mut self) -> Self {
self.qpack_mode = H3QpackMode::DynamicTableAllowed;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H3UniStreamType {
Control,
Push,
QpackEncoder,
QpackDecoder,
Unknown(u64),
}
impl H3UniStreamType {
#[must_use]
pub fn decode(stream_type: u64) -> Self {
match stream_type {
H3_STREAM_TYPE_CONTROL => Self::Control,
H3_STREAM_TYPE_PUSH => Self::Push,
H3_STREAM_TYPE_QPACK_ENCODER => Self::QpackEncoder,
H3_STREAM_TYPE_QPACK_DECODER => Self::QpackDecoder,
other => Self::Unknown(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownSetting {
pub id: u64,
pub value: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H3Settings {
pub qpack_max_table_capacity: Option<u64>,
pub max_field_section_size: Option<u64>,
pub qpack_blocked_streams: Option<u64>,
pub enable_connect_protocol: Option<bool>,
pub h3_datagram: Option<bool>,
pub unknown: Vec<UnknownSetting>,
}
impl H3Settings {
pub fn encode_payload(&self, out: &mut Vec<u8>) -> Result<(), H3NativeError> {
if let Some(v) = self.qpack_max_table_capacity {
encode_setting(out, H3_SETTING_QPACK_MAX_TABLE_CAPACITY, v)?;
}
if let Some(v) = self.max_field_section_size {
encode_setting(out, H3_SETTING_MAX_FIELD_SECTION_SIZE, v)?;
}
if let Some(v) = self.qpack_blocked_streams {
encode_setting(out, H3_SETTING_QPACK_BLOCKED_STREAMS, v)?;
}
if let Some(v) = self.enable_connect_protocol {
encode_setting(out, H3_SETTING_ENABLE_CONNECT_PROTOCOL, u64::from(v))?;
}
if let Some(v) = self.h3_datagram {
encode_setting(out, H3_SETTING_H3_DATAGRAM, u64::from(v))?;
}
for s in &self.unknown {
if is_http2_reserved_settings_id(s.id) {
return Err(H3NativeError::InvalidSettingValue(s.id));
}
encode_setting(out, s.id, s.value)?;
}
Ok(())
}
pub fn decode_payload(input: &[u8]) -> Result<Self, H3NativeError> {
let mut settings = Self::default();
let mut seen_ids = BTreeSet::new();
let mut pos = 0usize;
while pos < input.len() {
let (id, id_len) = decode_varint(input.get(pos..).ok_or(H3NativeError::UnexpectedEof)?)
.map_err(|_| H3NativeError::InvalidFrame("invalid setting id varint"))?;
pos += id_len;
let (value, val_len) =
decode_varint(input.get(pos..).ok_or(H3NativeError::UnexpectedEof)?)
.map_err(|_| H3NativeError::InvalidFrame("invalid setting value varint"))?;
pos += val_len;
if !seen_ids.insert(id) {
return Err(H3NativeError::DuplicateSetting(id));
}
match id {
id if is_http2_reserved_settings_id(id) => {
return Err(H3NativeError::InvalidSettingValue(id));
}
H3_SETTING_QPACK_MAX_TABLE_CAPACITY => {
settings.qpack_max_table_capacity = Some(value);
}
H3_SETTING_MAX_FIELD_SECTION_SIZE => {
settings.max_field_section_size = Some(value);
}
H3_SETTING_QPACK_BLOCKED_STREAMS => {
settings.qpack_blocked_streams = Some(value);
}
H3_SETTING_ENABLE_CONNECT_PROTOCOL => {
settings.enable_connect_protocol = Some(parse_bool_setting(id, value)?);
}
H3_SETTING_H3_DATAGRAM => {
settings.h3_datagram = Some(parse_bool_setting(id, value)?);
}
_ => settings.unknown.push(UnknownSetting { id, value }),
}
}
Ok(settings)
}
}
const fn is_http2_reserved_settings_id(id: u64) -> bool {
matches!(id, 0x00 | 0x02 | 0x03 | 0x04 | 0x05)
}
fn parse_bool_setting(id: u64, value: u64) -> Result<bool, H3NativeError> {
match value {
0 => Ok(false),
1 => Ok(true),
_ => Err(H3NativeError::InvalidSettingValue(id)),
}
}
fn encode_setting(out: &mut Vec<u8>, id: u64, value: u64) -> Result<(), H3NativeError> {
encode_varint(id, out).map_err(|_| H3NativeError::InvalidFrame("setting id out of range"))?;
encode_varint(value, out)
.map_err(|_| H3NativeError::InvalidFrame("setting value out of range"))?;
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum H3Frame {
Data(Vec<u8>),
Headers(Vec<u8>),
CancelPush(u64),
Settings(H3Settings),
PushPromise {
push_id: u64,
field_block: Vec<u8>,
},
Goaway(u64),
MaxPushId(u64),
Datagram {
quarter_stream_id: u64,
payload: Vec<u8>,
},
Unknown {
frame_type: u64,
payload: Vec<u8>,
},
}
impl H3Frame {
pub fn encode(&self, out: &mut Vec<u8>) -> Result<(), H3NativeError> {
let mut payload = Vec::new();
let frame_type = match self {
Self::Data(bytes) => {
payload.extend_from_slice(bytes);
H3_FRAME_DATA
}
Self::Headers(bytes) => {
payload.extend_from_slice(bytes);
H3_FRAME_HEADERS
}
Self::CancelPush(id) => {
encode_varint(*id, &mut payload)
.map_err(|_| H3NativeError::InvalidFrame("cancel_push id out of range"))?;
H3_FRAME_CANCEL_PUSH
}
Self::Settings(settings) => {
settings.encode_payload(&mut payload)?;
H3_FRAME_SETTINGS
}
Self::PushPromise {
push_id,
field_block,
} => {
encode_varint(*push_id, &mut payload)
.map_err(|_| H3NativeError::InvalidFrame("push_id out of range"))?;
payload.extend_from_slice(field_block);
H3_FRAME_PUSH_PROMISE
}
Self::Goaway(id) => {
encode_varint(*id, &mut payload)
.map_err(|_| H3NativeError::InvalidFrame("goaway id out of range"))?;
H3_FRAME_GOAWAY
}
Self::MaxPushId(id) => {
encode_varint(*id, &mut payload)
.map_err(|_| H3NativeError::InvalidFrame("max_push_id out of range"))?;
H3_FRAME_MAX_PUSH_ID
}
Self::Datagram {
quarter_stream_id,
payload: data,
} => {
encode_varint(*quarter_stream_id, &mut payload)
.map_err(|_| H3NativeError::InvalidFrame("quarter_stream_id out of range"))?;
payload.extend_from_slice(data);
H3_FRAME_DATAGRAM
}
Self::Unknown {
frame_type,
payload: body,
} => {
payload.extend_from_slice(body);
*frame_type
}
};
encode_varint(frame_type, out)
.map_err(|_| H3NativeError::InvalidFrame("frame type out of range"))?;
encode_varint(payload.len() as u64, out)
.map_err(|_| H3NativeError::InvalidFrame("frame length out of range"))?;
out.extend_from_slice(&payload);
Ok(())
}
pub fn decode(
input: &[u8],
config: &H3ConnectionConfig,
) -> Result<(Self, usize), H3NativeError> {
let (frame_type, type_len) =
decode_varint(input).map_err(|_| H3NativeError::InvalidFrame("frame type varint"))?;
let (len, len_len) = decode_varint(&input[type_len..])
.map_err(|_| H3NativeError::InvalidFrame("frame length varint"))?;
let len: usize = len
.try_into()
.map_err(|_| H3NativeError::InvalidFrame("frame length exceeds addressable range"))?;
if len > config.max_frame_payload_size {
return Err(H3NativeError::FrameTooLarge {
payload_size: len,
max_size: config.max_frame_payload_size,
});
}
let payload_start = type_len + len_len;
if frame_type == H3_FRAME_DATAGRAM {
let available = input.len().saturating_sub(payload_start);
let bounded_payload = &input[payload_start..payload_start + available.min(len)];
let (quarter_stream_id, n) = decode_varint(bounded_payload)
.map_err(|_| H3NativeError::InvalidFrame("quarter stream id varint"))?;
if available < len {
return Err(H3NativeError::InvalidFrame("insufficient frame payload"));
}
let payload = &input[payload_start..payload_start + len];
let consumed = payload_start + len;
return Ok((
Self::Datagram {
quarter_stream_id,
payload: payload[n..].to_vec(),
},
consumed,
));
}
if input.len().saturating_sub(payload_start) < len {
return Err(H3NativeError::UnexpectedEof);
}
let payload = &input[payload_start..payload_start + len];
let consumed = payload_start + len;
let frame = match frame_type {
H3_FRAME_DATA => Self::Data(payload.to_vec()),
H3_FRAME_HEADERS => Self::Headers(payload.to_vec()),
H3_FRAME_CANCEL_PUSH => {
let (id, n) = decode_varint(payload)
.map_err(|_| H3NativeError::InvalidFrame("cancel_push payload"))?;
if n != payload.len() {
return Err(H3NativeError::InvalidFrame("cancel_push trailing bytes"));
}
Self::CancelPush(id)
}
H3_FRAME_SETTINGS => Self::Settings(H3Settings::decode_payload(payload)?),
H3_FRAME_PUSH_PROMISE => {
let (push_id, n) = decode_varint(payload)
.map_err(|_| H3NativeError::InvalidFrame("push_promise push_id"))?;
let field_block = &payload[n..];
if field_block.is_empty() {
return Err(H3NativeError::InvalidFrame(
"push_promise empty field_block (RFC 9114 §7.2.5)",
));
}
Self::PushPromise {
push_id,
field_block: field_block.to_vec(),
}
}
H3_FRAME_GOAWAY => {
let (id, n) = decode_varint(payload)
.map_err(|_| H3NativeError::InvalidFrame("goaway payload"))?;
if n != payload.len() {
return Err(H3NativeError::InvalidFrame("goaway trailing bytes"));
}
Self::Goaway(id)
}
H3_FRAME_MAX_PUSH_ID => {
let (id, n) = decode_varint(payload)
.map_err(|_| H3NativeError::InvalidFrame("max_push_id payload"))?;
if n != payload.len() {
return Err(H3NativeError::InvalidFrame("max_push_id trailing bytes"));
}
Self::MaxPushId(id)
}
_ => Self::Unknown {
frame_type,
payload: payload.to_vec(),
},
};
Ok((frame, consumed))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H3ControlState {
local_settings_sent: bool,
remote_settings_received: bool,
}
impl H3ControlState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn build_local_settings(&mut self, settings: H3Settings) -> Result<H3Frame, H3NativeError> {
if self.local_settings_sent {
return Err(H3NativeError::ControlProtocol(
"SETTINGS already sent on local control stream",
));
}
self.local_settings_sent = true;
Ok(H3Frame::Settings(settings))
}
pub fn on_remote_control_frame(&mut self, frame: &H3Frame) -> Result<(), H3NativeError> {
if self.remote_settings_received {
match frame {
H3Frame::Settings(_) => {
return Err(H3NativeError::ControlProtocol(
"duplicate SETTINGS on remote control stream",
));
}
H3Frame::Data(_)
| H3Frame::Headers(_)
| H3Frame::PushPromise { .. }
| H3Frame::Datagram { .. } => {
return Err(H3NativeError::ControlProtocol(
"frame type not allowed on control stream",
));
}
H3Frame::CancelPush(_)
| H3Frame::Goaway(_)
| H3Frame::MaxPushId(_)
| H3Frame::Unknown { .. } => {}
}
Ok(())
} else {
match frame {
H3Frame::Settings(_) => {
self.remote_settings_received = true;
Ok(())
}
_ => Err(H3NativeError::ControlProtocol(
"first remote control frame must be SETTINGS",
)),
}
}
}
}
pub fn validate_bidirectional_frame(frame: &H3Frame) -> Result<(), H3NativeError> {
match frame {
H3Frame::Data(_) | H3Frame::Headers(_) => Ok(()),
H3Frame::PushPromise { .. } => Ok(()),
H3Frame::Datagram { .. } => Ok(()),
H3Frame::Settings(_) => Err(H3NativeError::StreamProtocol(
"SETTINGS frame not allowed on bidirectional stream",
)),
H3Frame::CancelPush(_) => Err(H3NativeError::StreamProtocol(
"CANCEL_PUSH frame not allowed on bidirectional stream",
)),
H3Frame::Goaway(_) => Err(H3NativeError::StreamProtocol(
"GOAWAY frame not allowed on bidirectional stream",
)),
H3Frame::MaxPushId(_) => Err(H3NativeError::StreamProtocol(
"MAX_PUSH_ID frame not allowed on bidirectional stream",
)),
H3Frame::Unknown { .. } => Ok(()),
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct H3PseudoHeaders {
pub method: Option<String>,
pub scheme: Option<String>,
pub authority: Option<String>,
pub path: Option<String>,
pub status: Option<u16>,
pub protocol: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct H3RequestHead {
pub pseudo: H3PseudoHeaders,
pub headers: Vec<(String, String)>,
}
impl H3RequestHead {
pub fn new(
pseudo: H3PseudoHeaders,
headers: Vec<(String, String)>,
) -> Result<Self, H3NativeError> {
validate_request_pseudo_headers(&pseudo)?;
for (name, value) in &headers {
validate_header_name(name)?;
if name.starts_with(':') {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"pseudo headers must not appear in regular header list",
));
}
validate_header_value(value)?;
}
Ok(Self { pseudo, headers })
}
pub fn new_with_settings(
pseudo: H3PseudoHeaders,
headers: Vec<(String, String)>,
enable_connect_protocol: bool,
) -> Result<Self, H3NativeError> {
validate_request_pseudo_headers_with_settings(&pseudo, enable_connect_protocol)?;
for (name, value) in &headers {
validate_header_name(name)?;
if name.starts_with(':') {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"pseudo headers must not appear in regular header list",
));
}
validate_header_value(value)?;
}
Ok(Self { pseudo, headers })
}
pub fn validate_connect_method(
&self,
enable_connect_protocol: bool,
) -> Result<(), H3NativeError> {
if self.pseudo.method.as_deref() != Some("CONNECT") {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"validate_connect_method called on non-CONNECT request",
));
}
validate_request_pseudo_headers_with_settings(&self.pseudo, enable_connect_protocol)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct H3ResponseHead {
pub status: u16,
pub headers: Vec<(String, String)>,
}
impl H3ResponseHead {
pub fn new(status: u16, headers: Vec<(String, String)>) -> Result<Self, H3NativeError> {
let pseudo = H3PseudoHeaders {
status: Some(status),
..H3PseudoHeaders::default()
};
validate_response_pseudo_headers(&pseudo)?;
for (name, value) in &headers {
validate_header_name(name)?;
if name.starts_with(':') {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"response must not include request pseudo headers",
));
}
validate_header_value(value)?;
}
Ok(Self { status, headers })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QpackFieldPlan {
StaticIndex(u64),
DynamicIndex(u64),
Literal {
name: String,
value: String,
},
DynamicNameLiteral {
name_index: u64,
value: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QpackInstructionNameRef {
Static(u64),
Dynamic(u64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QpackEncoderInstruction {
SetDynamicTableCapacity {
capacity: u64,
},
InsertWithNameReference {
name: QpackInstructionNameRef,
value: String,
},
InsertWithoutNameReference {
name: String,
value: String,
},
Duplicate {
index: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QpackDecoderInstruction {
HeaderAcknowledgement {
stream_id: u64,
},
StreamCancellation {
stream_id: u64,
},
InsertCountIncrement {
increment: u64,
},
}
#[must_use]
pub fn qpack_static_plan_for_request(head: &H3RequestHead) -> Vec<QpackFieldPlan> {
let mut out = Vec::new();
if let Some(method) = &head.pseudo.method {
if let Some(idx) = qpack_static_method_index(method) {
out.push(QpackFieldPlan::StaticIndex(idx));
} else {
out.push(QpackFieldPlan::Literal {
name: ":method".to_string(),
value: method.clone(),
});
}
}
if let Some(scheme) = &head.pseudo.scheme {
if let Some(idx) = qpack_static_scheme_index(scheme) {
out.push(QpackFieldPlan::StaticIndex(idx));
} else {
out.push(QpackFieldPlan::Literal {
name: ":scheme".to_string(),
value: scheme.clone(),
});
}
}
if let Some(path) = &head.pseudo.path {
if path == "/" {
out.push(QpackFieldPlan::StaticIndex(1));
} else {
out.push(QpackFieldPlan::Literal {
name: ":path".to_string(),
value: path.clone(),
});
}
}
if let Some(authority) = &head.pseudo.authority {
out.push(QpackFieldPlan::Literal {
name: ":authority".to_string(),
value: authority.clone(),
});
}
for (name, value) in &head.headers {
out.push(QpackFieldPlan::Literal {
name: name.clone(),
value: value.clone(),
});
}
out
}
#[must_use]
pub fn qpack_static_plan_for_response(head: &H3ResponseHead) -> Vec<QpackFieldPlan> {
let mut out = Vec::new();
if let Some(idx) = qpack_static_status_index(head.status) {
out.push(QpackFieldPlan::StaticIndex(idx));
} else {
out.push(QpackFieldPlan::Literal {
name: ":status".to_string(),
value: head.status.to_string(),
});
}
for (name, value) in &head.headers {
out.push(QpackFieldPlan::Literal {
name: name.clone(),
value: value.clone(),
});
}
out
}
pub fn qpack_encode_field_section(plan: &[QpackFieldPlan]) -> Result<Vec<u8>, H3NativeError> {
qpack_encode_field_section_with_context(plan, None)
}
pub fn qpack_encode_field_section_with_context(
plan: &[QpackFieldPlan],
qpack_context: Option<&QpackContext>,
) -> Result<Vec<u8>, H3NativeError> {
let mut out = Vec::new();
let required_insert_count = qpack_plan_required_insert_count(plan, qpack_context)?;
let encoded_insert_count = qpack_encode_required_insert_count(
required_insert_count,
qpack_context.map_or(0, |context| context.max_table_capacity),
)?;
qpack_encode_prefixed_int(&mut out, 0, 8, encoded_insert_count)?;
qpack_encode_prefixed_int(&mut out, 0, 7, 0)?;
let base = required_insert_count;
for field in plan {
match field {
QpackFieldPlan::StaticIndex(index) => {
if qpack_static_entry(*index).is_none() {
return Err(H3NativeError::InvalidFrame("unknown static qpack index"));
}
qpack_encode_prefixed_int(&mut out, 0b1100_0000, 6, *index)?;
}
QpackFieldPlan::DynamicIndex(index) => {
let context = qpack_context.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
if qpack_dynamic_entry(context.dynamic_table(), *index).is_none() {
return Err(H3NativeError::InvalidFrame("unknown dynamic qpack index"));
}
let relative = qpack_absolute_to_relative(base, *index)?;
qpack_encode_prefixed_int(&mut out, 0b1000_0000, 6, relative)?;
}
QpackFieldPlan::Literal { name, value } => {
qpack_encode_string(&mut out, 0b0010_0000, 3, name)?;
qpack_encode_string(&mut out, 0, 7, value)?;
}
QpackFieldPlan::DynamicNameLiteral { name_index, value } => {
let context = qpack_context.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
if qpack_dynamic_name(context.dynamic_table(), *name_index).is_none() {
return Err(H3NativeError::InvalidFrame(
"unknown dynamic qpack name index",
));
}
let relative = qpack_absolute_to_relative(base, *name_index)?;
qpack_encode_prefixed_int(&mut out, 0b0100_0000, 4, relative)?;
qpack_encode_string(&mut out, 0, 7, value)?;
}
}
}
Ok(out)
}
pub fn qpack_decode_field_section(
input: &[u8],
mode: H3QpackMode,
) -> Result<Vec<QpackFieldPlan>, H3NativeError> {
qpack_decode_field_section_with_context(input, mode, None)
}
fn qpack_decode_required_insert_count(
encoded_insert_count: u64,
total_inserts: u64,
max_table_capacity: usize,
) -> Result<u64, H3NativeError> {
if encoded_insert_count == 0 {
return Ok(0);
}
let max_entries = (max_table_capacity / 32) as u64;
if max_entries == 0 {
return Err(H3NativeError::QpackPolicy(
"required insert count requires dynamic table capacity",
));
}
let full_range = max_entries
.checked_mul(2)
.ok_or(H3NativeError::InvalidFrame(
"required insert count range overflow",
))?;
if encoded_insert_count > full_range {
return Err(H3NativeError::InvalidFrame(
"required insert count exceeds qpack full range",
));
}
let max_value = total_inserts
.checked_add(max_entries)
.ok_or(H3NativeError::InvalidFrame(
"required insert count exceeds addressable range",
))?;
let max_wrapped = (max_value / full_range) * full_range;
let mut required_insert_count = max_wrapped
.saturating_add(encoded_insert_count)
.saturating_sub(1);
if required_insert_count > max_value {
if required_insert_count <= full_range {
return Err(H3NativeError::InvalidFrame(
"required insert count decodes below zero",
));
}
required_insert_count -= full_range;
}
if required_insert_count == 0 {
return Err(H3NativeError::InvalidFrame(
"required insert count must decode to non-zero",
));
}
Ok(required_insert_count)
}
fn qpack_decode_base(
required_insert_count: u64,
sign: bool,
delta_base: u64,
) -> Result<u64, H3NativeError> {
if sign {
let signed_delta = delta_base
.checked_add(1)
.ok_or(H3NativeError::InvalidFrame(
"delta base exceeds required insert count",
))?;
required_insert_count
.checked_sub(signed_delta)
.ok_or(H3NativeError::InvalidFrame(
"delta base exceeds required insert count",
))
} else {
required_insert_count
.checked_add(delta_base)
.ok_or(H3NativeError::InvalidFrame(
"base exceeds addressable range",
))
}
}
fn qpack_encode_required_insert_count(
required_insert_count: u64,
max_table_capacity: usize,
) -> Result<u64, H3NativeError> {
if required_insert_count == 0 {
return Ok(0);
}
let max_entries = (max_table_capacity / 32) as u64;
if max_entries == 0 {
return Err(H3NativeError::QpackPolicy(
"required insert count requires dynamic table capacity",
));
}
let full_range = max_entries
.checked_mul(2)
.ok_or(H3NativeError::InvalidFrame(
"required insert count range overflow",
))?;
Ok((required_insert_count % full_range) + 1)
}
fn qpack_plan_required_insert_count(
plan: &[QpackFieldPlan],
qpack_context: Option<&QpackContext>,
) -> Result<u64, H3NativeError> {
let needs_dynamic = plan.iter().any(|field| {
matches!(
field,
QpackFieldPlan::DynamicIndex(_) | QpackFieldPlan::DynamicNameLiteral { .. }
)
});
if !needs_dynamic {
return Ok(0);
}
let context = qpack_context.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
Ok(context.dynamic_table().insertion_counter())
}
pub fn qpack_encode_encoder_instruction(
out: &mut Vec<u8>,
instruction: &QpackEncoderInstruction,
) -> Result<(), H3NativeError> {
match instruction {
QpackEncoderInstruction::SetDynamicTableCapacity { capacity } => {
qpack_encode_prefixed_int(out, 0b0010_0000, 5, *capacity)?;
}
QpackEncoderInstruction::InsertWithNameReference { name, value } => {
match name {
QpackInstructionNameRef::Static(index) => {
qpack_encode_prefixed_int(out, 0b1100_0000, 6, *index)?;
}
QpackInstructionNameRef::Dynamic(index) => {
qpack_encode_prefixed_int(out, 0b1000_0000, 6, *index)?;
}
}
qpack_encode_string(out, 0, 7, value)?;
}
QpackEncoderInstruction::InsertWithoutNameReference { name, value } => {
qpack_encode_string(out, 0b0100_0000, 5, name)?;
qpack_encode_string(out, 0, 7, value)?;
}
QpackEncoderInstruction::Duplicate { index } => {
qpack_encode_prefixed_int(out, 0, 5, *index)?;
}
}
Ok(())
}
pub fn qpack_decode_encoder_instruction(
input: &[u8],
) -> Result<(QpackEncoderInstruction, usize), H3NativeError> {
let first = *input.first().ok_or(H3NativeError::UnexpectedEof)?;
if (first & 0b1000_0000) != 0 {
let (index, index_extra) = qpack_decode_prefixed_int(first, 6, &input[1..])?;
let pos = 1 + index_extra;
let value_first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
let (value, value_extra) = qpack_decode_string(value_first, 7, &input[pos + 1..])?;
let name = if (first & 0b0100_0000) != 0 {
QpackInstructionNameRef::Static(index)
} else {
QpackInstructionNameRef::Dynamic(index)
};
return Ok((
QpackEncoderInstruction::InsertWithNameReference { name, value },
pos.saturating_add(1).saturating_add(value_extra),
));
}
if (first & 0b0100_0000) != 0 {
let (name, name_extra) = qpack_decode_string(first, 5, &input[1..])?;
let pos = 1 + name_extra;
let value_first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
let (value, value_extra) = qpack_decode_string(value_first, 7, &input[pos + 1..])?;
return Ok((
QpackEncoderInstruction::InsertWithoutNameReference { name, value },
pos.saturating_add(1).saturating_add(value_extra),
));
}
if (first & 0b0010_0000) != 0 {
let (capacity, extra) = qpack_decode_prefixed_int(first, 5, &input[1..])?;
return Ok((
QpackEncoderInstruction::SetDynamicTableCapacity { capacity },
1 + extra,
));
}
let (index, extra) = qpack_decode_prefixed_int(first, 5, &input[1..])?;
Ok((QpackEncoderInstruction::Duplicate { index }, 1 + extra))
}
pub fn qpack_encode_decoder_instruction(
out: &mut Vec<u8>,
instruction: &QpackDecoderInstruction,
) -> Result<(), H3NativeError> {
match instruction {
QpackDecoderInstruction::HeaderAcknowledgement { stream_id } => {
qpack_encode_prefixed_int(out, 0b1000_0000, 7, *stream_id)?;
}
QpackDecoderInstruction::StreamCancellation { stream_id } => {
qpack_encode_prefixed_int(out, 0b0100_0000, 6, *stream_id)?;
}
QpackDecoderInstruction::InsertCountIncrement { increment } => {
if *increment == 0 {
return Err(H3NativeError::InvalidFrame(
"qpack insert count increment must be non-zero",
));
}
qpack_encode_prefixed_int(out, 0, 6, *increment)?;
}
}
Ok(())
}
pub fn qpack_decode_decoder_instruction(
input: &[u8],
) -> Result<(QpackDecoderInstruction, usize), H3NativeError> {
let first = *input.first().ok_or(H3NativeError::UnexpectedEof)?;
if (first & 0b1000_0000) != 0 {
let (stream_id, extra) = qpack_decode_prefixed_int(first, 7, &input[1..])?;
return Ok((
QpackDecoderInstruction::HeaderAcknowledgement { stream_id },
1 + extra,
));
}
if (first & 0b0100_0000) != 0 {
let (stream_id, extra) = qpack_decode_prefixed_int(first, 6, &input[1..])?;
return Ok((
QpackDecoderInstruction::StreamCancellation { stream_id },
1 + extra,
));
}
let (increment, extra) = qpack_decode_prefixed_int(first, 6, &input[1..])?;
if increment == 0 {
return Err(H3NativeError::InvalidFrame(
"qpack insert count increment must be non-zero",
));
}
Ok((
QpackDecoderInstruction::InsertCountIncrement { increment },
1 + extra,
))
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct QpackDecoderFeedbackState {
known_received_count: u64,
acknowledged_streams: BTreeSet<u64>,
cancelled_streams: BTreeSet<u64>,
outstanding_references: BTreeMap<u64, Vec<u64>>,
first_error: Option<H3NativeError>,
}
impl QpackDecoderFeedbackState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn known_received_count(&self) -> u64 {
self.known_received_count
}
#[must_use]
pub fn acknowledged_stream_ids(&self) -> &BTreeSet<u64> {
&self.acknowledged_streams
}
#[must_use]
pub fn cancelled_stream_ids(&self) -> &BTreeSet<u64> {
&self.cancelled_streams
}
#[must_use]
pub fn outstanding_reference_count(&self) -> usize {
self.outstanding_references.values().map(Vec::len).sum()
}
#[must_use]
pub fn stream_outstanding_reference_count(&self, stream_id: u64) -> usize {
self.outstanding_references
.get(&stream_id)
.map_or(0, Vec::len)
}
#[must_use]
pub fn first_error(&self) -> Option<&H3NativeError> {
self.first_error.as_ref()
}
pub fn track_stream_references(
&mut self,
context: &mut QpackContext,
stream_id: u64,
references: &[u64],
) -> Result<(), H3NativeError> {
if self.acknowledged_streams.contains(&stream_id) {
return self.fail(H3NativeError::InvalidFrame(
"qpack stream already acknowledged",
));
}
if self.cancelled_streams.contains(&stream_id) {
return self.fail(H3NativeError::InvalidFrame(
"qpack stream already cancelled",
));
}
if self.outstanding_references.contains_key(&stream_id) {
return self.fail(H3NativeError::InvalidFrame("qpack stream already tracked"));
}
for insertion_id in references {
if context
.dynamic_table()
.get_by_insertion_id(*insertion_id)
.is_none()
{
return self.fail(H3NativeError::InvalidFrame(
"unknown dynamic qpack reference for stream",
));
}
}
for insertion_id in references {
let referenced = context.dynamic_table_mut().reference_entry(*insertion_id);
debug_assert!(referenced, "prechecked qpack reference must exist");
}
self.outstanding_references
.insert(stream_id, references.to_vec());
Ok(())
}
fn apply_header_acknowledgement(
&mut self,
context: &mut QpackContext,
stream_id: u64,
) -> Result<(), H3NativeError> {
if self.acknowledged_streams.contains(&stream_id) {
return self.fail(H3NativeError::InvalidFrame(
"duplicate qpack header acknowledgement",
));
}
if self.cancelled_streams.contains(&stream_id) {
return self.fail(H3NativeError::InvalidFrame(
"qpack acknowledgement after stream cancellation",
));
}
self.release_stream_references(context, stream_id)?;
self.acknowledged_streams.insert(stream_id);
Ok(())
}
fn apply_stream_cancellation(
&mut self,
context: &mut QpackContext,
stream_id: u64,
) -> Result<(), H3NativeError> {
if self.cancelled_streams.contains(&stream_id) {
return self.fail(H3NativeError::InvalidFrame(
"duplicate qpack stream cancellation",
));
}
if self.acknowledged_streams.contains(&stream_id) {
return self.fail(H3NativeError::InvalidFrame(
"qpack stream cancellation after acknowledgement",
));
}
self.release_stream_references(context, stream_id)?;
self.cancelled_streams.insert(stream_id);
Ok(())
}
fn release_stream_references(
&mut self,
context: &mut QpackContext,
stream_id: u64,
) -> Result<(), H3NativeError> {
let Some(references) = self.outstanding_references.get(&stream_id) else {
return self.fail(H3NativeError::InvalidFrame(
"unknown qpack decoder feedback stream",
));
};
let references = references.clone();
for insertion_id in &references {
if context
.dynamic_table()
.get_by_insertion_id(*insertion_id)
.is_none()
{
return self.fail(H3NativeError::InvalidFrame(
"tracked dynamic qpack reference missing",
));
}
}
self.outstanding_references.remove(&stream_id);
for insertion_id in references {
let released = context.dynamic_table_mut().unreference_entry(insertion_id);
debug_assert!(released, "prechecked qpack reference must still exist");
}
Ok(())
}
fn apply_insert_count_increment(
&mut self,
increment: u64,
insertion_counter: u64,
) -> Result<(), H3NativeError> {
if increment == 0 {
return self.fail(H3NativeError::InvalidFrame(
"qpack decoder feedback increment must be non-zero",
));
}
let Some(next) = self.known_received_count.checked_add(increment) else {
return self.fail(H3NativeError::InvalidFrame(
"qpack known received count overflow",
));
};
if next > insertion_counter {
return self.fail(H3NativeError::InvalidFrame(
"qpack known received count exceeds encoder insert count",
));
}
self.known_received_count = next;
Ok(())
}
fn fail<T>(&mut self, error: H3NativeError) -> Result<T, H3NativeError> {
self.record_error(&error);
Err(error)
}
fn record_error(&mut self, error: &H3NativeError) {
if self.first_error.is_none() {
self.first_error = Some(error.clone());
}
}
}
pub fn qpack_apply_decoder_instruction(
feedback: &mut QpackDecoderFeedbackState,
context: &mut QpackContext,
mode: H3QpackMode,
instruction: &QpackDecoderInstruction,
) -> Result<(), H3NativeError> {
if mode != H3QpackMode::DynamicTableAllowed {
let error = H3NativeError::QpackPolicy("decoder feedback requires dynamic qpack mode");
feedback.record_error(&error);
return Err(error);
}
match instruction {
QpackDecoderInstruction::HeaderAcknowledgement { stream_id } => {
feedback.apply_header_acknowledgement(context, *stream_id)
}
QpackDecoderInstruction::StreamCancellation { stream_id } => {
feedback.apply_stream_cancellation(context, *stream_id)
}
QpackDecoderInstruction::InsertCountIncrement { increment } => feedback
.apply_insert_count_increment(*increment, context.dynamic_table().insertion_counter()),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QpackFieldSectionMetadata {
encoded_insert_count: u64,
required_insert_count: u64,
base: u64,
prefix_len: usize,
}
impl QpackFieldSectionMetadata {
#[must_use]
pub fn encoded_insert_count(&self) -> u64 {
self.encoded_insert_count
}
#[must_use]
pub fn required_insert_count(&self) -> u64 {
self.required_insert_count
}
#[must_use]
pub fn base(&self) -> u64 {
self.base
}
#[must_use]
pub fn prefix_len(&self) -> usize {
self.prefix_len
}
}
pub fn qpack_field_section_metadata(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
) -> Result<QpackFieldSectionMetadata, H3NativeError> {
let mut pos = 0usize;
let first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
pos += 1;
let (encoded_insert_count, ric_extra) = qpack_decode_prefixed_int(first, 8, &input[pos..])?;
pos += ric_extra;
let second = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
pos += 1;
let sign = (second & 0x80) != 0;
let (delta_base, db_extra) = qpack_decode_prefixed_int(second, 7, &input[pos..])?;
pos += db_extra;
match mode {
H3QpackMode::StaticOnly => {
if encoded_insert_count != 0 {
return Err(H3NativeError::QpackPolicy(
"required insert count must be zero in static-only mode",
));
}
if sign || delta_base != 0 {
return Err(H3NativeError::QpackPolicy(
"base must be zero in static-only mode",
));
}
Ok(QpackFieldSectionMetadata {
encoded_insert_count,
required_insert_count: 0,
base: 0,
prefix_len: pos,
})
}
H3QpackMode::DynamicTableAllowed => {
if encoded_insert_count > 65536 {
return Err(H3NativeError::QpackPolicy(
"required insert count exceeds reasonable limit",
));
}
if encoded_insert_count == 0 {
if sign || delta_base != 0 {
return Err(H3NativeError::InvalidFrame(
"base must be zero without required insert count",
));
}
return Ok(QpackFieldSectionMetadata {
encoded_insert_count,
required_insert_count: 0,
base: 0,
prefix_len: pos,
});
}
let context = qpack_context.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
let required_insert_count = qpack_decode_required_insert_count(
encoded_insert_count,
context.dynamic_table().insertion_counter(),
context.max_table_capacity,
)?;
let base = qpack_decode_base(required_insert_count, sign, delta_base)?;
Ok(QpackFieldSectionMetadata {
encoded_insert_count,
required_insert_count,
base,
prefix_len: pos,
})
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QpackBlockedStreamStatus {
Ready,
Blocked,
Cancelled,
Failed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QpackBlockedStreamRecord {
stream_id: u64,
required_insert_count: u64,
base: u64,
status: QpackBlockedStreamStatus,
blocked_reason: Option<&'static str>,
protected_references: Vec<u64>,
blocked_field_section: Option<Vec<u8>>,
first_failure: Option<H3NativeError>,
}
impl QpackBlockedStreamRecord {
fn new(
stream_id: u64,
metadata: &QpackFieldSectionMetadata,
status: QpackBlockedStreamStatus,
blocked_reason: Option<&'static str>,
protected_references: Vec<u64>,
) -> Self {
Self {
stream_id,
required_insert_count: metadata.required_insert_count(),
base: metadata.base(),
status,
blocked_reason,
protected_references,
blocked_field_section: None,
first_failure: None,
}
}
fn failed(
stream_id: u64,
metadata: Option<&QpackFieldSectionMetadata>,
error: H3NativeError,
) -> Self {
Self {
stream_id,
required_insert_count: metadata
.map_or(0, QpackFieldSectionMetadata::required_insert_count),
base: metadata.map_or(0, QpackFieldSectionMetadata::base),
status: QpackBlockedStreamStatus::Failed,
blocked_reason: None,
protected_references: Vec::new(),
blocked_field_section: None,
first_failure: Some(error),
}
}
fn record_failure(&mut self, error: &H3NativeError) {
if self.first_failure.is_none() {
self.first_failure = Some(error.clone());
}
if self.status != QpackBlockedStreamStatus::Cancelled {
self.status = QpackBlockedStreamStatus::Failed;
self.blocked_reason = None;
}
}
fn is_reapable_terminal(&self) -> bool {
self.status != QpackBlockedStreamStatus::Blocked
&& self.protected_references.is_empty()
&& self.blocked_field_section.is_none()
}
#[must_use]
pub fn stream_id(&self) -> u64 {
self.stream_id
}
#[must_use]
pub fn required_insert_count(&self) -> u64 {
self.required_insert_count
}
#[must_use]
pub fn base(&self) -> u64 {
self.base
}
#[must_use]
pub fn status(&self) -> QpackBlockedStreamStatus {
self.status
}
#[must_use]
pub fn blocked_reason(&self) -> Option<&'static str> {
self.blocked_reason
}
#[must_use]
pub fn protected_references(&self) -> &[u64] {
&self.protected_references
}
#[must_use]
pub fn first_failure(&self) -> Option<&H3NativeError> {
self.first_failure.as_ref()
}
}
const MAX_RETAINED_TERMINAL_RECORDS: usize = 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QpackBlockedStreamScheduler {
settings_blocked_streams: u64,
streams: BTreeMap<u64, QpackBlockedStreamRecord>,
first_failure: Option<H3NativeError>,
}
impl QpackBlockedStreamScheduler {
#[must_use]
pub fn new(settings_blocked_streams: u64) -> Self {
Self {
settings_blocked_streams,
streams: BTreeMap::new(),
first_failure: None,
}
}
#[must_use]
pub fn from_settings(settings: &H3Settings) -> Self {
Self::new(settings.qpack_blocked_streams.unwrap_or(0))
}
#[must_use]
pub fn settings_blocked_streams(&self) -> u64 {
self.settings_blocked_streams
}
#[must_use]
pub fn blocked_stream_count(&self) -> u64 {
self.streams
.values()
.filter(|record| record.status == QpackBlockedStreamStatus::Blocked)
.count() as u64
}
#[must_use]
pub fn tracked_record_count(&self) -> usize {
self.streams.len()
}
fn reap_excess_records(&mut self) {
let terminal = self
.streams
.values()
.filter(|record| record.is_reapable_terminal())
.count();
if terminal <= MAX_RETAINED_TERMINAL_RECORDS {
return;
}
let excess = terminal - MAX_RETAINED_TERMINAL_RECORDS;
let victims: Vec<u64> = self
.streams
.iter()
.filter(|(_, record)| record.is_reapable_terminal())
.take(excess)
.map(|(stream_id, _)| *stream_id)
.collect();
for stream_id in victims {
self.streams.remove(&stream_id);
}
}
#[must_use]
pub fn record(&self, stream_id: u64) -> Option<&QpackBlockedStreamRecord> {
self.streams.get(&stream_id)
}
#[must_use]
pub fn first_failure(&self) -> Option<&H3NativeError> {
self.first_failure.as_ref()
}
pub fn submit_field_section(
&mut self,
context: &mut QpackContext,
feedback: &mut QpackDecoderFeedbackState,
mode: H3QpackMode,
stream_id: u64,
field_section: &[u8],
) -> Result<QpackBlockedStreamStatus, H3NativeError> {
self.reap_excess_records();
if self.streams.contains_key(&stream_id) {
return self.fail(H3NativeError::StreamProtocol(
"qpack stream already scheduled",
));
}
let metadata = match qpack_field_section_metadata(field_section, mode, Some(context)) {
Ok(metadata) => metadata,
Err(error) => {
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, None, error.clone()),
);
return self.fail(error);
}
};
let context_opt = (mode == H3QpackMode::DynamicTableAllowed).then_some(&*context);
let plan = match qpack_decode_field_section_with_context(field_section, mode, context_opt) {
Ok(plan) => plan,
Err(error) => {
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
};
let references = qpack_plan_dynamic_references(&plan);
let will_block = metadata.required_insert_count() > feedback.known_received_count();
if will_block && self.settings_blocked_streams == 0 {
let error = H3NativeError::QpackPolicy("qpack blocked stream capacity is zero");
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
if will_block && self.blocked_stream_count() >= self.settings_blocked_streams {
let error = H3NativeError::QpackPolicy("qpack blocked stream capacity exceeded");
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
if mode == H3QpackMode::DynamicTableAllowed {
if let Err(error) = feedback.track_stream_references(context, stream_id, &references) {
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
}
let status = if will_block {
QpackBlockedStreamStatus::Blocked
} else {
QpackBlockedStreamStatus::Ready
};
let blocked_reason =
will_block.then_some("required insert count exceeds known received count");
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::new(stream_id, &metadata, status, blocked_reason, references),
);
Ok(status)
}
pub fn submit_received_field_section(
&mut self,
context: &mut QpackContext,
feedback: &mut QpackDecoderFeedbackState,
mode: H3QpackMode,
stream_id: u64,
field_section: &[u8],
) -> Result<QpackBlockedStreamStatus, H3NativeError> {
self.reap_excess_records();
if self.streams.contains_key(&stream_id) {
return self.fail(H3NativeError::StreamProtocol(
"qpack stream already scheduled",
));
}
let metadata = match qpack_field_section_metadata(field_section, mode, Some(context)) {
Ok(metadata) => metadata,
Err(error) => {
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, None, error.clone()),
);
return self.fail(error);
}
};
if mode == H3QpackMode::DynamicTableAllowed
&& metadata.required_insert_count() > context.dynamic_table().insertion_counter()
{
if self.settings_blocked_streams == 0 {
let error = H3NativeError::QpackPolicy("qpack blocked stream capacity is zero");
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
if self.blocked_stream_count() >= self.settings_blocked_streams {
let error = H3NativeError::QpackPolicy("qpack blocked stream capacity exceeded");
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
let mut record = QpackBlockedStreamRecord::new(
stream_id,
&metadata,
QpackBlockedStreamStatus::Blocked,
Some("required insert count exceeds dynamic table state"),
Vec::new(),
);
record.blocked_field_section = Some(field_section.to_vec());
self.streams.insert(stream_id, record);
return Ok(QpackBlockedStreamStatus::Blocked);
}
let context_opt = (mode == H3QpackMode::DynamicTableAllowed).then_some(&*context);
let plan = match qpack_decode_field_section_with_context(field_section, mode, context_opt) {
Ok(plan) => plan,
Err(error) => {
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
};
let references = qpack_plan_dynamic_references(&plan);
if mode == H3QpackMode::DynamicTableAllowed {
if let Err(error) = feedback.track_stream_references(context, stream_id, &references) {
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::failed(stream_id, Some(&metadata), error.clone()),
);
return self.fail(error);
}
}
self.streams.insert(
stream_id,
QpackBlockedStreamRecord::new(
stream_id,
&metadata,
QpackBlockedStreamStatus::Ready,
None,
references,
),
);
Ok(QpackBlockedStreamStatus::Ready)
}
pub fn apply_decoder_instruction(
&mut self,
feedback: &mut QpackDecoderFeedbackState,
context: &mut QpackContext,
mode: H3QpackMode,
instruction: &QpackDecoderInstruction,
) -> Result<Vec<u64>, H3NativeError> {
match instruction {
QpackDecoderInstruction::InsertCountIncrement { .. } => {
qpack_apply_decoder_instruction(feedback, context, mode, instruction)
.map_err(|error| self.record_global_error(error))?;
Ok(self.unblock_ready(feedback.known_received_count()))
}
QpackDecoderInstruction::HeaderAcknowledgement { stream_id } => {
let had_record = self.streams.contains_key(stream_id);
if let Err(error) =
qpack_apply_decoder_instruction(feedback, context, mode, instruction)
{
if had_record {
self.record_stream_error(*stream_id, &error);
} else {
self.record_error(&error);
}
return Err(error);
}
self.streams.remove(stream_id);
Ok(Vec::new())
}
QpackDecoderInstruction::StreamCancellation { stream_id } => {
let had_record = self.streams.contains_key(stream_id);
if let Err(error) =
qpack_apply_decoder_instruction(feedback, context, mode, instruction)
{
if had_record {
self.record_stream_error(*stream_id, &error);
} else {
self.record_error(&error);
}
return Err(error);
}
self.streams.remove(stream_id);
Ok(Vec::new())
}
}
}
pub fn apply_encoder_instruction(
&mut self,
context: &mut QpackContext,
feedback: &mut QpackDecoderFeedbackState,
mode: H3QpackMode,
instruction: &QpackEncoderInstruction,
) -> Result<(Option<u64>, Vec<u64>), H3NativeError> {
let inserted = qpack_apply_encoder_instruction(context, mode, instruction)
.map_err(|error| self.record_global_error(error))?;
let mut unblocked = self.unblock_decodable(context, feedback, mode)?;
unblocked.extend(self.unblock_ready(feedback.known_received_count()));
Ok((inserted, unblocked))
}
pub fn cancel_stream(
&mut self,
feedback: &mut QpackDecoderFeedbackState,
context: &mut QpackContext,
mode: H3QpackMode,
stream_id: u64,
) -> Result<(), H3NativeError> {
self.apply_decoder_instruction(
feedback,
context,
mode,
&QpackDecoderInstruction::StreamCancellation { stream_id },
)
.map(|_| ())
}
fn unblock_ready(&mut self, known_received_count: u64) -> Vec<u64> {
let mut unblocked = Vec::new();
for record in self.streams.values_mut() {
if record.status == QpackBlockedStreamStatus::Blocked
&& record.required_insert_count <= known_received_count
{
record.status = QpackBlockedStreamStatus::Ready;
record.blocked_reason = None;
unblocked.push(record.stream_id);
}
}
unblocked
}
fn unblock_decodable(
&mut self,
context: &mut QpackContext,
feedback: &mut QpackDecoderFeedbackState,
mode: H3QpackMode,
) -> Result<Vec<u64>, H3NativeError> {
let ready_ids: Vec<u64> = self
.streams
.iter()
.filter(|(_, record)| {
record.status == QpackBlockedStreamStatus::Blocked
&& record.blocked_field_section.is_some()
&& record.required_insert_count <= context.dynamic_table().insertion_counter()
})
.map(|(stream_id, _)| *stream_id)
.collect();
let mut unblocked = Vec::new();
for stream_id in ready_ids {
let field_section = self
.streams
.get(&stream_id)
.and_then(|record| record.blocked_field_section.clone())
.ok_or(H3NativeError::InvalidFrame(
"qpack blocked field section missing",
))?;
let context_opt = (mode == H3QpackMode::DynamicTableAllowed).then_some(&*context);
let plan =
match qpack_decode_field_section_with_context(&field_section, mode, context_opt) {
Ok(plan) => plan,
Err(error) => {
self.record_stream_error(stream_id, &error);
return Err(error);
}
};
let references = qpack_plan_dynamic_references(&plan);
if mode == H3QpackMode::DynamicTableAllowed {
if let Err(error) =
feedback.track_stream_references(context, stream_id, &references)
{
self.record_stream_error(stream_id, &error);
return Err(error);
}
}
if let Some(record) = self.streams.get_mut(&stream_id) {
record.status = QpackBlockedStreamStatus::Ready;
record.blocked_reason = None;
record.protected_references = references;
record.blocked_field_section = None;
}
unblocked.push(stream_id);
}
Ok(unblocked)
}
fn record_stream_error(&mut self, stream_id: u64, error: &H3NativeError) {
self.record_error(error);
if let Some(record) = self.streams.get_mut(&stream_id) {
record.record_failure(error);
}
}
fn record_global_error(&mut self, error: H3NativeError) -> H3NativeError {
self.record_error(&error);
error
}
fn fail<T>(&mut self, error: H3NativeError) -> Result<T, H3NativeError> {
self.record_error(&error);
Err(error)
}
fn record_error(&mut self, error: &H3NativeError) {
if self.first_failure.is_none() {
self.first_failure = Some(error.clone());
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct QpackInstructionStreamOutcome {
instructions_processed: usize,
inserted_entry_ids: Vec<u64>,
unblocked_stream_ids: Vec<u64>,
}
impl QpackInstructionStreamOutcome {
fn record_encoder_result(&mut self, inserted: Option<u64>, mut unblocked: Vec<u64>) {
self.instructions_processed += 1;
if let Some(insertion_id) = inserted {
self.inserted_entry_ids.push(insertion_id);
}
self.unblocked_stream_ids.append(&mut unblocked);
}
fn record_decoder_result(&mut self, mut unblocked: Vec<u64>) {
self.instructions_processed += 1;
self.unblocked_stream_ids.append(&mut unblocked);
}
#[must_use]
pub fn instructions_processed(&self) -> usize {
self.instructions_processed
}
#[must_use]
pub fn inserted_entry_ids(&self) -> &[u64] {
&self.inserted_entry_ids
}
#[must_use]
pub fn unblocked_stream_ids(&self) -> &[u64] {
&self.unblocked_stream_ids
}
}
#[derive(Debug)]
pub struct QpackInstructionStreamState {
mode: H3QpackMode,
context: QpackContext,
decoder_feedback: QpackDecoderFeedbackState,
blocked_scheduler: QpackBlockedStreamScheduler,
encoder_stream_id: Option<u64>,
decoder_stream_id: Option<u64>,
first_failure: Option<H3NativeError>,
}
impl QpackInstructionStreamState {
pub fn new(
mode: H3QpackMode,
max_table_capacity: u64,
settings_blocked_streams: u64,
) -> Result<Self, H3NativeError> {
if mode == H3QpackMode::StaticOnly {
if max_table_capacity > 0 {
return Err(H3NativeError::QpackPolicy(
"dynamic qpack table disabled by policy",
));
}
if settings_blocked_streams > 0 {
return Err(H3NativeError::QpackPolicy(
"qpack blocked streams must be zero in static-only mode",
));
}
}
let max_table_capacity: usize = max_table_capacity.try_into().map_err(|_| {
H3NativeError::InvalidFrame("qpack dynamic table capacity exceeds addressable range")
})?;
Ok(Self {
mode,
context: QpackContext::new(max_table_capacity),
decoder_feedback: QpackDecoderFeedbackState::new(),
blocked_scheduler: QpackBlockedStreamScheduler::new(settings_blocked_streams),
encoder_stream_id: None,
decoder_stream_id: None,
first_failure: None,
})
}
pub fn from_settings(mode: H3QpackMode, settings: &H3Settings) -> Result<Self, H3NativeError> {
if mode == H3QpackMode::StaticOnly {
return Self::new(mode, 0, 0);
}
Self::new(
mode,
settings.qpack_max_table_capacity.unwrap_or(0),
settings.qpack_blocked_streams.unwrap_or(0),
)
}
#[must_use]
pub fn mode(&self) -> H3QpackMode {
self.mode
}
#[must_use]
pub fn context(&self) -> &QpackContext {
&self.context
}
#[must_use]
pub fn decoder_feedback(&self) -> &QpackDecoderFeedbackState {
&self.decoder_feedback
}
#[must_use]
pub fn blocked_scheduler(&self) -> &QpackBlockedStreamScheduler {
&self.blocked_scheduler
}
#[must_use]
pub fn encoder_stream_id(&self) -> Option<u64> {
self.encoder_stream_id
}
#[must_use]
pub fn decoder_stream_id(&self) -> Option<u64> {
self.decoder_stream_id
}
#[must_use]
pub fn known_received_count(&self) -> u64 {
self.decoder_feedback.known_received_count()
}
#[must_use]
pub fn blocked_stream_count(&self) -> u64 {
self.blocked_scheduler.blocked_stream_count()
}
#[must_use]
pub fn settings_blocked_streams(&self) -> u64 {
self.blocked_scheduler.settings_blocked_streams()
}
#[must_use]
pub fn first_failure(&self) -> Option<&H3NativeError> {
self.first_failure
.as_ref()
.or_else(|| self.blocked_scheduler.first_failure())
.or_else(|| self.decoder_feedback.first_error())
}
pub fn register_stream(
&mut self,
stream_id: u64,
kind: H3UniStreamType,
) -> Result<(), H3NativeError> {
if self.registered_stream_kind(stream_id).is_some() {
return self.fail(H3NativeError::StreamProtocol(
"qpack instruction stream id already registered",
));
}
match kind {
H3UniStreamType::QpackEncoder => {
if self.encoder_stream_id.is_some() {
return self.fail(H3NativeError::StreamProtocol(
"duplicate remote qpack encoder stream",
));
}
self.encoder_stream_id = Some(stream_id);
Ok(())
}
H3UniStreamType::QpackDecoder => {
if self.decoder_stream_id.is_some() {
return self.fail(H3NativeError::StreamProtocol(
"duplicate remote qpack decoder stream",
));
}
self.decoder_stream_id = Some(stream_id);
Ok(())
}
H3UniStreamType::Control | H3UniStreamType::Push | H3UniStreamType::Unknown(_) => self
.fail(H3NativeError::StreamProtocol(
"qpack instruction stream requires qpack stream type",
)),
}
}
pub fn register_from_connection(
&mut self,
connection: &H3ConnectionState,
stream_id: u64,
) -> Result<H3UniStreamType, H3NativeError> {
let kind =
connection
.remote_uni_stream_type(stream_id)
.ok_or(H3NativeError::StreamProtocol(
"unknown unidirectional stream",
))?;
self.register_stream(stream_id, kind)?;
Ok(kind)
}
pub fn ensure_stream_registered(
&mut self,
stream_id: u64,
kind: H3UniStreamType,
) -> Result<(), H3NativeError> {
match self.registered_stream_kind(stream_id) {
Some(actual) if actual == kind => Ok(()),
Some(_) => self.fail(H3NativeError::StreamProtocol(
"qpack instruction type does not match registered stream",
)),
None => self.register_stream(stream_id, kind),
}
}
pub fn feed_encoder_stream_bytes(
&mut self,
stream_id: u64,
bytes: &[u8],
) -> Result<QpackInstructionStreamOutcome, H3NativeError> {
self.ensure_stream_kind(stream_id, H3UniStreamType::QpackEncoder)?;
let mut pos = 0usize;
let mut outcome = QpackInstructionStreamOutcome::default();
while pos < bytes.len() {
let (instruction, consumed) = match qpack_decode_encoder_instruction(&bytes[pos..]) {
Ok(decoded) => decoded,
Err(error) => return self.fail(error),
};
let (inserted, unblocked) = match self.blocked_scheduler.apply_encoder_instruction(
&mut self.context,
&mut self.decoder_feedback,
self.mode,
&instruction,
) {
Ok(result) => result,
Err(error) => {
self.record_error(&error);
return Err(error);
}
};
outcome.record_encoder_result(inserted, unblocked);
pos += consumed;
}
Ok(outcome)
}
pub fn feed_decoder_stream_bytes(
&mut self,
stream_id: u64,
bytes: &[u8],
) -> Result<QpackInstructionStreamOutcome, H3NativeError> {
self.ensure_stream_kind(stream_id, H3UniStreamType::QpackDecoder)?;
let mut pos = 0usize;
let mut outcome = QpackInstructionStreamOutcome::default();
while pos < bytes.len() {
let (instruction, consumed) = match qpack_decode_decoder_instruction(&bytes[pos..]) {
Ok(decoded) => decoded,
Err(error) => return self.fail(error),
};
let unblocked = match self.blocked_scheduler.apply_decoder_instruction(
&mut self.decoder_feedback,
&mut self.context,
self.mode,
&instruction,
) {
Ok(result) => result,
Err(error) => {
self.record_error(&error);
return Err(error);
}
};
outcome.record_decoder_result(unblocked);
pos += consumed;
}
Ok(outcome)
}
pub fn feed_instruction_stream_bytes(
&mut self,
stream_id: u64,
kind: H3UniStreamType,
bytes: &[u8],
) -> Result<QpackInstructionStreamOutcome, H3NativeError> {
match kind {
H3UniStreamType::QpackEncoder => self.feed_encoder_stream_bytes(stream_id, bytes),
H3UniStreamType::QpackDecoder => self.feed_decoder_stream_bytes(stream_id, bytes),
H3UniStreamType::Control | H3UniStreamType::Push | H3UniStreamType::Unknown(_) => self
.fail(H3NativeError::StreamProtocol(
"qpack instruction stream requires qpack stream type",
)),
}
}
pub fn submit_field_section(
&mut self,
stream_id: u64,
field_section: &[u8],
) -> Result<QpackBlockedStreamStatus, H3NativeError> {
self.blocked_scheduler.submit_field_section(
&mut self.context,
&mut self.decoder_feedback,
self.mode,
stream_id,
field_section,
)
}
pub fn submit_received_field_section(
&mut self,
stream_id: u64,
field_section: &[u8],
) -> Result<QpackBlockedStreamStatus, H3NativeError> {
self.blocked_scheduler.submit_received_field_section(
&mut self.context,
&mut self.decoder_feedback,
self.mode,
stream_id,
field_section,
)
}
pub fn cancel_stream(&mut self, stream_id: u64) -> Result<(), H3NativeError> {
self.blocked_scheduler.cancel_stream(
&mut self.decoder_feedback,
&mut self.context,
self.mode,
stream_id,
)
}
fn ensure_stream_kind(
&mut self,
stream_id: u64,
expected: H3UniStreamType,
) -> Result<(), H3NativeError> {
match self.registered_stream_kind(stream_id) {
Some(actual) if actual == expected => Ok(()),
Some(_) => self.fail(H3NativeError::StreamProtocol(
"qpack instruction type does not match registered stream",
)),
None => self.fail(H3NativeError::StreamProtocol(
"unknown qpack instruction stream",
)),
}
}
fn registered_stream_kind(&self, stream_id: u64) -> Option<H3UniStreamType> {
if self.encoder_stream_id == Some(stream_id) {
return Some(H3UniStreamType::QpackEncoder);
}
if self.decoder_stream_id == Some(stream_id) {
return Some(H3UniStreamType::QpackDecoder);
}
None
}
fn fail<T>(&mut self, error: H3NativeError) -> Result<T, H3NativeError> {
self.record_error(&error);
Err(error)
}
fn record_error(&mut self, error: &H3NativeError) {
if self.first_failure.is_none() {
self.first_failure = Some(error.clone());
}
}
}
fn qpack_plan_dynamic_references(plan: &[QpackFieldPlan]) -> Vec<u64> {
let mut references = BTreeSet::new();
for field in plan {
match field {
QpackFieldPlan::DynamicIndex(index) => {
references.insert(*index);
}
QpackFieldPlan::DynamicNameLiteral { name_index, .. } => {
references.insert(*name_index);
}
QpackFieldPlan::StaticIndex(_) | QpackFieldPlan::Literal { .. } => {}
}
}
references.into_iter().collect()
}
pub fn qpack_apply_encoder_instruction(
context: &mut QpackContext,
mode: H3QpackMode,
instruction: &QpackEncoderInstruction,
) -> Result<Option<u64>, H3NativeError> {
if mode != H3QpackMode::DynamicTableAllowed {
return Err(H3NativeError::QpackPolicy(
"encoder instructions require dynamic qpack mode",
));
}
match instruction {
QpackEncoderInstruction::SetDynamicTableCapacity { capacity } => {
let capacity: usize = (*capacity).try_into().map_err(|_| {
H3NativeError::InvalidFrame(
"qpack dynamic table capacity exceeds addressable range",
)
})?;
context
.set_dynamic_table_capacity(capacity)
.map_err(qpack_capacity_error)?;
Ok(None)
}
QpackEncoderInstruction::InsertWithNameReference { name, value } => {
let name = match name {
QpackInstructionNameRef::Static(index) => qpack_static_name(*index)
.ok_or(H3NativeError::InvalidFrame(
"unknown static qpack name index",
))?
.to_string(),
QpackInstructionNameRef::Dynamic(index) => context
.dynamic_table()
.get_by_relative_index(*index)
.ok_or(H3NativeError::InvalidFrame(
"unknown dynamic qpack name index",
))?
.name()
.to_string(),
};
context
.insert_dynamic_entry(name, value.clone())
.map(Some)
.map_err(qpack_insert_error)
}
QpackEncoderInstruction::InsertWithoutNameReference { name, value } => context
.insert_dynamic_entry(name.clone(), value.clone())
.map(Some)
.map_err(qpack_insert_error),
QpackEncoderInstruction::Duplicate { index } => {
let entry = context
.dynamic_table()
.get_by_relative_index(*index)
.ok_or(H3NativeError::InvalidFrame(
"unknown dynamic qpack duplicate index",
))?;
let name = entry.name().to_string();
let value = entry.value().to_string();
context
.insert_dynamic_entry(name, value)
.map(Some)
.map_err(qpack_insert_error)
}
}
}
fn qpack_capacity_error(err: &'static str) -> H3NativeError {
match err {
"capacity exceeds peer limit" => {
H3NativeError::QpackPolicy("qpack dynamic table capacity exceeds peer limit")
}
"cannot reduce table capacity while entries are referenced" => H3NativeError::InvalidFrame(
"qpack dynamic table capacity shrink blocked by referenced entries",
),
_ => H3NativeError::InvalidFrame("qpack dynamic table capacity update failed"),
}
}
fn qpack_insert_error(err: &'static str) -> H3NativeError {
match err {
"entry larger than table capacity" => {
H3NativeError::InvalidFrame("qpack dynamic table entry exceeds capacity")
}
"cannot evict enough space (all entries referenced)" => {
H3NativeError::InvalidFrame("qpack dynamic table insert blocked by referenced entries")
}
_ => H3NativeError::InvalidFrame("qpack dynamic table insert failed"),
}
}
#[doc(hidden)]
pub fn fuzz_parse_status_code(value: &str) -> Result<u16, H3NativeError> {
parse_status_code(value)
}
#[doc(hidden)]
pub fn fuzz_qpack_decode_required_insert_count(
encoded_insert_count: u64,
total_inserts: u64,
max_table_capacity: usize,
) -> Result<u64, H3NativeError> {
qpack_decode_required_insert_count(encoded_insert_count, total_inserts, max_table_capacity)
}
#[doc(hidden)]
pub fn fuzz_qpack_decode_base(
required_insert_count: u64,
sign: bool,
delta_base: u64,
) -> Result<u64, H3NativeError> {
qpack_decode_base(required_insert_count, sign, delta_base)
}
fn qpack_relative_to_absolute(
base: u64,
relative_index: u64,
is_post_base: bool,
) -> Result<u64, H3NativeError> {
if is_post_base {
base.checked_add(relative_index)
.ok_or(H3NativeError::InvalidFrame(
"dynamic qpack post-base index overflow",
))
} else {
let plus_one = relative_index
.checked_add(1)
.ok_or(H3NativeError::InvalidFrame(
"dynamic qpack relative index +1 overflow (H3_QPACK_DECODER_STREAM_ERROR)",
))?;
base.checked_sub(plus_one)
.ok_or(H3NativeError::InvalidFrame(
"dynamic qpack relative index exceeds base (H3_QPACK_DECODER_STREAM_ERROR)",
))
}
}
fn qpack_absolute_to_relative(base: u64, absolute_index: u64) -> Result<u64, H3NativeError> {
let next = absolute_index
.checked_add(1)
.ok_or(H3NativeError::InvalidFrame(
"dynamic qpack absolute index overflow",
))?;
base.checked_sub(next).ok_or(H3NativeError::InvalidFrame(
"dynamic qpack absolute index exceeds base",
))
}
fn qpack_decode_field_section_with_context(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
) -> Result<Vec<QpackFieldPlan>, H3NativeError> {
let mut pos = 0usize;
let first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
pos += 1;
let (encoded_insert_count, ric_extra) = qpack_decode_prefixed_int(first, 8, &input[pos..])?;
pos += ric_extra;
let second = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
pos += 1;
let sign = (second & 0x80) != 0;
let (delta_base, db_extra) = qpack_decode_prefixed_int(second, 7, &input[pos..])?;
pos += db_extra;
let dynamic_base = match mode {
H3QpackMode::StaticOnly => {
if encoded_insert_count != 0 {
return Err(H3NativeError::QpackPolicy(
"required insert count must be zero in static-only mode",
));
}
if sign || delta_base != 0 {
return Err(H3NativeError::QpackPolicy(
"base must be zero in static-only mode",
));
}
None
}
H3QpackMode::DynamicTableAllowed => {
if encoded_insert_count > 65536 {
return Err(H3NativeError::QpackPolicy(
"required insert count exceeds reasonable limit",
));
}
if let Some(context) = qpack_context {
let total_inserts = context.dynamic_table().insertion_counter();
let required_insert_count = qpack_decode_required_insert_count(
encoded_insert_count,
total_inserts,
context.max_table_capacity,
)?;
if required_insert_count > total_inserts {
return Err(H3NativeError::QpackPolicy(
"required insert count exceeds dynamic table state",
));
}
let base = qpack_decode_base(required_insert_count, sign, delta_base)?;
if base > total_inserts {
return Err(H3NativeError::InvalidFrame(
"dynamic qpack base exceeds dynamic table state",
));
}
Some(base)
} else {
if encoded_insert_count != 0 || sign || delta_base != 0 {
return Err(H3NativeError::InvalidFrame(
"dynamic table context required",
));
}
None
}
}
};
let mut out = Vec::new();
while pos < input.len() {
let b = input[pos];
if (b & 0x80) != 0 {
let is_static = (b & 0x40) != 0;
let (index, extra) = qpack_decode_prefixed_int(b, 6, &input[pos + 1..])?;
pos += 1 + extra;
if !is_static && mode == H3QpackMode::StaticOnly {
return Err(H3NativeError::QpackPolicy(
"dynamic qpack index references not allowed in static-only mode",
));
}
if is_static {
if qpack_static_entry(index).is_none() {
return Err(H3NativeError::InvalidFrame("unknown static qpack index"));
}
out.push(QpackFieldPlan::StaticIndex(index));
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
} else {
let base = dynamic_base.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
let absolute_index = qpack_relative_to_absolute(base, index, false)?;
out.push(QpackFieldPlan::DynamicIndex(absolute_index));
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
}
continue;
}
if (b & 0x40) != 0 {
let is_static = (b & 0x10) != 0;
let (name_index, extra) = qpack_decode_prefixed_int(b, 4, &input[pos + 1..])?;
pos += 1 + extra;
if !is_static && mode == H3QpackMode::StaticOnly {
return Err(H3NativeError::QpackPolicy(
"dynamic qpack name references not allowed in static-only mode",
));
}
let value_first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
let (value, value_extra) = qpack_decode_string(value_first, 7, &input[pos + 1..])?;
pos += 1 + value_extra;
if is_static {
let name = qpack_static_name(name_index).ok_or(H3NativeError::InvalidFrame(
"unknown static qpack name index",
))?;
out.push(QpackFieldPlan::Literal {
name: name.to_string(),
value,
});
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
} else {
let base = dynamic_base.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
let absolute_name_index = qpack_relative_to_absolute(base, name_index, false)?;
out.push(QpackFieldPlan::DynamicNameLiteral {
name_index: absolute_name_index,
value,
});
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
}
continue;
}
if (b & 0x20) != 0 {
let (name, name_extra) = qpack_decode_string(b, 3, &input[pos + 1..])?;
pos += 1 + name_extra;
let value_first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
let (value, value_extra) = qpack_decode_string(value_first, 7, &input[pos + 1..])?;
pos += 1 + value_extra;
out.push(QpackFieldPlan::Literal { name, value });
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
continue;
}
if mode == H3QpackMode::StaticOnly {
return Err(H3NativeError::QpackPolicy(
"post-base/dynamic qpack line representations not allowed in static-only mode",
));
}
let base = dynamic_base.ok_or(H3NativeError::InvalidFrame(
"dynamic table context required",
))?;
if (b & 0x10) != 0 {
let (index, extra) = qpack_decode_prefixed_int(b, 4, &input[pos + 1..])?;
pos += 1 + extra;
let absolute_index = qpack_relative_to_absolute(base, index, true)?;
out.push(QpackFieldPlan::DynamicIndex(absolute_index));
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
continue;
}
let (name_index, extra) = qpack_decode_prefixed_int(b, 3, &input[pos + 1..])?;
pos += 1 + extra;
let value_first = *input.get(pos).ok_or(H3NativeError::UnexpectedEof)?;
let (value, value_extra) = qpack_decode_string(value_first, 7, &input[pos + 1..])?;
pos += 1 + value_extra;
let absolute_name_index = qpack_relative_to_absolute(base, name_index, true)?;
out.push(QpackFieldPlan::DynamicNameLiteral {
name_index: absolute_name_index,
value,
});
if out.len() > QPACK_MAX_DECODED_HEADERS {
return Err(H3NativeError::QpackPolicy(
"decoded header count exceeds safety limit",
));
}
}
Ok(out)
}
pub fn qpack_encode_request_field_section(head: &H3RequestHead) -> Result<Vec<u8>, H3NativeError> {
let plan = qpack_static_plan_for_request(head);
qpack_encode_field_section(&plan)
}
pub fn qpack_encode_response_field_section(
head: &H3ResponseHead,
) -> Result<Vec<u8>, H3NativeError> {
let plan = qpack_static_plan_for_response(head);
qpack_encode_field_section(&plan)
}
pub fn qpack_encode_trailer_field_section(
fields: &[(String, String)],
) -> Result<Vec<u8>, H3NativeError> {
let mut plan = Vec::with_capacity(fields.len());
for (name, value) in fields {
validate_header_name(name)?;
validate_header_value(value)?;
if name.starts_with(':') {
return Err(H3NativeError::InvalidFrame(
"pseudo header forbidden in HTTP/3 trailers",
));
}
plan.push(QpackFieldPlan::Literal {
name: name.clone(),
value: value.clone(),
});
}
qpack_encode_field_section(&plan)
}
pub fn qpack_plan_to_header_fields(
plan: &[QpackFieldPlan],
qpack_context: Option<&QpackContext>,
) -> Result<Vec<(String, String)>, H3NativeError> {
let mut out = Vec::with_capacity(plan.len());
for field in plan {
match field {
QpackFieldPlan::StaticIndex(index) => {
let (name, value) = qpack_static_entry(*index)
.ok_or(H3NativeError::InvalidFrame("unknown static qpack index"))?;
out.push((name.to_string(), value.to_string()));
}
QpackFieldPlan::DynamicIndex(index) => {
if let Some(context) = qpack_context {
let (name, value) = qpack_dynamic_entry(context.dynamic_table(), *index)
.ok_or(H3NativeError::InvalidFrame("unknown dynamic qpack index"))?;
out.push((name.to_string(), value.to_string()));
} else {
return Err(H3NativeError::InvalidFrame(
"dynamic table context required",
));
}
}
QpackFieldPlan::DynamicNameLiteral { name_index, value } => {
if let Some(context) = qpack_context {
let name = qpack_dynamic_name(context.dynamic_table(), *name_index).ok_or(
H3NativeError::InvalidFrame("unknown dynamic qpack name index"),
)?;
out.push((name.to_string(), value.clone()));
} else {
return Err(H3NativeError::InvalidFrame(
"dynamic table context required",
));
}
}
QpackFieldPlan::Literal { name, value } => {
out.push((name.clone(), value.clone()));
}
}
}
Ok(out)
}
fn decoded_field_section_size(fields: &[(String, String)]) -> Result<u64, H3NativeError> {
fields.iter().try_fold(0u64, |acc, (name, value)| {
let field_size = name
.len()
.checked_add(value.len())
.and_then(|size| size.checked_add(32))
.ok_or(H3NativeError::QpackPolicy(
"decoded field section exceeds addressable range",
))?;
let field_size = u64::try_from(field_size).map_err(|_| {
H3NativeError::QpackPolicy("decoded field section exceeds addressable range")
})?;
acc.checked_add(field_size)
.ok_or(H3NativeError::QpackPolicy(
"decoded field section exceeds addressable range",
))
})
}
pub fn qpack_decode_request_field_section(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
) -> Result<H3RequestHead, H3NativeError> {
qpack_decode_request_field_section_with_limit(input, mode, qpack_context, None)
}
pub fn qpack_decode_request_field_section_with_limit(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
max_field_section_size: Option<u64>,
) -> Result<H3RequestHead, H3NativeError> {
let plan = qpack_decode_field_section_with_context(input, mode, qpack_context)?;
let fields = qpack_plan_to_header_fields(&plan, qpack_context)?;
if let Some(max_size) = max_field_section_size {
if decoded_field_section_size(&fields)? > max_size {
return Err(H3NativeError::QpackPolicy(
"decoded field section exceeds maximum size limit",
));
}
}
header_fields_to_request_head(&fields)
}
pub fn qpack_decode_response_field_section(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
) -> Result<H3ResponseHead, H3NativeError> {
qpack_decode_response_field_section_with_limit(input, mode, qpack_context, None)
}
pub fn qpack_decode_trailer_field_section(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
) -> Result<Vec<(String, String)>, H3NativeError> {
let plan = qpack_decode_field_section_with_context(input, mode, qpack_context)?;
let fields = qpack_plan_to_header_fields(&plan, qpack_context)?;
for (name, value) in &fields {
validate_header_name(name)?;
validate_header_value(value)?;
if name.starts_with(':') {
return Err(H3NativeError::InvalidFrame(
"pseudo header forbidden in HTTP/3 trailers",
));
}
}
Ok(fields)
}
pub fn qpack_decode_response_field_section_with_limit(
input: &[u8],
mode: H3QpackMode,
qpack_context: Option<&QpackContext>,
max_field_section_size: Option<u64>,
) -> Result<H3ResponseHead, H3NativeError> {
let plan = qpack_decode_field_section_with_context(input, mode, qpack_context)?;
let fields = qpack_plan_to_header_fields(&plan, qpack_context)?;
if let Some(max_size) = max_field_section_size {
if decoded_field_section_size(&fields)? > max_size {
return Err(H3NativeError::QpackPolicy(
"decoded field section exceeds maximum size limit",
));
}
}
header_fields_to_response_head(&fields)
}
const H3_FORBIDDEN_HEADER_NAMES: &[&str] = &[
"connection",
"keep-alive",
"proxy-connection",
"transfer-encoding",
"upgrade",
];
fn validate_header_name(name: &str) -> Result<(), H3NativeError> {
if name.is_empty() {
return Err(H3NativeError::InvalidFrame("empty header field name"));
}
let bytes = name.as_bytes();
let start = if bytes[0] == b':' {
if bytes.len() == 1 {
return Err(H3NativeError::InvalidFrame("empty header field name"));
}
1
} else {
0
};
for &b in &bytes[start..] {
match b {
b'a'..=b'z'
| b'0'..=b'9'
| b'!'
| b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~' => {}
b'A'..=b'Z' => {
return Err(H3NativeError::InvalidFrame(
"header field name must be lowercase in HTTP/3",
));
}
_ => {
return Err(H3NativeError::InvalidFrame(
"header field name contains invalid character",
));
}
}
}
if H3_FORBIDDEN_HEADER_NAMES.contains(&name) {
return Err(H3NativeError::InvalidFrame(
"header field name forbidden in HTTP/3 (RFC 9114 §4.2)",
));
}
Ok(())
}
fn validate_header_value(value: &str) -> Result<(), H3NativeError> {
for &b in value.as_bytes() {
if b == 0 || b == b'\r' || b == b'\n' {
return Err(H3NativeError::InvalidFrame(
"header field value contains forbidden character (NUL, CR, or LF)",
));
}
}
Ok(())
}
fn validate_method_token(method: &str) -> Result<(), H3NativeError> {
if method.is_empty() {
return Err(H3NativeError::InvalidRequestPseudoHeader("empty :method"));
}
for &b in method.as_bytes() {
match b {
b'a'..=b'z'
| b'A'..=b'Z'
| b'0'..=b'9'
| b'!'
| b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~' => {}
_ => {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":method must be a valid HTTP token",
));
}
}
}
Ok(())
}
fn validate_scheme_syntax(scheme: &str) -> Result<(), H3NativeError> {
let Some((&first, rest)) = scheme.as_bytes().split_first() else {
return Err(H3NativeError::InvalidRequestPseudoHeader("empty :scheme"));
};
if !first.is_ascii_alphabetic() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":scheme must be a valid URI scheme",
));
}
for &b in rest {
match b {
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'+' | b'-' | b'.' => {}
_ => {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":scheme must be a valid URI scheme",
));
}
}
}
Ok(())
}
fn validate_authority_form(authority: &str) -> Result<(), H3NativeError> {
if authority.as_bytes().iter().any(u8::is_ascii_whitespace) {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority must be RFC authority-form without whitespace",
));
}
if authority.contains('@') {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority must not include userinfo",
));
}
if authority.contains(['/', '?', '#']) {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority must not contain path, query, or fragment",
));
}
if authority.starts_with('[') {
let bracket_end = authority
.find(']')
.ok_or(H3NativeError::InvalidRequestPseudoHeader(
":authority has invalid IPv6 literal",
))?;
let literal = &authority[1..bracket_end];
if literal.parse::<Ipv6Addr>().is_err() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority has invalid IPv6 literal",
));
}
let rest = &authority[bracket_end + 1..];
if rest.is_empty() {
return Ok(());
}
let Some(port_str) = rest.strip_prefix(':') else {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority has invalid IPv6 literal",
));
};
if port_str.is_empty() || port_str.parse::<u16>().is_err() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority has invalid port",
));
}
return Ok(());
}
if authority.matches(':').count() > 1 {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority IPv6 literals must use [addr] form",
));
}
if let Some((host, port_str)) = authority.rsplit_once(':') {
if host.is_empty() || port_str.is_empty() || port_str.parse::<u16>().is_err() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":authority has invalid port",
));
}
}
Ok(())
}
fn validate_request_path(method: &str, path: &str) -> Result<(), H3NativeError> {
if path == "*" {
if method != "OPTIONS" {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"asterisk-form :path requires OPTIONS",
));
}
return Ok(());
}
if !path.starts_with('/') {
return Err(H3NativeError::InvalidRequestPseudoHeader(
":path must start with /",
));
}
Ok(())
}
fn parse_status_code(value: &str) -> Result<u16, H3NativeError> {
let bytes = value.as_bytes();
if bytes.len() != 3 || !bytes.iter().all(u8::is_ascii_digit) {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"invalid :status value",
));
}
value
.parse::<u16>()
.map_err(|_| H3NativeError::InvalidResponsePseudoHeader("invalid :status value"))
}
fn header_fields_to_request_head(
fields: &[(String, String)],
) -> Result<H3RequestHead, H3NativeError> {
let mut pseudo = H3PseudoHeaders::default();
let mut headers = Vec::new();
let mut saw_regular_headers = false;
for (name, value) in fields {
validate_header_name(name)?;
validate_header_value(value)?;
if name.starts_with(':') {
if saw_regular_headers {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"request pseudo headers must precede regular headers",
));
}
match name.as_str() {
":method" => {
if pseudo.method.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"duplicate :method",
));
}
pseudo.method = Some(value.clone());
}
":scheme" => {
if pseudo.scheme.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"duplicate :scheme",
));
}
pseudo.scheme = Some(value.clone());
}
":authority" => {
if pseudo.authority.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"duplicate :authority",
));
}
pseudo.authority = Some(value.clone());
}
":path" => {
if pseudo.path.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader("duplicate :path"));
}
pseudo.path = Some(value.clone());
}
":status" => {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"request must not include :status",
));
}
_ => {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"unknown request pseudo header",
));
}
}
} else {
saw_regular_headers = true;
headers.push((name.clone(), value.clone()));
}
}
H3RequestHead::new(pseudo, headers)
}
fn header_fields_to_response_head(
fields: &[(String, String)],
) -> Result<H3ResponseHead, H3NativeError> {
let mut status: Option<u16> = None;
let mut headers = Vec::new();
let mut saw_regular_headers = false;
for (name, value) in fields {
validate_header_name(name)?;
validate_header_value(value)?;
if name.starts_with(':') {
if saw_regular_headers {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"response pseudo headers must precede regular headers",
));
}
match name.as_str() {
":status" => {
if status.is_some() {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"duplicate :status",
));
}
let parsed = parse_status_code(value)?;
status = Some(parsed);
}
_ => {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"response must not include request pseudo headers",
));
}
}
} else {
saw_regular_headers = true;
headers.push((name.clone(), value.clone()));
}
}
let status = status.ok_or(H3NativeError::InvalidResponsePseudoHeader(
"missing :status",
))?;
H3ResponseHead::new(status, headers)
}
fn qpack_encode_prefixed_int(
out: &mut Vec<u8>,
prefix_bits: u8,
prefix_len: u8,
mut value: u64,
) -> Result<(), H3NativeError> {
if !(1..=8).contains(&prefix_len) {
return Err(H3NativeError::InvalidFrame(
"invalid qpack integer prefix length",
));
}
let max_in_prefix = (1u64 << prefix_len) - 1;
if value < max_in_prefix {
out.push(prefix_bits | (value as u8));
return Ok(());
}
out.push(prefix_bits | (max_in_prefix as u8));
value -= max_in_prefix;
while value >= 128 {
out.push(((value as u8) & 0x7F) | 0x80);
value >>= 7;
}
out.push(value as u8);
Ok(())
}
fn qpack_decode_prefixed_int(
first: u8,
prefix_len: u8,
input: &[u8],
) -> Result<(u64, usize), H3NativeError> {
if !(1..=8).contains(&prefix_len) {
return Err(H3NativeError::InvalidFrame(
"invalid qpack integer prefix length",
));
}
let mask = ((1u16 << prefix_len) - 1) as u8;
let mut value = u64::from(first & mask);
let max_in_prefix = u64::from(mask);
if value < max_in_prefix {
return Ok((value, 0));
}
let mut shift = 0u32;
let mut consumed = 0usize;
loop {
let byte = *input.get(consumed).ok_or(H3NativeError::UnexpectedEof)?;
consumed += 1;
let part = u64::from(byte & 0x7F);
let shifted = part
.checked_shl(shift)
.ok_or(H3NativeError::InvalidFrame("qpack integer overflow"))?;
value = value
.checked_add(shifted)
.ok_or(H3NativeError::InvalidFrame("qpack integer overflow"))?;
if (byte & 0x80) == 0 {
return Ok((value, consumed));
}
shift = shift.saturating_add(7);
if shift > 56 {
return Err(H3NativeError::InvalidFrame("qpack integer overflow"));
}
}
}
fn qpack_encode_string(
out: &mut Vec<u8>,
prefix_bits: u8,
prefix_len: u8,
value: &str,
) -> Result<(), H3NativeError> {
let bytes = value.as_bytes();
let huffman_len = hpack_huffman_encoded_size(bytes);
if huffman_len < bytes.len() {
qpack_encode_prefixed_int(
out,
prefix_bits | (1u8 << prefix_len),
prefix_len,
huffman_len as u64,
)?;
let mut encoded = BytesMut::with_capacity(huffman_len);
hpack_encode_huffman(&mut encoded, bytes);
out.extend_from_slice(&encoded);
} else {
qpack_encode_prefixed_int(out, prefix_bits, prefix_len, bytes.len() as u64)?;
out.extend_from_slice(bytes);
}
Ok(())
}
fn qpack_decode_string(
first: u8,
prefix_len: u8,
input: &[u8],
) -> Result<(String, usize), H3NativeError> {
if prefix_len >= 8 {
return Err(H3NativeError::InvalidFrame(
"qpack string prefix length must be less than 8",
));
}
let huffman_bit = 1u8 << prefix_len;
let (len, extra) = qpack_decode_prefixed_int(first, prefix_len, input)?;
let len: usize = len.try_into().map_err(|_| {
H3NativeError::InvalidFrame("qpack string length exceeds addressable range")
})?;
if input.len().saturating_sub(extra) < len {
return Err(H3NativeError::UnexpectedEof);
}
let bytes = &input[extra..extra + len];
let value = if (first & huffman_bit) != 0 {
let encoded = Bytes::copy_from_slice(bytes);
hpack_decode_huffman(&encoded)
.map_err(|_| H3NativeError::InvalidFrame("invalid qpack huffman string"))?
} else {
std::str::from_utf8(bytes)
.map_err(|_| H3NativeError::InvalidFrame("qpack string is not valid utf-8"))?
.to_string()
};
Ok((value, extra + len))
}
fn qpack_static_name(index: u64) -> Option<&'static str> {
qpack_static_entry(index).map(|(name, _)| name)
}
fn qpack_static_entry(index: u64) -> Option<(&'static str, &'static str)> {
match index {
0 => Some((":authority", "")),
1 => Some((":path", "/")),
2 => Some(("age", "0")),
3 => Some(("content-disposition", "")),
4 => Some(("content-length", "0")),
5 => Some(("cookie", "")),
6 => Some(("date", "")),
7 => Some(("etag", "")),
8 => Some(("if-modified-since", "")),
9 => Some(("if-none-match", "")),
10 => Some(("last-modified", "")),
11 => Some(("link", "")),
12 => Some(("location", "")),
13 => Some(("referer", "")),
14 => Some(("set-cookie", "")),
15 => Some((":method", "CONNECT")),
16 => Some((":method", "DELETE")),
17 => Some((":method", "GET")),
18 => Some((":method", "HEAD")),
19 => Some((":method", "OPTIONS")),
20 => Some((":method", "POST")),
21 => Some((":method", "PUT")),
22 => Some((":scheme", "http")),
23 => Some((":scheme", "https")),
24 => Some((":status", "103")),
25 => Some((":status", "200")),
26 => Some((":status", "304")),
27 => Some((":status", "404")),
28 => Some((":status", "503")),
29 => Some(("accept", "*/*")),
30 => Some(("accept", "application/dns-message")),
31 => Some(("accept-encoding", "gzip, deflate, br")),
32 => Some(("accept-ranges", "bytes")),
33 => Some(("access-control-allow-headers", "cache-control")),
34 => Some(("access-control-allow-headers", "content-type")),
35 => Some(("access-control-allow-origin", "*")),
36 => Some(("cache-control", "max-age=0")),
37 => Some(("cache-control", "max-age=2592000")),
38 => Some(("cache-control", "max-age=604800")),
39 => Some(("cache-control", "no-cache")),
40 => Some(("cache-control", "no-store")),
41 => Some(("cache-control", "public, max-age=31536000")),
42 => Some(("content-encoding", "br")),
43 => Some(("content-encoding", "gzip")),
44 => Some(("content-type", "application/dns-message")),
45 => Some(("content-type", "application/javascript")),
46 => Some(("content-type", "application/json")),
47 => Some(("content-type", "application/x-www-form-urlencoded")),
48 => Some(("content-type", "image/gif")),
49 => Some(("content-type", "image/jpeg")),
50 => Some(("content-type", "image/png")),
51 => Some(("content-type", "text/css")),
52 => Some(("content-type", "text/html; charset=utf-8")),
53 => Some(("content-type", "text/plain")),
54 => Some(("content-type", "text/plain;charset=utf-8")),
55 => Some(("range", "bytes=0-")),
56 => Some(("strict-transport-security", "max-age=31536000")),
57 => Some((
"strict-transport-security",
"max-age=31536000; includesubdomains",
)),
58 => Some((
"strict-transport-security",
"max-age=31536000; includesubdomains; preload",
)),
59 => Some(("vary", "accept-encoding")),
60 => Some(("vary", "origin")),
61 => Some(("x-content-type-options", "nosniff")),
62 => Some(("x-xss-protection", "1; mode=block")),
63 => Some((":status", "100")),
64 => Some((":status", "204")),
65 => Some((":status", "206")),
66 => Some((":status", "302")),
67 => Some((":status", "400")),
68 => Some((":status", "403")),
69 => Some((":status", "421")),
70 => Some((":status", "425")),
71 => Some((":status", "500")),
72 => Some(("accept-language", "")),
73 => Some(("access-control-allow-credentials", "FALSE")),
74 => Some(("access-control-allow-credentials", "TRUE")),
75 => Some(("access-control-allow-headers", "*")),
76 => Some(("access-control-allow-methods", "get")),
77 => Some(("access-control-allow-methods", "get, post, options")),
78 => Some(("access-control-allow-methods", "options")),
79 => Some(("access-control-expose-headers", "content-length")),
80 => Some(("access-control-request-headers", "content-type")),
81 => Some(("access-control-request-method", "get")),
82 => Some(("access-control-request-method", "post")),
83 => Some(("alt-svc", "clear")),
84 => Some(("authorization", "")),
85 => Some((
"content-security-policy",
"script-src 'none'; object-src 'none'; base-uri 'none'",
)),
86 => Some(("early-data", "1")),
87 => Some(("expect-ct", "")),
88 => Some(("forwarded", "")),
89 => Some(("if-range", "")),
90 => Some(("origin", "")),
91 => Some(("purpose", "prefetch")),
92 => Some(("server", "")),
93 => Some(("timing-allow-origin", "*")),
94 => Some(("upgrade-insecure-requests", "1")),
95 => Some(("user-agent", "")),
96 => Some(("x-forwarded-for", "")),
97 => Some(("x-frame-options", "deny")),
98 => Some(("x-frame-options", "sameorigin")),
_ => None,
}
}
fn qpack_static_method_index(method: &str) -> Option<u64> {
match method {
"CONNECT" => Some(15),
"DELETE" => Some(16),
"GET" => Some(17),
"HEAD" => Some(18),
"OPTIONS" => Some(19),
"POST" => Some(20),
"PUT" => Some(21),
_ => None,
}
}
fn qpack_static_scheme_index(scheme: &str) -> Option<u64> {
match scheme {
"http" => Some(22),
"https" => Some(23),
_ => None,
}
}
fn qpack_static_status_index(status: u16) -> Option<u64> {
match status {
103 => Some(24),
200 => Some(25),
304 => Some(26),
404 => Some(27),
503 => Some(28),
100 => Some(63),
204 => Some(64),
206 => Some(65),
302 => Some(66),
400 => Some(67),
403 => Some(68),
421 => Some(69),
425 => Some(70),
500 => Some(71),
_ => None,
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H3RequestStreamState {
header_blocks_seen: u8,
saw_data: bool,
end_stream: bool,
}
impl H3RequestStreamState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn on_frame(&mut self, frame: &H3Frame) -> Result<(), H3NativeError> {
if self.end_stream {
return Err(H3NativeError::ControlProtocol(
"request stream already finished",
));
}
match frame {
H3Frame::Headers(_) => {
if self.header_blocks_seen == 0 {
self.header_blocks_seen = 1;
return Ok(());
}
if self.header_blocks_seen == 1 {
self.header_blocks_seen = 2;
return Ok(());
}
Err(H3NativeError::ControlProtocol(
"invalid HEADERS ordering on request stream",
))
}
H3Frame::Data(_) => {
if self.header_blocks_seen == 0 {
return Err(H3NativeError::ControlProtocol(
"DATA before initial HEADERS on request stream",
));
}
if self.header_blocks_seen > 1 {
return Err(H3NativeError::ControlProtocol(
"DATA not allowed after trailing HEADERS",
));
}
self.saw_data = true;
Ok(())
}
H3Frame::Datagram { .. } => {
if self.header_blocks_seen == 0 {
return Err(H3NativeError::ControlProtocol(
"DATAGRAM before initial HEADERS on request stream",
));
}
Ok(())
}
H3Frame::PushPromise { .. } | H3Frame::Unknown { .. } => Ok(()),
H3Frame::Settings(_)
| H3Frame::CancelPush(_)
| H3Frame::Goaway(_)
| H3Frame::MaxPushId(_) => Err(H3NativeError::ControlProtocol(
"control frames are not valid on request streams",
)),
}
}
fn on_informational_response_headers(&mut self) -> Result<(), H3NativeError> {
if self.end_stream {
return Err(H3NativeError::ControlProtocol(
"request stream already finished",
));
}
if self.header_blocks_seen != 0 || self.saw_data {
return Err(H3NativeError::ControlProtocol(
"informational response HEADERS must precede final response HEADERS",
));
}
Ok(())
}
pub fn mark_end_stream(&mut self) -> Result<(), H3NativeError> {
if self.header_blocks_seen == 0 {
return Err(H3NativeError::ControlProtocol(
"request stream ended before initial HEADERS",
));
}
self.end_stream = true;
Ok(())
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct H3PushStreamState {
push_id: Option<u64>,
response: H3RequestStreamState,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H3ConnectionState {
config: H3ConnectionConfig,
control: H3ControlState,
request_streams: BTreeMap<u64, H3RequestStreamState>,
finished_request_streams: BTreeSet<u64>,
max_contiguous_finished_request_stream_id: Option<u64>,
push_streams: BTreeMap<u64, H3PushStreamState>,
used_push_ids: BTreeSet<u64>,
uni_stream_types: BTreeMap<u64, H3UniStreamType>,
control_stream_id: Option<u64>,
qpack_encoder_stream_id: Option<u64>,
qpack_decoder_stream_id: Option<u64>,
goaway_id: Option<u64>,
}
impl H3ConnectionState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn new_client() -> Self {
Self::new()
}
#[must_use]
pub fn new_server() -> Self {
Self::with_config(H3ConnectionConfig {
endpoint_role: H3EndpointRole::Server,
..H3ConnectionConfig::default()
})
}
#[must_use]
pub fn with_config(config: H3ConnectionConfig) -> Self {
Self {
config,
control: H3ControlState::default(),
request_streams: BTreeMap::new(),
finished_request_streams: BTreeSet::new(),
max_contiguous_finished_request_stream_id: None,
push_streams: BTreeMap::new(),
used_push_ids: BTreeSet::new(),
uni_stream_types: BTreeMap::new(),
control_stream_id: None,
qpack_encoder_stream_id: None,
qpack_decoder_stream_id: None,
goaway_id: None,
}
}
fn is_request_stream_finished(&self, stream_id: u64) -> bool {
if let Some(max_contig) = self.max_contiguous_finished_request_stream_id {
if stream_id <= max_contig {
return true;
}
}
self.finished_request_streams.contains(&stream_id)
}
pub fn on_control_frame(&mut self, frame: &H3Frame) -> Result<(), H3NativeError> {
self.control.on_remote_control_frame(frame)?;
if self.config.endpoint_role == H3EndpointRole::Client
&& matches!(frame, H3Frame::MaxPushId(_))
{
return Err(H3NativeError::ControlProtocol(
"client must not receive MAX_PUSH_ID",
));
}
if let H3Frame::Goaway(id) = frame {
if self.config.endpoint_role == H3EndpointRole::Client
&& !is_client_initiated_bidirectional_stream_id(*id)
{
return Err(H3NativeError::ControlProtocol(
"GOAWAY id must be a client-initiated bidirectional stream id",
));
}
if self.goaway_id.is_some_and(|prev| *id > prev) {
return Err(H3NativeError::ControlProtocol(
"GOAWAY id must not increase",
));
}
self.goaway_id = Some(*id);
}
Ok(())
}
pub fn on_request_stream_frame(
&mut self,
stream_id: u64,
frame: &H3Frame,
) -> Result<(), H3NativeError> {
if !is_client_initiated_bidirectional_stream_id(stream_id) {
return Err(H3NativeError::StreamProtocol(
"request stream id must be client-initiated bidirectional",
));
}
if self.uni_stream_types.contains_key(&stream_id) {
return Err(H3NativeError::StreamProtocol(
"request stream id is registered as unidirectional",
));
}
if self.is_request_stream_finished(stream_id) {
return Err(H3NativeError::ControlProtocol(
"request stream already finished",
));
}
if self.config.endpoint_role == H3EndpointRole::Client
&& let Some(goaway_id) = self.goaway_id
&& stream_id >= goaway_id
{
return Err(H3NativeError::ControlProtocol(
"request stream id rejected after GOAWAY",
));
}
let request_stream_exists = self.request_streams.contains_key(&stream_id);
if matches!(frame, H3Frame::Unknown { .. }) && !request_stream_exists {
return Ok(());
}
if let Some(limit) = self.config.max_concurrent_request_streams
&& !request_stream_exists
&& self.request_streams.len() as u64 >= limit
{
return Err(H3NativeError::ConcurrentStreamLimitExceeded {
active: self.request_streams.len() as u64,
limit,
});
}
if let Some(state) = self.request_streams.get_mut(&stream_id) {
return state.on_frame(frame);
}
let mut state = H3RequestStreamState::new();
state.on_frame(frame)?;
self.request_streams.insert(stream_id, state);
Ok(())
}
pub fn on_informational_response_headers(
&mut self,
stream_id: u64,
) -> Result<(), H3NativeError> {
if self.config.endpoint_role != H3EndpointRole::Client {
return Err(H3NativeError::ControlProtocol(
"informational response HEADERS are client-side only",
));
}
if !is_client_initiated_bidirectional_stream_id(stream_id) {
return Err(H3NativeError::StreamProtocol(
"request stream id must be client-initiated bidirectional",
));
}
if self.uni_stream_types.contains_key(&stream_id) {
return Err(H3NativeError::StreamProtocol(
"request stream id is registered as unidirectional",
));
}
if self.is_request_stream_finished(stream_id) {
return Err(H3NativeError::ControlProtocol(
"request stream already finished",
));
}
if let Some(goaway_id) = self.goaway_id
&& stream_id >= goaway_id
{
return Err(H3NativeError::ControlProtocol(
"request stream id rejected after GOAWAY",
));
}
let request_stream_exists = self.request_streams.contains_key(&stream_id);
if let Some(limit) = self.config.max_concurrent_request_streams
&& !request_stream_exists
&& self.request_streams.len() as u64 >= limit
{
return Err(H3NativeError::ConcurrentStreamLimitExceeded {
active: self.request_streams.len() as u64,
limit,
});
}
if let Some(state) = self.request_streams.get_mut(&stream_id) {
return state.on_informational_response_headers();
}
let mut state = H3RequestStreamState::new();
state.on_informational_response_headers()?;
self.request_streams.insert(stream_id, state);
Ok(())
}
#[must_use]
pub fn active_request_stream_count(&self) -> u64 {
self.request_streams.len() as u64
}
pub fn set_max_concurrent_request_streams(&mut self, limit: Option<u64>) {
self.config.max_concurrent_request_streams = limit;
}
pub fn finish_request_stream(&mut self, stream_id: u64) -> Result<(), H3NativeError> {
if self.is_request_stream_finished(stream_id) {
return Err(H3NativeError::ControlProtocol(
"request stream already finished",
));
}
let state =
self.request_streams
.get_mut(&stream_id)
.ok_or(H3NativeError::ControlProtocol(
"unknown request stream on finish",
))?;
state.mark_end_stream()?;
self.request_streams.remove(&stream_id);
self.record_terminal_request_stream(stream_id);
Ok(())
}
pub fn abort_request_stream(&mut self, stream_id: u64) -> Result<bool, H3NativeError> {
if !is_client_initiated_bidirectional_stream_id(stream_id) {
return Err(H3NativeError::StreamProtocol(
"request stream id must be client-initiated bidirectional",
));
}
if self.uni_stream_types.contains_key(&stream_id) {
return Err(H3NativeError::StreamProtocol(
"request stream id is registered as unidirectional",
));
}
if self.is_request_stream_finished(stream_id) {
return Ok(false);
}
let retired_live_state = self.request_streams.remove(&stream_id).is_some();
self.record_terminal_request_stream(stream_id);
Ok(retired_live_state)
}
fn record_terminal_request_stream(&mut self, stream_id: u64) {
self.finished_request_streams.insert(stream_id);
let mut next_expected = self
.max_contiguous_finished_request_stream_id
.map_or(0, |id| id + 4);
while self.finished_request_streams.remove(&next_expected) {
self.max_contiguous_finished_request_stream_id = Some(next_expected);
next_expected += 4;
}
}
pub fn on_push_stream_header(
&mut self,
stream_id: u64,
push_id: u64,
) -> Result<(), H3NativeError> {
match self.uni_stream_types.get(&stream_id) {
Some(H3UniStreamType::Push) => {}
Some(_) => {
return Err(H3NativeError::StreamProtocol(
"push stream header requires a push stream",
));
}
None => {
return Err(H3NativeError::StreamProtocol(
"unknown unidirectional stream",
));
}
}
let state = self
.push_streams
.get_mut(&stream_id)
.ok_or(H3NativeError::StreamProtocol("unknown push stream"))?;
if state.push_id.is_some() {
return Err(H3NativeError::StreamProtocol(
"push stream header already received",
));
}
if !self.used_push_ids.insert(push_id) {
return Err(H3NativeError::StreamProtocol(
"duplicate push id in push stream header",
));
}
state.push_id = Some(push_id);
Ok(())
}
pub fn on_remote_uni_stream_type(
&mut self,
stream_id: u64,
stream_type: u64,
) -> Result<H3UniStreamType, H3NativeError> {
if !is_unidirectional_stream_id(stream_id) {
return Err(H3NativeError::StreamProtocol(
"unidirectional stream type requires unidirectional stream id",
));
}
if !is_peer_initiated_unidirectional_stream_id(stream_id, self.config.endpoint_role) {
return Err(H3NativeError::StreamProtocol(
"unidirectional stream type requires peer-initiated unidirectional stream id",
));
}
let kind = H3UniStreamType::decode(stream_type);
if self.uni_stream_types.contains_key(&stream_id) {
return Err(H3NativeError::StreamProtocol(
"unidirectional stream type already set",
));
}
match kind {
H3UniStreamType::Control => {
if self.control_stream_id.is_some() {
return Err(H3NativeError::ControlProtocol(
"duplicate remote control stream",
));
}
self.control_stream_id = Some(stream_id);
}
H3UniStreamType::QpackEncoder => {
if self.qpack_encoder_stream_id.is_some() {
return Err(H3NativeError::StreamProtocol(
"duplicate remote qpack encoder stream",
));
}
self.qpack_encoder_stream_id = Some(stream_id);
}
H3UniStreamType::QpackDecoder => {
if self.qpack_decoder_stream_id.is_some() {
return Err(H3NativeError::StreamProtocol(
"duplicate remote qpack decoder stream",
));
}
self.qpack_decoder_stream_id = Some(stream_id);
}
H3UniStreamType::Push => {
if self.config.endpoint_role != H3EndpointRole::Client {
return Err(H3NativeError::StreamProtocol(
"server endpoint must not receive push streams",
));
}
self.push_streams.entry(stream_id).or_default();
}
H3UniStreamType::Unknown(_) => {
}
}
self.uni_stream_types.insert(stream_id, kind);
Ok(kind)
}
pub fn on_uni_stream_frame(
&mut self,
stream_id: u64,
frame: &H3Frame,
) -> Result<(), H3NativeError> {
let kind =
self.uni_stream_types
.get(&stream_id)
.copied()
.ok_or(H3NativeError::StreamProtocol(
"unknown unidirectional stream",
))?;
match kind {
H3UniStreamType::Control => self.on_control_frame(frame),
H3UniStreamType::Push => {
let state = self.push_streams.entry(stream_id).or_default();
if state.push_id.is_none() {
return Err(H3NativeError::StreamProtocol("push stream missing push id"));
}
state.response.on_frame(frame)
}
H3UniStreamType::QpackEncoder | H3UniStreamType::QpackDecoder => Err(
H3NativeError::StreamProtocol("qpack streams carry instructions, not h3 frames"),
),
H3UniStreamType::Unknown(_) => {
Ok(())
}
}
}
#[must_use]
pub fn remote_uni_stream_type(&self, stream_id: u64) -> Option<H3UniStreamType> {
self.uni_stream_types.get(&stream_id).copied()
}
#[must_use]
pub fn qpack_encoder_stream_id(&self) -> Option<u64> {
self.qpack_encoder_stream_id
}
#[must_use]
pub fn qpack_decoder_stream_id(&self) -> Option<u64> {
self.qpack_decoder_stream_id
}
pub fn register_qpack_instruction_stream(
&self,
qpack: &mut QpackInstructionStreamState,
stream_id: u64,
) -> Result<H3UniStreamType, H3NativeError> {
qpack.register_from_connection(self, stream_id)
}
pub fn feed_qpack_instruction_stream_bytes(
&self,
qpack: &mut QpackInstructionStreamState,
stream_id: u64,
bytes: &[u8],
) -> Result<QpackInstructionStreamOutcome, H3NativeError> {
let kind = self
.remote_uni_stream_type(stream_id)
.ok_or(H3NativeError::StreamProtocol(
"unknown unidirectional stream",
))?;
qpack.ensure_stream_registered(stream_id, kind)?;
qpack.feed_instruction_stream_bytes(stream_id, kind, bytes)
}
#[must_use]
pub fn goaway_id(&self) -> Option<u64> {
self.goaway_id
}
#[must_use]
pub fn qpack_mode(&self) -> H3QpackMode {
self.config.qpack_mode
}
#[must_use]
pub fn endpoint_role(&self) -> H3EndpointRole {
self.config.endpoint_role
}
}
fn is_unidirectional_stream_id(stream_id: u64) -> bool {
(stream_id & 0x2) != 0
}
fn is_client_initiated_bidirectional_stream_id(stream_id: u64) -> bool {
stream_id.trailing_zeros() >= 2
}
fn is_client_initiated_unidirectional_stream_id(stream_id: u64) -> bool {
(stream_id & 0x3) == 0x2
}
fn is_server_initiated_unidirectional_stream_id(stream_id: u64) -> bool {
(stream_id & 0x3) == 0x3
}
fn is_peer_initiated_unidirectional_stream_id(
stream_id: u64,
endpoint_role: H3EndpointRole,
) -> bool {
match endpoint_role {
H3EndpointRole::Client => is_server_initiated_unidirectional_stream_id(stream_id),
H3EndpointRole::Server => is_client_initiated_unidirectional_stream_id(stream_id),
}
}
pub fn validate_request_pseudo_headers(headers: &H3PseudoHeaders) -> Result<(), H3NativeError> {
validate_request_pseudo_headers_with_settings(headers, false)
}
pub fn validate_request_pseudo_headers_with_settings(
headers: &H3PseudoHeaders,
enable_connect_protocol: bool,
) -> Result<(), H3NativeError> {
let method = headers
.method
.as_deref()
.ok_or(H3NativeError::InvalidRequestPseudoHeader("missing :method"))?;
validate_header_value(method)?;
validate_method_token(method)?;
if headers.status.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"request must not include :status",
));
}
if method == "CONNECT" {
let authority =
headers
.authority
.as_deref()
.ok_or(H3NativeError::InvalidRequestPseudoHeader(
"CONNECT request missing :authority",
))?;
validate_header_value(authority)?;
if authority.is_empty() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"CONNECT request missing :authority",
));
}
validate_authority_form(authority)?;
if enable_connect_protocol {
if let Some(protocol) = &headers.protocol {
validate_header_value(protocol)?;
if protocol.is_empty() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"extended CONNECT request :protocol must not be empty",
));
}
} else {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"extended CONNECT request missing :protocol",
));
}
if let Some(scheme) = &headers.scheme {
validate_header_value(scheme)?;
validate_scheme_syntax(scheme)?;
}
if let Some(path) = &headers.path {
validate_header_value(path)?;
validate_request_path("CONNECT", path)?;
}
} else {
if headers.scheme.is_some() || headers.path.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"CONNECT request must not include :scheme or :path",
));
}
if headers.protocol.is_some() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"CONNECT request must not include :protocol (extended CONNECT not enabled)",
));
}
}
return Ok(());
}
let scheme = headers
.scheme
.as_deref()
.ok_or(H3NativeError::InvalidRequestPseudoHeader("missing :scheme"))?;
validate_header_value(scheme)?;
if scheme.is_empty() {
return Err(H3NativeError::InvalidRequestPseudoHeader("empty :scheme"));
}
validate_scheme_syntax(scheme)?;
let path = headers
.path
.as_deref()
.ok_or(H3NativeError::InvalidRequestPseudoHeader("missing :path"))?;
validate_header_value(path)?;
if path.is_empty() {
return Err(H3NativeError::InvalidRequestPseudoHeader("empty :path"));
}
validate_request_path(method, path)?;
if let Some(authority) = headers.authority.as_deref() {
validate_header_value(authority)?;
if authority.is_empty() {
return Err(H3NativeError::InvalidRequestPseudoHeader(
"empty :authority",
));
}
validate_authority_form(authority)?;
}
Ok(())
}
pub fn validate_response_pseudo_headers(headers: &H3PseudoHeaders) -> Result<(), H3NativeError> {
let status = headers
.status
.ok_or(H3NativeError::InvalidResponsePseudoHeader(
"missing :status",
))?;
if !(100..=999).contains(&status) {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"status must be in 100..=999",
));
}
if status == 101 {
return Err(H3NativeError::InvalidResponsePseudoHeader(
"HTTP/3 does not support 101 Switching Protocols",
));
}
if headers.method.is_some()
|| headers.scheme.is_some()
|| headers.authority.is_some()
|| headers.path.is_some()
{
return Err(H3NativeError::InvalidResponsePseudoHeader(
"response must not include request pseudo headers",
));
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct QpackDynamicEntry {
name: String,
value: String,
size: usize,
reference_count: usize,
insertion_order: u64,
}
impl QpackDynamicEntry {
fn new(name: String, value: String, insertion_order: u64) -> Self {
let size = name.len().saturating_add(value.len()).saturating_add(32);
Self {
name,
value,
size,
reference_count: 0,
insertion_order,
}
}
fn add_reference(&mut self) {
self.reference_count = self.reference_count.saturating_add(1);
}
fn remove_reference(&mut self) {
self.reference_count = self.reference_count.saturating_sub(1);
}
fn is_referenced(&self) -> bool {
self.reference_count > 0
}
pub fn name(&self) -> &str {
&self.name
}
pub fn value(&self) -> &str {
&self.value
}
pub fn insertion_id(&self) -> u64 {
self.insertion_order
}
}
#[derive(Debug)]
pub struct QpackDynamicTable {
entries: Vec<QpackDynamicEntry>,
max_capacity: usize,
current_size: usize,
insertion_counter: u64,
evicted_count: usize,
}
impl QpackDynamicTable {
pub fn new(max_capacity: usize) -> Self {
Self {
entries: Vec::new(),
max_capacity,
current_size: 0,
insertion_counter: 0,
evicted_count: 0,
}
}
pub fn insert(&mut self, name: String, value: String) -> Result<u64, &'static str> {
let entry = QpackDynamicEntry::new(name, value, self.insertion_counter);
let entry_size = entry.size;
if entry_size > self.max_capacity {
return Err("entry larger than table capacity");
}
while self.current_size.saturating_add(entry_size) > self.max_capacity {
if !self.evict_lru_unreferenced() {
return Err("cannot evict enough space (all entries referenced)");
}
}
let insertion_id = self.insertion_counter;
self.entries.push(entry);
self.current_size = self.current_size.saturating_add(entry_size);
self.insertion_counter += 1;
Ok(insertion_id)
}
pub fn set_capacity(&mut self, max_capacity: usize) -> Result<(), &'static str> {
if self.current_size <= max_capacity {
self.max_capacity = max_capacity;
return Ok(());
}
let mut candidates = Vec::new();
let mut freed = 0usize;
for (index, entry) in self.entries.iter().enumerate() {
if entry.is_referenced() {
continue;
}
candidates.push(index);
freed += entry.size;
if self.current_size - freed <= max_capacity {
break;
}
}
if self.current_size - freed > max_capacity {
return Err("cannot reduce table capacity while entries are referenced");
}
self.max_capacity = max_capacity;
for index in candidates.into_iter().rev() {
let evicted = self.entries.remove(index);
self.current_size -= evicted.size;
self.evicted_count += 1;
}
Ok(())
}
fn evict_lru_unreferenced(&mut self) -> bool {
let mut lru_index = None;
let mut lru_insertion_order = u64::MAX;
for (i, entry) in self.entries.iter().enumerate() {
if !entry.is_referenced() && entry.insertion_order < lru_insertion_order {
lru_insertion_order = entry.insertion_order;
lru_index = Some(i);
}
}
if let Some(index) = lru_index {
let evicted = self.entries.remove(index);
self.current_size -= evicted.size;
self.evicted_count += 1;
true
} else {
false
}
}
pub fn reference_entry(&mut self, insertion_id: u64) -> bool {
if let Some(entry) = self
.entries
.iter_mut()
.find(|e| e.insertion_order == insertion_id)
{
entry.add_reference();
true
} else {
false
}
}
pub fn unreference_entry(&mut self, insertion_id: u64) -> bool {
if let Some(entry) = self
.entries
.iter_mut()
.find(|e| e.insertion_order == insertion_id)
{
entry.remove_reference();
true
} else {
false
}
}
pub fn get_by_absolute_index(&self, absolute_index: u64) -> Option<&QpackDynamicEntry> {
self.get_by_insertion_id(absolute_index)
}
pub fn get_by_insertion_id(&self, insertion_id: u64) -> Option<&QpackDynamicEntry> {
if insertion_id >= self.insertion_counter {
return None;
}
self.entries
.iter()
.find(|entry| entry.insertion_order == insertion_id)
}
pub fn get_by_relative_index(&self, relative_index: u64) -> Option<&QpackDynamicEntry> {
let insertion_id = self
.insertion_counter
.checked_sub(1)?
.checked_sub(relative_index)?;
self.get_by_insertion_id(insertion_id)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn size(&self) -> usize {
self.current_size
}
pub fn capacity(&self) -> usize {
self.max_capacity
}
pub fn insertion_counter(&self) -> u64 {
self.insertion_counter
}
pub fn evicted_count(&self) -> usize {
self.evicted_count
}
}
impl Default for QpackDynamicTable {
fn default() -> Self {
Self::new(4096) }
}
pub fn qpack_dynamic_entry(table: &QpackDynamicTable, absolute_index: u64) -> Option<(&str, &str)> {
table
.get_by_absolute_index(absolute_index)
.map(|entry| (entry.name(), entry.value()))
}
pub fn qpack_dynamic_name(table: &QpackDynamicTable, absolute_index: u64) -> Option<&str> {
table
.get_by_absolute_index(absolute_index)
.map(|entry| entry.name())
}
#[derive(Debug)]
pub struct QpackContext {
dynamic_table: QpackDynamicTable,
max_table_capacity: usize,
}
impl QpackContext {
pub fn new(max_table_capacity: usize) -> Self {
Self {
dynamic_table: QpackDynamicTable::new(max_table_capacity),
max_table_capacity,
}
}
pub fn dynamic_table(&self) -> &QpackDynamicTable {
&self.dynamic_table
}
pub fn dynamic_table_mut(&mut self) -> &mut QpackDynamicTable {
&mut self.dynamic_table
}
pub fn max_table_capacity(&self) -> usize {
self.max_table_capacity
}
pub fn set_dynamic_table_capacity(&mut self, capacity: usize) -> Result<(), &'static str> {
if capacity > self.max_table_capacity {
return Err("capacity exceeds peer limit");
}
self.dynamic_table.set_capacity(capacity)
}
pub fn insert_dynamic_entry(
&mut self,
name: String,
value: String,
) -> Result<u64, &'static str> {
self.dynamic_table.insert(name, value)
}
}
impl Default for QpackContext {
fn default() -> Self {
Self::new(4096)
}
}
#[cfg(test)]
include!("h3_native_tests.rs");