Skip to main content

net_wire/http2/hpack/
string.rs

1//! RFC 7541 string literal framing with opaque encoded payloads.
2
3use core::fmt;
4
5use super::integer::{
6    HpackInteger, HpackIntegerParseError, canonical_encoded_len, canonical_integer,
7    write_canonical_for_valid_prefix,
8};
9
10/// A validated, exact borrowed HPACK string literal representation.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct HpackStringLiteral<'a> {
13    bytes: &'a [u8],
14    length: HpackInteger<'a>,
15}
16
17impl<'a> HpackStringLiteral<'a> {
18    /// Parses one RFC 7541 section 5.2 string literal and excludes any following bytes.
19    pub fn parse(bytes: &'a [u8]) -> Result<Self, HpackStringLiteralParseError> {
20        let length =
21            HpackInteger::parse(bytes, 7).map_err(HpackStringLiteralParseError::InvalidLength)?;
22        let encoded_length = usize::try_from(length.value()).map_err(|_| {
23            HpackStringLiteralParseError::EncodedLengthNotRepresentable {
24                encoded_length: length.value(),
25            }
26        })?;
27        let length_length = length.as_bytes().len();
28        let total = length_length.checked_add(encoded_length).ok_or(
29            HpackStringLiteralParseError::TotalLengthOverflow {
30                length_length,
31                encoded_length,
32            },
33        )?;
34        if bytes.len() < total {
35            return Err(HpackStringLiteralParseError::IncompleteEncodedPayload {
36                required: encoded_length,
37                available: bytes.len() - length_length,
38            });
39        }
40
41        Ok(Self {
42            bytes: &bytes[..total],
43            length,
44        })
45    }
46
47    /// Returns the exact string literal representation, excluding any following bytes.
48    pub const fn as_bytes(self) -> &'a [u8] {
49        self.bytes
50    }
51
52    /// Reports whether `encoded_bytes` are RFC 7541 Huffman-coded.
53    pub const fn is_huffman(self) -> bool {
54        self.length.high_bits() != 0
55    }
56
57    /// Returns the opaque encoded payload without decoding Huffman data.
58    pub fn encoded_bytes(self) -> &'a [u8] {
59        &self.bytes[self.length.as_bytes().len()..]
60    }
61
62    /// Returns the exact raw-preserving HPACK length representation.
63    pub const fn length(self) -> HpackInteger<'a> {
64        self.length
65    }
66}
67
68/// Failure to parse an HPACK string literal.
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub enum HpackStringLiteralParseError {
71    /// The 7-bit HPACK length integer is malformed, incomplete, or overflows `u64`.
72    InvalidLength(HpackIntegerParseError),
73    /// The decoded length cannot be represented as `usize`.
74    EncodedLengthNotRepresentable {
75        /// Decoded encoded-payload length.
76        encoded_length: u64,
77    },
78    /// The length encoding and payload length cannot be added as `usize`.
79    TotalLengthOverflow {
80        /// Bytes used by the HPACK length integer.
81        length_length: usize,
82        /// Decoded encoded-payload length.
83        encoded_length: usize,
84    },
85    /// The input ended before the declared opaque encoded payload.
86    IncompleteEncodedPayload {
87        /// Declared encoded-payload length.
88        required: usize,
89        /// Encoded-payload bytes available after the length integer.
90        available: usize,
91    },
92}
93
94impl fmt::Display for HpackStringLiteralParseError {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self {
97            Self::InvalidLength(error) => write!(f, "invalid HPACK string length: {error}"),
98            Self::EncodedLengthNotRepresentable { encoded_length } => write!(
99                f,
100                "HPACK string encoded length {encoded_length} cannot be represented as usize"
101            ),
102            Self::TotalLengthOverflow {
103                length_length,
104                encoded_length,
105            } => write!(
106                f,
107                "HPACK string total length overflows usize: length uses {length_length} bytes and payload is {encoded_length} bytes"
108            ),
109            Self::IncompleteEncodedPayload {
110                required,
111                available,
112            } => write!(
113                f,
114                "HPACK string encoded payload is incomplete: need {required} bytes, have {available}"
115            ),
116        }
117    }
118}
119
120/// Builds a canonical HPACK string literal around already encoded payload bytes.
121pub struct HpackStringLiteralBuilder<'a, 'b> {
122    destination: &'a mut [u8],
123    huffman: bool,
124    encoded_bytes: &'b [u8],
125}
126
127impl<'a, 'b> HpackStringLiteralBuilder<'a, 'b> {
128    /// Creates a builder for opaque payload bytes already Huffman-encoded when `huffman` is true.
129    ///
130    /// This builder only frames the bytes; it does not perform Huffman encoding.
131    pub fn new_encoded(destination: &'a mut [u8], huffman: bool, encoded_bytes: &'b [u8]) -> Self {
132        Self {
133            destination,
134            huffman,
135            encoded_bytes,
136        }
137    }
138
139    /// Validates length and capacity before atomically writing the complete string literal.
140    pub fn build(self) -> Result<HpackStringLiteral<'a>, HpackStringLiteralBuildError> {
141        let encoded_length = u64::try_from(self.encoded_bytes.len()).map_err(|_| {
142            HpackStringLiteralBuildError::EncodedLengthNotRepresentable {
143                encoded_length: self.encoded_bytes.len(),
144            }
145        })?;
146        let length_length = canonical_encoded_len(encoded_length, 0x7f);
147        let total = length_length.checked_add(self.encoded_bytes.len()).ok_or(
148            HpackStringLiteralBuildError::TotalLengthOverflow {
149                length_length,
150                encoded_length: self.encoded_bytes.len(),
151            },
152        )?;
153        if self.destination.len() < total {
154            return Err(HpackStringLiteralBuildError::BufferTooShort {
155                required: total,
156                available: self.destination.len(),
157            });
158        }
159
160        let literal = &mut self.destination[..total];
161        let (length_destination, payload_destination) = literal.split_at_mut(length_length);
162        write_canonical_for_valid_prefix(
163            length_destination,
164            7,
165            0x7f,
166            if self.huffman { 0x80 } else { 0 },
167            encoded_length,
168        );
169        payload_destination.copy_from_slice(self.encoded_bytes);
170
171        let bytes = &*literal;
172        Ok(HpackStringLiteral {
173            bytes,
174            length: canonical_integer(
175                &bytes[..length_length],
176                7,
177                if self.huffman { 0x80 } else { 0 },
178                encoded_length,
179            ),
180        })
181    }
182}
183
184/// Failure to build an HPACK string literal.
185#[derive(Clone, Copy, Debug, Eq, PartialEq)]
186pub enum HpackStringLiteralBuildError {
187    /// The payload length cannot be represented as `u64`.
188    EncodedLengthNotRepresentable {
189        /// Opaque encoded-payload length.
190        encoded_length: usize,
191    },
192    /// The length encoding and payload length cannot be added as `usize`.
193    TotalLengthOverflow {
194        /// Bytes required by the HPACK length integer.
195        length_length: usize,
196        /// Opaque encoded-payload length.
197        encoded_length: usize,
198    },
199    /// The caller buffer cannot contain the complete string literal.
200    BufferTooShort {
201        /// Required bytes.
202        required: usize,
203        /// Available bytes.
204        available: usize,
205    },
206}
207
208impl fmt::Display for HpackStringLiteralBuildError {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            Self::EncodedLengthNotRepresentable { encoded_length } => write!(
212                f,
213                "HPACK string encoded length {encoded_length} cannot be represented as u64"
214            ),
215            Self::TotalLengthOverflow {
216                length_length,
217                encoded_length,
218            } => write!(
219                f,
220                "HPACK string total length overflows usize: length uses {length_length} bytes and payload is {encoded_length} bytes"
221            ),
222            Self::BufferTooShort {
223                required,
224                available,
225            } => write!(
226                f,
227                "HPACK string buffer is too short: need {required} bytes, have {available}"
228            ),
229        }
230    }
231}