use crate::error::QpackError;
use super::dynamic_table::DynamicTable;
use super::huffman;
use super::integer;
use super::table::STATIC_TABLE;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncoderInstruction {
SetDynamicTableCapacity { capacity: u64 },
InsertWithNameReference {
is_static: bool,
name_index: u64,
value: Vec<u8>,
},
InsertWithLiteralName { name: Vec<u8>, value: Vec<u8> },
Duplicate { relative_index: u64 },
}
#[derive(Debug)]
pub struct EncoderStream {
send_buffer: Vec<u8>,
max_table_capacity: u64,
}
impl Default for EncoderStream {
fn default() -> Self {
Self::new()
}
}
impl EncoderStream {
pub fn new() -> Self {
Self {
send_buffer: Vec::new(),
max_table_capacity: 0,
}
}
pub fn write_stream_type(&mut self) {
self.send_buffer.push(0x02);
}
pub fn set_max_table_capacity(&mut self, capacity: u64) {
self.max_table_capacity = capacity;
}
pub fn encode_set_capacity(&mut self, capacity: u64) -> Result<(), QpackError> {
if self.max_table_capacity == 0 {
return Err(QpackError::DynamicTableDisabled);
}
if capacity > self.max_table_capacity {
return Err(QpackError::CapacityExceeded);
}
integer::encode_integer_to_vec(&mut self.send_buffer, capacity, 5, 0x20);
Ok(())
}
pub fn encode_insert_with_name_ref(
&mut self,
is_static: bool,
name_index: u64,
value: &[u8],
) -> Result<(), QpackError> {
if self.max_table_capacity == 0 {
return Err(QpackError::DynamicTableDisabled);
}
let prefix = if is_static { 0xc0 } else { 0x80 };
integer::encode_integer_to_vec(&mut self.send_buffer, name_index, 6, prefix);
encode_string(&mut self.send_buffer, value);
Ok(())
}
pub fn encode_insert_with_literal_name(
&mut self,
name: &[u8],
value: &[u8],
) -> Result<(), QpackError> {
if self.max_table_capacity == 0 {
return Err(QpackError::DynamicTableDisabled);
}
encode_string_with_prefix(&mut self.send_buffer, name, 5, 0x40);
encode_string(&mut self.send_buffer, value);
Ok(())
}
pub fn encode_duplicate(&mut self, relative_index: u64) -> Result<(), QpackError> {
if self.max_table_capacity == 0 {
return Err(QpackError::DynamicTableDisabled);
}
integer::encode_integer_to_vec(&mut self.send_buffer, relative_index, 5, 0x00);
Ok(())
}
pub fn get_data(&self) -> &[u8] {
&self.send_buffer
}
pub fn consume_data(&mut self, len: usize) {
if len >= self.send_buffer.len() {
self.send_buffer.clear();
} else {
self.send_buffer.drain(..len);
}
}
pub fn has_pending(&self) -> bool {
!self.send_buffer.is_empty()
}
}
#[derive(Debug)]
pub struct EncoderStreamReceiver {
recv_buffer: Vec<u8>,
max_table_capacity: u64,
}
impl Default for EncoderStreamReceiver {
fn default() -> Self {
Self::new()
}
}
impl EncoderStreamReceiver {
pub fn new() -> Self {
Self {
recv_buffer: Vec::new(),
max_table_capacity: 0,
}
}
pub fn set_max_table_capacity(&mut self, capacity: u64) {
self.max_table_capacity = capacity;
}
pub fn receive(&mut self, data: &[u8]) {
self.recv_buffer.extend_from_slice(data);
}
pub fn process(
&mut self,
table: &mut DynamicTable,
) -> Result<Option<EncoderInstruction>, QpackError> {
if self.recv_buffer.is_empty() {
return Ok(None);
}
let first = self.recv_buffer[0];
let result = if first & 0x80 != 0 {
self.decode_insert_with_name_ref(table)
} else if first & 0x40 != 0 {
self.decode_insert_with_literal_name(table)
} else if first & 0x20 != 0 {
self.decode_set_capacity(table)
} else {
self.decode_duplicate(table)
};
match result {
Err(QpackError::BufferTooShort) => Ok(None),
other => other,
}
}
fn decode_set_capacity(
&mut self,
table: &mut DynamicTable,
) -> Result<Option<EncoderInstruction>, QpackError> {
let (capacity, consumed) = integer::decode_integer(&self.recv_buffer, 5)?;
if capacity > self.max_table_capacity {
return Err(QpackError::DecodeFailed);
}
self.recv_buffer.drain(..consumed);
table.set_capacity(capacity);
Ok(Some(EncoderInstruction::SetDynamicTableCapacity {
capacity,
}))
}
fn decode_insert_with_name_ref(
&mut self,
table: &mut DynamicTable,
) -> Result<Option<EncoderInstruction>, QpackError> {
let is_static = (self.recv_buffer[0] & 0x40) != 0;
let (name_index, mut consumed) = integer::decode_integer(&self.recv_buffer, 6)?;
let (value, value_len) = decode_string(&self.recv_buffer[consumed..])?;
consumed += value_len;
let name = if is_static {
STATIC_TABLE
.get(name_index as usize)
.ok_or(QpackError::InvalidIndex(name_index))?
.name()
.to_vec()
} else {
table
.get_by_relative_index_encoder(name_index)
.ok_or(QpackError::InvalidIndex(name_index))?
.name
.clone()
};
table
.insert(name, value.clone())
.ok_or(QpackError::DecodeFailed)?;
self.recv_buffer.drain(..consumed);
Ok(Some(EncoderInstruction::InsertWithNameReference {
is_static,
name_index,
value,
}))
}
fn decode_insert_with_literal_name(
&mut self,
table: &mut DynamicTable,
) -> Result<Option<EncoderInstruction>, QpackError> {
let (name, mut consumed) = decode_string_with_prefix(&self.recv_buffer, 5)?;
let (value, value_len) = decode_string(&self.recv_buffer[consumed..])?;
consumed += value_len;
table
.insert(name.clone(), value.clone())
.ok_or(QpackError::DecodeFailed)?;
self.recv_buffer.drain(..consumed);
Ok(Some(EncoderInstruction::InsertWithLiteralName {
name,
value,
}))
}
fn decode_duplicate(
&mut self,
table: &mut DynamicTable,
) -> Result<Option<EncoderInstruction>, QpackError> {
let (relative_index, consumed) = integer::decode_integer(&self.recv_buffer, 5)?;
table
.duplicate(relative_index)
.ok_or(QpackError::InvalidIndex(relative_index))?;
self.recv_buffer.drain(..consumed);
Ok(Some(EncoderInstruction::Duplicate { relative_index }))
}
pub fn buffer(&self) -> &[u8] {
&self.recv_buffer
}
}
fn encode_string(buf: &mut Vec<u8>, data: &[u8]) {
encode_string_with_prefix(buf, data, 7, 0x00);
}
fn encode_string_with_prefix(buf: &mut Vec<u8>, data: &[u8], prefix_bits: u8, base: u8) {
let huffman_len = huffman::encoded_len(data);
if huffman_len < data.len() {
let huffman_flag = 1u8 << prefix_bits;
integer::encode_integer_to_vec(buf, huffman_len as u64, prefix_bits, base | huffman_flag);
let start = buf.len();
buf.resize(start + huffman_len, 0);
huffman::encode(&mut buf[start..], data);
} else {
integer::encode_integer_to_vec(buf, data.len() as u64, prefix_bits, base);
buf.extend_from_slice(data);
}
}
fn decode_string(data: &[u8]) -> Result<(Vec<u8>, usize), QpackError> {
decode_string_with_prefix(data, 7)
}
fn decode_string_with_prefix(data: &[u8], prefix_bits: u8) -> Result<(Vec<u8>, usize), QpackError> {
if data.is_empty() {
return Err(QpackError::BufferTooShort);
}
let huffman_flag = 1u8 << prefix_bits;
let is_huffman = (data[0] & huffman_flag) != 0;
let (length, prefix_len) = integer::decode_integer(data, prefix_bits)?;
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))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_set_capacity() {
let mut stream = EncoderStream::new();
stream.set_max_table_capacity(4096);
stream.encode_set_capacity(220).unwrap();
assert_eq!(stream.get_data(), &[0x3f, 0xbd, 0x01]);
}
#[test]
fn test_encode_set_capacity_exceeds_max() {
let mut stream = EncoderStream::new();
stream.set_max_table_capacity(100);
assert_eq!(
stream.encode_set_capacity(200),
Err(QpackError::CapacityExceeded)
);
assert!(stream.get_data().is_empty());
}
#[test]
fn test_encode_set_capacity_when_disabled() {
let mut stream = EncoderStream::new();
assert_eq!(
stream.encode_set_capacity(100),
Err(QpackError::DynamicTableDisabled)
);
assert!(stream.get_data().is_empty());
}
#[test]
fn test_encode_insert_with_name_ref_static() {
let mut stream = EncoderStream::new();
stream.set_max_table_capacity(4096);
stream
.encode_insert_with_name_ref(true, 0, b"www.example.com")
.unwrap();
let data = stream.get_data();
assert_eq!(data[0], 0xc0);
}
#[test]
fn test_encode_insert_when_disabled() {
let mut stream = EncoderStream::new();
assert_eq!(
stream.encode_insert_with_name_ref(true, 0, b"value"),
Err(QpackError::DynamicTableDisabled)
);
assert_eq!(
stream.encode_insert_with_literal_name(b"name", b"value"),
Err(QpackError::DynamicTableDisabled)
);
assert_eq!(
stream.encode_duplicate(0),
Err(QpackError::DynamicTableDisabled)
);
assert!(stream.get_data().is_empty());
}
#[test]
fn test_encode_insert_with_literal_name() {
let mut stream = EncoderStream::new();
stream.set_max_table_capacity(4096);
stream
.encode_insert_with_literal_name(b"custom-key", b"custom-value")
.unwrap();
let data = stream.get_data();
assert_eq!(data[0] & 0xc0, 0x40);
}
#[test]
fn test_encode_duplicate() {
let mut stream = EncoderStream::new();
stream.set_max_table_capacity(4096);
stream.encode_duplicate(5).unwrap();
assert_eq!(stream.get_data(), &[0x05]);
}
#[test]
fn test_decode_insert_with_name_ref_static() {
let mut table = DynamicTable::with_capacity(4096);
let mut receiver = EncoderStreamReceiver::new();
receiver.set_max_table_capacity(4096);
let mut data = vec![0xc0, 0x0f];
data.extend_from_slice(b"www.example.com");
receiver.receive(&data);
let instruction = receiver.process(&mut table).unwrap().unwrap();
match instruction {
EncoderInstruction::InsertWithNameReference {
is_static,
name_index,
value,
} => {
assert!(is_static);
assert_eq!(name_index, 0);
assert_eq!(value, b"www.example.com");
}
_ => panic!("Unexpected instruction"),
}
assert_eq!(table.len(), 1);
let entry = table.get_by_absolute_index(0).unwrap();
assert_eq!(entry.name, b":authority");
assert_eq!(entry.value, b"www.example.com");
}
}