Skip to main content

net_wire/qpack/
string.rs

1//! RFC 9204 section 4.1.2 N-bit-prefixed string literals.
2
3use core::fmt;
4
5use super::integer::{
6    QPACK_INTEGER_MAX, QpackInteger, QpackIntegerBuildError, QpackIntegerParseError,
7    canonical_encoded_len, integer_from_prevalidated, write_canonical_prevalidated,
8};
9
10/// A validated borrowed QPACK string literal whose payload remains opaque.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct QpackStringLiteral<'a> {
13    bytes: &'a [u8],
14    prefix_bits: u8,
15    previous_high_bits: u8,
16    huffman: bool,
17    length: QpackInteger<'a>,
18    encoded_payload: &'a [u8],
19}
20
21impl<'a> QpackStringLiteral<'a> {
22    /// Parses one N-bit-prefixed QPACK string literal and excludes following bytes.
23    pub fn parse(bytes: &'a [u8], prefix_bits: u8) -> Result<Self, QpackStringLiteralParseError> {
24        let (integer_bits, huffman_mask) = string_bits(prefix_bits)?;
25        let length = QpackInteger::parse(bytes, integer_bits)
26            .map_err(QpackStringLiteralParseError::Length)?;
27        let payload_len = usize::try_from(length.value()).map_err(|_| {
28            QpackStringLiteralParseError::LengthTooLarge {
29                value: length.value(),
30            }
31        })?;
32        let end = length.as_bytes().len().checked_add(payload_len).ok_or(
33            QpackStringLiteralParseError::LengthTooLarge {
34                value: length.value(),
35            },
36        )?;
37        if bytes.len() < end {
38            return Err(QpackStringLiteralParseError::Incomplete {
39                required: end,
40                available: bytes.len(),
41            });
42        }
43        let first = bytes[0];
44        Ok(Self {
45            bytes: &bytes[..end],
46            prefix_bits,
47            previous_high_bits: first & !controlled_mask(prefix_bits),
48            huffman: first & huffman_mask != 0,
49            length,
50            encoded_payload: &bytes[length.as_bytes().len()..end],
51        })
52    }
53
54    /// Returns the total RFC 9204 string prefix width N, including H.
55    pub const fn prefix_bits(self) -> u8 {
56        self.prefix_bits
57    }
58
59    /// Returns the previous representation's high bits, excluding the H flag.
60    pub const fn previous_high_bits(self) -> u8 {
61        self.previous_high_bits
62    }
63
64    /// Returns whether the opaque payload is RFC 7541 Huffman-coded.
65    pub const fn is_huffman(self) -> bool {
66        self.huffman
67    }
68
69    /// Returns the exact parsed length integer, including any legal noncanonical encoding.
70    pub const fn length(self) -> QpackInteger<'a> {
71        self.length
72    }
73
74    /// Returns the opaque encoded payload; this does not Huffman-decode it.
75    pub const fn encoded_payload(self) -> &'a [u8] {
76        self.encoded_payload
77    }
78
79    /// Returns the complete exact string-literal bytes.
80    pub const fn as_bytes(self) -> &'a [u8] {
81        self.bytes
82    }
83}
84
85/// Failure to parse a QPACK N-bit-prefixed string literal.
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub enum QpackStringLiteralParseError {
88    /// N must be in the inclusive range 2 through 8.
89    InvalidPrefixBits {
90        /// Supplied prefix width.
91        prefix_bits: u8,
92    },
93    /// The embedded N-1-bit length integer is invalid.
94    Length(#[doc = "The nested length-integer failure."] QpackIntegerParseError),
95    /// The payload length cannot be represented as an index on this target.
96    LengthTooLarge {
97        /// Parsed payload length.
98        value: u64,
99    },
100    /// The payload ends after the available input.
101    Incomplete {
102        /// Bytes required for the complete literal.
103        required: usize,
104        /// Bytes available.
105        available: usize,
106    },
107}
108
109impl fmt::Display for QpackStringLiteralParseError {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        match self {
112            Self::InvalidPrefixBits { prefix_bits } => {
113                write!(
114                    f,
115                    "QPACK string prefix width must be 2 through 8, got {prefix_bits}"
116                )
117            }
118            Self::Length(error) => write!(f, "QPACK string length is invalid: {error}"),
119            Self::LengthTooLarge { value } => {
120                write!(f, "QPACK string length cannot be indexed: {value}")
121            }
122            Self::Incomplete {
123                required,
124                available,
125            } => write!(
126                f,
127                "QPACK string input is incomplete: need {required} bytes, have {available}"
128            ),
129        }
130    }
131}
132
133/// Builds a canonical QPACK N-bit-prefixed string literal in caller-owned storage.
134pub struct QpackStringLiteralBuilder<'a, 'payload> {
135    destination: &'a mut [u8],
136    prefix_bits: u8,
137    previous_high_bits: u8,
138    huffman: bool,
139    encoded_payload: &'payload [u8],
140}
141
142impl<'a, 'payload> QpackStringLiteralBuilder<'a, 'payload> {
143    /// Creates a builder around opaque, already-encoded payload bytes.
144    pub fn new(
145        destination: &'a mut [u8],
146        prefix_bits: u8,
147        previous_high_bits: u8,
148        huffman: bool,
149        encoded_payload: &'payload [u8],
150    ) -> Self {
151        Self {
152            destination,
153            prefix_bits,
154            previous_high_bits,
155            huffman,
156            encoded_payload,
157        }
158    }
159
160    /// Validates all inputs and capacity before atomically writing a canonical literal.
161    pub fn build(self) -> Result<QpackStringLiteral<'a>, QpackStringLiteralBuildError> {
162        let plan = string_plan(
163            self.prefix_bits,
164            self.previous_high_bits,
165            self.huffman,
166            self.encoded_payload,
167        )?;
168        if self.destination.len() < plan.required {
169            return Err(QpackStringLiteralBuildError::BufferTooShort {
170                required: plan.required,
171                available: self.destination.len(),
172            });
173        }
174        write_string_prevalidated(&mut self.destination[..plan.required], plan);
175        Ok(string_from_prevalidated(
176            &self.destination[..plan.required],
177            plan,
178        ))
179    }
180}
181
182/// A fully prevalidated canonical string-literal write plan.
183#[derive(Clone, Copy)]
184pub(super) struct QpackStringLiteralPlan<'a> {
185    prefix_bits: u8,
186    previous_high_bits: u8,
187    huffman: bool,
188    encoded_payload: &'a [u8],
189    integer_bits: u8,
190    high_bits: u8,
191    payload_len: u64,
192    length_len: usize,
193    required: usize,
194}
195
196impl QpackStringLiteralPlan<'_> {
197    /// Returns the fully prevalidated literal length.
198    pub(super) const fn required(self) -> usize {
199        self.required
200    }
201}
202
203/// Preflights one canonical string literal without mutating destination storage.
204pub(super) fn string_plan<'a>(
205    prefix_bits: u8,
206    previous_high_bits: u8,
207    huffman: bool,
208    encoded_payload: &'a [u8],
209) -> Result<QpackStringLiteralPlan<'a>, QpackStringLiteralBuildError> {
210    let (integer_bits, huffman_mask) = string_bits(prefix_bits)
211        .map_err(|_| QpackStringLiteralBuildError::InvalidPrefixBits { prefix_bits })?;
212    if previous_high_bits & controlled_mask(prefix_bits) != 0 {
213        return Err(QpackStringLiteralBuildError::HighBitsOverlap {
214            high_bits: previous_high_bits,
215            prefix_bits,
216        });
217    }
218    let payload_len = u64::try_from(encoded_payload.len()).map_err(|_| {
219        QpackStringLiteralBuildError::PayloadLengthTooLarge {
220            length: encoded_payload.len(),
221        }
222    })?;
223    if payload_len > QPACK_INTEGER_MAX {
224        return Err(QpackStringLiteralBuildError::Length(
225            QpackIntegerBuildError::ValueTooLarge { value: payload_len },
226        ));
227    }
228    let length_len = canonical_encoded_len(payload_len, integer_bits);
229    let required = length_len
230        .checked_add(encoded_payload.len())
231        .ok_or(QpackStringLiteralBuildError::RequiredLengthOverflow)?;
232    let high_bits = previous_high_bits | if huffman { huffman_mask } else { 0 };
233    Ok(QpackStringLiteralPlan {
234        prefix_bits,
235        previous_high_bits,
236        huffman,
237        encoded_payload,
238        integer_bits,
239        high_bits,
240        payload_len,
241        length_len,
242        required,
243    })
244}
245
246/// Writes a fully prevalidated string literal.
247pub(super) fn write_string_prevalidated(destination: &mut [u8], plan: QpackStringLiteralPlan<'_>) {
248    let (length_destination, payload_destination) = destination.split_at_mut(plan.length_len);
249    write_canonical_prevalidated(
250        length_destination,
251        plan.integer_bits,
252        plan.high_bits,
253        plan.payload_len,
254    );
255    payload_destination.copy_from_slice(plan.encoded_payload);
256}
257
258/// Creates an exact string-literal view after a prevalidated write.
259pub(super) fn string_from_prevalidated<'a>(
260    bytes: &'a [u8],
261    plan: QpackStringLiteralPlan<'_>,
262) -> QpackStringLiteral<'a> {
263    let length = integer_from_prevalidated(
264        &bytes[..plan.length_len],
265        plan.integer_bits,
266        plan.high_bits,
267        plan.payload_len,
268    );
269    QpackStringLiteral {
270        bytes,
271        prefix_bits: plan.prefix_bits,
272        previous_high_bits: plan.previous_high_bits,
273        huffman: plan.huffman,
274        length,
275        encoded_payload: &bytes[plan.length_len..],
276    }
277}
278
279/// Failure to build a QPACK N-bit-prefixed string literal.
280#[derive(Clone, Copy, Debug, Eq, PartialEq)]
281pub enum QpackStringLiteralBuildError {
282    /// N must be in the inclusive range 2 through 8.
283    InvalidPrefixBits {
284        /// Supplied prefix width.
285        prefix_bits: u8,
286    },
287    /// Previous-representation bits overlap H or the length prefix.
288    HighBitsOverlap {
289        /// Supplied high bits.
290        high_bits: u8,
291        /// Total string prefix width.
292        prefix_bits: u8,
293    },
294    /// The payload length cannot be represented as a QPACK integer.
295    PayloadLengthTooLarge {
296        /// Supplied payload length.
297        length: usize,
298    },
299    /// Computing the total output length overflowed `usize`.
300    RequiredLengthOverflow,
301    /// The caller destination cannot contain the complete literal.
302    BufferTooShort {
303        /// Bytes required.
304        required: usize,
305        /// Bytes available.
306        available: usize,
307    },
308    /// The embedded canonical length builder failed before output mutation.
309    Length(#[doc = "The nested length-builder failure."] QpackIntegerBuildError),
310}
311
312impl fmt::Display for QpackStringLiteralBuildError {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        match self {
315            Self::InvalidPrefixBits { prefix_bits } => {
316                write!(
317                    f,
318                    "QPACK string prefix width must be 2 through 8, got {prefix_bits}"
319                )
320            }
321            Self::HighBitsOverlap {
322                high_bits,
323                prefix_bits,
324            } => write!(
325                f,
326                "QPACK string high bits {high_bits:#04x} overlap the {prefix_bits}-bit string prefix"
327            ),
328            Self::PayloadLengthTooLarge { length } => {
329                write!(
330                    f,
331                    "QPACK string payload length cannot be represented: {length}"
332                )
333            }
334            Self::RequiredLengthOverflow => {
335                f.write_str("QPACK string required length overflows usize")
336            }
337            Self::BufferTooShort {
338                required,
339                available,
340            } => write!(
341                f,
342                "QPACK string buffer is too short: need {required} bytes, have {available}"
343            ),
344            Self::Length(error) => write!(f, "QPACK string length cannot be built: {error}"),
345        }
346    }
347}
348
349fn string_bits(prefix_bits: u8) -> Result<(u8, u8), QpackStringLiteralParseError> {
350    match prefix_bits {
351        2..=8 => Ok((prefix_bits - 1, 1u8 << (prefix_bits - 1))),
352        _ => Err(QpackStringLiteralParseError::InvalidPrefixBits { prefix_bits }),
353    }
354}
355
356const fn controlled_mask(prefix_bits: u8) -> u8 {
357    match prefix_bits {
358        2..=7 => (1u8 << prefix_bits) - 1,
359        8 => u8::MAX,
360        _ => 0,
361    }
362}