use super::{
encode_string, read_exact, read_first_byte, read_string_with_huffman, read_varint,
validate_value,
};
use crate::{
h3::{H3Error, H3ErrorCode},
headers::{entry_name::EntryName, huffman, integer_prefix},
};
use futures_lite::io::AsyncRead;
const INSERT_WITH_NAME_REF: u8 = 0x80;
const NAME_REF_STATIC_FLAG: u8 = 0x40;
const INSERT_WITH_LITERAL_NAME: u8 = 0x40;
const LITERAL_NAME_HUFFMAN_FLAG: u8 = 0x20;
const SET_DYNAMIC_TABLE_CAPACITY: u8 = 0x20;
const DUPLICATE: u8 = 0x00;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(in crate::headers) enum EncoderInstruction {
SetCapacity(usize),
InsertWithStaticNameRef { name_index: usize, value: Vec<u8> },
InsertWithDynamicNameRef {
relative_index: usize,
value: Vec<u8>,
},
InsertWithLiteralName {
name: EntryName<'static>,
value: Vec<u8>,
},
Duplicate { relative_index: usize },
}
pub(in crate::headers) async fn parse(
max_entry_size: usize,
stream: &mut (impl AsyncRead + Unpin),
) -> Result<Option<EncoderInstruction>, H3Error> {
parse_inner(max_entry_size, stream)
.await
.map_err(|()| H3ErrorCode::QpackEncoderStreamError.into())
}
async fn parse_inner(
max_entry_size: usize,
stream: &mut (impl AsyncRead + Unpin),
) -> Result<Option<EncoderInstruction>, ()> {
let Some(first) = read_first_byte(stream).await? else {
return Ok(None);
};
let instr = if first & INSERT_WITH_NAME_REF != 0 {
let is_static = first & NAME_REF_STATIC_FLAG != 0;
let index = read_varint(first, 6, stream).await?;
let value = read_string_with_huffman(max_entry_size, stream).await?;
validate_value(&value)?;
if is_static {
EncoderInstruction::InsertWithStaticNameRef {
name_index: index,
value,
}
} else {
EncoderInstruction::InsertWithDynamicNameRef {
relative_index: index,
value,
}
}
} else if first & INSERT_WITH_LITERAL_NAME != 0 {
let is_huffman = first & LITERAL_NAME_HUFFMAN_FLAG != 0;
let name_len = read_varint(first, 5, stream).await?;
let name_bytes = read_exact(name_len, max_entry_size, stream).await?;
let name_bytes = if is_huffman {
huffman::decode(&name_bytes).map_err(|e| {
log::error!("QPACK encoder: huffman name decode failed: {e:?}");
})?
} else {
name_bytes
};
let name = EntryName::try_from(name_bytes).map_err(|()| {
log::error!("QPACK encoder: invalid literal name");
})?;
let value = read_string_with_huffman(max_entry_size, stream).await?;
validate_value(&value)?;
EncoderInstruction::InsertWithLiteralName { name, value }
} else if first & SET_DYNAMIC_TABLE_CAPACITY != 0 {
let capacity = read_varint(first, 5, stream).await?;
EncoderInstruction::SetCapacity(capacity)
} else {
let relative_index = read_varint(first, 5, stream).await?;
EncoderInstruction::Duplicate { relative_index }
};
Ok(Some(instr))
}
pub(in crate::headers) fn encode_set_capacity(capacity: usize) -> Vec<u8> {
let mut buf = Vec::with_capacity(integer_prefix::encoded_length(capacity, 5));
integer_prefix::encode_into(capacity, 5, &mut buf);
buf[0] |= SET_DYNAMIC_TABLE_CAPACITY;
buf
}
pub(in crate::headers) fn encode_insert_with_literal_name(name: &[u8], value: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(name.len() + value.len() + 4);
let name_start = buf.len();
encode_string(name, 5, &mut buf);
buf[name_start] |= INSERT_WITH_LITERAL_NAME;
encode_string(value, 7, &mut buf);
buf
}
pub(in crate::headers) fn encode_insert_with_name_ref(
name_index: usize,
is_static: bool,
value: &[u8],
) -> Vec<u8> {
let mut buf = Vec::with_capacity(value.len() + 4);
let start = buf.len();
integer_prefix::encode_into(name_index, 6, &mut buf);
buf[start] |= INSERT_WITH_NAME_REF | if is_static { NAME_REF_STATIC_FLAG } else { 0 };
encode_string(value, 7, &mut buf);
buf
}
pub(in crate::headers) fn encode_duplicate(relative_index: usize) -> Vec<u8> {
let mut buf = Vec::with_capacity(integer_prefix::encoded_length(relative_index, 5));
integer_prefix::encode_into(relative_index, 5, &mut buf);
buf[0] |= DUPLICATE;
buf
}
#[cfg(test)]
mod spec_vectors {
use super::*;
use futures_lite::future::block_on;
#[track_caller]
fn parse_one(mut bytes: &[u8]) -> EncoderInstruction {
let instr = block_on(parse(usize::MAX, &mut bytes))
.expect("parse ok")
.expect("not eof");
assert!(bytes.is_empty(), "trailing bytes after parse: {bytes:?}");
instr
}
#[test]
fn b2_set_capacity_220() {
assert_eq!(
parse_one(&[0x3f, 0xbd, 0x01]),
EncoderInstruction::SetCapacity(220),
);
}
#[test]
fn b2_insert_name_ref_static_authority() {
let bytes = [
0xc0, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
0x63, 0x6f, 0x6d,
];
assert_eq!(
parse_one(&bytes),
EncoderInstruction::InsertWithStaticNameRef {
name_index: 0,
value: b"www.example.com".to_vec(),
},
);
}
#[test]
fn b2_insert_name_ref_static_path() {
let bytes = [
0xc1, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68,
];
assert_eq!(
parse_one(&bytes),
EncoderInstruction::InsertWithStaticNameRef {
name_index: 1,
value: b"/sample/path".to_vec(),
},
);
}
#[test]
fn b3_insert_literal_name_custom_key() {
let bytes = [
0x4a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65,
];
assert_eq!(
parse_one(&bytes),
EncoderInstruction::InsertWithLiteralName {
name: EntryName::try_from(b"custom-key".to_vec()).unwrap(),
value: b"custom-value".to_vec(),
},
);
}
#[test]
fn b4_duplicate_relative_index_2() {
assert_eq!(
parse_one(&[0x02]),
EncoderInstruction::Duplicate { relative_index: 2 },
);
}
#[test]
fn b5_insert_name_ref_dynamic() {
let bytes = [
0x81, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x32,
];
assert_eq!(
parse_one(&bytes),
EncoderInstruction::InsertWithDynamicNameRef {
relative_index: 1,
value: b"custom-value2".to_vec(),
},
);
}
}