Skip to main content

net_wire/http2/hpack/
encoder.rs

1//! Stateful, allocation-free encoding of HPACK fields and leading size updates.
2
3use core::fmt;
4
5use super::context::PendingSizes;
6use super::huffman::{HpackHuffmanEncodeError, HpackHuffmanEncoder};
7use super::integer::{canonical_encoded_len, write_canonical_for_valid_prefix};
8use super::representation::HpackLiteralMode;
9use super::representation_builder::{
10    HpackDynamicTableSizeUpdateBuilder, HpackIndexedFieldBuilder, HpackRepresentationBuildError,
11};
12use super::table::{
13    HPACK_STATIC_TABLE_LEN, HpackDynamicTable, HpackDynamicTableError, HpackHeaderFieldRef,
14    HpackStaticTable,
15};
16
17/// A decoded literal-field name for [`HpackBlockEncoder::encode_literal`].
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum HpackEncodeLiteralName<'a> {
20    /// Reuses a static or dynamic table name index after verifying its decoded name.
21    Indexed {
22        /// The combined static/dynamic table index to emit.
23        index: u64,
24        /// The decoded opaque name bytes expected at `index`.
25        decoded: &'a [u8],
26    },
27    /// Emits decoded opaque name bytes as a plain literal string.
28    Literal(&'a [u8]),
29}
30
31/// Explicit Huffman choices for one HPACK literal field.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct HpackLiteralHuffman(u8);
34
35impl HpackLiteralHuffman {
36    /// Encodes neither literal string with Huffman coding.
37    pub const NONE: Self = Self(0);
38    /// Encodes a literal name with Huffman coding.
39    pub const NAME: Self = Self(1);
40    /// Encodes the literal value with Huffman coding.
41    pub const VALUE: Self = Self(2);
42    /// Encodes both emitted literal strings with Huffman coding.
43    pub const BOTH: Self = Self(3);
44
45    /// Creates a strategy from independent name and value choices.
46    pub const fn new(name: bool, value: bool) -> Self {
47        Self((name as u8) | ((value as u8) << 1))
48    }
49
50    /// Returns whether a literal name is Huffman encoded.
51    pub const fn encodes_name(self) -> bool {
52        self.0 & Self::NAME.0 != 0
53    }
54
55    /// Returns whether a literal value is Huffman encoded.
56    pub const fn encodes_value(self) -> bool {
57        self.0 & Self::VALUE.0 != 0
58    }
59}
60
61/// Failure to construct or advance an HPACK block encoder.
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum HpackEncodeError {
64    /// The configured allowed maximum exceeds the table's physical capacity.
65    AllowedMaximumExceedsCapacity {
66        /// The requested policy maximum.
67        allowed: usize,
68        /// The physical table capacity.
69        capacity: usize,
70    },
71    /// A required leading dynamic-table size update has not been emitted.
72    DynamicTableSizeUpdateRequired,
73    /// A required leading dynamic-table size update did not match the exact pending value.
74    DynamicTableSizeUpdateMismatch {
75        /// The exact next required maximum.
76        expected: usize,
77        /// The requested maximum.
78        requested: usize,
79    },
80    /// A dynamic-table update was requested after a header field.
81    DynamicTableSizeUpdateAfterField,
82    /// A dynamic-table update exceeds the encoder's configured allowed maximum.
83    DynamicTableSizeUpdateExceedsAllowed {
84        /// The requested maximum.
85        requested: usize,
86        /// The encoder policy maximum.
87        allowed: usize,
88    },
89    /// A combined static/dynamic table index cannot be represented on the wire.
90    IndexNotRepresentable {
91        /// The requested index.
92        index: u64,
93    },
94    /// A combined static/dynamic table index is unavailable.
95    UnavailableIndex {
96        /// The requested index.
97        index: usize,
98    },
99    /// An indexed literal name did not match the supplied decoded name.
100    IndexedLiteralNameMismatch {
101        /// The combined static/dynamic table index.
102        index: u64,
103    },
104    /// Huffman name encoding was requested for an indexed name with no emitted name bytes.
105    HuffmanNameForIndexedName,
106    /// Huffman encoding a literal name failed during preflight.
107    HuffmanName(HpackHuffmanEncodeError),
108    /// Huffman encoding a literal value failed during preflight.
109    HuffmanValue(HpackHuffmanEncodeError),
110    /// An encoded string length cannot be represented by the HPACK integer format.
111    StringLengthNotRepresentable {
112        /// The encoded string length.
113        length: usize,
114    },
115    /// The exact HPACK literal representation length overflows `usize`.
116    RepresentationLengthOverflow,
117    /// Calculating an incremental-indexing entry size failed before output was written.
118    DynamicTableEntrySize(HpackDynamicTableError),
119    /// A dynamic-table maximum cannot be represented on the wire.
120    MaximumSizeNotRepresentable {
121        /// The requested maximum.
122        maximum_size: usize,
123    },
124    /// A representation could not be built in the supplied destination.
125    Representation(HpackRepresentationBuildError),
126}
127
128impl fmt::Display for HpackEncodeError {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::AllowedMaximumExceedsCapacity { allowed, capacity } => write!(
132                f,
133                "HPACK allowed maximum {allowed} exceeds table capacity {capacity}"
134            ),
135            Self::DynamicTableSizeUpdateRequired => {
136                f.write_str("HPACK dynamic-table size update is required before a header field")
137            }
138            Self::DynamicTableSizeUpdateMismatch {
139                expected,
140                requested,
141            } => write!(
142                f,
143                "HPACK dynamic-table update {requested} does not match required update {expected}"
144            ),
145            Self::DynamicTableSizeUpdateAfterField => {
146                f.write_str("HPACK dynamic-table update follows a header field")
147            }
148            Self::DynamicTableSizeUpdateExceedsAllowed { requested, allowed } => write!(
149                f,
150                "HPACK dynamic-table update {requested} exceeds allowed maximum {allowed}"
151            ),
152            Self::IndexNotRepresentable { index } => {
153                write!(f, "HPACK index {index} cannot be represented on the wire")
154            }
155            Self::UnavailableIndex { index } => {
156                write!(f, "HPACK index {index} is unavailable")
157            }
158            Self::IndexedLiteralNameMismatch { index } => {
159                write!(
160                    f,
161                    "HPACK indexed literal name at index {index} does not match"
162                )
163            }
164            Self::HuffmanNameForIndexedName => {
165                f.write_str("HPACK Huffman name encoding requires an emitted literal name")
166            }
167            Self::HuffmanName(error) => {
168                write!(f, "could not Huffman encode HPACK literal name: {error}")
169            }
170            Self::HuffmanValue(error) => {
171                write!(f, "could not Huffman encode HPACK literal value: {error}")
172            }
173            Self::StringLengthNotRepresentable { length } => write!(
174                f,
175                "HPACK string length {length} cannot be represented on the wire"
176            ),
177            Self::RepresentationLengthOverflow => {
178                f.write_str("HPACK literal representation length overflows usize")
179            }
180            Self::DynamicTableEntrySize(error) => write!(
181                f,
182                "could not calculate HPACK dynamic-table entry size: {error:?}"
183            ),
184            Self::MaximumSizeNotRepresentable { maximum_size } => write!(
185                f,
186                "HPACK maximum size {maximum_size} cannot be represented on the wire"
187            ),
188            Self::Representation(error) => {
189                write!(f, "could not build HPACK representation: {error}")
190            }
191        }
192    }
193}
194
195/// An encoder for one HPACK block borrowing connection-owned dynamic-table state.
196pub struct HpackBlockEncoder<'table, 'storage, 'entries> {
197    table: &'table mut HpackDynamicTable<'storage, 'entries>,
198    pending: &'table mut PendingSizes,
199    allowed_maximum_size: usize,
200    has_emitted_field: bool,
201}
202
203impl<'table, 'storage, 'entries> HpackBlockEncoder<'table, 'storage, 'entries> {
204    pub(super) fn from_context(
205        table: &'table mut HpackDynamicTable<'storage, 'entries>,
206        pending: &'table mut PendingSizes,
207        allowed_maximum_size: usize,
208    ) -> Self {
209        Self {
210            table,
211            pending,
212            allowed_maximum_size,
213            has_emitted_field: false,
214        }
215    }
216
217    /// Returns whether a leading dynamic-table size update must precede a field.
218    pub const fn requires_size_update(&self) -> bool {
219        self.pending.next().is_some()
220    }
221
222    /// Returns whether this block has emitted a header-field representation.
223    pub const fn has_emitted_field(&self) -> bool {
224        self.has_emitted_field
225    }
226
227    /// Encodes one currently resolvable combined static/dynamic table index.
228    pub fn encode_indexed<'destination>(
229        &mut self,
230        destination: &'destination mut [u8],
231        index: u64,
232    ) -> Result<&'destination [u8], HpackEncodeError> {
233        if self.pending.next().is_some() {
234            return Err(HpackEncodeError::DynamicTableSizeUpdateRequired);
235        }
236        self.resolve(index)?;
237        let output = HpackIndexedFieldBuilder::new(destination, index)
238            .build()
239            .map_err(HpackEncodeError::Representation)?;
240        self.has_emitted_field = true;
241        Ok(output)
242    }
243
244    /// Encodes one plain literal field and inserts it only for incremental indexing.
245    ///
246    /// Inputs are decoded opaque bytes. This convenience method always emits non-Huffman strings.
247    pub fn encode_literal<'destination>(
248        &mut self,
249        destination: &'destination mut [u8],
250        mode: HpackLiteralMode,
251        name: HpackEncodeLiteralName<'_>,
252        value: &[u8],
253    ) -> Result<&'destination [u8], HpackEncodeError> {
254        self.encode_literal_with_huffman(destination, mode, name, value, HpackLiteralHuffman::NONE)
255    }
256
257    /// Encodes one literal field with explicit Huffman choices for emitted strings.
258    ///
259    /// Inputs and any incremental-indexing table entry remain decoded opaque bytes.
260    pub fn encode_literal_with_huffman<'destination>(
261        &mut self,
262        destination: &'destination mut [u8],
263        mode: HpackLiteralMode,
264        name: HpackEncodeLiteralName<'_>,
265        value: &[u8],
266        strategy: HpackLiteralHuffman,
267    ) -> Result<&'destination [u8], HpackEncodeError> {
268        if self.pending.next().is_some() {
269            return Err(HpackEncodeError::DynamicTableSizeUpdateRequired);
270        }
271
272        let (decoded_name, name_index, literal_name) = match name {
273            HpackEncodeLiteralName::Indexed { index, decoded } => {
274                if strategy.encodes_name() {
275                    return Err(HpackEncodeError::HuffmanNameForIndexedName);
276                }
277                let resolved = self.resolve(index)?;
278                if resolved.name() != decoded {
279                    return Err(HpackEncodeError::IndexedLiteralNameMismatch { index });
280                }
281                (decoded, index, None)
282            }
283            HpackEncodeLiteralName::Literal(decoded) => (decoded, 0, Some(decoded)),
284        };
285        let entry_size = if mode == HpackLiteralMode::IncrementalIndexing {
286            Some(
287                HpackDynamicTable::entry_size(decoded_name, value)
288                    .map_err(HpackEncodeError::DynamicTableEntrySize)?,
289            )
290        } else {
291            None
292        };
293        let name_encoded_len = match literal_name {
294            Some(decoded) if strategy.encodes_name() => {
295                HpackHuffmanEncoder::required_encoded_len(decoded)
296                    .map_err(HpackEncodeError::HuffmanName)?
297            }
298            Some(decoded) => decoded.len(),
299            None => 0,
300        };
301        let value_encoded_len = if strategy.encodes_value() {
302            HpackHuffmanEncoder::required_encoded_len(value)
303                .map_err(HpackEncodeError::HuffmanValue)?
304        } else {
305            value.len()
306        };
307        let name_string_len = literal_name
308            .map(|_| string_len(name_encoded_len))
309            .transpose()?;
310        let value_string_len = string_len(value_encoded_len)?;
311        let required = canonical_encoded_len(name_index, prefix_mask(mode))
312            .checked_add(name_string_len.unwrap_or(0))
313            .and_then(|length| length.checked_add(value_string_len))
314            .ok_or(HpackEncodeError::RepresentationLengthOverflow)?;
315        if destination.len() < required {
316            return Err(HpackEncodeError::Representation(
317                HpackRepresentationBuildError::BufferTooShort {
318                    required,
319                    available: destination.len(),
320                },
321            ));
322        }
323
324        let representation = &mut destination[..required];
325        let name_index_len = canonical_encoded_len(name_index, prefix_mask(mode));
326        let (name_index_destination, remainder) = representation.split_at_mut(name_index_len);
327        write_canonical_for_valid_prefix(
328            name_index_destination,
329            prefix_bits(mode),
330            prefix_mask(mode),
331            high_bits(mode),
332            name_index,
333        );
334        let remainder = if let Some(decoded) = literal_name {
335            let encoded_len = name_encoded_len;
336            let string_len = name_string_len.unwrap_or(0);
337            let (string_destination, remainder) = remainder.split_at_mut(string_len);
338            write_string(
339                string_destination,
340                decoded,
341                encoded_len,
342                strategy.encodes_name(),
343            );
344            remainder
345        } else {
346            remainder
347        };
348        write_string(
349            remainder,
350            value,
351            value_encoded_len,
352            strategy.encodes_value(),
353        );
354
355        if let Some(entry_size) = entry_size {
356            self.table
357                .insert_prevalidated(decoded_name, value, entry_size);
358        }
359        self.has_emitted_field = true;
360        Ok(&*representation)
361    }
362
363    /// Encodes and applies one leading RFC dynamic-table maximum update.
364    pub fn encode_dynamic_table_size_update<'destination>(
365        &mut self,
366        destination: &'destination mut [u8],
367        maximum_size: usize,
368    ) -> Result<&'destination [u8], HpackEncodeError> {
369        if self.has_emitted_field {
370            return Err(HpackEncodeError::DynamicTableSizeUpdateAfterField);
371        }
372        if let Some(expected) = self.pending.next()
373            && maximum_size != expected
374        {
375            return Err(HpackEncodeError::DynamicTableSizeUpdateMismatch {
376                expected,
377                requested: maximum_size,
378            });
379        }
380        let permitted_maximum_size = self.allowed_maximum_size.min(self.table.capacity());
381        if maximum_size > permitted_maximum_size {
382            return Err(HpackEncodeError::DynamicTableSizeUpdateExceedsAllowed {
383                requested: maximum_size,
384                allowed: permitted_maximum_size,
385            });
386        }
387        let maximum_size_u64 = u64::try_from(maximum_size)
388            .map_err(|_| HpackEncodeError::MaximumSizeNotRepresentable { maximum_size })?;
389        let required = canonical_encoded_len(maximum_size_u64, 0x1f);
390        if destination.len() < required {
391            return Err(HpackEncodeError::Representation(
392                HpackRepresentationBuildError::BufferTooShort {
393                    required,
394                    available: destination.len(),
395                },
396            ));
397        }
398        let output = HpackDynamicTableSizeUpdateBuilder::new(destination, maximum_size_u64)
399            .build()
400            .map_err(HpackEncodeError::Representation)?;
401        self.table.set_maximum_size_prevalidated(maximum_size);
402        if self.pending.next().is_some() {
403            self.pending.advance();
404        }
405        Ok(output)
406    }
407
408    /// Establishes successful semantic completion of this HPACK block.
409    pub fn finish(self) -> Result<(), HpackEncodeError> {
410        if self.pending.next().is_some() {
411            Err(HpackEncodeError::DynamicTableSizeUpdateRequired)
412        } else {
413            Ok(())
414        }
415    }
416
417    fn resolve(&self, index: u64) -> Result<HpackHeaderFieldRef<'_>, HpackEncodeError> {
418        let index = usize::try_from(index)
419            .map_err(|_| HpackEncodeError::IndexNotRepresentable { index })?;
420        if index == 0 {
421            return Err(HpackEncodeError::UnavailableIndex { index });
422        }
423        if index <= HPACK_STATIC_TABLE_LEN {
424            return HpackStaticTable::get(index)
425                .ok_or(HpackEncodeError::UnavailableIndex { index });
426        }
427        self.table
428            .get(index - HPACK_STATIC_TABLE_LEN)
429            .ok_or(HpackEncodeError::UnavailableIndex { index })
430    }
431}
432
433fn prefix_bits(mode: HpackLiteralMode) -> u8 {
434    match mode {
435        HpackLiteralMode::IncrementalIndexing => 6,
436        HpackLiteralMode::WithoutIndexing | HpackLiteralMode::NeverIndexed => 4,
437    }
438}
439
440fn prefix_mask(mode: HpackLiteralMode) -> u8 {
441    (1 << prefix_bits(mode)) - 1
442}
443
444fn high_bits(mode: HpackLiteralMode) -> u8 {
445    match mode {
446        HpackLiteralMode::IncrementalIndexing => 0x40,
447        HpackLiteralMode::WithoutIndexing => 0,
448        HpackLiteralMode::NeverIndexed => 0x10,
449    }
450}
451
452fn string_len(encoded_len: usize) -> Result<usize, HpackEncodeError> {
453    let encoded_len_u64 =
454        u64::try_from(encoded_len).map_err(|_| HpackEncodeError::StringLengthNotRepresentable {
455            length: encoded_len,
456        })?;
457    canonical_encoded_len(encoded_len_u64, 0x7f)
458        .checked_add(encoded_len)
459        .ok_or(HpackEncodeError::RepresentationLengthOverflow)
460}
461
462fn write_string(destination: &mut [u8], decoded: &[u8], encoded_len: usize, huffman: bool) {
463    let encoded_len_u64 = encoded_len as u64;
464    let length_len = canonical_encoded_len(encoded_len_u64, 0x7f);
465    let (length_destination, payload_destination) = destination.split_at_mut(length_len);
466    write_canonical_for_valid_prefix(
467        length_destination,
468        7,
469        0x7f,
470        if huffman { 0x80 } else { 0 },
471        encoded_len_u64,
472    );
473    if huffman {
474        HpackHuffmanEncoder::encode_prevalidated(decoded, payload_destination);
475    } else {
476        payload_destination.copy_from_slice(decoded);
477    }
478}