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 provided string would be larger after huffman encoding.
52    InflatedHuffmanEncoding,
53
54    /// The QPACK header block's huffman encoding is invalid.
55    InvalidHuffmanEncoding,
56
57    /// The QPACK static table index provided doesn't exist.
58    InvalidStaticTableIndex,
59
60    /// The decoded QPACK header name or value is not valid.
61    InvalidHeaderValue,
62
63    /// The decoded header list exceeded the size limit.
64    HeaderListTooLarge,
65}
66
67impl std::fmt::Display for Error {
68    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
69        write!(f, "{self:?}")
70    }
71}
72
73impl std::error::Error for Error {
74    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
75        None
76    }
77}
78
79impl From<octets::BufferTooShortError> for Error {
80    fn from(_err: octets::BufferTooShortError) -> Self {
81        Error::BufferTooShort
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use crate::*;
88
89    use super::*;
90
91    #[test]
92    fn encode_decode() {
93        let mut encoded = [0u8; 240];
94
95        let headers = vec![
96            h3::Header::new(b":path", b"/rsrc.php/v3/yn/r/rIPZ9Qkrdd9.png"),
97            h3::Header::new(b"accept-encoding", b"gzip, deflate, br"),
98            h3::Header::new(b"accept-language", b"en-US,en;q=0.9"),
99            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"),
100            h3::Header::new(b"accept", b"image/webp,image/apng,image/*,*/*;q=0.8"),
101            h3::Header::new(b"referer", b"https://static.xx.fbcdn.net/rsrc.php/v3/yT/l/0,cross/dzXGESIlGQQ.css"),
102            h3::Header::new(b":authority", b"static.xx.fbcdn.net"),
103            h3::Header::new(b":scheme", b"https"),
104            h3::Header::new(b":method", b"GET"),
105        ];
106
107        let mut enc = Encoder::new();
108        assert_eq!(enc.encode(&headers, &mut encoded), Ok(240));
109
110        let mut dec = Decoder::new();
111        assert_eq!(dec.decode(&encoded, u64::MAX), Ok(headers));
112    }
113
114    #[test]
115    fn lower_case() {
116        let mut encoded = [0u8; 35];
117
118        let headers_expected = vec![
119            h3::Header::new(b":status", b"200"),
120            h3::Header::new(b":path", b"/HeLlO"),
121            h3::Header::new(b"woot", b"woot"),
122            h3::Header::new(b"hello", b"WorlD"),
123            h3::Header::new(b"foo", b"BaR"),
124        ];
125
126        // Header.
127        let headers_in = vec![
128            h3::Header::new(b":StAtUs", b"200"),
129            h3::Header::new(b":PaTh", b"/HeLlO"),
130            h3::Header::new(b"WooT", b"woot"),
131            h3::Header::new(b"hello", b"WorlD"),
132            h3::Header::new(b"fOo", b"BaR"),
133        ];
134
135        let mut enc = Encoder::new();
136        assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35));
137
138        let mut dec = Decoder::new();
139        let headers_out = dec.decode(&encoded, u64::MAX).unwrap();
140
141        assert_eq!(headers_expected, headers_out);
142
143        // HeaderRef.
144        let headers_in = vec![
145            h3::HeaderRef::new(b":StAtUs", b"200"),
146            h3::HeaderRef::new(b":PaTh", b"/HeLlO"),
147            h3::HeaderRef::new(b"WooT", b"woot"),
148            h3::HeaderRef::new(b"hello", b"WorlD"),
149            h3::HeaderRef::new(b"fOo", b"BaR"),
150        ];
151
152        let mut enc = Encoder::new();
153        assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35));
154
155        let mut dec = Decoder::new();
156        let headers_out = dec.decode(&encoded, u64::MAX).unwrap();
157
158        assert_eq!(headers_expected, headers_out);
159    }
160
161    #[test]
162    fn lower_ascii_range() {
163        let mut encoded = [0u8; 50];
164        let mut enc = Encoder::new();
165
166        // Indexed name with literal value
167        let headers1 = vec![h3::Header::new(b"location", b"															")];
168        assert_eq!(enc.encode(&headers1, &mut encoded), Ok(19));
169
170        // Literal name and value
171        let headers2 = vec![h3::Header::new(b"a", b"")];
172        assert_eq!(enc.encode(&headers2, &mut encoded), Ok(20));
173
174        let headers3 = vec![h3::Header::new(b"															", b"hello")];
175        assert_eq!(enc.encode(&headers3, &mut encoded), Ok(24));
176    }
177
178    #[test]
179    fn extended_ascii_range() {
180        let mut encoded = [0u8; 50];
181        let mut enc = Encoder::new();
182
183        let name = b"location";
184        let value = "£££££££££££££££";
185
186        // Indexed name with literal value
187        let headers1 = vec![h3::Header::new(name, value.as_bytes())];
188        assert_eq!(enc.encode(&headers1, &mut encoded), Ok(34));
189
190        // Literal name and value
191        let value = "ððððððððððððððð";
192        let headers2 = vec![h3::Header::new(b"a", value.as_bytes())];
193        assert_eq!(enc.encode(&headers2, &mut encoded), Ok(35));
194
195        let headers3 = vec![h3::Header::new(value.as_bytes(), b"hello")];
196        assert_eq!(enc.encode(&headers3, &mut encoded), Ok(39));
197    }
198}
199
200pub use decoder::Decoder;
201pub use encoder::Encoder;
202
203mod decoder;
204mod encoder;
205mod huffman;
206mod static_table;