use super::{
FieldSection, PseudoHeaders, huffman,
static_table::{PseudoHeaderName, StaticHeaderName, static_entry},
varint,
};
use crate::{
HeaderName, HeaderValue, Headers, Method, Status,
headers::qpack::{
INDEXED_FIELD_LINE, LITERAL_WITH_LITERAL_NAME, LITERAL_WITH_NAME_REF, NAME_REF_STATIC_FLAG,
huffman::HuffmanError, varint::VarIntError,
},
};
use core::convert::TryFrom;
use std::{
borrow::Cow,
fmt::{self, Display, Formatter},
};
#[derive(Debug, thiserror::Error, Clone, Copy)]
#[non_exhaustive]
pub enum DecoderError {
#[error(transparent)]
Huffman(#[from] HuffmanError),
#[error(transparent)]
VarInt(#[from] VarIntError),
#[error("static table index {0} out of range (0-98)")]
InvalidStaticIndex(usize),
#[error("dynamic table references are not yet supported")]
DynamicTableUnsupported,
#[error("unexpected end of field section")]
UnexpectedEnd,
#[error("required insert count must be zero without dynamic table")]
NonZeroInsertCount,
#[error("invalid header name")]
InvalidHeaderName,
#[error("invalid header value")]
InvalidHeaderValue,
#[error("method not recongized")]
UnrecognizedMethod,
#[error("invalid status")]
InvalidStatus,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum FieldLine {
Header(HeaderName<'static>, HeaderValue),
Pseudo(PseudoHeader),
}
impl Display for FieldLine {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
FieldLine::Header(header_name, header_value) => {
write!(f, "{header_name}: {header_value}")
}
FieldLine::Pseudo(pseudo_header) => write!(f, "{pseudo_header}"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum PseudoHeader {
Method(Method),
Status(Status),
Other(PseudoHeaderName, Option<Cow<'static, str>>),
}
impl PseudoHeader {
fn apply(self, pseudos: &mut PseudoHeaders<'static>) {
match self {
PseudoHeader::Method(m) => {
pseudos.method.get_or_insert(m);
}
PseudoHeader::Status(s) => {
pseudos.status.get_or_insert(s);
}
PseudoHeader::Other(PseudoHeaderName::Authority, Some(v)) => {
pseudos.authority.get_or_insert(v);
}
PseudoHeader::Other(PseudoHeaderName::Path, Some(v)) => {
pseudos.path.get_or_insert(v);
}
PseudoHeader::Other(PseudoHeaderName::Scheme, Some(v)) => {
pseudos.scheme.get_or_insert(v);
}
PseudoHeader::Other(PseudoHeaderName::Method | PseudoHeaderName::Status, _)
| PseudoHeader::Other(_, None) => {}
}
}
}
impl Display for PseudoHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
PseudoHeader::Method(method) => write!(f, ":method: {method}"),
PseudoHeader::Status(status) => write!(f, ":status: {status}"),
PseudoHeader::Other(pseudo_header_name, Some(value)) => {
write!(f, "{pseudo_header_name}: {value}")
}
PseudoHeader::Other(pseudo_header_name, None) => write!(f, "{pseudo_header_name}:"),
}
}
}
fn decode_string(input: &[u8], prefix_size: u8) -> Result<(Vec<u8>, &[u8]), DecoderError> {
let [first, ..] = input else {
return Err(DecoderError::UnexpectedEnd);
};
let huffman_encoded = first & (1 << prefix_size) != 0;
let (length, rest) = varint::decode(input, prefix_size)?;
if rest.len() < length {
return Err(DecoderError::UnexpectedEnd);
}
let (string_bytes, rest) = rest.split_at(length);
let decoded = if huffman_encoded {
huffman::decode(string_bytes)?
} else {
string_bytes.to_vec()
};
Ok((decoded, rest))
}
impl FieldSection<'static> {
pub fn decode(encoded: &[u8]) -> Result<Self, DecoderError> {
let (required_insert_count, rest) = varint::decode(encoded, 8)?;
if required_insert_count != 0 {
return Err(DecoderError::NonZeroInsertCount);
}
let (_delta_base, mut rest) = varint::decode(rest, 7)?;
let mut pseudo_headers = PseudoHeaders::default();
let mut headers = Headers::new();
while !rest.is_empty() {
let first = rest[0];
let (field_line, rest_) = if first & INDEXED_FIELD_LINE != 0 {
decode_indexed(rest)?
} else if first & LITERAL_WITH_NAME_REF != 0 {
decode_literal_with_name_ref(rest)?
} else if first & LITERAL_WITH_LITERAL_NAME != 0 {
decode_literal_with_literal_name(rest)?
} else {
return Err(DecoderError::DynamicTableUnsupported);
};
rest = rest_;
match field_line {
FieldLine::Header(name, value) => {
headers.append(name, value);
}
FieldLine::Pseudo(pseudo) => pseudo.apply(&mut pseudo_headers),
}
}
Ok(Self {
pseudo_headers,
headers: Cow::Owned(headers),
})
}
}
fn decode_indexed(input: &[u8]) -> Result<(FieldLine, &[u8]), DecoderError> {
let first = input[0];
let is_static = first & LITERAL_WITH_NAME_REF != 0;
if !is_static {
return Err(DecoderError::DynamicTableUnsupported);
}
let (index, rest) = varint::decode(input, 6)?;
let (name, value) = static_entry(index)?;
let field_line = match name {
StaticHeaderName::Header(known) => {
FieldLine::Header(HeaderName::from(*known), HeaderValue::from(*value))
}
StaticHeaderName::Pseudo(PseudoHeaderName::Method) => {
FieldLine::Pseudo(PseudoHeader::Method(Method::try_from(*value).unwrap()))
}
StaticHeaderName::Pseudo(PseudoHeaderName::Status) => {
FieldLine::Pseudo(PseudoHeader::Status(value.parse().unwrap()))
}
StaticHeaderName::Pseudo(other) => {
FieldLine::Pseudo(PseudoHeader::Other(*other, Some(Cow::Borrowed(value))))
}
};
Ok((field_line, rest))
}
fn decode_literal_with_name_ref(input: &[u8]) -> Result<(FieldLine, &[u8]), DecoderError> {
let first = input[0];
let is_static = first & NAME_REF_STATIC_FLAG != 0;
if !is_static {
return Err(DecoderError::DynamicTableUnsupported);
}
let (index, rest) = varint::decode(input, 4)?;
let (name, _) = static_entry(index)?;
let (value_bytes, rest) = decode_string(rest, 7)?;
let field_line = match name {
StaticHeaderName::Header(known) => {
FieldLine::Header(HeaderName::from(*known), HeaderValue::parse(&value_bytes))
}
StaticHeaderName::Pseudo(PseudoHeaderName::Method) => {
FieldLine::Pseudo(PseudoHeader::Method(
Method::parse(&value_bytes).map_err(|_| DecoderError::UnrecognizedMethod)?,
))
}
StaticHeaderName::Pseudo(PseudoHeaderName::Status) => {
FieldLine::Pseudo(PseudoHeader::Status(
std::str::from_utf8(&value_bytes)
.map_err(|_| DecoderError::InvalidStatus)?
.parse()
.map_err(|_| DecoderError::InvalidStatus)?,
))
}
StaticHeaderName::Pseudo(pseudo) => FieldLine::Pseudo(PseudoHeader::Other(
*pseudo,
Some(Cow::Owned(
String::from_utf8(value_bytes).map_err(|_| DecoderError::InvalidHeaderValue)?,
)),
)),
};
Ok((field_line, rest))
}
fn decode_literal_with_literal_name(input: &[u8]) -> Result<(FieldLine, &[u8]), DecoderError> {
let (name_bytes, rest) = decode_string(input, 3)?;
let (value_bytes, rest) = decode_string(rest, 7)?;
let name = {
let bytes: &[u8] = &name_bytes;
HeaderName::parse(bytes)
.map(|n| n.to_owned())
.map_err(|_| DecoderError::InvalidHeaderName)
}?;
let value = {
let bytes: &[u8] = &value_bytes;
HeaderValue::parse(bytes)
};
Ok((FieldLine::Header(name, value), rest))
}