use core::fmt;
use super::{
QpackEncoderInstruction, QpackEncoderInstructions, QpackEncoderInstructionsParseError,
};
use crate::qpack::huffman::{QpackHuffmanDecodeError, QpackHuffmanDecoder};
use crate::qpack::string::QpackStringLiteral;
use crate::qpack::table::{
QPACK_STATIC_TABLE_LEN, QpackDynamicTable, QpackDynamicTableError, QpackStaticTable,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QpackEncoderInstructionApplier {
maximum_dynamic_table_capacity: u64,
}
impl QpackEncoderInstructionApplier {
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 apply<F>(
&self,
table: &mut QpackDynamicTable<'_, '_>,
instruction: QpackEncoderInstruction<'_>,
scratch: &mut [u8],
evictable: F,
) -> Result<QpackEncoderInstructionApplyOutcome, QpackEncoderInstructionApplyError>
where
F: FnMut(u64) -> bool,
{
match instruction {
QpackEncoderInstruction::SetDynamicTableCapacity(instruction) => {
let requested = instruction.capacity().value();
if requested > self.maximum_dynamic_table_capacity {
return Err(QpackEncoderInstructionApplyError::CapacityExceedsMaximum {
requested,
maximum: self.maximum_dynamic_table_capacity,
});
}
let capacity = usize::try_from(requested).map_err(|_| {
QpackEncoderInstructionApplyError::CapacityNotRepresentable { value: requested }
})?;
table
.set_capacity(capacity, evictable)
.map_err(QpackEncoderInstructionApplyError::CapacityChange)?;
Ok(QpackEncoderInstructionApplyOutcome::CapacityUpdated { capacity })
}
QpackEncoderInstruction::InsertWithNameReference(instruction) => {
let value = instruction.value();
if instruction.is_static() {
let index = instruction.name_index().value();
if index >= QPACK_STATIC_TABLE_LEN as u64 {
return Err(
QpackEncoderInstructionApplyError::StaticNameIndexOutOfRange { index },
);
}
let name = QpackStaticTable::get(index as usize)
.ok_or(
QpackEncoderInstructionApplyError::StaticNameIndexOutOfRange { index },
)?
.name();
let value_len = decoded_len(value, false)?;
let required = if value.is_huffman() { value_len } else { 0 };
preflight_insert(table, name.len(), value_len, required, scratch.len())?;
let value = if value.is_huffman() {
decode(value, &mut scratch[..value_len], false)?
} else {
value.encoded_payload()
};
insert(table, name, value, evictable)
} else {
let relative = instruction.name_index().value();
let name_len = table
.get_encoder_relative(relative)
.map_err(QpackEncoderInstructionApplyError::DynamicNameReference)?
.name()
.len();
let value_len = decoded_len(value, false)?;
let required =
checked_sum(name_len, if value.is_huffman() { value_len } else { 0 })?;
preflight_insert(table, name_len, value_len, required, scratch.len())?;
let (name_destination, value_destination) =
scratch[..required].split_at_mut(name_len);
let source = table
.get_encoder_relative(relative)
.map_err(QpackEncoderInstructionApplyError::DynamicNameReference)?;
name_destination.copy_from_slice(source.name());
let value = if value.is_huffman() {
decode(value, value_destination, false)?
} else {
value.encoded_payload()
};
insert(table, name_destination, value, evictable)
}
}
QpackEncoderInstruction::InsertWithLiteralName(instruction) => {
let name_literal = instruction.name();
let value_literal = instruction.value();
let name_len = decoded_len(name_literal, true)?;
let value_len = decoded_len(value_literal, false)?;
let name_scratch = if name_literal.is_huffman() {
name_len
} else {
0
};
let value_scratch = if value_literal.is_huffman() {
value_len
} else {
0
};
let required = checked_sum(name_scratch, value_scratch)?;
preflight_insert(table, name_len, value_len, required, scratch.len())?;
let (name_destination, value_destination) =
scratch[..required].split_at_mut(name_scratch);
let name = if name_literal.is_huffman() {
decode(name_literal, name_destination, true)?
} else {
name_literal.encoded_payload()
};
let value = if value_literal.is_huffman() {
decode(value_literal, value_destination, false)?
} else {
value_literal.encoded_payload()
};
insert(table, name, value, evictable)
}
QpackEncoderInstruction::Duplicate(instruction) => {
let relative = instruction.index().value();
let source = table
.get_encoder_relative(relative)
.map_err(QpackEncoderInstructionApplyError::DuplicateReference)?;
let name_len = source.name().len();
let value_len = source.value().len();
let required = checked_sum(name_len, value_len)?;
preflight_insert(table, name_len, value_len, required, scratch.len())?;
let source = table
.get_encoder_relative(relative)
.map_err(QpackEncoderInstructionApplyError::DuplicateReference)?;
let (name, value) = scratch[..required].split_at_mut(name_len);
name.copy_from_slice(source.name());
value.copy_from_slice(source.value());
insert(table, name, value, evictable)
}
}
}
pub fn apply_sequence<F>(
&self,
table: &mut QpackDynamicTable<'_, '_>,
bytes: &[u8],
scratch: &mut [u8],
mut evictable: F,
) -> Result<(), QpackEncoderInstructionsApplyError>
where
F: FnMut(u64) -> bool,
{
let instructions = QpackEncoderInstructions::parse(bytes)
.map_err(QpackEncoderInstructionsApplyError::Parse)?;
let mut offset = 0usize;
for instruction in instructions.iter() {
let instruction = instruction.map_err(QpackEncoderInstructionsApplyError::Parse)?;
self.apply(table, instruction, scratch, &mut evictable)
.map_err(|error| QpackEncoderInstructionsApplyError::Apply { offset, error })?;
offset += instruction.as_bytes().len();
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackEncoderInstructionApplyOutcome {
CapacityUpdated {
capacity: usize,
},
Inserted {
absolute_index: u64,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackEncoderInstructionApplyError {
CapacityExceedsMaximum {
requested: u64,
maximum: u64,
},
CapacityNotRepresentable {
value: u64,
},
CapacityChange(QpackDynamicTableError),
StaticNameIndexOutOfRange {
index: u64,
},
DynamicNameReference(QpackDynamicTableError),
DuplicateReference(QpackDynamicTableError),
LiteralNameHuffman(QpackHuffmanDecodeError),
ValueHuffman(QpackHuffmanDecodeError),
ScratchLengthOverflow,
ScratchTooShort {
required: usize,
available: usize,
},
Insert(QpackDynamicTableError),
}
impl QpackEncoderInstructionApplyError {
pub const fn is_encoder_stream_error(self) -> bool {
match self {
Self::CapacityExceedsMaximum { .. }
| Self::StaticNameIndexOutOfRange { .. }
| Self::DynamicNameReference(_)
| Self::DuplicateReference(_) => true,
Self::LiteralNameHuffman(
QpackHuffmanDecodeError::EosSymbol
| QpackHuffmanDecodeError::InvalidHuffmanCode
| QpackHuffmanDecodeError::InvalidPadding,
)
| Self::ValueHuffman(
QpackHuffmanDecodeError::EosSymbol
| QpackHuffmanDecodeError::InvalidHuffmanCode
| QpackHuffmanDecodeError::InvalidPadding,
) => true,
Self::CapacityChange(QpackDynamicTableError::EvictionBlocked { .. }) => true,
Self::Insert(QpackDynamicTableError::EntryLargerThanCapacity { .. })
| Self::Insert(QpackDynamicTableError::EvictionBlocked { .. }) => true,
Self::CapacityNotRepresentable { .. }
| Self::CapacityChange(_)
| Self::LiteralNameHuffman(_)
| Self::ValueHuffman(_)
| Self::ScratchLengthOverflow
| Self::ScratchTooShort { .. }
| Self::Insert(_) => false,
}
}
}
impl fmt::Display for QpackEncoderInstructionApplyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CapacityExceedsMaximum { requested, maximum } => {
write!(f, "QPACK capacity {requested} exceeds maximum {maximum}")
}
Self::CapacityNotRepresentable { value } => {
write!(f, "QPACK capacity cannot be represented: {value}")
}
Self::CapacityChange(error) => write!(f, "QPACK capacity change failed: {error}"),
Self::StaticNameIndexOutOfRange { index } => {
write!(f, "QPACK static name index is out of range: {index}")
}
Self::DynamicNameReference(error) => {
write!(f, "QPACK dynamic name reference failed: {error}")
}
Self::DuplicateReference(error) => {
write!(f, "QPACK Duplicate reference failed: {error}")
}
Self::LiteralNameHuffman(error) => {
write!(f, "QPACK literal name Huffman decode failed: {error}")
}
Self::ValueHuffman(error) => write!(f, "QPACK value Huffman decode failed: {error}"),
Self::ScratchLengthOverflow => f.write_str("QPACK scratch length overflows usize"),
Self::ScratchTooShort {
required,
available,
} => write!(
f,
"QPACK scratch is too short: need {required} bytes, have {available}"
),
Self::Insert(error) => write!(f, "QPACK insertion failed: {error}"),
}
}
}
impl core::error::Error for QpackEncoderInstructionApplyError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QpackEncoderInstructionsApplyError {
Parse(QpackEncoderInstructionsParseError),
Apply {
offset: usize,
error: QpackEncoderInstructionApplyError,
},
}
impl QpackEncoderInstructionsApplyError {
pub const fn is_encoder_stream_error(self) -> bool {
match self {
Self::Parse(_) => true,
Self::Apply { error, .. } => error.is_encoder_stream_error(),
}
}
}
impl fmt::Display for QpackEncoderInstructionsApplyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(error) => {
write!(f, "QPACK encoder instruction sequence is invalid: {error}")
}
Self::Apply { offset, error } => {
write!(
f,
"QPACK encoder instruction application failed at byte {offset}: {error}"
)
}
}
}
}
impl core::error::Error for QpackEncoderInstructionsApplyError {}
fn decoded_len(
literal: QpackStringLiteral<'_>,
name: bool,
) -> Result<usize, QpackEncoderInstructionApplyError> {
if literal.is_huffman() {
QpackHuffmanDecoder::required_decoded_len(literal.encoded_payload()).map_err(|error| {
if name {
QpackEncoderInstructionApplyError::LiteralNameHuffman(error)
} else {
QpackEncoderInstructionApplyError::ValueHuffman(error)
}
})
} else {
Ok(literal.encoded_payload().len())
}
}
fn decode<'a>(
literal: QpackStringLiteral<'_>,
destination: &'a mut [u8],
name: bool,
) -> Result<&'a [u8], QpackEncoderInstructionApplyError> {
QpackHuffmanDecoder::new(literal.encoded_payload(), destination)
.decode()
.map_err(|error| {
if name {
QpackEncoderInstructionApplyError::LiteralNameHuffman(error)
} else {
QpackEncoderInstructionApplyError::ValueHuffman(error)
}
})
}
fn checked_sum(left: usize, right: usize) -> Result<usize, QpackEncoderInstructionApplyError> {
left.checked_add(right)
.ok_or(QpackEncoderInstructionApplyError::ScratchLengthOverflow)
}
fn preflight_insert(
table: &QpackDynamicTable<'_, '_>,
name_len: usize,
value_len: usize,
required: usize,
available: usize,
) -> Result<(), QpackEncoderInstructionApplyError> {
let entry_size = name_len
.checked_add(value_len)
.and_then(|length| length.checked_add(32))
.ok_or(QpackEncoderInstructionApplyError::Insert(
QpackDynamicTableError::EntrySizeOverflow,
))?;
if entry_size > table.capacity() {
return Err(QpackEncoderInstructionApplyError::Insert(
QpackDynamicTableError::EntryLargerThanCapacity {
entry_size,
capacity: table.capacity(),
},
));
}
if table.insert_count() == u64::MAX {
return Err(QpackEncoderInstructionApplyError::Insert(
QpackDynamicTableError::InsertCountOverflow,
));
}
if available < required {
return Err(QpackEncoderInstructionApplyError::ScratchTooShort {
required,
available,
});
}
Ok(())
}
fn insert<F>(
table: &mut QpackDynamicTable<'_, '_>,
name: &[u8],
value: &[u8],
evictable: F,
) -> Result<QpackEncoderInstructionApplyOutcome, QpackEncoderInstructionApplyError>
where
F: FnMut(u64) -> bool,
{
table
.insert(name, value, evictable)
.map(|absolute_index| QpackEncoderInstructionApplyOutcome::Inserted { absolute_index })
.map_err(QpackEncoderInstructionApplyError::Insert)
}