1use core::fmt;
4
5use super::{
6 QpackEncoderInstruction, QpackEncoderInstructions, QpackEncoderInstructionsParseError,
7};
8use crate::qpack::huffman::{QpackHuffmanDecodeError, QpackHuffmanDecoder};
9use crate::qpack::string::QpackStringLiteral;
10use crate::qpack::table::{
11 QPACK_STATIC_TABLE_LEN, QpackDynamicTable, QpackDynamicTableError, QpackStaticTable,
12};
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct QpackEncoderInstructionApplier {
17 maximum_dynamic_table_capacity: u64,
18}
19
20impl QpackEncoderInstructionApplier {
21 pub const fn new(maximum_dynamic_table_capacity: u64) -> Self {
23 Self {
24 maximum_dynamic_table_capacity,
25 }
26 }
27
28 pub const fn maximum_dynamic_table_capacity(self) -> u64 {
30 self.maximum_dynamic_table_capacity
31 }
32
33 pub fn apply<F>(
39 &self,
40 table: &mut QpackDynamicTable<'_, '_>,
41 instruction: QpackEncoderInstruction<'_>,
42 scratch: &mut [u8],
43 evictable: F,
44 ) -> Result<QpackEncoderInstructionApplyOutcome, QpackEncoderInstructionApplyError>
45 where
46 F: FnMut(u64) -> bool,
47 {
48 match instruction {
49 QpackEncoderInstruction::SetDynamicTableCapacity(instruction) => {
50 let requested = instruction.capacity().value();
51 if requested > self.maximum_dynamic_table_capacity {
52 return Err(QpackEncoderInstructionApplyError::CapacityExceedsMaximum {
53 requested,
54 maximum: self.maximum_dynamic_table_capacity,
55 });
56 }
57 let capacity = usize::try_from(requested).map_err(|_| {
58 QpackEncoderInstructionApplyError::CapacityNotRepresentable { value: requested }
59 })?;
60 table
61 .set_capacity(capacity, evictable)
62 .map_err(QpackEncoderInstructionApplyError::CapacityChange)?;
63 Ok(QpackEncoderInstructionApplyOutcome::CapacityUpdated { capacity })
64 }
65 QpackEncoderInstruction::InsertWithNameReference(instruction) => {
66 let value = instruction.value();
67 if instruction.is_static() {
68 let index = instruction.name_index().value();
69 if index >= QPACK_STATIC_TABLE_LEN as u64 {
70 return Err(
71 QpackEncoderInstructionApplyError::StaticNameIndexOutOfRange { index },
72 );
73 }
74 let name = QpackStaticTable::get(index as usize)
75 .ok_or(
76 QpackEncoderInstructionApplyError::StaticNameIndexOutOfRange { index },
77 )?
78 .name();
79 let value_len = decoded_len(value, false)?;
80 let required = if value.is_huffman() { value_len } else { 0 };
81 preflight_insert(table, name.len(), value_len, required, scratch.len())?;
82 let value = if value.is_huffman() {
83 decode(value, &mut scratch[..value_len], false)?
84 } else {
85 value.encoded_payload()
86 };
87 insert(table, name, value, evictable)
88 } else {
89 let relative = instruction.name_index().value();
90 let name_len = table
91 .get_encoder_relative(relative)
92 .map_err(QpackEncoderInstructionApplyError::DynamicNameReference)?
93 .name()
94 .len();
95 let value_len = decoded_len(value, false)?;
96 let required =
97 checked_sum(name_len, if value.is_huffman() { value_len } else { 0 })?;
98 preflight_insert(table, name_len, value_len, required, scratch.len())?;
99 let (name_destination, value_destination) =
100 scratch[..required].split_at_mut(name_len);
101 let source = table
102 .get_encoder_relative(relative)
103 .map_err(QpackEncoderInstructionApplyError::DynamicNameReference)?;
104 name_destination.copy_from_slice(source.name());
105 let value = if value.is_huffman() {
106 decode(value, value_destination, false)?
107 } else {
108 value.encoded_payload()
109 };
110 insert(table, name_destination, value, evictable)
111 }
112 }
113 QpackEncoderInstruction::InsertWithLiteralName(instruction) => {
114 let name_literal = instruction.name();
115 let value_literal = instruction.value();
116 let name_len = decoded_len(name_literal, true)?;
117 let value_len = decoded_len(value_literal, false)?;
118 let name_scratch = if name_literal.is_huffman() {
119 name_len
120 } else {
121 0
122 };
123 let value_scratch = if value_literal.is_huffman() {
124 value_len
125 } else {
126 0
127 };
128 let required = checked_sum(name_scratch, value_scratch)?;
129 preflight_insert(table, name_len, value_len, required, scratch.len())?;
130 let (name_destination, value_destination) =
131 scratch[..required].split_at_mut(name_scratch);
132 let name = if name_literal.is_huffman() {
133 decode(name_literal, name_destination, true)?
134 } else {
135 name_literal.encoded_payload()
136 };
137 let value = if value_literal.is_huffman() {
138 decode(value_literal, value_destination, false)?
139 } else {
140 value_literal.encoded_payload()
141 };
142 insert(table, name, value, evictable)
143 }
144 QpackEncoderInstruction::Duplicate(instruction) => {
145 let relative = instruction.index().value();
146 let source = table
147 .get_encoder_relative(relative)
148 .map_err(QpackEncoderInstructionApplyError::DuplicateReference)?;
149 let name_len = source.name().len();
150 let value_len = source.value().len();
151 let required = checked_sum(name_len, value_len)?;
152 preflight_insert(table, name_len, value_len, required, scratch.len())?;
153 let source = table
154 .get_encoder_relative(relative)
155 .map_err(QpackEncoderInstructionApplyError::DuplicateReference)?;
156 let (name, value) = scratch[..required].split_at_mut(name_len);
157 name.copy_from_slice(source.name());
158 value.copy_from_slice(source.value());
159 insert(table, name, value, evictable)
160 }
161 }
162 }
163
164 pub fn apply_sequence<F>(
171 &self,
172 table: &mut QpackDynamicTable<'_, '_>,
173 bytes: &[u8],
174 scratch: &mut [u8],
175 mut evictable: F,
176 ) -> Result<(), QpackEncoderInstructionsApplyError>
177 where
178 F: FnMut(u64) -> bool,
179 {
180 let instructions = QpackEncoderInstructions::parse(bytes)
181 .map_err(QpackEncoderInstructionsApplyError::Parse)?;
182 let mut offset = 0usize;
183
184 for instruction in instructions.iter() {
185 let instruction = instruction.map_err(QpackEncoderInstructionsApplyError::Parse)?;
186 self.apply(table, instruction, scratch, &mut evictable)
187 .map_err(|error| QpackEncoderInstructionsApplyError::Apply { offset, error })?;
188 offset += instruction.as_bytes().len();
189 }
190
191 Ok(())
192 }
193}
194
195#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub enum QpackEncoderInstructionApplyOutcome {
198 CapacityUpdated {
200 capacity: usize,
202 },
203 Inserted {
205 absolute_index: u64,
207 },
208}
209
210#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212pub enum QpackEncoderInstructionApplyError {
213 CapacityExceedsMaximum {
215 requested: u64,
217 maximum: u64,
219 },
220 CapacityNotRepresentable {
222 value: u64,
224 },
225 CapacityChange(QpackDynamicTableError),
227 StaticNameIndexOutOfRange {
229 index: u64,
231 },
232 DynamicNameReference(QpackDynamicTableError),
234 DuplicateReference(QpackDynamicTableError),
236 LiteralNameHuffman(QpackHuffmanDecodeError),
238 ValueHuffman(QpackHuffmanDecodeError),
240 ScratchLengthOverflow,
242 ScratchTooShort {
244 required: usize,
246 available: usize,
248 },
249 Insert(QpackDynamicTableError),
251}
252
253impl QpackEncoderInstructionApplyError {
254 pub const fn is_encoder_stream_error(self) -> bool {
256 match self {
257 Self::CapacityExceedsMaximum { .. }
258 | Self::StaticNameIndexOutOfRange { .. }
259 | Self::DynamicNameReference(_)
260 | Self::DuplicateReference(_) => true,
261 Self::LiteralNameHuffman(
262 QpackHuffmanDecodeError::EosSymbol
263 | QpackHuffmanDecodeError::InvalidHuffmanCode
264 | QpackHuffmanDecodeError::InvalidPadding,
265 )
266 | Self::ValueHuffman(
267 QpackHuffmanDecodeError::EosSymbol
268 | QpackHuffmanDecodeError::InvalidHuffmanCode
269 | QpackHuffmanDecodeError::InvalidPadding,
270 ) => true,
271 Self::CapacityChange(QpackDynamicTableError::EvictionBlocked { .. }) => true,
272 Self::Insert(QpackDynamicTableError::EntryLargerThanCapacity { .. })
273 | Self::Insert(QpackDynamicTableError::EvictionBlocked { .. }) => true,
274 Self::CapacityNotRepresentable { .. }
275 | Self::CapacityChange(_)
276 | Self::LiteralNameHuffman(_)
277 | Self::ValueHuffman(_)
278 | Self::ScratchLengthOverflow
279 | Self::ScratchTooShort { .. }
280 | Self::Insert(_) => false,
281 }
282 }
283}
284
285impl fmt::Display for QpackEncoderInstructionApplyError {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 match self {
288 Self::CapacityExceedsMaximum { requested, maximum } => {
289 write!(f, "QPACK capacity {requested} exceeds maximum {maximum}")
290 }
291 Self::CapacityNotRepresentable { value } => {
292 write!(f, "QPACK capacity cannot be represented: {value}")
293 }
294 Self::CapacityChange(error) => write!(f, "QPACK capacity change failed: {error}"),
295 Self::StaticNameIndexOutOfRange { index } => {
296 write!(f, "QPACK static name index is out of range: {index}")
297 }
298 Self::DynamicNameReference(error) => {
299 write!(f, "QPACK dynamic name reference failed: {error}")
300 }
301 Self::DuplicateReference(error) => {
302 write!(f, "QPACK Duplicate reference failed: {error}")
303 }
304 Self::LiteralNameHuffman(error) => {
305 write!(f, "QPACK literal name Huffman decode failed: {error}")
306 }
307 Self::ValueHuffman(error) => write!(f, "QPACK value Huffman decode failed: {error}"),
308 Self::ScratchLengthOverflow => f.write_str("QPACK scratch length overflows usize"),
309 Self::ScratchTooShort {
310 required,
311 available,
312 } => write!(
313 f,
314 "QPACK scratch is too short: need {required} bytes, have {available}"
315 ),
316 Self::Insert(error) => write!(f, "QPACK insertion failed: {error}"),
317 }
318 }
319}
320
321impl core::error::Error for QpackEncoderInstructionApplyError {}
322
323#[derive(Clone, Copy, Debug, Eq, PartialEq)]
325pub enum QpackEncoderInstructionsApplyError {
326 Parse(QpackEncoderInstructionsParseError),
328 Apply {
330 offset: usize,
332 error: QpackEncoderInstructionApplyError,
334 },
335}
336
337impl QpackEncoderInstructionsApplyError {
338 pub const fn is_encoder_stream_error(self) -> bool {
340 match self {
341 Self::Parse(_) => true,
342 Self::Apply { error, .. } => error.is_encoder_stream_error(),
343 }
344 }
345}
346
347impl fmt::Display for QpackEncoderInstructionsApplyError {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 match self {
350 Self::Parse(error) => {
351 write!(f, "QPACK encoder instruction sequence is invalid: {error}")
352 }
353 Self::Apply { offset, error } => {
354 write!(
355 f,
356 "QPACK encoder instruction application failed at byte {offset}: {error}"
357 )
358 }
359 }
360 }
361}
362
363impl core::error::Error for QpackEncoderInstructionsApplyError {}
364
365fn decoded_len(
366 literal: QpackStringLiteral<'_>,
367 name: bool,
368) -> Result<usize, QpackEncoderInstructionApplyError> {
369 if literal.is_huffman() {
370 QpackHuffmanDecoder::required_decoded_len(literal.encoded_payload()).map_err(|error| {
371 if name {
372 QpackEncoderInstructionApplyError::LiteralNameHuffman(error)
373 } else {
374 QpackEncoderInstructionApplyError::ValueHuffman(error)
375 }
376 })
377 } else {
378 Ok(literal.encoded_payload().len())
379 }
380}
381
382fn decode<'a>(
383 literal: QpackStringLiteral<'_>,
384 destination: &'a mut [u8],
385 name: bool,
386) -> Result<&'a [u8], QpackEncoderInstructionApplyError> {
387 QpackHuffmanDecoder::new(literal.encoded_payload(), destination)
388 .decode()
389 .map_err(|error| {
390 if name {
391 QpackEncoderInstructionApplyError::LiteralNameHuffman(error)
392 } else {
393 QpackEncoderInstructionApplyError::ValueHuffman(error)
394 }
395 })
396}
397
398fn checked_sum(left: usize, right: usize) -> Result<usize, QpackEncoderInstructionApplyError> {
399 left.checked_add(right)
400 .ok_or(QpackEncoderInstructionApplyError::ScratchLengthOverflow)
401}
402
403fn preflight_insert(
404 table: &QpackDynamicTable<'_, '_>,
405 name_len: usize,
406 value_len: usize,
407 required: usize,
408 available: usize,
409) -> Result<(), QpackEncoderInstructionApplyError> {
410 let entry_size = name_len
411 .checked_add(value_len)
412 .and_then(|length| length.checked_add(32))
413 .ok_or(QpackEncoderInstructionApplyError::Insert(
414 QpackDynamicTableError::EntrySizeOverflow,
415 ))?;
416 if entry_size > table.capacity() {
417 return Err(QpackEncoderInstructionApplyError::Insert(
418 QpackDynamicTableError::EntryLargerThanCapacity {
419 entry_size,
420 capacity: table.capacity(),
421 },
422 ));
423 }
424 if table.insert_count() == u64::MAX {
425 return Err(QpackEncoderInstructionApplyError::Insert(
426 QpackDynamicTableError::InsertCountOverflow,
427 ));
428 }
429 if available < required {
430 return Err(QpackEncoderInstructionApplyError::ScratchTooShort {
431 required,
432 available,
433 });
434 }
435 Ok(())
436}
437
438fn insert<F>(
439 table: &mut QpackDynamicTable<'_, '_>,
440 name: &[u8],
441 value: &[u8],
442 evictable: F,
443) -> Result<QpackEncoderInstructionApplyOutcome, QpackEncoderInstructionApplyError>
444where
445 F: FnMut(u64) -> bool,
446{
447 table
448 .insert(name, value, evictable)
449 .map(|absolute_index| QpackEncoderInstructionApplyOutcome::Inserted { absolute_index })
450 .map_err(QpackEncoderInstructionApplyError::Insert)
451}