Skip to main content

net_wire/qpack/
integer.rs

1//! RFC 9204 prefixed integer parsing and construction.
2
3use core::fmt;
4
5/// Greatest RFC 9204 prefixed integer this implementation accepts: the 62-bit ceiling.
6pub const QPACK_INTEGER_MAX: u64 = (1u64 << 62) - 1;
7
8/// A validated, exact borrowed QPACK prefixed integer representation.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct QpackInteger<'a> {
11    bytes: &'a [u8],
12    prefix_bits: u8,
13    value: u64,
14    high_bits: u8,
15}
16
17impl<'a> QpackInteger<'a> {
18    /// Parses one RFC 9204 section 4.1.1 integer and excludes following bytes.
19    pub fn parse(bytes: &'a [u8], prefix_bits: u8) -> Result<Self, QpackIntegerParseError> {
20        let prefix_mask = prefix_mask(prefix_bits)?;
21        let first = *bytes.first().ok_or(QpackIntegerParseError::Incomplete {
22            required: 1,
23            available: 0,
24        })?;
25        let prefix_value = first & prefix_mask;
26        let high_bits = first & !prefix_mask;
27        if prefix_value < prefix_mask {
28            return Ok(Self {
29                bytes: &bytes[..1],
30                prefix_bits,
31                value: u64::from(prefix_value),
32                high_bits,
33            });
34        }
35
36        let mut value = u64::from(prefix_mask);
37        let mut shift = 0u32;
38        let mut index = 1usize;
39        loop {
40            let octet = *bytes.get(index).ok_or(QpackIntegerParseError::Incomplete {
41                required: index
42                    .checked_add(1)
43                    .ok_or(QpackIntegerParseError::Overflow)?,
44                available: bytes.len(),
45            })?;
46            if shift >= u64::BITS {
47                return Err(QpackIntegerParseError::Overflow);
48            }
49            let contribution = u64::from(octet & 0x7f)
50                .checked_mul(1u64 << shift)
51                .ok_or(QpackIntegerParseError::Overflow)?;
52            value = value
53                .checked_add(contribution)
54                .ok_or(QpackIntegerParseError::Overflow)?;
55            index = index
56                .checked_add(1)
57                .ok_or(QpackIntegerParseError::Overflow)?;
58            if octet & 0x80 == 0 {
59                if value > QPACK_INTEGER_MAX {
60                    return Err(QpackIntegerParseError::ValueTooLarge { value });
61                }
62                return Ok(Self {
63                    bytes: &bytes[..index],
64                    prefix_bits,
65                    value,
66                    high_bits,
67                });
68            }
69            shift = shift
70                .checked_add(7)
71                .ok_or(QpackIntegerParseError::Overflow)?;
72        }
73    }
74
75    /// Returns the decoded integer value.
76    pub const fn value(self) -> u64 {
77        self.value
78    }
79
80    /// Returns the exact encoded representation, excluding following bytes.
81    pub const fn as_bytes(self) -> &'a [u8] {
82        self.bytes
83    }
84
85    /// Returns the number of controlled low bits in the first octet.
86    pub const fn prefix_bits(self) -> u8 {
87        self.prefix_bits
88    }
89
90    /// Returns the preserved uncontrolled high bits of the first octet.
91    pub const fn high_bits(self) -> u8 {
92        self.high_bits
93    }
94}
95
96/// Failure to parse a QPACK prefixed integer.
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub enum QpackIntegerParseError {
99    /// The prefix width is outside the inclusive range 1 through 8.
100    InvalidPrefixBits {
101        /// Supplied prefix width.
102        prefix_bits: u8,
103    },
104    /// The input ended before the first octet or a terminating continuation octet.
105    Incomplete {
106        /// Required bytes to include the missing octet.
107        required: usize,
108        /// Available input bytes.
109        available: usize,
110    },
111    /// The encoded number cannot be represented as a `u64`.
112    Overflow,
113    /// The decoded number exceeds QPACK's 62-bit required decoding limit.
114    ValueTooLarge {
115        /// Decoded value above [`QPACK_INTEGER_MAX`].
116        value: u64,
117    },
118}
119
120impl fmt::Display for QpackIntegerParseError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::InvalidPrefixBits { prefix_bits } => write!(
124                f,
125                "QPACK integer prefix width must be 1 through 8, got {prefix_bits}"
126            ),
127            Self::Incomplete {
128                required,
129                available,
130            } => write!(
131                f,
132                "QPACK integer input is incomplete: need {required} bytes, have {available}"
133            ),
134            Self::Overflow => f.write_str("QPACK integer exceeds u64"),
135            Self::ValueTooLarge { value } => {
136                write!(f, "QPACK integer exceeds the 62-bit limit: {value}")
137            }
138        }
139    }
140}
141
142/// Builds a canonical RFC 9204 section 4.1.1 integer in caller-owned storage.
143pub struct QpackIntegerBuilder<'a> {
144    destination: &'a mut [u8],
145    prefix_bits: u8,
146    high_bits: u8,
147    value: u64,
148}
149
150impl<'a> QpackIntegerBuilder<'a> {
151    /// Creates a builder with the supplied prefix width, high bits, and value.
152    pub fn new(destination: &'a mut [u8], prefix_bits: u8, high_bits: u8, value: u64) -> Self {
153        Self {
154            destination,
155            prefix_bits,
156            high_bits,
157            value,
158        }
159    }
160
161    /// Validates all inputs and capacity before writing a canonical representation.
162    pub fn build(self) -> Result<QpackInteger<'a>, QpackIntegerBuildError> {
163        let prefix_mask = prefix_mask(self.prefix_bits).map_err(|_| {
164            QpackIntegerBuildError::InvalidPrefixBits {
165                prefix_bits: self.prefix_bits,
166            }
167        })?;
168        if self.high_bits & prefix_mask != 0 {
169            return Err(QpackIntegerBuildError::HighBitsOverlap {
170                high_bits: self.high_bits,
171                prefix_bits: self.prefix_bits,
172            });
173        }
174        if self.value > QPACK_INTEGER_MAX {
175            return Err(QpackIntegerBuildError::ValueTooLarge { value: self.value });
176        }
177        let required = encoded_len(self.value, prefix_mask);
178        if self.destination.len() < required {
179            return Err(QpackIntegerBuildError::BufferTooShort {
180                required,
181                available: self.destination.len(),
182            });
183        }
184        let bytes = &mut self.destination[..required];
185        write_canonical_prevalidated(bytes, self.prefix_bits, self.high_bits, self.value);
186        Ok(integer_from_prevalidated(
187            bytes,
188            self.prefix_bits,
189            self.high_bits,
190            self.value,
191        ))
192    }
193}
194
195/// Failure to build a QPACK prefixed integer.
196#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub enum QpackIntegerBuildError {
198    /// The prefix width is outside the inclusive range 1 through 8.
199    InvalidPrefixBits {
200        /// Supplied prefix width.
201        prefix_bits: u8,
202    },
203    /// Supplied high bits overlap the integer-controlled low prefix bits.
204    HighBitsOverlap {
205        /// Supplied high bits.
206        high_bits: u8,
207        /// Number of integer-controlled low bits.
208        prefix_bits: u8,
209    },
210    /// The value exceeds QPACK's 62-bit required decoding limit.
211    ValueTooLarge {
212        /// Supplied value above [`QPACK_INTEGER_MAX`].
213        value: u64,
214    },
215    /// The caller buffer cannot contain the complete integer representation.
216    BufferTooShort {
217        /// Required bytes.
218        required: usize,
219        /// Available bytes.
220        available: usize,
221    },
222}
223
224impl fmt::Display for QpackIntegerBuildError {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self {
227            Self::InvalidPrefixBits { prefix_bits } => write!(
228                f,
229                "QPACK integer prefix width must be 1 through 8, got {prefix_bits}"
230            ),
231            Self::HighBitsOverlap {
232                high_bits,
233                prefix_bits,
234            } => write!(
235                f,
236                "QPACK integer high bits {high_bits:#04x} overlap the {prefix_bits}-bit prefix"
237            ),
238            Self::ValueTooLarge { value } => {
239                write!(f, "QPACK integer exceeds the 62-bit limit: {value}")
240            }
241            Self::BufferTooShort {
242                required,
243                available,
244            } => write!(
245                f,
246                "QPACK integer buffer is too short: need {required} bytes, have {available}"
247            ),
248        }
249    }
250}
251
252/// Returns the canonical encoded length after validating the prefix width and value.
253pub(super) fn canonical_encoded_len(value: u64, prefix_bits: u8) -> usize {
254    let prefix_mask = match prefix_bits {
255        1..=7 => (1u8 << prefix_bits) - 1,
256        8 => u8::MAX,
257        _ => 0,
258    };
259    encoded_len(value, prefix_mask)
260}
261
262/// Writes a canonical integer after complete validation has established its exact size.
263///
264/// Callers must have validated the prefix width, high-bit separation, value ceiling,
265/// exact canonical length, and destination capacity before invoking this handoff.
266pub(super) fn write_canonical_prevalidated(
267    bytes: &mut [u8],
268    prefix_bits: u8,
269    high_bits: u8,
270    value: u64,
271) {
272    let prefix_mask = match prefix_bits {
273        1..=7 => (1u8 << prefix_bits) - 1,
274        8 => u8::MAX,
275        _ => 0,
276    };
277    if value < u64::from(prefix_mask) {
278        bytes[0] = high_bits | value as u8;
279        return;
280    }
281
282    bytes[0] = high_bits | prefix_mask;
283    let mut remaining = value - u64::from(prefix_mask);
284    let mut index = 1usize;
285    while remaining >= 0x80 {
286        bytes[index] = (remaining as u8 & 0x7f) | 0x80;
287        remaining >>= 7;
288        index += 1;
289    }
290    bytes[index] = remaining as u8;
291}
292
293/// Creates an integer view over a canonical representation written by the prevalidated handoff.
294pub(super) fn integer_from_prevalidated<'a>(
295    bytes: &'a [u8],
296    prefix_bits: u8,
297    high_bits: u8,
298    value: u64,
299) -> QpackInteger<'a> {
300    QpackInteger {
301        bytes,
302        prefix_bits,
303        value,
304        high_bits,
305    }
306}
307
308fn prefix_mask(prefix_bits: u8) -> Result<u8, QpackIntegerParseError> {
309    match prefix_bits {
310        1..=7 => Ok((1u8 << prefix_bits) - 1),
311        8 => Ok(u8::MAX),
312        _ => Err(QpackIntegerParseError::InvalidPrefixBits { prefix_bits }),
313    }
314}
315
316fn encoded_len(value: u64, prefix_mask: u8) -> usize {
317    if value < u64::from(prefix_mask) {
318        return 1;
319    }
320    let mut required = 2usize;
321    let mut remaining = value - u64::from(prefix_mask);
322    while remaining >= 0x80 {
323        required += 1;
324        remaining >>= 7;
325    }
326    required
327}