Skip to main content

net_wire/qpack/field/line/
builder.rs

1//! Atomic canonical builders for QPACK field-line representations.
2
3use core::fmt;
4
5use super::{
6    QpackIndexedFieldLine, QpackIndexedPostBaseFieldLine, QpackLiteralNameFieldLine,
7    QpackLiteralNameReferenceFieldLine, QpackLiteralPostBaseNameReferenceFieldLine,
8};
9use crate::qpack::integer::{
10    QPACK_INTEGER_MAX, QpackIntegerBuildError, canonical_encoded_len, integer_from_prevalidated,
11    write_canonical_prevalidated,
12};
13use crate::qpack::string::{
14    QpackStringLiteralBuildError, QpackStringLiteralPlan, string_from_prevalidated, string_plan,
15    write_string_prevalidated,
16};
17
18/// Failure to build a QPACK field line.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum QpackFieldLineBuildError {
21    /// The indexed field-line index is invalid.
22    IndexedIndex(#[doc = "The nested integer failure."] QpackIntegerBuildError),
23    /// The indexed post-base field-line index is invalid.
24    IndexedPostBaseIndex(#[doc = "The nested integer failure."] QpackIntegerBuildError),
25    /// The literal name-reference field-line name index is invalid.
26    LiteralNameReferenceNameIndex(#[doc = "The nested integer failure."] QpackIntegerBuildError),
27    /// The literal name-reference field-line value is invalid.
28    LiteralNameReferenceValue(
29        #[doc = "The nested string-literal failure."] QpackStringLiteralBuildError,
30    ),
31    /// The literal post-base name-reference field-line name index is invalid.
32    LiteralPostBaseNameReferenceNameIndex(
33        #[doc = "The nested integer failure."] QpackIntegerBuildError,
34    ),
35    /// The literal post-base name-reference field-line value is invalid.
36    LiteralPostBaseNameReferenceValue(
37        #[doc = "The nested string-literal failure."] QpackStringLiteralBuildError,
38    ),
39    /// The literal-name field-line name is invalid.
40    LiteralNameName(#[doc = "The nested string-literal failure."] QpackStringLiteralBuildError),
41    /// The literal-name field-line value is invalid.
42    LiteralNameValue(#[doc = "The nested string-literal failure."] QpackStringLiteralBuildError),
43    /// The complete field-line length cannot be represented as `usize`.
44    RequiredLengthOverflow,
45    /// The caller buffer cannot contain the complete field line.
46    BufferTooShort {
47        /// Bytes required for the complete field line.
48        required: usize,
49        /// Bytes available in the caller buffer.
50        available: usize,
51    },
52}
53
54impl fmt::Display for QpackFieldLineBuildError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::IndexedIndex(error) => {
58                write!(f, "QPACK Indexed Field Line index cannot be built: {error}")
59            }
60            Self::IndexedPostBaseIndex(error) => write!(
61                f,
62                "QPACK Indexed Field Line With Post-Base Index cannot be built: {error}"
63            ),
64            Self::LiteralNameReferenceNameIndex(error) => write!(
65                f,
66                "QPACK Literal Field Line With Name Reference name index cannot be built: {error}"
67            ),
68            Self::LiteralNameReferenceValue(error) => write!(
69                f,
70                "QPACK Literal Field Line With Name Reference value cannot be built: {error}"
71            ),
72            Self::LiteralPostBaseNameReferenceNameIndex(error) => write!(
73                f,
74                "QPACK Literal Field Line With Post-Base Name Reference name index cannot be built: {error}"
75            ),
76            Self::LiteralPostBaseNameReferenceValue(error) => write!(
77                f,
78                "QPACK Literal Field Line With Post-Base Name Reference value cannot be built: {error}"
79            ),
80            Self::LiteralNameName(error) => write!(
81                f,
82                "QPACK Literal Field Line With Literal Name name cannot be built: {error}"
83            ),
84            Self::LiteralNameValue(error) => write!(
85                f,
86                "QPACK Literal Field Line With Literal Name value cannot be built: {error}"
87            ),
88            Self::RequiredLengthOverflow => {
89                f.write_str("QPACK field-line required length overflows usize")
90            }
91            Self::BufferTooShort {
92                required,
93                available,
94            } => write!(
95                f,
96                "QPACK field-line buffer is too short: need {required} bytes, have {available}"
97            ),
98        }
99    }
100}
101
102/// Input to the prevalidated field-line writer.
103///
104/// Dynamic indices have already been converted to the wire-relative form required
105/// by their representation. Payloads are opaque encoded bytes; their Huffman flags
106/// only control the corresponding wire bits.
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub(crate) enum QpackFieldLinePlanInput<'a> {
109    IndexedStatic {
110        index: u64,
111    },
112    IndexedDynamicPreBase {
113        relative_index: u64,
114    },
115    IndexedDynamicPostBase {
116        post_base_index: u64,
117    },
118    LiteralStaticNameReference {
119        never_indexed: bool,
120        index: u64,
121        value_huffman: bool,
122        encoded_value: &'a [u8],
123    },
124    LiteralDynamicPreBaseNameReference {
125        never_indexed: bool,
126        relative_index: u64,
127        value_huffman: bool,
128        encoded_value: &'a [u8],
129    },
130    LiteralDynamicPostBaseNameReference {
131        never_indexed: bool,
132        post_base_index: u64,
133        value_huffman: bool,
134        encoded_value: &'a [u8],
135    },
136    LiteralName {
137        never_indexed: bool,
138        name_huffman: bool,
139        encoded_name: &'a [u8],
140        value_huffman: bool,
141        encoded_value: &'a [u8],
142    },
143}
144
145/// A fully validated, allocation-free field-line write plan.
146#[derive(Clone, Copy)]
147pub(crate) struct QpackFieldLinePlan<'a> {
148    kind: QpackFieldLinePlanKind<'a>,
149    encoded_len: usize,
150}
151
152#[derive(Clone, Copy)]
153enum QpackFieldLinePlanKind<'a> {
154    Integer {
155        prefix_bits: u8,
156        high_bits: u8,
157        value: u64,
158    },
159    NameReference {
160        prefix_bits: u8,
161        high_bits: u8,
162        index: u64,
163        index_len: usize,
164        value: QpackStringLiteralPlan<'a>,
165    },
166    LiteralName {
167        name: QpackStringLiteralPlan<'a>,
168        value: QpackStringLiteralPlan<'a>,
169    },
170}
171
172impl QpackFieldLinePlan<'_> {
173    /// Returns a harmless canonical plan for opaque caller scratch initialization.
174    pub(crate) const fn empty() -> Self {
175        Self {
176            kind: QpackFieldLinePlanKind::Integer {
177                prefix_bits: 6,
178                high_bits: 0xc0,
179                value: 0,
180            },
181            encoded_len: 1,
182        }
183    }
184
185    /// Returns the exact number of bytes this plan writes.
186    pub(crate) const fn encoded_len(self) -> usize {
187        self.encoded_len
188    }
189
190    /// Writes this plan into its exact preallocated output region.
191    ///
192    /// A mismatched region is untouched rather than partially written. The type is
193    /// crate-private so the field-section commit path can pass only the region it
194    /// preflighted; with that contract, every split handed to the prevalidated
195    /// integer and string writers has its exact validated length.
196    pub(crate) fn write(self, destination: &mut [u8]) {
197        if destination.len() != self.encoded_len {
198            return;
199        }
200
201        match self.kind {
202            QpackFieldLinePlanKind::Integer {
203                prefix_bits,
204                high_bits,
205                value,
206            } => write_canonical_prevalidated(destination, prefix_bits, high_bits, value),
207            QpackFieldLinePlanKind::NameReference {
208                prefix_bits,
209                high_bits,
210                index,
211                index_len,
212                value,
213            } => {
214                let (index_destination, value_destination) = destination.split_at_mut(index_len);
215                write_canonical_prevalidated(index_destination, prefix_bits, high_bits, index);
216                write_string_prevalidated(value_destination, value);
217            }
218            QpackFieldLinePlanKind::LiteralName { name, value } => {
219                let (name_destination, value_destination) =
220                    destination.split_at_mut(name.required());
221                write_string_prevalidated(name_destination, name);
222                write_string_prevalidated(value_destination, value);
223            }
224        }
225    }
226}
227
228/// Validates one field-line emission without mutating output storage.
229pub(crate) fn plan_field_line(
230    input: QpackFieldLinePlanInput<'_>,
231) -> Result<QpackFieldLinePlan<'_>, QpackFieldLineBuildError> {
232    match input {
233        QpackFieldLinePlanInput::IndexedStatic { index } => {
234            plan_integer(index, 6, 0xc0, QpackFieldLineBuildError::IndexedIndex)
235        }
236        QpackFieldLinePlanInput::IndexedDynamicPreBase { relative_index } => plan_integer(
237            relative_index,
238            6,
239            0x80,
240            QpackFieldLineBuildError::IndexedIndex,
241        ),
242        QpackFieldLinePlanInput::IndexedDynamicPostBase { post_base_index } => plan_integer(
243            post_base_index,
244            4,
245            0x10,
246            QpackFieldLineBuildError::IndexedPostBaseIndex,
247        ),
248        QpackFieldLinePlanInput::LiteralStaticNameReference {
249            never_indexed,
250            index,
251            value_huffman,
252            encoded_value,
253        } => plan_name_reference(
254            index,
255            4,
256            0x50 | if never_indexed { 0x20 } else { 0 },
257            value_huffman,
258            encoded_value,
259            QpackFieldLineBuildError::LiteralNameReferenceNameIndex,
260            QpackFieldLineBuildError::LiteralNameReferenceValue,
261        ),
262        QpackFieldLinePlanInput::LiteralDynamicPreBaseNameReference {
263            never_indexed,
264            relative_index,
265            value_huffman,
266            encoded_value,
267        } => plan_name_reference(
268            relative_index,
269            4,
270            0x40 | if never_indexed { 0x20 } else { 0 },
271            value_huffman,
272            encoded_value,
273            QpackFieldLineBuildError::LiteralNameReferenceNameIndex,
274            QpackFieldLineBuildError::LiteralNameReferenceValue,
275        ),
276        QpackFieldLinePlanInput::LiteralDynamicPostBaseNameReference {
277            never_indexed,
278            post_base_index,
279            value_huffman,
280            encoded_value,
281        } => plan_name_reference(
282            post_base_index,
283            3,
284            if never_indexed { 0x08 } else { 0 },
285            value_huffman,
286            encoded_value,
287            QpackFieldLineBuildError::LiteralPostBaseNameReferenceNameIndex,
288            QpackFieldLineBuildError::LiteralPostBaseNameReferenceValue,
289        ),
290        QpackFieldLinePlanInput::LiteralName {
291            never_indexed,
292            name_huffman,
293            encoded_name,
294            value_huffman,
295            encoded_value,
296        } => {
297            let name = string_plan(
298                4,
299                0x20 | if never_indexed { 0x10 } else { 0 },
300                name_huffman,
301                encoded_name,
302            )
303            .map_err(QpackFieldLineBuildError::LiteralNameName)?;
304            let value = string_plan(8, 0, value_huffman, encoded_value)
305                .map_err(QpackFieldLineBuildError::LiteralNameValue)?;
306            let encoded_len = checked_sum(name.required(), value.required())?;
307            Ok(QpackFieldLinePlan {
308                kind: QpackFieldLinePlanKind::LiteralName { name, value },
309                encoded_len,
310            })
311        }
312    }
313}
314
315fn plan_integer(
316    value: u64,
317    prefix_bits: u8,
318    high_bits: u8,
319    error: fn(QpackIntegerBuildError) -> QpackFieldLineBuildError,
320) -> Result<QpackFieldLinePlan<'static>, QpackFieldLineBuildError> {
321    let encoded_len = integer_length(value, prefix_bits).map_err(error)?;
322    Ok(QpackFieldLinePlan {
323        kind: QpackFieldLinePlanKind::Integer {
324            prefix_bits,
325            high_bits,
326            value,
327        },
328        encoded_len,
329    })
330}
331
332fn plan_name_reference<'a>(
333    index: u64,
334    prefix_bits: u8,
335    high_bits: u8,
336    value_huffman: bool,
337    encoded_value: &'a [u8],
338    index_error: fn(QpackIntegerBuildError) -> QpackFieldLineBuildError,
339    value_error: fn(QpackStringLiteralBuildError) -> QpackFieldLineBuildError,
340) -> Result<QpackFieldLinePlan<'a>, QpackFieldLineBuildError> {
341    let index_len = integer_length(index, prefix_bits).map_err(index_error)?;
342    let value = string_plan(8, 0, value_huffman, encoded_value).map_err(value_error)?;
343    let encoded_len = checked_sum(index_len, value.required())?;
344    Ok(QpackFieldLinePlan {
345        kind: QpackFieldLinePlanKind::NameReference {
346            prefix_bits,
347            high_bits,
348            index,
349            index_len,
350            value,
351        },
352        encoded_len,
353    })
354}
355
356/// Builds an Indexed Field Line in caller-owned storage.
357pub struct QpackIndexedFieldLineBuilder<'buffer> {
358    destination: &'buffer mut [u8],
359    is_static: bool,
360    index: u64,
361}
362
363impl<'buffer> QpackIndexedFieldLineBuilder<'buffer> {
364    /// Creates a builder for a static- or dynamic-table index.
365    pub fn new(destination: &'buffer mut [u8], is_static: bool, index: u64) -> Self {
366        Self {
367            destination,
368            is_static,
369            index,
370        }
371    }
372
373    /// Validates all inputs and capacity before atomically writing the field line.
374    pub fn build(self) -> Result<QpackIndexedFieldLine<'buffer>, QpackFieldLineBuildError> {
375        let length =
376            integer_length(self.index, 6).map_err(QpackFieldLineBuildError::IndexedIndex)?;
377        ensure_capacity(self.destination, length)?;
378        let high_bits = 0x80 | if self.is_static { 0x40 } else { 0 };
379        write_canonical_prevalidated(&mut self.destination[..length], 6, high_bits, self.index);
380        let bytes = &self.destination[..length];
381        Ok(QpackIndexedFieldLine {
382            bytes,
383            index: integer_from_prevalidated(bytes, 6, high_bits, self.index),
384        })
385    }
386}
387
388/// Builds an Indexed Field Line With Post-Base Index in caller-owned storage.
389pub struct QpackIndexedPostBaseFieldLineBuilder<'buffer> {
390    destination: &'buffer mut [u8],
391    index: u64,
392}
393
394impl<'buffer> QpackIndexedPostBaseFieldLineBuilder<'buffer> {
395    /// Creates a builder for the supplied post-base index.
396    pub fn new(destination: &'buffer mut [u8], index: u64) -> Self {
397        Self { destination, index }
398    }
399
400    /// Validates all inputs and capacity before atomically writing the field line.
401    pub fn build(self) -> Result<QpackIndexedPostBaseFieldLine<'buffer>, QpackFieldLineBuildError> {
402        let length = integer_length(self.index, 4)
403            .map_err(QpackFieldLineBuildError::IndexedPostBaseIndex)?;
404        ensure_capacity(self.destination, length)?;
405        write_canonical_prevalidated(&mut self.destination[..length], 4, 0x10, self.index);
406        let bytes = &self.destination[..length];
407        Ok(QpackIndexedPostBaseFieldLine {
408            bytes,
409            index: integer_from_prevalidated(bytes, 4, 0x10, self.index),
410        })
411    }
412}
413
414/// Builds a Literal Field Line With Name Reference in caller-owned storage.
415pub struct QpackLiteralNameReferenceFieldLineBuilder<'buffer, 'value> {
416    destination: &'buffer mut [u8],
417    never_indexed: bool,
418    is_static: bool,
419    name_index: u64,
420    value_huffman: bool,
421    encoded_value: &'value [u8],
422}
423
424impl<'buffer, 'value> QpackLiteralNameReferenceFieldLineBuilder<'buffer, 'value> {
425    /// Creates a builder around an opaque, already-encoded value payload.
426    ///
427    /// `value_huffman` controls the wire H bit; this builder does not Huffman-encode the payload.
428    pub fn new(
429        destination: &'buffer mut [u8],
430        never_indexed: bool,
431        is_static: bool,
432        name_index: u64,
433        value_huffman: bool,
434        encoded_value: &'value [u8],
435    ) -> Self {
436        Self {
437            destination,
438            never_indexed,
439            is_static,
440            name_index,
441            value_huffman,
442            encoded_value,
443        }
444    }
445
446    /// Validates all inputs and capacity before atomically writing the field line.
447    pub fn build(
448        self,
449    ) -> Result<QpackLiteralNameReferenceFieldLine<'buffer>, QpackFieldLineBuildError> {
450        let name_length = integer_length(self.name_index, 4)
451            .map_err(QpackFieldLineBuildError::LiteralNameReferenceNameIndex)?;
452        let value_plan = string_plan(8, 0, self.value_huffman, self.encoded_value)
453            .map_err(QpackFieldLineBuildError::LiteralNameReferenceValue)?;
454        let required = checked_sum(name_length, value_plan.required())?;
455        ensure_capacity(self.destination, required)?;
456        let high_bits = 0x40
457            | if self.never_indexed { 0x20 } else { 0 }
458            | if self.is_static { 0x10 } else { 0 };
459        let (name_destination, value_destination) =
460            self.destination[..required].split_at_mut(name_length);
461        write_canonical_prevalidated(name_destination, 4, high_bits, self.name_index);
462        write_string_prevalidated(value_destination, value_plan);
463        let bytes = &self.destination[..required];
464        Ok(QpackLiteralNameReferenceFieldLine {
465            bytes,
466            name_index: integer_from_prevalidated(
467                &bytes[..name_length],
468                4,
469                high_bits,
470                self.name_index,
471            ),
472            value: string_from_prevalidated(&bytes[name_length..], value_plan),
473        })
474    }
475}
476
477/// Builds a Literal Field Line With Post-Base Name Reference in caller-owned storage.
478pub struct QpackLiteralPostBaseNameReferenceFieldLineBuilder<'buffer, 'value> {
479    destination: &'buffer mut [u8],
480    never_indexed: bool,
481    name_index: u64,
482    value_huffman: bool,
483    encoded_value: &'value [u8],
484}
485
486impl<'buffer, 'value> QpackLiteralPostBaseNameReferenceFieldLineBuilder<'buffer, 'value> {
487    /// Creates a builder around an opaque, already-encoded value payload.
488    ///
489    /// `value_huffman` controls the wire H bit; this builder does not Huffman-encode the payload.
490    pub fn new(
491        destination: &'buffer mut [u8],
492        never_indexed: bool,
493        name_index: u64,
494        value_huffman: bool,
495        encoded_value: &'value [u8],
496    ) -> Self {
497        Self {
498            destination,
499            never_indexed,
500            name_index,
501            value_huffman,
502            encoded_value,
503        }
504    }
505
506    /// Validates all inputs and capacity before atomically writing the field line.
507    pub fn build(
508        self,
509    ) -> Result<QpackLiteralPostBaseNameReferenceFieldLine<'buffer>, QpackFieldLineBuildError> {
510        let name_length = integer_length(self.name_index, 3)
511            .map_err(QpackFieldLineBuildError::LiteralPostBaseNameReferenceNameIndex)?;
512        let value_plan = string_plan(8, 0, self.value_huffman, self.encoded_value)
513            .map_err(QpackFieldLineBuildError::LiteralPostBaseNameReferenceValue)?;
514        let required = checked_sum(name_length, value_plan.required())?;
515        ensure_capacity(self.destination, required)?;
516        let high_bits = if self.never_indexed { 0x08 } else { 0 };
517        let (name_destination, value_destination) =
518            self.destination[..required].split_at_mut(name_length);
519        write_canonical_prevalidated(name_destination, 3, high_bits, self.name_index);
520        write_string_prevalidated(value_destination, value_plan);
521        let bytes = &self.destination[..required];
522        Ok(QpackLiteralPostBaseNameReferenceFieldLine {
523            bytes,
524            name_index: integer_from_prevalidated(
525                &bytes[..name_length],
526                3,
527                high_bits,
528                self.name_index,
529            ),
530            value: string_from_prevalidated(&bytes[name_length..], value_plan),
531        })
532    }
533}
534
535/// Builds a Literal Field Line With Literal Name in caller-owned storage.
536pub struct QpackLiteralNameFieldLineBuilder<'buffer, 'name, 'value> {
537    destination: &'buffer mut [u8],
538    never_indexed: bool,
539    name_huffman: bool,
540    encoded_name: &'name [u8],
541    value_huffman: bool,
542    encoded_value: &'value [u8],
543}
544
545impl<'buffer, 'name, 'value> QpackLiteralNameFieldLineBuilder<'buffer, 'name, 'value> {
546    /// Creates a builder around opaque, already-encoded name and value payloads.
547    ///
548    /// The Huffman flags only control the wire H bits; this builder does not encode either payload.
549    pub fn new(
550        destination: &'buffer mut [u8],
551        never_indexed: bool,
552        name_huffman: bool,
553        encoded_name: &'name [u8],
554        value_huffman: bool,
555        encoded_value: &'value [u8],
556    ) -> Self {
557        Self {
558            destination,
559            never_indexed,
560            name_huffman,
561            encoded_name,
562            value_huffman,
563            encoded_value,
564        }
565    }
566
567    /// Validates all inputs and capacity before atomically writing the field line.
568    pub fn build(self) -> Result<QpackLiteralNameFieldLine<'buffer>, QpackFieldLineBuildError> {
569        let name_high_bits = 0x20 | if self.never_indexed { 0x10 } else { 0 };
570        let name_plan = string_plan(4, name_high_bits, self.name_huffman, self.encoded_name)
571            .map_err(QpackFieldLineBuildError::LiteralNameName)?;
572        let value_plan = string_plan(8, 0, self.value_huffman, self.encoded_value)
573            .map_err(QpackFieldLineBuildError::LiteralNameValue)?;
574        let required = checked_sum(name_plan.required(), value_plan.required())?;
575        ensure_capacity(self.destination, required)?;
576        let (name_destination, value_destination) =
577            self.destination[..required].split_at_mut(name_plan.required());
578        write_string_prevalidated(name_destination, name_plan);
579        write_string_prevalidated(value_destination, value_plan);
580        let bytes = &self.destination[..required];
581        Ok(QpackLiteralNameFieldLine {
582            bytes,
583            name: string_from_prevalidated(&bytes[..name_plan.required()], name_plan),
584            value: string_from_prevalidated(&bytes[name_plan.required()..], value_plan),
585        })
586    }
587}
588
589fn integer_length(value: u64, prefix_bits: u8) -> Result<usize, QpackIntegerBuildError> {
590    if value > QPACK_INTEGER_MAX {
591        return Err(QpackIntegerBuildError::ValueTooLarge { value });
592    }
593    Ok(canonical_encoded_len(value, prefix_bits))
594}
595
596fn checked_sum(left: usize, right: usize) -> Result<usize, QpackFieldLineBuildError> {
597    left.checked_add(right)
598        .ok_or(QpackFieldLineBuildError::RequiredLengthOverflow)
599}
600
601fn ensure_capacity(destination: &[u8], required: usize) -> Result<(), QpackFieldLineBuildError> {
602    if destination.len() < required {
603        return Err(QpackFieldLineBuildError::BufferTooShort {
604            required,
605            available: destination.len(),
606        });
607    }
608    Ok(())
609}