Skip to main content

net_wire/qpack/field/section/
decode.rs

1//! Transactional semantic decoding of one RFC 9204 encoded field section.
2
3use core::{fmt, iter::FusedIterator};
4
5use super::context::{QpackFieldSectionContext, QpackFieldSectionContextError};
6use super::prefix::{QpackFieldSectionPrefix, QpackFieldSectionPrefixParseError};
7use crate::qpack::field::line::{QpackFieldLine, QpackFieldLines, QpackFieldLinesParseError};
8use crate::qpack::huffman::{QpackHuffmanDecodeError, QpackHuffmanDecoder, decode_prevalidated};
9use crate::qpack::string::QpackStringLiteral;
10use crate::qpack::table::{
11    QPACK_STATIC_TABLE_LEN, QpackDynamicTable, QpackDynamicTableError, QpackHeaderFieldRef,
12    QpackStaticTable,
13};
14
15/// Metadata for one packed decoded field in [`QpackFieldSectionOutput`].
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct QpackDecodedFieldEntry {
18    offset: usize,
19    name_len: usize,
20    value_len: usize,
21    never_indexed: bool,
22}
23
24impl QpackDecodedFieldEntry {
25    /// An empty metadata entry suitable for caller-owned metadata storage.
26    pub const EMPTY: Self = Self {
27        offset: 0,
28        name_len: 0,
29        value_len: 0,
30        never_indexed: false,
31    };
32
33    /// Returns whether the wire representation prohibited intermediary indexing.
34    pub const fn never_indexed(self) -> bool {
35        self.never_indexed
36    }
37}
38
39/// Caller-owned storage for decoded QPACK field-section bytes and metadata.
40pub struct QpackFieldSectionOutput<'bytes, 'fields> {
41    bytes: &'bytes mut [u8],
42    fields: &'fields mut [QpackDecodedFieldEntry],
43    len: usize,
44}
45
46impl<'bytes, 'fields> QpackFieldSectionOutput<'bytes, 'fields> {
47    /// Creates empty decoded field-section output backed by caller storage.
48    pub fn new(bytes: &'bytes mut [u8], fields: &'fields mut [QpackDecodedFieldEntry]) -> Self {
49        Self {
50            bytes,
51            fields,
52            len: 0,
53        }
54    }
55
56    /// Returns the byte-storage capacity.
57    pub const fn byte_capacity(&self) -> usize {
58        self.bytes.len()
59    }
60
61    /// Returns the field-metadata capacity.
62    pub const fn field_capacity(&self) -> usize {
63        self.fields.len()
64    }
65
66    /// Returns the number of committed decoded fields.
67    pub const fn len(&self) -> usize {
68        self.len
69    }
70
71    /// Returns whether no decoded fields are committed.
72    pub const fn is_empty(&self) -> bool {
73        self.len == 0
74    }
75}
76
77/// One decoded QPACK field borrowed from committed output storage.
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub struct QpackDecodedFieldRef<'a> {
80    name: &'a [u8],
81    value: &'a [u8],
82    never_indexed: bool,
83}
84
85impl<'a> QpackDecodedFieldRef<'a> {
86    /// Returns the decoded field name.
87    pub const fn name(self) -> &'a [u8] {
88        self.name
89    }
90
91    /// Returns the decoded field value.
92    pub const fn value(self) -> &'a [u8] {
93        self.value
94    }
95
96    /// Returns whether the wire representation prohibited intermediary indexing.
97    pub const fn never_indexed(self) -> bool {
98        self.never_indexed
99    }
100}
101
102/// A committed decoded QPACK field section borrowed from caller-owned output.
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
104pub struct QpackDecodedFieldSection<'a> {
105    bytes: &'a [u8],
106    fields: &'a [QpackDecodedFieldEntry],
107    required_insert_count: u64,
108    base: u64,
109    contains_dynamic_references: bool,
110}
111
112impl<'a> QpackDecodedFieldSection<'a> {
113    /// Returns the resolved Required Insert Count.
114    pub const fn required_insert_count(self) -> u64 {
115        self.required_insert_count
116    }
117
118    /// Returns the resolved Base.
119    pub const fn base(self) -> u64 {
120        self.base
121    }
122
123    /// Returns whether any decoded field-line representation referenced the dynamic table.
124    pub const fn contains_dynamic_references(self) -> bool {
125        self.contains_dynamic_references
126    }
127
128    /// Returns the number of decoded fields.
129    pub const fn len(self) -> usize {
130        self.fields.len()
131    }
132
133    /// Returns whether the section contains no fields.
134    pub const fn is_empty(self) -> bool {
135        self.fields.is_empty()
136    }
137
138    /// Returns one decoded field when its validated metadata remains in bounds.
139    pub fn get(self, index: usize) -> Option<QpackDecodedFieldRef<'a>> {
140        self.fields
141            .get(index)
142            .and_then(|entry| field_from_entry(self.bytes, *entry))
143    }
144
145    /// Iterates over decoded fields in wire order.
146    pub const fn iter(self) -> QpackDecodedFieldIter<'a> {
147        QpackDecodedFieldIter {
148            bytes: self.bytes,
149            fields: self.fields,
150            index: 0,
151        }
152    }
153}
154
155/// An iterator over decoded QPACK fields in wire order.
156#[derive(Clone, Copy, Debug)]
157pub struct QpackDecodedFieldIter<'a> {
158    bytes: &'a [u8],
159    fields: &'a [QpackDecodedFieldEntry],
160    index: usize,
161}
162
163impl<'a> Iterator for QpackDecodedFieldIter<'a> {
164    type Item = QpackDecodedFieldRef<'a>;
165
166    fn next(&mut self) -> Option<Self::Item> {
167        let entry = *self.fields.get(self.index)?;
168        self.index = self.index.saturating_add(1);
169        field_from_entry(self.bytes, entry)
170    }
171
172    fn size_hint(&self) -> (usize, Option<usize>) {
173        let remaining = self.fields.len().saturating_sub(self.index);
174        (remaining, Some(remaining))
175    }
176}
177
178impl ExactSizeIterator for QpackDecodedFieldIter<'_> {
179    fn len(&self) -> usize {
180        self.fields.len().saturating_sub(self.index)
181    }
182}
183
184impl FusedIterator for QpackDecodedFieldIter<'_> {}
185
186/// A field section whose Required Insert Count has not yet been received.
187#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub struct QpackFieldSectionBlocked {
189    required_insert_count: u64,
190    base: u64,
191}
192
193impl QpackFieldSectionBlocked {
194    /// Returns the resolved Required Insert Count.
195    pub const fn required_insert_count(self) -> u64 {
196        self.required_insert_count
197    }
198
199    /// Returns the resolved Base.
200    pub const fn base(self) -> u64 {
201        self.base
202    }
203}
204
205/// The outcome of decoding one complete QPACK field section.
206#[derive(Clone, Copy, Debug, Eq, PartialEq)]
207pub enum QpackFieldSectionDecodeOutcome<'a> {
208    /// The section was decoded into caller-owned output.
209    Decoded(QpackDecodedFieldSection<'a>),
210    /// The section is blocked awaiting dynamic-table insertions.
211    Blocked(QpackFieldSectionBlocked),
212}
213
214/// A failure to semantically decode one QPACK field section.
215#[derive(Clone, Copy, Debug, Eq, PartialEq)]
216pub enum QpackFieldSectionDecodeError {
217    /// The field-section prefix is malformed.
218    Prefix(QpackFieldSectionPrefixParseError),
219    /// The field-section prefix cannot be resolved for this decoder context.
220    Context(QpackFieldSectionContextError),
221    /// The encoded field-line sequence is malformed.
222    FieldLines(QpackFieldLinesParseError),
223    /// A static-table index is outside RFC 9204 Appendix A.
224    StaticIndexOutOfRange {
225        /// The requested zero-based static index.
226        index: u64,
227    },
228    /// A dynamic-table reference is invalid for this field section.
229    DynamicReference(QpackDynamicTableError),
230    /// A literal field name has invalid Huffman coding.
231    LiteralNameHuffman(QpackHuffmanDecodeError),
232    /// A field value has invalid Huffman coding.
233    ValueHuffman(QpackHuffmanDecodeError),
234    /// The caller byte output cannot contain the decoded section.
235    OutputBytesTooShort {
236        /// Bytes required.
237        required: usize,
238        /// Bytes available.
239        available: usize,
240    },
241    /// The caller metadata output cannot contain the decoded section.
242    OutputFieldsTooShort {
243        /// Fields required.
244        required: usize,
245        /// Fields available.
246        available: usize,
247    },
248    /// Counting packed output bytes or fields overflowed `usize`.
249    OutputLengthOverflow,
250}
251
252impl QpackFieldSectionDecodeError {
253    /// Returns whether this error represents QPACK decompression failure.
254    pub const fn is_decompression_failed(self) -> bool {
255        matches!(
256            self,
257            Self::Prefix(_)
258                | Self::Context(_)
259                | Self::FieldLines(_)
260                | Self::StaticIndexOutOfRange { .. }
261                | Self::DynamicReference(_)
262                | Self::LiteralNameHuffman(_)
263                | Self::ValueHuffman(_)
264        )
265    }
266
267    /// Returns whether this error is solely insufficient caller output provisioning.
268    pub const fn is_output_provisioning_error(self) -> bool {
269        !self.is_decompression_failed()
270    }
271}
272
273impl fmt::Display for QpackFieldSectionDecodeError {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        match self {
276            Self::Prefix(error) => write!(f, "invalid QPACK field-section prefix: {error}"),
277            Self::Context(error) => write!(f, "invalid QPACK field-section context: {error}"),
278            Self::FieldLines(error) => write!(f, "invalid QPACK field lines: {error}"),
279            Self::StaticIndexOutOfRange { index } => {
280                write!(f, "QPACK static index {index} is out of range")
281            }
282            Self::DynamicReference(error) => write!(f, "invalid QPACK dynamic reference: {error}"),
283            Self::LiteralNameHuffman(error) => {
284                write!(f, "invalid QPACK literal-name Huffman data: {error}")
285            }
286            Self::ValueHuffman(error) => write!(f, "invalid QPACK value Huffman data: {error}"),
287            Self::OutputBytesTooShort {
288                required,
289                available,
290            } => write!(
291                f,
292                "QPACK field-section output bytes are too short: need {required}, have {available}"
293            ),
294            Self::OutputFieldsTooShort {
295                required,
296                available,
297            } => write!(
298                f,
299                "QPACK field-section output fields are too short: need {required}, have {available}"
300            ),
301            Self::OutputLengthOverflow => {
302                f.write_str("QPACK field-section output length overflows usize")
303            }
304        }
305    }
306}
307
308impl core::error::Error for QpackFieldSectionDecodeError {}
309
310/// A QPACK field-section decoder configured with the locally advertised maximum table capacity.
311#[derive(Clone, Copy, Debug, Eq, PartialEq)]
312pub struct QpackFieldSectionDecoder {
313    maximum_dynamic_table_capacity: u64,
314}
315
316impl QpackFieldSectionDecoder {
317    /// Creates a decoder configured with the locally advertised maximum dynamic-table capacity.
318    pub const fn new(maximum_dynamic_table_capacity: u64) -> Self {
319        Self {
320            maximum_dynamic_table_capacity,
321        }
322    }
323
324    /// Returns the locally advertised maximum dynamic-table capacity.
325    pub const fn maximum_dynamic_table_capacity(self) -> u64 {
326        self.maximum_dynamic_table_capacity
327    }
328
329    /// Decodes one complete field-section payload into caller-owned output transactionally.
330    pub fn decode<'output>(
331        &self,
332        encoded: &[u8],
333        table: &QpackDynamicTable<'_, '_>,
334        output: &'output mut QpackFieldSectionOutput<'_, '_>,
335    ) -> Result<QpackFieldSectionDecodeOutcome<'output>, QpackFieldSectionDecodeError> {
336        let prefix = QpackFieldSectionPrefix::parse(encoded)
337            .map_err(QpackFieldSectionDecodeError::Prefix)?;
338        let context = QpackFieldSectionContext::decode(
339            prefix,
340            table.insert_count(),
341            self.maximum_dynamic_table_capacity,
342        )
343        .map_err(QpackFieldSectionDecodeError::Context)?;
344        if context.required_insert_count() > table.insert_count() {
345            return Ok(QpackFieldSectionDecodeOutcome::Blocked(
346                QpackFieldSectionBlocked {
347                    required_insert_count: context.required_insert_count(),
348                    base: context.base(),
349                },
350            ));
351        }
352
353        let lines = QpackFieldLines::parse(&encoded[prefix.as_bytes().len()..])
354            .map_err(QpackFieldSectionDecodeError::FieldLines)?;
355        let required = preflight(lines, table, context)?;
356        if output.fields.len() < required.fields {
357            return Err(QpackFieldSectionDecodeError::OutputFieldsTooShort {
358                required: required.fields,
359                available: output.fields.len(),
360            });
361        }
362        if output.bytes.len() < required.bytes {
363            return Err(QpackFieldSectionDecodeError::OutputBytesTooShort {
364                required: required.bytes,
365                available: output.bytes.len(),
366            });
367        }
368
369        // Revalidate immediately before the infallible, prevalidated commit pass. The encoded
370        // bytes and table are immutably borrowed, so this establishes the facts used below.
371        let verified = preflight(lines, table, context)?;
372        if verified != required {
373            return Err(QpackFieldSectionDecodeError::OutputLengthOverflow);
374        }
375        commit(
376            lines,
377            table,
378            context,
379            output,
380            required.bytes,
381            required.fields,
382        )?;
383
384        let bytes = &output.bytes[..required.bytes];
385        let fields = &output.fields[..required.fields];
386        Ok(QpackFieldSectionDecodeOutcome::Decoded(
387            QpackDecodedFieldSection {
388                bytes,
389                fields,
390                required_insert_count: context.required_insert_count(),
391                base: context.base(),
392                contains_dynamic_references: verified.contains_dynamic_references,
393            },
394        ))
395    }
396}
397
398#[derive(Clone, Copy, Debug, Eq, PartialEq)]
399struct Preflight {
400    bytes: usize,
401    fields: usize,
402    contains_dynamic_references: bool,
403}
404
405fn preflight(
406    lines: QpackFieldLines<'_>,
407    table: &QpackDynamicTable<'_, '_>,
408    context: QpackFieldSectionContext,
409) -> Result<Preflight, QpackFieldSectionDecodeError> {
410    let mut bytes = 0usize;
411    let mut fields = 0usize;
412    let mut contains_dynamic_references = false;
413    for line in lines.iter() {
414        let line = match line {
415            Ok(line) => line,
416            // QpackFieldLines::parse validated this exact immutable sequence.
417            Err(_) => return Err(QpackFieldSectionDecodeError::OutputLengthOverflow),
418        };
419        let (name, value, references_dynamic_table) = line_lengths(line, table, context)?;
420        bytes = bytes
421            .checked_add(name)
422            .and_then(|length| length.checked_add(value))
423            .ok_or(QpackFieldSectionDecodeError::OutputLengthOverflow)?;
424        fields = fields
425            .checked_add(1)
426            .ok_or(QpackFieldSectionDecodeError::OutputLengthOverflow)?;
427        contains_dynamic_references |= references_dynamic_table;
428    }
429    Ok(Preflight {
430        bytes,
431        fields,
432        contains_dynamic_references,
433    })
434}
435
436fn line_lengths(
437    line: QpackFieldLine<'_>,
438    table: &QpackDynamicTable<'_, '_>,
439    context: QpackFieldSectionContext,
440) -> Result<(usize, usize, bool), QpackFieldSectionDecodeError> {
441    match line {
442        QpackFieldLine::Indexed(line) => {
443            let field = resolve_indexed(line.is_static(), line.index().value(), table, context)?;
444            Ok((field.name().len(), field.value().len(), !line.is_static()))
445        }
446        QpackFieldLine::IndexedPostBase(line) => {
447            let field = table
448                .get_post_base(
449                    context.required_insert_count(),
450                    context.base(),
451                    line.index().value(),
452                )
453                .map_err(QpackFieldSectionDecodeError::DynamicReference)?;
454            Ok((field.name().len(), field.value().len(), true))
455        }
456        QpackFieldLine::LiteralNameReference(line) => {
457            let name =
458                resolve_indexed(line.is_static(), line.name_index().value(), table, context)?
459                    .name();
460            Ok((
461                name.len(),
462                literal_len(line.value(), false)?,
463                !line.is_static(),
464            ))
465        }
466        QpackFieldLine::LiteralPostBaseNameReference(line) => {
467            let name = table
468                .get_post_base(
469                    context.required_insert_count(),
470                    context.base(),
471                    line.name_index().value(),
472                )
473                .map_err(QpackFieldSectionDecodeError::DynamicReference)?
474                .name();
475            Ok((name.len(), literal_len(line.value(), false)?, true))
476        }
477        QpackFieldLine::LiteralName(line) => Ok((
478            literal_len(line.name(), true)?,
479            literal_len(line.value(), false)?,
480            false,
481        )),
482    }
483}
484
485fn resolve_indexed<'a>(
486    is_static: bool,
487    index: u64,
488    table: &'a QpackDynamicTable<'_, '_>,
489    context: QpackFieldSectionContext,
490) -> Result<QpackHeaderFieldRef<'a>, QpackFieldSectionDecodeError> {
491    if is_static {
492        static_field(index)
493    } else {
494        table
495            .get_field_relative(context.required_insert_count(), context.base(), index)
496            .map_err(QpackFieldSectionDecodeError::DynamicReference)
497    }
498}
499
500fn literal_len(
501    literal: QpackStringLiteral<'_>,
502    name: bool,
503) -> Result<usize, QpackFieldSectionDecodeError> {
504    if !literal.is_huffman() {
505        return Ok(literal.encoded_payload().len());
506    }
507    QpackHuffmanDecoder::required_decoded_len(literal.encoded_payload()).map_err(|error| {
508        if name {
509            QpackFieldSectionDecodeError::LiteralNameHuffman(error)
510        } else {
511            QpackFieldSectionDecodeError::ValueHuffman(error)
512        }
513    })
514}
515
516fn static_field(index: u64) -> Result<QpackHeaderFieldRef<'static>, QpackFieldSectionDecodeError> {
517    if index >= QPACK_STATIC_TABLE_LEN as u64 {
518        return Err(QpackFieldSectionDecodeError::StaticIndexOutOfRange { index });
519    }
520    QpackStaticTable::get(index as usize)
521        .ok_or(QpackFieldSectionDecodeError::StaticIndexOutOfRange { index })
522}
523
524fn commit(
525    lines: QpackFieldLines<'_>,
526    table: &QpackDynamicTable<'_, '_>,
527    context: QpackFieldSectionContext,
528    output: &mut QpackFieldSectionOutput<'_, '_>,
529    required_bytes: usize,
530    required_fields: usize,
531) -> Result<(), QpackFieldSectionDecodeError> {
532    let mut offset = 0usize;
533    let mut field_index = 0usize;
534    for line in lines.iter() {
535        let line = match line {
536            Ok(line) => line,
537            Err(_) => return Err(QpackFieldSectionDecodeError::OutputLengthOverflow),
538        };
539        let entry = write_prevalidated_line(line, table, context, &mut output.bytes[offset..])?;
540        output.fields[field_index] = QpackDecodedFieldEntry {
541            offset,
542            name_len: entry.name_len,
543            value_len: entry.value_len,
544            never_indexed: entry.never_indexed,
545        };
546        offset += entry.name_len + entry.value_len;
547        field_index += 1;
548    }
549    if offset != required_bytes || field_index != required_fields {
550        return Err(QpackFieldSectionDecodeError::OutputLengthOverflow);
551    }
552    output.len = required_fields;
553    Ok(())
554}
555
556fn write_prevalidated_line(
557    line: QpackFieldLine<'_>,
558    table: &QpackDynamicTable<'_, '_>,
559    context: QpackFieldSectionContext,
560    destination: &mut [u8],
561) -> Result<QpackDecodedFieldEntry, QpackFieldSectionDecodeError> {
562    match line {
563        QpackFieldLine::Indexed(line) => {
564            let field = resolve_indexed(line.is_static(), line.index().value(), table, context)?;
565            Ok(write_field(field, destination, false))
566        }
567        QpackFieldLine::IndexedPostBase(line) => {
568            let field = table
569                .get_post_base(
570                    context.required_insert_count(),
571                    context.base(),
572                    line.index().value(),
573                )
574                .map_err(QpackFieldSectionDecodeError::DynamicReference)?;
575            Ok(write_field(field, destination, false))
576        }
577        QpackFieldLine::LiteralNameReference(line) => {
578            let name =
579                resolve_indexed(line.is_static(), line.name_index().value(), table, context)?
580                    .name();
581            let value_len = literal_len(line.value(), false)?;
582            Ok(write_name_and_literal(
583                name,
584                line.value(),
585                value_len,
586                line.is_never_indexed(),
587                destination,
588            ))
589        }
590        QpackFieldLine::LiteralPostBaseNameReference(line) => {
591            let name = table
592                .get_post_base(
593                    context.required_insert_count(),
594                    context.base(),
595                    line.name_index().value(),
596                )
597                .map_err(QpackFieldSectionDecodeError::DynamicReference)?
598                .name();
599            let value_len = literal_len(line.value(), false)?;
600            Ok(write_name_and_literal(
601                name,
602                line.value(),
603                value_len,
604                line.is_never_indexed(),
605                destination,
606            ))
607        }
608        QpackFieldLine::LiteralName(line) => {
609            let name_len = literal_len(line.name(), true)?;
610            let value_len = literal_len(line.value(), false)?;
611            write_prevalidated_literal(line.name(), name_len, destination);
612            write_prevalidated_literal(line.value(), value_len, &mut destination[name_len..]);
613            Ok(QpackDecodedFieldEntry {
614                offset: 0,
615                name_len,
616                value_len,
617                never_indexed: line.is_never_indexed(),
618            })
619        }
620    }
621}
622
623fn write_field(
624    field: QpackHeaderFieldRef<'_>,
625    destination: &mut [u8],
626    never_indexed: bool,
627) -> QpackDecodedFieldEntry {
628    let name_len = field.name().len();
629    let value_len = field.value().len();
630    destination[..name_len].copy_from_slice(field.name());
631    destination[name_len..name_len + value_len].copy_from_slice(field.value());
632    QpackDecodedFieldEntry {
633        offset: 0,
634        name_len,
635        value_len,
636        never_indexed,
637    }
638}
639
640fn write_name_and_literal(
641    name: &[u8],
642    value: QpackStringLiteral<'_>,
643    value_len: usize,
644    never_indexed: bool,
645    destination: &mut [u8],
646) -> QpackDecodedFieldEntry {
647    destination[..name.len()].copy_from_slice(name);
648    write_prevalidated_literal(value, value_len, &mut destination[name.len()..]);
649    QpackDecodedFieldEntry {
650        offset: 0,
651        name_len: name.len(),
652        value_len,
653        never_indexed,
654    }
655}
656
657fn write_prevalidated_literal(
658    literal: QpackStringLiteral<'_>,
659    decoded_len: usize,
660    destination: &mut [u8],
661) {
662    if literal.is_huffman() {
663        decode_prevalidated(literal.encoded_payload(), &mut destination[..decoded_len]);
664    } else {
665        destination[..decoded_len].copy_from_slice(literal.encoded_payload());
666    }
667}
668
669fn field_from_entry<'a>(
670    bytes: &'a [u8],
671    entry: QpackDecodedFieldEntry,
672) -> Option<QpackDecodedFieldRef<'a>> {
673    let name_end = entry.offset.checked_add(entry.name_len)?;
674    let value_end = name_end.checked_add(entry.value_len)?;
675    Some(QpackDecodedFieldRef {
676        name: bytes.get(entry.offset..name_end)?,
677        value: bytes.get(name_end..value_end)?,
678        never_indexed: entry.never_indexed,
679    })
680}