1use core::fmt;
4
5use super::context::{QpackFieldSectionContext, QpackFieldSectionContextError};
6use super::prefix::{QpackFieldSectionPrefixBuildError, plan_field_section_prefix};
7use crate::qpack::field::line::{
8 QpackFieldLineBuildError, QpackFieldLinePlan, QpackFieldLinePlanInput, plan_field_line,
9};
10use crate::qpack::state::encoder::{QpackEncoderState, QpackEncoderStateError};
11use crate::qpack::table::{QPACK_STATIC_TABLE_LEN, QpackDynamicTable, QpackDynamicTableError};
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct QpackFieldSectionEncoder {
16 maximum_dynamic_table_capacity: u64,
17}
18
19impl QpackFieldSectionEncoder {
20 pub const fn new(maximum_dynamic_table_capacity: u64) -> Self {
22 Self {
23 maximum_dynamic_table_capacity,
24 }
25 }
26
27 pub const fn maximum_dynamic_table_capacity(self) -> u64 {
29 self.maximum_dynamic_table_capacity
30 }
31
32 pub fn encode<'plan, 'output>(
38 &self,
39 stream_id: u64,
40 plans: &[QpackFieldPlan<'plan>],
41 base: QpackFieldSectionBase,
42 table: &QpackDynamicTable<'_, '_>,
43 state: &mut QpackEncoderState<'_, '_>,
44 buffers: QpackFieldSectionEncodeBuffers<'_, 'plan, 'output>,
45 ) -> Result<QpackEncodedFieldSection<'output>, QpackFieldSectionEncodeError> {
46 let QpackFieldSectionEncodeBuffers {
47 reference_scratch,
48 field_plan_scratch,
49 destination,
50 } = buffers;
51 let reference_count = dynamic_occurrence_count(plans)?;
52 if reference_scratch.len() < reference_count {
53 return Err(QpackFieldSectionEncodeError::ReferenceScratchTooShort {
54 required: reference_count,
55 available: reference_scratch.len(),
56 });
57 }
58 if field_plan_scratch.len() < plans.len() {
59 return Err(QpackFieldSectionEncodeError::FieldPlanScratchTooShort {
60 required: plans.len(),
61 available: field_plan_scratch.len(),
62 });
63 }
64
65 let references = &mut reference_scratch[..reference_count];
66 let mut reference_index = 0usize;
67 for (field_index, plan) in plans.iter().enumerate() {
68 if let Some(absolute_index) = dynamic_absolute(plan) {
69 table.get_absolute(absolute_index).map_err(|error| {
70 QpackFieldSectionEncodeError::Plan(
71 QpackFieldSectionPlanError::DynamicReference { field_index, error },
72 )
73 })?;
74 references[reference_index] = absolute_index;
75 reference_index += 1;
76 } else if let Some(index) = static_index(plan)
77 && index >= QPACK_STATIC_TABLE_LEN as u64
78 {
79 return Err(QpackFieldSectionEncodeError::Plan(
80 QpackFieldSectionPlanError::StaticIndexOutOfRange { field_index, index },
81 ));
82 }
83 }
84
85 let required_insert_count = required_insert_count(references)?;
86 let insert_count = table.insert_count();
87 let base = match base {
88 QpackFieldSectionBase::RequiredInsertCount => required_insert_count,
89 QpackFieldSectionBase::Explicit(base) => {
90 if base > insert_count {
91 return Err(QpackFieldSectionEncodeError::BaseExceedsInsertCount {
92 base,
93 insert_count,
94 });
95 }
96 base
97 }
98 };
99 let (negative, delta_base) = prefix_delta(required_insert_count, base);
100 let encoded_required_insert_count = QpackFieldSectionContext::encode_required_insert_count(
101 required_insert_count,
102 self.maximum_dynamic_table_capacity,
103 )
104 .map_err(QpackFieldSectionEncodeError::RequiredInsertCountEncoding)?;
105 let prefix = plan_field_section_prefix(encoded_required_insert_count, negative, delta_base)
106 .map_err(QpackFieldSectionEncodeError::PrefixPlanning)?;
107
108 let mut total = prefix.encoded_len();
109 for (field_index, input) in plans.iter().enumerate() {
110 let input = field_line_input(*input, base);
111 let wire_plan = plan_field_line(input).map_err(|error| {
112 QpackFieldSectionEncodeError::Plan(QpackFieldSectionPlanError::FieldLine {
113 field_index,
114 error,
115 })
116 })?;
117 total = total
118 .checked_add(wire_plan.encoded_len())
119 .ok_or(QpackFieldSectionEncodeError::OutputLengthOverflow)?;
120 field_plan_scratch[field_index].wire_plan = wire_plan;
121 }
122 if destination.len() < total {
123 return Err(QpackFieldSectionEncodeError::DestinationTooShort {
124 required: total,
125 available: destination.len(),
126 });
127 }
128
129 let reservation = state
130 .reserve_registration(stream_id, required_insert_count, references, insert_count)
131 .map_err(QpackFieldSectionEncodeError::EncoderStateReservation)?;
132 let (prefix_destination, mut fields_destination) =
133 destination[..total].split_at_mut(prefix.encoded_len());
134 prefix.write(prefix_destination);
135 for slot in &field_plan_scratch[..plans.len()] {
136 let length = slot.wire_plan.encoded_len();
137 let (field_destination, remaining) = fields_destination.split_at_mut(length);
138 slot.wire_plan.write(field_destination);
139 fields_destination = remaining;
140 }
141 reservation.commit();
142 Ok(QpackEncodedFieldSection {
143 bytes: &destination[..total],
144 required_insert_count,
145 base,
146 })
147 }
148}
149
150pub struct QpackFieldSectionEncodeBuffers<'scratch, 'plan, 'output> {
152 reference_scratch: &'scratch mut [u64],
153 field_plan_scratch: &'scratch mut [QpackFieldSectionPlanSlot<'plan>],
154 destination: &'output mut [u8],
155}
156
157impl<'scratch, 'plan, 'output> QpackFieldSectionEncodeBuffers<'scratch, 'plan, 'output> {
158 pub fn new(
160 reference_scratch: &'scratch mut [u64],
161 field_plan_scratch: &'scratch mut [QpackFieldSectionPlanSlot<'plan>],
162 destination: &'output mut [u8],
163 ) -> Self {
164 Self {
165 reference_scratch,
166 field_plan_scratch,
167 destination,
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub enum QpackFieldPlan<'a> {
175 IndexedStatic {
177 index: u64,
179 },
180 IndexedDynamic {
182 absolute_index: u64,
184 },
185 LiteralStaticNameReference {
187 never_indexed: bool,
189 name_index: u64,
191 value_huffman: bool,
193 encoded_value: &'a [u8],
195 },
196 LiteralDynamicNameReference {
198 never_indexed: bool,
200 name_absolute_index: u64,
202 value_huffman: bool,
204 encoded_value: &'a [u8],
206 },
207 LiteralName {
209 never_indexed: bool,
211 name_huffman: bool,
213 encoded_name: &'a [u8],
215 value_huffman: bool,
217 encoded_value: &'a [u8],
219 },
220}
221
222#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224pub enum QpackFieldSectionBase {
225 RequiredInsertCount,
227 Explicit(#[doc = "The explicit Base."] u64),
229}
230
231#[derive(Clone, Copy)]
236pub struct QpackFieldSectionPlanSlot<'a> {
237 wire_plan: QpackFieldLinePlan<'a>,
238}
239impl<'a> QpackFieldSectionPlanSlot<'a> {
240 pub const EMPTY: Self = Self {
242 wire_plan: QpackFieldLinePlan::empty(),
243 };
244}
245
246#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248pub struct QpackEncodedFieldSection<'a> {
249 bytes: &'a [u8],
250 required_insert_count: u64,
251 base: u64,
252}
253impl<'a> QpackEncodedFieldSection<'a> {
254 pub const fn as_bytes(self) -> &'a [u8] {
256 self.bytes
257 }
258 pub const fn required_insert_count(self) -> u64 {
260 self.required_insert_count
261 }
262 pub const fn base(self) -> u64 {
264 self.base
265 }
266 pub const fn len(self) -> usize {
268 self.bytes.len()
269 }
270 pub const fn is_empty(self) -> bool {
272 self.bytes.is_empty()
273 }
274}
275
276#[derive(Clone, Copy, Debug, Eq, PartialEq)]
278pub enum QpackFieldSectionPlanError {
279 StaticIndexOutOfRange {
281 field_index: usize,
283 index: u64,
285 },
286 DynamicReference {
288 field_index: usize,
290 error: QpackDynamicTableError,
292 },
293
294 FieldLine {
296 field_index: usize,
298 error: QpackFieldLineBuildError,
300 },
301}
302impl fmt::Display for QpackFieldSectionPlanError {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 match self {
305 Self::StaticIndexOutOfRange { field_index, index } => write!(
306 f,
307 "QPACK field {field_index} has static-table index {index} outside the table"
308 ),
309 Self::DynamicReference { field_index, error } => write!(
310 f,
311 "QPACK field {field_index} has an invalid dynamic reference: {error}"
312 ),
313
314 Self::FieldLine { field_index, error } => {
315 write!(f, "QPACK field {field_index} cannot be planned: {error}")
316 }
317 }
318 }
319}
320impl core::error::Error for QpackFieldSectionPlanError {}
321
322#[derive(Clone, Copy, Debug, Eq, PartialEq)]
324pub enum QpackFieldSectionEncodeError {
325 ReferenceCountOverflow,
327 ReferenceScratchTooShort {
329 required: usize,
331 available: usize,
333 },
334 FieldPlanScratchTooShort {
336 required: usize,
338 available: usize,
340 },
341 Plan(QpackFieldSectionPlanError),
343 RequiredInsertCountOverflow {
345 absolute_index: u64,
347 },
348 BaseExceedsInsertCount {
350 base: u64,
352 insert_count: u64,
354 },
355
356 RequiredInsertCountEncoding(QpackFieldSectionContextError),
358 PrefixPlanning(QpackFieldSectionPrefixBuildError),
360 OutputLengthOverflow,
362 DestinationTooShort {
364 required: usize,
366 available: usize,
368 },
369 EncoderStateReservation(QpackEncoderStateError),
371}
372impl QpackFieldSectionEncodeError {
373 pub const fn is_provisioning_error(self) -> bool {
375 matches!(
376 self,
377 Self::ReferenceScratchTooShort { .. }
378 | Self::FieldPlanScratchTooShort { .. }
379 | Self::DestinationTooShort { .. }
380 )
381 }
382}
383impl fmt::Display for QpackFieldSectionEncodeError {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 match self {
386 Self::ReferenceCountOverflow => {
387 f.write_str("QPACK dynamic reference count overflows usize")
388 }
389 Self::ReferenceScratchTooShort {
390 required,
391 available,
392 } => write!(
393 f,
394 "QPACK reference scratch needs {required} slots, has {available}"
395 ),
396 Self::FieldPlanScratchTooShort {
397 required,
398 available,
399 } => write!(
400 f,
401 "QPACK field-plan scratch needs {required} slots, has {available}"
402 ),
403 Self::Plan(error) => write!(f, "QPACK field-section plan is invalid: {error}"),
404 Self::RequiredInsertCountOverflow { absolute_index } => write!(
405 f,
406 "QPACK Required Insert Count overflows for absolute index {absolute_index}"
407 ),
408 Self::BaseExceedsInsertCount { base, insert_count } => {
409 write!(f, "QPACK Base {base} exceeds insert count {insert_count}")
410 }
411
412 Self::RequiredInsertCountEncoding(error) => {
413 write!(f, "QPACK Required Insert Count cannot be encoded: {error}")
414 }
415 Self::PrefixPlanning(error) => {
416 write!(f, "QPACK field-section prefix cannot be planned: {error}")
417 }
418 Self::OutputLengthOverflow => {
419 f.write_str("QPACK field-section output length overflows usize")
420 }
421 Self::DestinationTooShort {
422 required,
423 available,
424 } => write!(
425 f,
426 "QPACK field-section destination needs {required} bytes, has {available}"
427 ),
428 Self::EncoderStateReservation(error) => write!(
429 f,
430 "QPACK encoder state cannot reserve field section: {error}"
431 ),
432 }
433 }
434}
435impl core::error::Error for QpackFieldSectionEncodeError {}
436
437fn dynamic_occurrence_count(
438 plans: &[QpackFieldPlan<'_>],
439) -> Result<usize, QpackFieldSectionEncodeError> {
440 let mut count = 0usize;
441 for plan in plans {
442 if dynamic_absolute(plan).is_some() {
443 count = count
444 .checked_add(1)
445 .ok_or(QpackFieldSectionEncodeError::ReferenceCountOverflow)?;
446 }
447 }
448 Ok(count)
449}
450fn dynamic_absolute(plan: &QpackFieldPlan<'_>) -> Option<u64> {
451 match *plan {
452 QpackFieldPlan::IndexedDynamic { absolute_index } => Some(absolute_index),
453 QpackFieldPlan::LiteralDynamicNameReference {
454 name_absolute_index,
455 ..
456 } => Some(name_absolute_index),
457 _ => None,
458 }
459}
460fn static_index(plan: &QpackFieldPlan<'_>) -> Option<u64> {
461 match *plan {
462 QpackFieldPlan::IndexedStatic { index } => Some(index),
463 QpackFieldPlan::LiteralStaticNameReference { name_index, .. } => Some(name_index),
464 _ => None,
465 }
466}
467fn required_insert_count(references: &[u64]) -> Result<u64, QpackFieldSectionEncodeError> {
468 let mut largest = match references.first() {
469 Some(value) => *value,
470 None => return Ok(0),
471 };
472 for &reference in &references[1..] {
473 if reference > largest {
474 largest = reference;
475 }
476 }
477 largest
478 .checked_add(1)
479 .ok_or(QpackFieldSectionEncodeError::RequiredInsertCountOverflow {
480 absolute_index: largest,
481 })
482}
483fn prefix_delta(required_insert_count: u64, base: u64) -> (bool, u64) {
484 if base >= required_insert_count {
485 return (false, base - required_insert_count);
486 }
487 (true, required_insert_count - base - 1)
488}
489fn field_line_input<'a>(plan: QpackFieldPlan<'a>, base: u64) -> QpackFieldLinePlanInput<'a> {
490 match plan {
491 QpackFieldPlan::IndexedStatic { index } => QpackFieldLinePlanInput::IndexedStatic { index },
492 QpackFieldPlan::IndexedDynamic { absolute_index } => {
493 dynamic_indexed_input(absolute_index, base)
494 }
495 QpackFieldPlan::LiteralStaticNameReference {
496 never_indexed,
497 name_index,
498 value_huffman,
499 encoded_value,
500 } => QpackFieldLinePlanInput::LiteralStaticNameReference {
501 never_indexed,
502 index: name_index,
503 value_huffman,
504 encoded_value,
505 },
506 QpackFieldPlan::LiteralDynamicNameReference {
507 never_indexed,
508 name_absolute_index,
509 value_huffman,
510 encoded_value,
511 } => dynamic_name_input(
512 never_indexed,
513 name_absolute_index,
514 value_huffman,
515 encoded_value,
516 base,
517 ),
518 QpackFieldPlan::LiteralName {
519 never_indexed,
520 name_huffman,
521 encoded_name,
522 value_huffman,
523 encoded_value,
524 } => QpackFieldLinePlanInput::LiteralName {
525 never_indexed,
526 name_huffman,
527 encoded_name,
528 value_huffman,
529 encoded_value,
530 },
531 }
532}
533fn dynamic_indexed_input(absolute_index: u64, base: u64) -> QpackFieldLinePlanInput<'static> {
534 if absolute_index < base {
535 QpackFieldLinePlanInput::IndexedDynamicPreBase {
536 relative_index: base - absolute_index - 1,
537 }
538 } else {
539 QpackFieldLinePlanInput::IndexedDynamicPostBase {
540 post_base_index: absolute_index - base,
541 }
542 }
543}
544fn dynamic_name_input<'a>(
545 never_indexed: bool,
546 absolute_index: u64,
547 value_huffman: bool,
548 encoded_value: &'a [u8],
549 base: u64,
550) -> QpackFieldLinePlanInput<'a> {
551 if absolute_index < base {
552 QpackFieldLinePlanInput::LiteralDynamicPreBaseNameReference {
553 never_indexed,
554 relative_index: base - absolute_index - 1,
555 value_huffman,
556 encoded_value,
557 }
558 } else {
559 QpackFieldLinePlanInput::LiteralDynamicPostBaseNameReference {
560 never_indexed,
561 post_base_index: absolute_index - base,
562 value_huffman,
563 encoded_value,
564 }
565 }
566}