Skip to main content

specter/transport/h2/hpack_impl/
decoder.rs

1//! HPACK decoder (RFC 7541).
2
3use super::dynamic_table::DynamicTable;
4use super::error::HpackError;
5use super::huffman::huffman_decode;
6use super::integer::decode_integer;
7use super::static_table::get_static_entry;
8
9const STATIC_TABLE_SIZE: usize = 61;
10
11/// HPACK decoder.
12pub struct Decoder {
13    dynamic_table: DynamicTable,
14}
15
16impl Decoder {
17    /// Create a new decoder.
18    pub fn new() -> Self {
19        Self {
20            dynamic_table: DynamicTable::new(4096),
21        }
22    }
23
24    /// Set the maximum dynamic table size.
25    pub fn set_max_table_size(&mut self, size: usize) {
26        self.dynamic_table.set_max_size(size);
27    }
28
29    /// Decode a header block using a callback.
30    ///
31    /// The callback is invoked for each decoded header field with (name, value).
32    pub fn decode_with_cb<F>(&mut self, data: &[u8], mut cb: F) -> Result<(), HpackError>
33    where
34        F: FnMut(&[u8], &[u8]),
35    {
36        let mut pos = 0;
37
38        while pos < data.len() {
39            let byte = data[pos];
40
41            // Check for dynamic table size update (RFC 7541 Section 6.3)
42            // Prefix: 001xxxxx (3-bit pattern)
43            if (byte & 0xE0) == 0x20 {
44                let (size, consumed) = decode_integer(&data[pos..], 5, 0x1F)?;
45                self.dynamic_table.set_max_size(size);
46                pos += consumed + 1;
47                continue;
48            }
49
50            // Indexed header field representation (RFC 7541 Section 6.1)
51            // Prefix: 1xxxxxxx
52            if (byte & 0x80) != 0 {
53                let (index, consumed) = decode_integer(&data[pos..], 7, 0x7F)?;
54                pos += consumed + 1;
55
56                let (name, value) = self.get_entry(index)?;
57                cb(name, value);
58                continue;
59            }
60
61            // Literal header field with incremental indexing (RFC 7541 Section 6.2.1)
62            // Prefix: 01xxxxxx
63            if (byte & 0xC0) == 0x40 {
64                let (name_idx, consumed) = decode_integer(&data[pos..], 6, 0x3F)?;
65                pos += consumed + 1;
66
67                let name = if name_idx == 0 {
68                    // New name
69                    let (name_bytes, name_consumed) = self.decode_string_literal(&data[pos..])?;
70                    pos += name_consumed;
71                    name_bytes
72                } else {
73                    // Indexed name
74                    let (name_bytes, _) = self.get_entry(name_idx)?;
75                    name_bytes.to_vec()
76                };
77
78                let (value_bytes, value_consumed) = self.decode_string_literal(&data[pos..])?;
79                pos += value_consumed;
80
81                cb(&name, &value_bytes);
82
83                // Add to dynamic table
84                self.dynamic_table.add(name, value_bytes);
85                continue;
86            }
87
88            // Literal header field without indexing (RFC 7541 Section 6.2.2)
89            // Prefix: 0000xxxx
90            if (byte & 0xF0) == 0x00 {
91                let (name_idx, consumed) = decode_integer(&data[pos..], 4, 0x0F)?;
92                pos += consumed + 1;
93
94                let name = if name_idx == 0 {
95                    let (name_bytes, name_consumed) = self.decode_string_literal(&data[pos..])?;
96                    pos += name_consumed;
97                    name_bytes
98                } else {
99                    let (name_bytes, _) = self.get_entry(name_idx)?;
100                    name_bytes.to_vec()
101                };
102
103                let (value_bytes, value_consumed) = self.decode_string_literal(&data[pos..])?;
104                pos += value_consumed;
105
106                cb(&name, &value_bytes);
107                continue;
108            }
109
110            // Literal header field never indexed (RFC 7541 Section 6.2.3)
111            // Prefix: 0001xxxx
112            if (byte & 0xF0) == 0x10 {
113                let (name_idx, consumed) = decode_integer(&data[pos..], 4, 0x0F)?;
114                pos += consumed + 1;
115
116                let name = if name_idx == 0 {
117                    let (name_bytes, name_consumed) = self.decode_string_literal(&data[pos..])?;
118                    pos += name_consumed;
119                    name_bytes
120                } else {
121                    let (name_bytes, _) = self.get_entry(name_idx)?;
122                    name_bytes.to_vec()
123                };
124
125                let (value_bytes, value_consumed) = self.decode_string_literal(&data[pos..])?;
126                pos += value_consumed;
127
128                cb(&name, &value_bytes);
129                continue;
130            }
131
132            return Err(HpackError::Decode(format!(
133                "Invalid header field representation: 0x{:02x}",
134                byte
135            )));
136        }
137
138        Ok(())
139    }
140
141    /// Get an entry from either static or dynamic table by combined index.
142    fn get_entry(&self, index: usize) -> Result<(&[u8], &[u8]), HpackError> {
143        if index == 0 {
144            return Err(HpackError::InvalidIndex(0));
145        }
146
147        if index <= STATIC_TABLE_SIZE {
148            // Static table entry
149            get_static_entry(index).ok_or(HpackError::InvalidIndex(index))
150        } else {
151            // Dynamic table entry
152            let dynamic_idx = index - STATIC_TABLE_SIZE;
153            self.dynamic_table
154                .get(dynamic_idx)
155                .map(|e| (e.name(), e.value()))
156                .ok_or(HpackError::InvalidIndex(index))
157        }
158    }
159
160    /// Decode a string literal (RFC 7541 Section 5.2).
161    fn decode_string_literal(&self, data: &[u8]) -> Result<(Vec<u8>, usize), HpackError> {
162        if data.is_empty() {
163            return Err(HpackError::UnexpectedEof);
164        }
165
166        let h_flag = (data[0] & 0x80) != 0;
167        let (length, length_consumed) = decode_integer(data, 7, 0x7F)?;
168
169        let data_start = length_consumed + 1;
170        if data_start + length > data.len() {
171            return Err(HpackError::UnexpectedEof);
172        }
173
174        let string_data = &data[data_start..data_start + length];
175
176        let decoded = if h_flag {
177            // Huffman encoded
178            huffman_decode(string_data)?
179        } else {
180            // Literal
181            string_data.to_vec()
182        };
183
184        Ok((decoded, data_start + length))
185    }
186}
187
188impl Default for Decoder {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::transport::h2::hpack_impl::encoder::Encoder;
198
199    #[test]
200    fn test_decode_indexed_header() {
201        let mut decoder = Decoder::new();
202        // Encode index 2 (:method GET) = 0x82
203        let data = [0x82];
204        let mut headers = Vec::new();
205        decoder
206            .decode_with_cb(&data, |name, value| {
207                headers.push((name.to_vec(), value.to_vec()));
208            })
209            .unwrap();
210
211        assert_eq!(headers.len(), 1);
212        assert_eq!(headers[0].0, b":method");
213        assert_eq!(headers[0].1, b"GET");
214    }
215
216    #[test]
217    fn test_decode_literal() {
218        let mut encoder = Encoder::new();
219        let headers = [(b"custom-key".as_slice(), b"custom-value".as_slice())];
220        let encoded = encoder.encode(&headers);
221
222        let mut decoder = Decoder::new();
223        let mut headers = Vec::new();
224        decoder
225            .decode_with_cb(&encoded, |name, value| {
226                headers.push((name.to_vec(), value.to_vec()));
227            })
228            .unwrap();
229
230        assert_eq!(headers.len(), 1);
231        assert_eq!(headers[0].0, b"custom-key");
232        assert_eq!(headers[0].1, b"custom-value");
233    }
234
235    #[test]
236    fn test_round_trip() {
237        let headers = [
238            (b":method".as_slice(), b"GET".as_slice()),
239            (b":scheme".as_slice(), b"http".as_slice()),
240            (b":path".as_slice(), b"/".as_slice()),
241            (b":authority".as_slice(), b"www.example.com".as_slice()),
242        ];
243
244        let mut encoder = Encoder::new();
245        let encoded = encoder.encode(&headers);
246
247        let mut decoder = Decoder::new();
248        let mut decoded = Vec::new();
249        decoder
250            .decode_with_cb(&encoded, |name, value| {
251                decoded.push((name.to_vec(), value.to_vec()));
252            })
253            .unwrap();
254
255        assert_eq!(decoded.len(), 4);
256        assert_eq!(decoded[0].0, b":method");
257        assert_eq!(decoded[0].1, b"GET");
258    }
259}