Skip to main content

net_wire/http2/hpack/
decoder.rs

1//! Stateful, allocation-free decoding of complete HPACK header blocks.
2
3use core::fmt;
4
5use super::context::PendingSizes;
6use super::huffman::{HpackHuffmanDecodeError, HpackHuffmanDecoder};
7use super::representation::{HpackLiteralMode, HpackRepresentation, HpackRepresentationParseError};
8use super::string::HpackStringLiteral;
9use super::table::{
10    HPACK_STATIC_TABLE_LEN, HpackDynamicTable, HpackDynamicTableError, HpackHeaderFieldRef,
11    HpackStaticTable,
12};
13
14/// The indexing semantics of a decoded HPACK header field.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum HpackDecodedFieldMode {
17    /// The field was selected by an indexed representation.
18    Indexed,
19    /// The literal field was inserted into the dynamic table.
20    IncrementalIndexing,
21    /// The literal field was not inserted into the dynamic table.
22    WithoutIndexing,
23    /// The literal field was not inserted and is marked sensitive.
24    NeverIndexed,
25}
26
27/// A decoded byte-oriented header field backed entirely by the caller output buffer.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub struct HpackDecodedField<'a> {
30    name: &'a [u8],
31    value: &'a [u8],
32    mode: HpackDecodedFieldMode,
33}
34
35impl<'a> HpackDecodedField<'a> {
36    /// Returns the decoded opaque header name.
37    pub const fn name(self) -> &'a [u8] {
38        self.name
39    }
40
41    /// Returns the decoded opaque header value.
42    pub const fn value(self) -> &'a [u8] {
43        self.value
44    }
45
46    /// Returns the representation's indexing semantics.
47    pub const fn mode(self) -> HpackDecodedFieldMode {
48        self.mode
49    }
50}
51
52/// One explicit result of advancing an HPACK header-block decoder.
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub enum HpackDecodeStep<'a> {
55    /// A header field was decoded into caller-owned output.
56    Field(HpackDecodedField<'a>),
57    /// A leading dynamic-table maximum update was applied.
58    DynamicTableSizeUpdate {
59        /// The requested and applied RFC dynamic-table maximum.
60        maximum_size: usize,
61    },
62    /// The supplied block has no remaining representations.
63    Complete,
64}
65
66/// Failure to construct or advance an HPACK block decoder.
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub enum HpackDecodeError {
69    /// A representation could not be parsed.
70    Representation(HpackRepresentationParseError),
71    /// A combined static/dynamic table index cannot be represented as `usize`.
72    IndexNotRepresentable {
73        /// The wire index.
74        index: u64,
75    },
76    /// A combined static/dynamic table index is unavailable.
77    UnavailableIndex {
78        /// The wire index.
79        index: u64,
80    },
81    /// A dynamic-table update cannot be represented as `usize`.
82    MaximumSizeNotRepresentable {
83        /// The wire maximum size.
84        maximum_size: u64,
85    },
86    /// The configured allowed maximum exceeds the table's physical capacity.
87    AllowedMaximumExceedsCapacity {
88        /// The requested policy maximum.
89        allowed: usize,
90        /// The physical table capacity.
91        capacity: usize,
92    },
93    /// A required leading dynamic-table size update has not been received.
94    DynamicTableSizeUpdateRequired,
95    /// A leading dynamic-table update did not match the exact pending value.
96    DynamicTableSizeUpdateMismatch {
97        /// The exact next required maximum.
98        expected: usize,
99        /// The wire-requested maximum.
100        requested: usize,
101    },
102    /// A dynamic-table update appeared after a header field.
103    DynamicTableSizeUpdateAfterField,
104    /// A dynamic-table update exceeds the decoder's configured allowed maximum.
105    DynamicTableSizeUpdateExceedsAllowed {
106        /// The wire-requested maximum.
107        requested: usize,
108        /// The decoder policy maximum.
109        allowed: usize,
110    },
111    /// Applying a dynamic-table update failed.
112    DynamicTableSizeUpdate(HpackDynamicTableError),
113    /// Decoding a literal name's Huffman bytes failed.
114    HuffmanName(HpackHuffmanDecodeError),
115    /// Decoding a literal value's Huffman bytes failed.
116    HuffmanValue(HpackHuffmanDecodeError),
117    /// Name and value lengths cannot be added as `usize`.
118    DecodedLengthOverflow,
119    /// The RFC dynamic-table entry-size calculation overflowed `usize`.
120    EntrySizeOverflow,
121    /// The caller output cannot contain the complete decoded field.
122    OutputTooShort {
123        /// Bytes required for the complete field.
124        required: usize,
125        /// Bytes supplied by the caller.
126        available: usize,
127    },
128}
129
130impl fmt::Display for HpackDecodeError {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self {
133            Self::Representation(error) => write!(f, "invalid HPACK representation: {error}"),
134            Self::IndexNotRepresentable { index } => {
135                write!(f, "HPACK index {index} cannot be represented as usize")
136            }
137            Self::UnavailableIndex { index } => write!(f, "HPACK index {index} is unavailable"),
138            Self::MaximumSizeNotRepresentable { maximum_size } => write!(
139                f,
140                "HPACK maximum size {maximum_size} cannot be represented as usize"
141            ),
142            Self::AllowedMaximumExceedsCapacity { allowed, capacity } => write!(
143                f,
144                "HPACK allowed maximum {allowed} exceeds table capacity {capacity}"
145            ),
146            Self::DynamicTableSizeUpdateRequired => {
147                f.write_str("HPACK dynamic-table size update is required before a header field")
148            }
149            Self::DynamicTableSizeUpdateMismatch {
150                expected,
151                requested,
152            } => write!(
153                f,
154                "HPACK dynamic-table update {requested} does not match required update {expected}"
155            ),
156            Self::DynamicTableSizeUpdateAfterField => {
157                f.write_str("HPACK dynamic-table update follows a header field")
158            }
159            Self::DynamicTableSizeUpdateExceedsAllowed { requested, allowed } => write!(
160                f,
161                "HPACK dynamic-table update {requested} exceeds allowed maximum {allowed}"
162            ),
163            Self::DynamicTableSizeUpdate(error) => {
164                write!(f, "HPACK dynamic-table update failed: {error:?}")
165            }
166            Self::HuffmanName(error) => write!(f, "invalid HPACK Huffman name: {error}"),
167            Self::HuffmanValue(error) => write!(f, "invalid HPACK Huffman value: {error}"),
168            Self::DecodedLengthOverflow => {
169                f.write_str("HPACK decoded field length overflows usize")
170            }
171            Self::EntrySizeOverflow => {
172                f.write_str("HPACK dynamic-table entry size overflows usize")
173            }
174            Self::OutputTooShort {
175                required,
176                available,
177            } => write!(
178                f,
179                "HPACK output is too short: need {required} bytes, have {available}"
180            ),
181        }
182    }
183}
184
185/// A bounded decoder for one complete HPACK block borrowing connection-owned table state.
186pub struct HpackBlockDecoder<'block, 'table, 'storage, 'entries> {
187    remaining: &'block [u8],
188    table: &'table mut HpackDynamicTable<'storage, 'entries>,
189    pending: &'table mut PendingSizes,
190    allowed_maximum_size: usize,
191    saw_field: bool,
192}
193
194impl<'block, 'table, 'storage, 'entries> HpackBlockDecoder<'block, 'table, 'storage, 'entries> {
195    pub(super) fn from_context(
196        block: &'block [u8],
197        table: &'table mut HpackDynamicTable<'storage, 'entries>,
198        pending: &'table mut PendingSizes,
199        allowed_maximum_size: usize,
200    ) -> Self {
201        Self {
202            remaining: block,
203            table,
204            pending,
205            allowed_maximum_size,
206            saw_field: false,
207        }
208    }
209
210    /// Returns the representation bytes not yet consumed.
211    pub const fn remaining(&self) -> &'block [u8] {
212        self.remaining
213    }
214
215    /// Returns whether every representation in this block has been consumed.
216    ///
217    /// This does not establish semantic success when a required leading size update is absent;
218    /// call [`Self::decode_next`] and receive [`HpackDecodeStep::Complete`] to validate that case.
219    pub const fn is_complete(&self) -> bool {
220        self.remaining.is_empty()
221    }
222
223    /// Decodes or applies exactly one representation without partial caller-visible effects.
224    pub fn decode_next<'output>(
225        &mut self,
226        output: &'output mut [u8],
227    ) -> Result<HpackDecodeStep<'output>, HpackDecodeError> {
228        if self.remaining.is_empty() {
229            if self.pending.next().is_some() {
230                return Err(HpackDecodeError::DynamicTableSizeUpdateRequired);
231            }
232            return Ok(HpackDecodeStep::Complete);
233        }
234        let representation =
235            HpackRepresentation::parse(self.remaining).map_err(HpackDecodeError::Representation)?;
236        match representation {
237            HpackRepresentation::DynamicTableSizeUpdate(update) => {
238                if self.saw_field {
239                    return Err(HpackDecodeError::DynamicTableSizeUpdateAfterField);
240                }
241                let maximum_size = usize::try_from(update.size_value()).map_err(|_| {
242                    HpackDecodeError::MaximumSizeNotRepresentable {
243                        maximum_size: update.size_value(),
244                    }
245                })?;
246                if let Some(expected) = self.pending.next()
247                    && maximum_size != expected
248                {
249                    return Err(HpackDecodeError::DynamicTableSizeUpdateMismatch {
250                        expected,
251                        requested: maximum_size,
252                    });
253                }
254                if maximum_size > self.allowed_maximum_size {
255                    return Err(HpackDecodeError::DynamicTableSizeUpdateExceedsAllowed {
256                        requested: maximum_size,
257                        allowed: self.allowed_maximum_size,
258                    });
259                }
260                self.table
261                    .set_maximum_size(maximum_size)
262                    .map_err(HpackDecodeError::DynamicTableSizeUpdate)?;
263                self.remaining = &self.remaining[update.as_bytes().len()..];
264                if self.pending.next().is_some() {
265                    self.pending.advance();
266                }
267                Ok(HpackDecodeStep::DynamicTableSizeUpdate { maximum_size })
268            }
269            HpackRepresentation::Indexed(indexed) => {
270                if self.pending.next().is_some() {
271                    return Err(HpackDecodeError::DynamicTableSizeUpdateRequired);
272                }
273                let consumed = indexed.as_bytes().len();
274                let (name_len, required) = {
275                    let field = self.resolve(indexed.index_value())?;
276                    let name_len = field.name().len();
277                    let required = name_len
278                        .checked_add(field.value().len())
279                        .ok_or(HpackDecodeError::DecodedLengthOverflow)?;
280                    if output.len() < required {
281                        return Err(HpackDecodeError::OutputTooShort {
282                            required,
283                            available: output.len(),
284                        });
285                    }
286                    output[..name_len].copy_from_slice(field.name());
287                    output[name_len..required].copy_from_slice(field.value());
288                    (name_len, required)
289                };
290                self.remaining = &self.remaining[consumed..];
291                self.saw_field = true;
292                Ok(HpackDecodeStep::Field(HpackDecodedField {
293                    name: &output[..name_len],
294                    value: &output[name_len..required],
295                    mode: HpackDecodedFieldMode::Indexed,
296                }))
297            }
298            HpackRepresentation::Literal(literal) => {
299                if self.pending.next().is_some() {
300                    return Err(HpackDecodeError::DynamicTableSizeUpdateRequired);
301                }
302                let mode = match literal.mode() {
303                    HpackLiteralMode::IncrementalIndexing => {
304                        HpackDecodedFieldMode::IncrementalIndexing
305                    }
306                    HpackLiteralMode::WithoutIndexing => HpackDecodedFieldMode::WithoutIndexing,
307                    HpackLiteralMode::NeverIndexed => HpackDecodedFieldMode::NeverIndexed,
308                };
309                let name = match literal.name() {
310                    Some(name) => NameSource::Literal(name),
311                    None => NameSource::Borrowed(self.resolve(literal.name_index_value())?.name()),
312                };
313                let value = literal.value();
314                let name_len = name.required_len()?;
315                let value_len = literal_len(value, false)?;
316                let required = name_len
317                    .checked_add(value_len)
318                    .ok_or(HpackDecodeError::DecodedLengthOverflow)?;
319                if output.len() < required {
320                    return Err(HpackDecodeError::OutputTooShort {
321                        required,
322                        available: output.len(),
323                    });
324                }
325                let entry_size = (mode == HpackDecodedFieldMode::IncrementalIndexing)
326                    .then(|| {
327                        HpackDynamicTable::entry_size(
328                            &output[..name_len],
329                            &output[name_len..required],
330                        )
331                    })
332                    .transpose()
333                    .map_err(|_| HpackDecodeError::EntrySizeOverflow)?;
334                write_name(name, &mut output[..name_len])?;
335                write_literal(value, &mut output[name_len..required], false)?;
336                if let Some(entry_size) = entry_size {
337                    self.table.insert_prevalidated(
338                        &output[..name_len],
339                        &output[name_len..required],
340                        entry_size,
341                    );
342                }
343                self.remaining = &self.remaining[literal.as_bytes().len()..];
344                self.saw_field = true;
345                Ok(HpackDecodeStep::Field(HpackDecodedField {
346                    name: &output[..name_len],
347                    value: &output[name_len..required],
348                    mode,
349                }))
350            }
351        }
352    }
353
354    fn resolve(&self, index: u64) -> Result<HpackHeaderFieldRef<'_>, HpackDecodeError> {
355        let index = usize::try_from(index)
356            .map_err(|_| HpackDecodeError::IndexNotRepresentable { index })?;
357        if index == 0 {
358            return Err(HpackDecodeError::UnavailableIndex { index: 0 });
359        }
360        if index <= HPACK_STATIC_TABLE_LEN {
361            return HpackStaticTable::get(index).ok_or(HpackDecodeError::UnavailableIndex {
362                index: index as u64,
363            });
364        }
365        let dynamic = index - HPACK_STATIC_TABLE_LEN;
366        self.table
367            .get(dynamic)
368            .ok_or(HpackDecodeError::UnavailableIndex {
369                index: index as u64,
370            })
371    }
372}
373
374#[derive(Clone, Copy)]
375enum NameSource<'a> {
376    Borrowed(&'a [u8]),
377    Literal(HpackStringLiteral<'a>),
378}
379
380impl NameSource<'_> {
381    fn required_len(self) -> Result<usize, HpackDecodeError> {
382        match self {
383            Self::Borrowed(bytes) => Ok(bytes.len()),
384            Self::Literal(literal) => literal_len(literal, true),
385        }
386    }
387}
388
389fn literal_len(literal: HpackStringLiteral<'_>, name: bool) -> Result<usize, HpackDecodeError> {
390    if literal.is_huffman() {
391        HpackHuffmanDecoder::required_decoded_len(literal.encoded_bytes()).map_err(|error| {
392            if name {
393                HpackDecodeError::HuffmanName(error)
394            } else {
395                HpackDecodeError::HuffmanValue(error)
396            }
397        })
398    } else {
399        Ok(literal.encoded_bytes().len())
400    }
401}
402
403fn write_name(source: NameSource<'_>, output: &mut [u8]) -> Result<(), HpackDecodeError> {
404    match source {
405        NameSource::Borrowed(bytes) => output.copy_from_slice(bytes),
406        NameSource::Literal(literal) => write_literal(literal, output, true)?,
407    }
408    Ok(())
409}
410
411fn write_literal(
412    literal: HpackStringLiteral<'_>,
413    output: &mut [u8],
414    name: bool,
415) -> Result<(), HpackDecodeError> {
416    if literal.is_huffman() {
417        HpackHuffmanDecoder::new(literal.encoded_bytes(), output)
418            .decode()
419            .map_err(|error| {
420                if name {
421                    HpackDecodeError::HuffmanName(error)
422                } else {
423                    HpackDecodeError::HuffmanValue(error)
424                }
425            })?;
426    } else {
427        output.copy_from_slice(literal.encoded_bytes());
428    }
429    Ok(())
430}