Skip to main content

quiche/h3/qpack/
mod.rs

1// Copyright (C) 2019, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! HTTP/3 header compression (QPACK).
28
29pub use encoder::encode_int;
30pub use encoder::encode_str;
31
32pub const INDEXED: u8 = 0b1000_0000;
33pub const INDEXED_WITH_POST_BASE: u8 = 0b0001_0000;
34pub const LITERAL: u8 = 0b0010_0000;
35pub const LITERAL_WITH_NAME_REF: u8 = 0b0100_0000;
36
37/// A specialized [`Result`] type for quiche QPACK operations.
38///
39/// This type is used throughout quiche's QPACK public API for any operation
40/// that can produce an error.
41///
42/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
43pub type Result<T> = std::result::Result<T, Error>;
44
45/// A QPACK error.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum Error {
48    /// The provided buffer is too short.
49    BufferTooShort,
50
51    /// The QPACK header block's huffman encoding is invalid.
52    InvalidHuffmanEncoding,
53
54    /// The QPACK static table index provided doesn't exist.
55    InvalidStaticTableIndex,
56
57    /// The decoded QPACK header name or value is not valid.
58    InvalidHeaderValue,
59
60    /// The decoded header list exceeded the size limit.
61    HeaderListTooLarge,
62}
63
64impl std::fmt::Display for Error {
65    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66        write!(f, "{self:?}")
67    }
68}
69
70impl std::error::Error for Error {
71    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
72        None
73    }
74}
75
76impl From<octets::BufferTooShortError> for Error {
77    fn from(_err: octets::BufferTooShortError) -> Self {
78        Error::BufferTooShort
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use crate::h3::qpack::Error::HeaderListTooLarge;
85    use crate::*;
86
87    use super::*;
88
89    #[test]
90    fn encode_decode() {
91        let mut encoded = [0u8; 240];
92
93        let headers = vec![
94            h3::Header::new(b":path", b"/rsrc.php/v3/yn/r/rIPZ9Qkrdd9.png"),
95            h3::Header::new(b"accept-encoding", b"gzip, deflate, br"),
96            h3::Header::new(b"accept-language", b"en-US,en;q=0.9"),
97            h3::Header::new(b"user-agent", b"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.70 Safari/537.36"),
98            h3::Header::new(b"accept", b"image/webp,image/apng,image/*,*/*;q=0.8"),
99            h3::Header::new(b"referer", b"https://static.xx.fbcdn.net/rsrc.php/v3/yT/l/0,cross/dzXGESIlGQQ.css"),
100            h3::Header::new(b":authority", b"static.xx.fbcdn.net"),
101            h3::Header::new(b":scheme", b"https"),
102            h3::Header::new(b":method", b"GET"),
103        ];
104
105        let mut enc = Encoder::new();
106        assert_eq!(enc.encode(&headers, &mut encoded), Ok(240));
107
108        let mut dec = Decoder::new();
109        assert_eq!(dec.decode(&encoded, u64::MAX), Ok(headers));
110    }
111
112    #[test]
113    fn encode_decode_small_max_field_section_size() {
114        let mut encoded = [0u8; 102];
115
116        const NUM_HDRS: usize = 10;
117
118        let headers = vec![h3::Header::new(b"hello", b"world"); NUM_HDRS];
119
120        // The size of a field list is calculated based on the uncompressed size
121        // of fields, including the length of the name and value in bytes plus
122        // an overhead of 32 bytes for each field. See
123        // https://datatracker.ietf.org/doc/html/rfc9114#section-4.2.2
124        let qpack_field_section_size =
125            (b"hello".len() + b"world".len() + 32) * NUM_HDRS;
126
127        let mut enc = Encoder::new();
128        assert_eq!(enc.encode(&headers, &mut encoded), Ok(102));
129
130        let mut dec = Decoder::new();
131
132        // Equal max_size param ok
133        assert_eq!(
134            dec.decode(&encoded, qpack_field_section_size as u64),
135            Ok(headers.clone())
136        );
137
138        // Oversized max_size param ok
139        assert_eq!(
140            dec.decode(&encoded, qpack_field_section_size as u64 + 1),
141            Ok(headers.clone())
142        );
143
144        // Smaller max_size param (forgetting 32 byte overhead) fails
145        let wrong_qpack_field_section_size =
146            (b"hello".len() + b"world".len()) * NUM_HDRS;
147        assert_eq!(
148            dec.decode(&encoded, wrong_qpack_field_section_size as u64),
149            Err(HeaderListTooLarge)
150        );
151    }
152
153    #[test]
154    fn lower_case() {
155        let mut encoded = [0u8; 35];
156
157        let headers_expected = vec![
158            h3::Header::new(b":status", b"200"),
159            h3::Header::new(b":path", b"/HeLlO"),
160            h3::Header::new(b"woot", b"woot"),
161            h3::Header::new(b"hello", b"WorlD"),
162            h3::Header::new(b"foo", b"BaR"),
163        ];
164
165        // Header.
166        let headers_in = vec![
167            h3::Header::new(b":StAtUs", b"200"),
168            h3::Header::new(b":PaTh", b"/HeLlO"),
169            h3::Header::new(b"WooT", b"woot"),
170            h3::Header::new(b"hello", b"WorlD"),
171            h3::Header::new(b"fOo", b"BaR"),
172        ];
173
174        let mut enc = Encoder::new();
175        assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35));
176
177        let mut dec = Decoder::new();
178        let headers_out = dec.decode(&encoded, u64::MAX).unwrap();
179
180        assert_eq!(headers_expected, headers_out);
181
182        // HeaderRef.
183        let headers_in = vec![
184            h3::HeaderRef::new(b":StAtUs", b"200"),
185            h3::HeaderRef::new(b":PaTh", b"/HeLlO"),
186            h3::HeaderRef::new(b"WooT", b"woot"),
187            h3::HeaderRef::new(b"hello", b"WorlD"),
188            h3::HeaderRef::new(b"fOo", b"BaR"),
189        ];
190
191        let mut enc = Encoder::new();
192        assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35));
193
194        let mut dec = Decoder::new();
195        let headers_out = dec.decode(&encoded, u64::MAX).unwrap();
196
197        assert_eq!(headers_expected, headers_out);
198    }
199
200    #[test]
201    fn lower_ascii_range() {
202        let mut encoded = [0u8; 50];
203        let mut enc = Encoder::new();
204
205        // Indexed name with literal value
206        let headers1 = vec![h3::Header::new(b"location", b"															")];
207        assert_eq!(enc.encode(&headers1, &mut encoded), Ok(19));
208
209        // Literal name and value
210        let headers2 = vec![h3::Header::new(b"a", b"")];
211        assert_eq!(enc.encode(&headers2, &mut encoded), Ok(20));
212
213        let headers3 = vec![h3::Header::new(b"															", b"hello")];
214        assert_eq!(enc.encode(&headers3, &mut encoded), Ok(24));
215    }
216
217    #[test]
218    fn extended_ascii_range() {
219        let mut encoded = [0u8; 50];
220        let mut enc = Encoder::new();
221
222        let name = b"location";
223        let value = "£££££££££££££££";
224
225        // Indexed name with literal value
226        let headers1 = vec![h3::Header::new(name, value.as_bytes())];
227        assert_eq!(enc.encode(&headers1, &mut encoded), Ok(34));
228
229        // Literal name and value
230        let value = "ððððððððððððððð";
231        let headers2 = vec![h3::Header::new(b"a", value.as_bytes())];
232        assert_eq!(enc.encode(&headers2, &mut encoded), Ok(35));
233
234        let headers3 = vec![h3::Header::new(value.as_bytes(), b"hello")];
235        assert_eq!(enc.encode(&headers3, &mut encoded), Ok(39));
236    }
237}
238
239pub use decoder::Decoder;
240pub use encoder::Encoder;
241
242mod decoder;
243mod encoder;
244mod static_table;