use std::borrow::Cow;
use crate::error::QpackError;
use super::dynamic_table::DynamicTable;
use super::header::Header;
use super::huffman;
use super::integer;
use super::table::{STATIC_TABLE_LEN, get_static_entry};
#[derive(Debug)]
pub enum DecodeOutput {
Decoded(Vec<Header>),
Blocked,
}
fn header_from_decoded(name: Vec<u8>, value: Vec<u8>) -> Header {
Header::from_validated_parts_internal(Cow::Owned(name), Cow::Owned(value))
}
#[derive(Debug, Default)]
pub struct Decoder {
max_field_section_size: u64,
}
impl Decoder {
pub fn new() -> Self {
Self {
max_field_section_size: 16 * 1024,
}
}
pub fn max_field_section_size(mut self, size: u64) -> Self {
self.max_field_section_size = size;
self
}
pub fn decode(&self, data: &[u8]) -> Result<Vec<Header>, QpackError> {
if data.len() < 2 {
return Err(QpackError::BufferTooShort);
}
let mut offset = 0;
let (ric, ric_len) = integer::decode_integer(&data[offset..], 8)?;
offset += ric_len;
if ric != 0 {
return Err(QpackError::DecodeFailed);
}
if offset >= data.len() {
return Err(QpackError::BufferTooShort);
}
let (_, delta_len) = integer::decode_integer(&data[offset..], 7)?;
offset += delta_len;
let mut headers = Vec::new();
let mut total_size = 0u64;
while offset < data.len() {
let (header, consumed) = self.decode_header(&data[offset..])?;
total_size += (header.name().len() + header.value().len() + 32) as u64;
if total_size > self.max_field_section_size {
return Err(QpackError::DecodeFailed);
}
headers.push(header);
offset += consumed;
}
Ok(headers)
}
fn decode_header(&self, data: &[u8]) -> Result<(Header, usize), QpackError> {
if data.is_empty() {
return Err(QpackError::BufferTooShort);
}
let first = data[0];
if first & 0x80 != 0 {
self.decode_indexed_field(data)
} else if first & 0x40 != 0 {
self.decode_literal_with_name_ref(data)
} else if first & 0x20 != 0 {
self.decode_literal_with_literal_name(data)
} else if first & 0x10 != 0 {
Err(QpackError::DecodeFailed)
} else {
Err(QpackError::DecodeFailed)
}
}
fn decode_indexed_field(&self, data: &[u8]) -> Result<(Header, usize), QpackError> {
let is_static = (data[0] & 0x40) != 0;
if !is_static {
return Err(QpackError::DecodeFailed);
}
let (index, consumed) = integer::decode_integer(data, 6)?;
if index as usize >= STATIC_TABLE_LEN {
return Err(QpackError::InvalidIndex(index));
}
let entry = get_static_entry(index as usize).ok_or(QpackError::InvalidIndex(index))?;
Ok((entry.clone(), consumed))
}
fn decode_literal_with_name_ref(&self, data: &[u8]) -> Result<(Header, usize), QpackError> {
let is_static = (data[0] & 0x10) != 0;
if !is_static {
return Err(QpackError::DecodeFailed);
}
let mut offset = 0;
let (index, index_len) = integer::decode_integer(data, 4)?;
offset += index_len;
if index as usize >= STATIC_TABLE_LEN {
return Err(QpackError::InvalidIndex(index));
}
let entry = get_static_entry(index as usize).ok_or(QpackError::InvalidIndex(index))?;
let name = entry.name().to_vec();
let (value, value_len) = self.decode_string(&data[offset..])?;
offset += value_len;
Ok((header_from_decoded(name, value), offset))
}
fn decode_literal_with_literal_name(&self, data: &[u8]) -> Result<(Header, usize), QpackError> {
let mut offset = 0;
let (name_len_value, prefix_len) = integer::decode_integer(data, 3)?;
offset += prefix_len;
let is_huffman = (data[0] & 0x08) != 0;
let (name, name_bytes) =
self.decode_string_with_len(&data[offset..], name_len_value as usize, is_huffman)?;
offset += name_bytes;
let (value, value_len) = self.decode_string(&data[offset..])?;
offset += value_len;
Ok((header_from_decoded(name, value), offset))
}
fn decode_string(&self, data: &[u8]) -> Result<(Vec<u8>, usize), QpackError> {
if data.is_empty() {
return Err(QpackError::BufferTooShort);
}
let is_huffman = (data[0] & 0x80) != 0;
let (length, prefix_len) = integer::decode_integer(data, 7)?;
let total_len = prefix_len + length as usize;
if data.len() < total_len {
return Err(QpackError::BufferTooShort);
}
let encoded = &data[prefix_len..total_len];
let decoded = if is_huffman {
huffman::decode(encoded)?
} else {
encoded.to_vec()
};
Ok((decoded, total_len))
}
fn decode_string_with_len(
&self,
data: &[u8],
length: usize,
is_huffman: bool,
) -> Result<(Vec<u8>, usize), QpackError> {
if data.len() < length {
return Err(QpackError::BufferTooShort);
}
let encoded = &data[..length];
let decoded = if is_huffman {
huffman::decode(encoded)?
} else {
encoded.to_vec()
};
Ok((decoded, length))
}
}
#[derive(Debug)]
pub struct DynamicDecoder {
table: DynamicTable,
max_field_section_size: u64,
max_table_capacity: u64,
last_required_insert_count: u64,
}
impl Default for DynamicDecoder {
fn default() -> Self {
Self::new()
}
}
impl DynamicDecoder {
pub fn new() -> Self {
Self {
table: DynamicTable::new(),
max_field_section_size: 16 * 1024,
max_table_capacity: 0,
last_required_insert_count: 0,
}
}
pub fn max_field_section_size(mut self, size: u64) -> Self {
self.max_field_section_size = size;
self
}
pub fn set_max_field_section_size(&mut self, size: u64) {
self.max_field_section_size = size;
}
pub fn set_max_table_capacity(&mut self, capacity: u64) {
self.max_table_capacity = capacity;
}
pub fn set_table_capacity(&mut self, capacity: u64) {
self.table.set_capacity(capacity);
}
pub fn table(&self) -> &DynamicTable {
&self.table
}
pub fn table_mut(&mut self) -> &mut DynamicTable {
&mut self.table
}
pub fn decode(&mut self, data: &[u8]) -> Result<DecodeOutput, QpackError> {
if data.len() < 2 {
return Err(QpackError::BufferTooShort);
}
let mut offset = 0;
let (enc_insert_count, ric_len) = integer::decode_integer(&data[offset..], 8)?;
offset += ric_len;
let required_insert_count = self.decode_required_insert_count(enc_insert_count)?;
self.last_required_insert_count = required_insert_count;
if required_insert_count > 0 && self.table.insert_count() < required_insert_count {
return Ok(DecodeOutput::Blocked);
}
if offset >= data.len() {
return Err(QpackError::BufferTooShort);
}
let sign = (data[offset] & 0x80) != 0;
let (delta_base, delta_len) = integer::decode_integer(&data[offset..], 7)?;
offset += delta_len;
let base = if sign {
if required_insert_count <= delta_base {
return Err(QpackError::DecodeFailed);
}
required_insert_count - delta_base - 1
} else {
required_insert_count + delta_base
};
let mut headers = Vec::new();
let mut total_size = 0u64;
while offset < data.len() {
let (header, consumed) =
self.decode_header(&data[offset..], base, required_insert_count)?;
total_size += (header.name().len() + header.value().len() + 32) as u64;
if total_size > self.max_field_section_size {
return Err(QpackError::DecodeFailed);
}
headers.push(header);
offset += consumed;
}
Ok(DecodeOutput::Decoded(headers))
}
fn decode_required_insert_count(&self, enc_insert_count: u64) -> Result<u64, QpackError> {
if enc_insert_count == 0 {
return Ok(0);
}
let max_entries = self.max_table_capacity / 32;
if max_entries == 0 {
return Err(QpackError::DecodeFailed);
}
let full_range = 2 * max_entries;
if enc_insert_count > full_range {
return Err(QpackError::DecodeFailed);
}
let total_inserts = self.table.insert_count();
let max_value = total_inserts + max_entries;
let max_wrapped = (max_value / full_range) * full_range;
let mut req_insert_count = max_wrapped + enc_insert_count - 1;
if req_insert_count > max_value {
if req_insert_count <= full_range {
return Err(QpackError::DecodeFailed);
}
req_insert_count -= full_range;
}
if req_insert_count == 0 {
return Err(QpackError::DecodeFailed);
}
Ok(req_insert_count)
}
fn decode_header(
&self,
data: &[u8],
base: u64,
required_insert_count: u64,
) -> Result<(Header, usize), QpackError> {
if data.is_empty() {
return Err(QpackError::BufferTooShort);
}
let first = data[0];
if first & 0x80 != 0 {
self.decode_indexed_field(data, base, required_insert_count)
} else if first & 0x40 != 0 {
self.decode_literal_with_name_ref(data, base, required_insert_count)
} else if first & 0x20 != 0 {
self.decode_literal_with_literal_name(data)
} else if first & 0x10 != 0 {
self.decode_indexed_field_post_base(data, base, required_insert_count)
} else {
self.decode_literal_with_post_base_name_ref(data, base, required_insert_count)
}
}
fn decode_indexed_field(
&self,
data: &[u8],
base: u64,
required_insert_count: u64,
) -> Result<(Header, usize), QpackError> {
let is_static = (data[0] & 0x40) != 0;
let (index, consumed) = integer::decode_integer(data, 6)?;
if is_static {
if index as usize >= STATIC_TABLE_LEN {
return Err(QpackError::InvalidIndex(index));
}
let entry = get_static_entry(index as usize).ok_or(QpackError::InvalidIndex(index))?;
Ok((entry.clone(), consumed))
} else {
if index < base {
let absolute_index = base - index - 1;
if absolute_index >= required_insert_count {
return Err(QpackError::DecodeFailed);
}
}
let entry = self
.table
.get_by_relative_index_repr(index, base)
.ok_or(QpackError::InvalidIndex(index))?;
Ok((
header_from_decoded(entry.name.clone(), entry.value.clone()),
consumed,
))
}
}
fn decode_indexed_field_post_base(
&self,
data: &[u8],
base: u64,
required_insert_count: u64,
) -> Result<(Header, usize), QpackError> {
let (post_base_index, consumed) = integer::decode_integer(data, 4)?;
let absolute_index = base + post_base_index;
if absolute_index >= required_insert_count {
return Err(QpackError::DecodeFailed);
}
let entry = self
.table
.get_by_post_base_index(post_base_index, base)
.ok_or(QpackError::InvalidIndex(post_base_index))?;
Ok((
header_from_decoded(entry.name.clone(), entry.value.clone()),
consumed,
))
}
fn decode_literal_with_name_ref(
&self,
data: &[u8],
base: u64,
required_insert_count: u64,
) -> Result<(Header, usize), QpackError> {
let is_static = (data[0] & 0x10) != 0;
let mut offset = 0;
let (index, index_len) = integer::decode_integer(data, 4)?;
offset += index_len;
let name = if is_static {
if index as usize >= STATIC_TABLE_LEN {
return Err(QpackError::InvalidIndex(index));
}
let entry = get_static_entry(index as usize).ok_or(QpackError::InvalidIndex(index))?;
entry.name().to_vec()
} else {
if index < base {
let absolute_index = base - index - 1;
if absolute_index >= required_insert_count {
return Err(QpackError::DecodeFailed);
}
}
let entry = self
.table
.get_by_relative_index_repr(index, base)
.ok_or(QpackError::InvalidIndex(index))?;
entry.name.clone()
};
let (value, value_len) = decode_string(&data[offset..])?;
offset += value_len;
Ok((header_from_decoded(name, value), offset))
}
fn decode_literal_with_post_base_name_ref(
&self,
data: &[u8],
base: u64,
required_insert_count: u64,
) -> Result<(Header, usize), QpackError> {
let mut offset = 0;
let (post_base_index, index_len) = integer::decode_integer(data, 3)?;
offset += index_len;
let absolute_index = base + post_base_index;
if absolute_index >= required_insert_count {
return Err(QpackError::DecodeFailed);
}
let entry = self
.table
.get_by_post_base_index(post_base_index, base)
.ok_or(QpackError::InvalidIndex(post_base_index))?;
let name = entry.name.clone();
let (value, value_len) = decode_string(&data[offset..])?;
offset += value_len;
Ok((header_from_decoded(name, value), offset))
}
fn decode_literal_with_literal_name(&self, data: &[u8]) -> Result<(Header, usize), QpackError> {
let mut offset = 0;
let (name_len_value, prefix_len) = integer::decode_integer(data, 3)?;
offset += prefix_len;
let is_huffman = (data[0] & 0x08) != 0;
let (name, name_bytes) =
decode_string_with_len(&data[offset..], name_len_value as usize, is_huffman)?;
offset += name_bytes;
let (value, value_len) = decode_string(&data[offset..])?;
offset += value_len;
Ok((header_from_decoded(name, value), offset))
}
pub fn insert(&mut self, name: Vec<u8>, value: Vec<u8>) -> Option<u64> {
self.table.insert(name, value)
}
pub fn last_required_insert_count(&self) -> u64 {
self.last_required_insert_count
}
}
fn decode_string(data: &[u8]) -> Result<(Vec<u8>, usize), QpackError> {
if data.is_empty() {
return Err(QpackError::BufferTooShort);
}
let is_huffman = (data[0] & 0x80) != 0;
let (length, prefix_len) = integer::decode_integer(data, 7)?;
let total_len = prefix_len + length as usize;
if data.len() < total_len {
return Err(QpackError::BufferTooShort);
}
let encoded = &data[prefix_len..total_len];
let decoded = if is_huffman {
huffman::decode(encoded)?
} else {
encoded.to_vec()
};
Ok((decoded, total_len))
}
fn decode_string_with_len(
data: &[u8],
length: usize,
is_huffman: bool,
) -> Result<(Vec<u8>, usize), QpackError> {
if data.len() < length {
return Err(QpackError::BufferTooShort);
}
let encoded = &data[..length];
let decoded = if is_huffman {
huffman::decode(encoded)?
} else {
encoded.to_vec()
};
Ok((decoded, length))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::qpack::encoder::Encoder;
use crate::qpack::header::Header;
#[test]
fn test_decode_indexed_field() {
let decoder = Decoder::new();
let data = [0x00, 0x00, 0xd1]; let headers = decoder.decode(&data).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name(), b":method");
assert_eq!(headers[0].value(), b"GET");
}
#[test]
fn test_decode_literal_with_name_ref() {
let decoder = Decoder::new();
let data = [0x00, 0x00, 0x5f, 0x09, 0x03, b'2', b'0', b'1'];
let headers = decoder.decode(&data).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name(), b":status");
assert_eq!(headers[0].value(), b"201");
}
#[test]
fn test_encode_decode_roundtrip() {
let encoder = Encoder::new().use_huffman(false);
let decoder = Decoder::new();
let original = vec![
Header::new(b":method", b"GET").unwrap(),
Header::new(b":scheme", b"https").unwrap(),
Header::new(b":path", b"/").unwrap(),
];
let mut buf = vec![0u8; 128];
let len = encoder.encode(&mut buf, &original).unwrap();
let decoded = decoder.decode(&buf[..len]).unwrap();
assert_eq!(decoded.len(), original.len());
for (dec, orig) in decoded.iter().zip(original.iter()) {
assert_eq!(dec.name(), orig.name());
assert_eq!(dec.value(), orig.value());
}
}
#[test]
fn test_encode_decode_with_huffman() {
let encoder = Encoder::new().use_huffman(true);
let decoder = Decoder::new();
let original = vec![
Header::new(b":method", b"GET").unwrap(),
Header::new(b":authority", b"www.example.com").unwrap(),
];
let mut buf = vec![0u8; 128];
let len = encoder.encode(&mut buf, &original).unwrap();
let decoded = decoder.decode(&buf[..len]).unwrap();
assert_eq!(decoded.len(), original.len());
assert_eq!(decoded[0].name(), b":method");
assert_eq!(decoded[0].value(), b"GET");
assert_eq!(decoded[1].name(), b":authority");
assert_eq!(decoded[1].value(), b"www.example.com");
}
#[test]
fn test_decode_buffer_too_short() {
let decoder = Decoder::new();
assert!(decoder.decode(&[0x00]).is_err());
}
#[test]
fn test_decode_invalid_index() {
let decoder = Decoder::new();
let data = [0x00, 0x00, 0xff, 0x24]; let result = decoder.decode(&data);
assert!(result.is_err());
}
#[test]
fn test_dynamic_decoder_static_only() {
let mut decoder = DynamicDecoder::new();
let data = [0x00, 0x00, 0xd1]; let DecodeOutput::Decoded(headers) = decoder.decode(&data).unwrap() else {
panic!("unexpected Blocked");
};
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name(), b":method");
assert_eq!(headers[0].value(), b"GET");
assert_eq!(decoder.last_required_insert_count(), 0);
}
#[test]
fn test_dynamic_encoder_decoder_roundtrip() {
use crate::qpack::encoder::DynamicEncoder;
let mut encoder = DynamicEncoder::new().use_huffman(false);
let mut decoder = DynamicDecoder::new();
encoder.set_max_table_capacity(4096);
encoder.set_table_capacity(1024);
decoder.set_max_table_capacity(4096);
decoder.set_table_capacity(1024);
encoder.insert(b":authority".to_vec(), b"www.example.com".to_vec());
decoder.insert(b":authority".to_vec(), b"www.example.com".to_vec());
let headers = vec![Header::new(b":authority", b"www.example.com").unwrap()];
let mut buf = vec![0u8; 64];
let len = encoder.encode(&mut buf, &headers, 0).unwrap();
let DecodeOutput::Decoded(decoded) = decoder.decode(&buf[..len]).unwrap() else {
panic!("unexpected Blocked");
};
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0].name(), b":authority");
assert_eq!(decoded[0].value(), b"www.example.com");
}
#[test]
fn test_dynamic_decoder_with_dynamic_ref() {
let mut decoder = DynamicDecoder::new();
decoder.set_max_table_capacity(4096);
decoder.set_table_capacity(1024);
decoder.insert(b"custom-header".to_vec(), b"custom-value".to_vec());
let data = [0x02, 0x00, 0x80];
let DecodeOutput::Decoded(headers) = decoder.decode(&data).unwrap() else {
panic!("unexpected Blocked");
};
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name(), b"custom-header");
assert_eq!(headers[0].value(), b"custom-value");
assert_eq!(decoder.last_required_insert_count(), 1);
}
#[test]
fn test_dynamic_decoder_blocking() {
let mut decoder = DynamicDecoder::new();
decoder.set_max_table_capacity(4096);
decoder.set_table_capacity(1024);
let data = [0x02, 0x00, 0x80];
let result = decoder.decode(&data).unwrap();
assert!(matches!(result, DecodeOutput::Blocked));
}
#[test]
fn test_dynamic_ref_absolute_index_exceeds_ric() {
let mut decoder = DynamicDecoder::new();
decoder.set_max_table_capacity(4096);
decoder.set_table_capacity(1024);
decoder.insert(b"name0".to_vec(), b"value0".to_vec()); decoder.insert(b"name1".to_vec(), b"value1".to_vec()); decoder.insert(b"name2".to_vec(), b"value2".to_vec());
let data = [0x03, 0x01, 0x80];
let result = decoder.decode(&data);
assert!(
result.is_err(),
"absolute index >= RIC should be QPACK_DECOMPRESSION_FAILED"
);
}
}