use core::{fmt, iter::FusedIterator};
use super::context::{QpackFieldSectionContext, QpackFieldSectionContextError};
use super::prefix::{QpackFieldSectionPrefix, QpackFieldSectionPrefixParseError};
use crate::qpack::field::line::{QpackFieldLine, QpackFieldLines, QpackFieldLinesParseError};
use crate::qpack::huffman::{QpackHuffmanDecodeError, QpackHuffmanDecoder, decode_prevalidated};
use crate::qpack::string::QpackStringLiteral;
use crate::qpack::table::{
QPACK_STATIC_TABLE_LEN, QpackDynamicTable, QpackDynamicTableError, QpackHeaderFieldRef,
QpackStaticTable,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackDecodedFieldEntry {
offset: usize,
name_len: usize,
value_len: usize,
never_indexed: bool,
}
impl QpackDecodedFieldEntry {
pub const EMPTY: Self = Self {
offset: 0,
name_len: 0,
value_len: 0,
never_indexed: false,
};
pub const fn never_indexed(self) -> bool {
self.never_indexed
}
}
pub struct QpackFieldSectionOutput<'bytes, 'fields> {
bytes: &'bytes mut [u8],
fields: &'fields mut [QpackDecodedFieldEntry],
len: usize,
}
impl<'bytes, 'fields> QpackFieldSectionOutput<'bytes, 'fields> {
pub fn new(bytes: &'bytes mut [u8], fields: &'fields mut [QpackDecodedFieldEntry]) -> Self {
Self {
bytes,
fields,
len: 0,
}
}
pub const fn byte_capacity(&self) -> usize {
self.bytes.len()
}
pub const fn field_capacity(&self) -> usize {
self.fields.len()
}
pub const fn len(&self) -> usize {
self.len
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackDecodedFieldRef<'a> {
name: &'a [u8],
value: &'a [u8],
never_indexed: bool,
}
impl<'a> QpackDecodedFieldRef<'a> {
pub const fn name(self) -> &'a [u8] {
self.name
}
pub const fn value(self) -> &'a [u8] {
self.value
}
pub const fn never_indexed(self) -> bool {
self.never_indexed
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackDecodedFieldSection<'a> {
bytes: &'a [u8],
fields: &'a [QpackDecodedFieldEntry],
required_insert_count: u64,
base: u64,
contains_dynamic_references: bool,
}
impl<'a> QpackDecodedFieldSection<'a> {
pub const fn required_insert_count(self) -> u64 {
self.required_insert_count
}
pub const fn base(self) -> u64 {
self.base
}
pub const fn contains_dynamic_references(self) -> bool {
self.contains_dynamic_references
}
pub const fn len(self) -> usize {
self.fields.len()
}
pub const fn is_empty(self) -> bool {
self.fields.is_empty()
}
pub fn get(self, index: usize) -> Option<QpackDecodedFieldRef<'a>> {
self.fields
.get(index)
.and_then(|entry| field_from_entry(self.bytes, *entry))
}
pub const fn iter(self) -> QpackDecodedFieldIter<'a> {
QpackDecodedFieldIter {
bytes: self.bytes,
fields: self.fields,
index: 0,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct QpackDecodedFieldIter<'a> {
bytes: &'a [u8],
fields: &'a [QpackDecodedFieldEntry],
index: usize,
}
impl<'a> Iterator for QpackDecodedFieldIter<'a> {
type Item = QpackDecodedFieldRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
let entry = *self.fields.get(self.index)?;
self.index = self.index.saturating_add(1);
field_from_entry(self.bytes, entry)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.fields.len().saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for QpackDecodedFieldIter<'_> {
fn len(&self) -> usize {
self.fields.len().saturating_sub(self.index)
}
}
impl FusedIterator for QpackDecodedFieldIter<'_> {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackFieldSectionBlocked {
required_insert_count: u64,
base: u64,
}
impl QpackFieldSectionBlocked {
pub const fn required_insert_count(self) -> u64 {
self.required_insert_count
}
pub const fn base(self) -> u64 {
self.base
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackFieldSectionDecodeOutcome<'a> {
Decoded(QpackDecodedFieldSection<'a>),
Blocked(QpackFieldSectionBlocked),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackFieldSectionDecodeError {
Prefix(QpackFieldSectionPrefixParseError),
Context(QpackFieldSectionContextError),
FieldLines(QpackFieldLinesParseError),
StaticIndexOutOfRange {
index: u64,
},
DynamicReference(QpackDynamicTableError),
LiteralNameHuffman(QpackHuffmanDecodeError),
ValueHuffman(QpackHuffmanDecodeError),
OutputBytesTooShort {
required: usize,
available: usize,
},
OutputFieldsTooShort {
required: usize,
available: usize,
},
OutputLengthOverflow,
}
impl QpackFieldSectionDecodeError {
pub const fn is_decompression_failed(self) -> bool {
matches!(
self,
Self::Prefix(_)
| Self::Context(_)
| Self::FieldLines(_)
| Self::StaticIndexOutOfRange { .. }
| Self::DynamicReference(_)
| Self::LiteralNameHuffman(_)
| Self::ValueHuffman(_)
)
}
pub const fn is_output_provisioning_error(self) -> bool {
!self.is_decompression_failed()
}
}
impl fmt::Display for QpackFieldSectionDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Prefix(error) => write!(f, "invalid QPACK field-section prefix: {error}"),
Self::Context(error) => write!(f, "invalid QPACK field-section context: {error}"),
Self::FieldLines(error) => write!(f, "invalid QPACK field lines: {error}"),
Self::StaticIndexOutOfRange { index } => {
write!(f, "QPACK static index {index} is out of range")
}
Self::DynamicReference(error) => write!(f, "invalid QPACK dynamic reference: {error}"),
Self::LiteralNameHuffman(error) => {
write!(f, "invalid QPACK literal-name Huffman data: {error}")
}
Self::ValueHuffman(error) => write!(f, "invalid QPACK value Huffman data: {error}"),
Self::OutputBytesTooShort {
required,
available,
} => write!(
f,
"QPACK field-section output bytes are too short: need {required}, have {available}"
),
Self::OutputFieldsTooShort {
required,
available,
} => write!(
f,
"QPACK field-section output fields are too short: need {required}, have {available}"
),
Self::OutputLengthOverflow => {
f.write_str("QPACK field-section output length overflows usize")
}
}
}
}
impl core::error::Error for QpackFieldSectionDecodeError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackFieldSectionDecoder {
maximum_dynamic_table_capacity: u64,
}
impl QpackFieldSectionDecoder {
pub const fn new(maximum_dynamic_table_capacity: u64) -> Self {
Self {
maximum_dynamic_table_capacity,
}
}
pub const fn maximum_dynamic_table_capacity(self) -> u64 {
self.maximum_dynamic_table_capacity
}
pub fn decode<'output>(
&self,
encoded: &[u8],
table: &QpackDynamicTable<'_, '_>,
output: &'output mut QpackFieldSectionOutput<'_, '_>,
) -> Result<QpackFieldSectionDecodeOutcome<'output>, QpackFieldSectionDecodeError> {
let prefix = QpackFieldSectionPrefix::parse(encoded)
.map_err(QpackFieldSectionDecodeError::Prefix)?;
let context = QpackFieldSectionContext::decode(
prefix,
table.insert_count(),
self.maximum_dynamic_table_capacity,
)
.map_err(QpackFieldSectionDecodeError::Context)?;
if context.required_insert_count() > table.insert_count() {
return Ok(QpackFieldSectionDecodeOutcome::Blocked(
QpackFieldSectionBlocked {
required_insert_count: context.required_insert_count(),
base: context.base(),
},
));
}
let lines = QpackFieldLines::parse(&encoded[prefix.as_bytes().len()..])
.map_err(QpackFieldSectionDecodeError::FieldLines)?;
let required = preflight(lines, table, context)?;
if output.fields.len() < required.fields {
return Err(QpackFieldSectionDecodeError::OutputFieldsTooShort {
required: required.fields,
available: output.fields.len(),
});
}
if output.bytes.len() < required.bytes {
return Err(QpackFieldSectionDecodeError::OutputBytesTooShort {
required: required.bytes,
available: output.bytes.len(),
});
}
let verified = preflight(lines, table, context)?;
if verified != required {
return Err(QpackFieldSectionDecodeError::OutputLengthOverflow);
}
commit(
lines,
table,
context,
output,
required.bytes,
required.fields,
)?;
let bytes = &output.bytes[..required.bytes];
let fields = &output.fields[..required.fields];
Ok(QpackFieldSectionDecodeOutcome::Decoded(
QpackDecodedFieldSection {
bytes,
fields,
required_insert_count: context.required_insert_count(),
base: context.base(),
contains_dynamic_references: verified.contains_dynamic_references,
},
))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Preflight {
bytes: usize,
fields: usize,
contains_dynamic_references: bool,
}
fn preflight(
lines: QpackFieldLines<'_>,
table: &QpackDynamicTable<'_, '_>,
context: QpackFieldSectionContext,
) -> Result<Preflight, QpackFieldSectionDecodeError> {
let mut bytes = 0usize;
let mut fields = 0usize;
let mut contains_dynamic_references = false;
for line in lines.iter() {
let line = match line {
Ok(line) => line,
Err(_) => return Err(QpackFieldSectionDecodeError::OutputLengthOverflow),
};
let (name, value, references_dynamic_table) = line_lengths(line, table, context)?;
bytes = bytes
.checked_add(name)
.and_then(|length| length.checked_add(value))
.ok_or(QpackFieldSectionDecodeError::OutputLengthOverflow)?;
fields = fields
.checked_add(1)
.ok_or(QpackFieldSectionDecodeError::OutputLengthOverflow)?;
contains_dynamic_references |= references_dynamic_table;
}
Ok(Preflight {
bytes,
fields,
contains_dynamic_references,
})
}
fn line_lengths(
line: QpackFieldLine<'_>,
table: &QpackDynamicTable<'_, '_>,
context: QpackFieldSectionContext,
) -> Result<(usize, usize, bool), QpackFieldSectionDecodeError> {
match line {
QpackFieldLine::Indexed(line) => {
let field = resolve_indexed(line.is_static(), line.index().value(), table, context)?;
Ok((field.name().len(), field.value().len(), !line.is_static()))
}
QpackFieldLine::IndexedPostBase(line) => {
let field = table
.get_post_base(
context.required_insert_count(),
context.base(),
line.index().value(),
)
.map_err(QpackFieldSectionDecodeError::DynamicReference)?;
Ok((field.name().len(), field.value().len(), true))
}
QpackFieldLine::LiteralNameReference(line) => {
let name =
resolve_indexed(line.is_static(), line.name_index().value(), table, context)?
.name();
Ok((
name.len(),
literal_len(line.value(), false)?,
!line.is_static(),
))
}
QpackFieldLine::LiteralPostBaseNameReference(line) => {
let name = table
.get_post_base(
context.required_insert_count(),
context.base(),
line.name_index().value(),
)
.map_err(QpackFieldSectionDecodeError::DynamicReference)?
.name();
Ok((name.len(), literal_len(line.value(), false)?, true))
}
QpackFieldLine::LiteralName(line) => Ok((
literal_len(line.name(), true)?,
literal_len(line.value(), false)?,
false,
)),
}
}
fn resolve_indexed<'a>(
is_static: bool,
index: u64,
table: &'a QpackDynamicTable<'_, '_>,
context: QpackFieldSectionContext,
) -> Result<QpackHeaderFieldRef<'a>, QpackFieldSectionDecodeError> {
if is_static {
static_field(index)
} else {
table
.get_field_relative(context.required_insert_count(), context.base(), index)
.map_err(QpackFieldSectionDecodeError::DynamicReference)
}
}
fn literal_len(
literal: QpackStringLiteral<'_>,
name: bool,
) -> Result<usize, QpackFieldSectionDecodeError> {
if !literal.is_huffman() {
return Ok(literal.encoded_payload().len());
}
QpackHuffmanDecoder::required_decoded_len(literal.encoded_payload()).map_err(|error| {
if name {
QpackFieldSectionDecodeError::LiteralNameHuffman(error)
} else {
QpackFieldSectionDecodeError::ValueHuffman(error)
}
})
}
fn static_field(index: u64) -> Result<QpackHeaderFieldRef<'static>, QpackFieldSectionDecodeError> {
if index >= QPACK_STATIC_TABLE_LEN as u64 {
return Err(QpackFieldSectionDecodeError::StaticIndexOutOfRange { index });
}
QpackStaticTable::get(index as usize)
.ok_or(QpackFieldSectionDecodeError::StaticIndexOutOfRange { index })
}
fn commit(
lines: QpackFieldLines<'_>,
table: &QpackDynamicTable<'_, '_>,
context: QpackFieldSectionContext,
output: &mut QpackFieldSectionOutput<'_, '_>,
required_bytes: usize,
required_fields: usize,
) -> Result<(), QpackFieldSectionDecodeError> {
let mut offset = 0usize;
let mut field_index = 0usize;
for line in lines.iter() {
let line = match line {
Ok(line) => line,
Err(_) => return Err(QpackFieldSectionDecodeError::OutputLengthOverflow),
};
let entry = write_prevalidated_line(line, table, context, &mut output.bytes[offset..])?;
output.fields[field_index] = QpackDecodedFieldEntry {
offset,
name_len: entry.name_len,
value_len: entry.value_len,
never_indexed: entry.never_indexed,
};
offset += entry.name_len + entry.value_len;
field_index += 1;
}
if offset != required_bytes || field_index != required_fields {
return Err(QpackFieldSectionDecodeError::OutputLengthOverflow);
}
output.len = required_fields;
Ok(())
}
fn write_prevalidated_line(
line: QpackFieldLine<'_>,
table: &QpackDynamicTable<'_, '_>,
context: QpackFieldSectionContext,
destination: &mut [u8],
) -> Result<QpackDecodedFieldEntry, QpackFieldSectionDecodeError> {
match line {
QpackFieldLine::Indexed(line) => {
let field = resolve_indexed(line.is_static(), line.index().value(), table, context)?;
Ok(write_field(field, destination, false))
}
QpackFieldLine::IndexedPostBase(line) => {
let field = table
.get_post_base(
context.required_insert_count(),
context.base(),
line.index().value(),
)
.map_err(QpackFieldSectionDecodeError::DynamicReference)?;
Ok(write_field(field, destination, false))
}
QpackFieldLine::LiteralNameReference(line) => {
let name =
resolve_indexed(line.is_static(), line.name_index().value(), table, context)?
.name();
let value_len = literal_len(line.value(), false)?;
Ok(write_name_and_literal(
name,
line.value(),
value_len,
line.is_never_indexed(),
destination,
))
}
QpackFieldLine::LiteralPostBaseNameReference(line) => {
let name = table
.get_post_base(
context.required_insert_count(),
context.base(),
line.name_index().value(),
)
.map_err(QpackFieldSectionDecodeError::DynamicReference)?
.name();
let value_len = literal_len(line.value(), false)?;
Ok(write_name_and_literal(
name,
line.value(),
value_len,
line.is_never_indexed(),
destination,
))
}
QpackFieldLine::LiteralName(line) => {
let name_len = literal_len(line.name(), true)?;
let value_len = literal_len(line.value(), false)?;
write_prevalidated_literal(line.name(), name_len, destination);
write_prevalidated_literal(line.value(), value_len, &mut destination[name_len..]);
Ok(QpackDecodedFieldEntry {
offset: 0,
name_len,
value_len,
never_indexed: line.is_never_indexed(),
})
}
}
}
fn write_field(
field: QpackHeaderFieldRef<'_>,
destination: &mut [u8],
never_indexed: bool,
) -> QpackDecodedFieldEntry {
let name_len = field.name().len();
let value_len = field.value().len();
destination[..name_len].copy_from_slice(field.name());
destination[name_len..name_len + value_len].copy_from_slice(field.value());
QpackDecodedFieldEntry {
offset: 0,
name_len,
value_len,
never_indexed,
}
}
fn write_name_and_literal(
name: &[u8],
value: QpackStringLiteral<'_>,
value_len: usize,
never_indexed: bool,
destination: &mut [u8],
) -> QpackDecodedFieldEntry {
destination[..name.len()].copy_from_slice(name);
write_prevalidated_literal(value, value_len, &mut destination[name.len()..]);
QpackDecodedFieldEntry {
offset: 0,
name_len: name.len(),
value_len,
never_indexed,
}
}
fn write_prevalidated_literal(
literal: QpackStringLiteral<'_>,
decoded_len: usize,
destination: &mut [u8],
) {
if literal.is_huffman() {
decode_prevalidated(literal.encoded_payload(), &mut destination[..decoded_len]);
} else {
destination[..decoded_len].copy_from_slice(literal.encoded_payload());
}
}
fn field_from_entry<'a>(
bytes: &'a [u8],
entry: QpackDecodedFieldEntry,
) -> Option<QpackDecodedFieldRef<'a>> {
let name_end = entry.offset.checked_add(entry.name_len)?;
let value_end = name_end.checked_add(entry.value_len)?;
Some(QpackDecodedFieldRef {
name: bytes.get(entry.offset..name_end)?,
value: bytes.get(name_end..value_end)?,
never_indexed: entry.never_indexed,
})
}