/src/h2/src/hpack/decoder.rs
Line | Count | Source |
1 | | use super::{header::BytesStr, huffman, Header}; |
2 | | use crate::frame; |
3 | | |
4 | | use bytes::{Buf, Bytes, BytesMut}; |
5 | | use http::header; |
6 | | use http::method::{self, Method}; |
7 | | use http::status::{self, StatusCode}; |
8 | | |
9 | | use std::cmp; |
10 | | use std::collections::VecDeque; |
11 | | use std::io::Cursor; |
12 | | use std::ops::ControlFlow; |
13 | | use std::str::Utf8Error; |
14 | | |
15 | | /// Decodes headers using HPACK |
16 | | #[derive(Debug)] |
17 | | pub struct Decoder { |
18 | | // Protocol indicated that the max table size will update |
19 | | max_size_update: Option<usize>, |
20 | | last_max_update: usize, |
21 | | table: Table, |
22 | | buffer: BytesMut, |
23 | | } |
24 | | |
25 | | /// Represents all errors that can be encountered while performing the decoding |
26 | | /// of an HPACK header set. |
27 | | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
28 | | pub enum DecoderError { |
29 | | InvalidRepresentation, |
30 | | InvalidIntegerPrefix, |
31 | | InvalidTableIndex, |
32 | | InvalidHuffmanCode, |
33 | | InvalidUtf8, |
34 | | InvalidStatusCode, |
35 | | InvalidPseudoheader, |
36 | | InvalidMaxDynamicSize, |
37 | | IntegerOverflow, |
38 | | NeedMore(NeedMore), |
39 | | } |
40 | | |
41 | | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
42 | | pub enum NeedMore { |
43 | | UnexpectedEndOfStream, |
44 | | IntegerUnderflow, |
45 | | StringUnderflow, |
46 | | } |
47 | | |
48 | | enum Representation { |
49 | | /// Indexed header field representation |
50 | | /// |
51 | | /// An indexed header field representation identifies an entry in either the |
52 | | /// static table or the dynamic table (see Section 2.3). |
53 | | /// |
54 | | /// # Header encoding |
55 | | /// |
56 | | /// ```text |
57 | | /// 0 1 2 3 4 5 6 7 |
58 | | /// +---+---+---+---+---+---+---+---+ |
59 | | /// | 1 | Index (7+) | |
60 | | /// +---+---------------------------+ |
61 | | /// ``` |
62 | | Indexed, |
63 | | |
64 | | /// Literal Header Field with Incremental Indexing |
65 | | /// |
66 | | /// A literal header field with incremental indexing representation results |
67 | | /// in appending a header field to the decoded header list and inserting it |
68 | | /// as a new entry into the dynamic table. |
69 | | /// |
70 | | /// # Header encoding |
71 | | /// |
72 | | /// ```text |
73 | | /// 0 1 2 3 4 5 6 7 |
74 | | /// +---+---+---+---+---+---+---+---+ |
75 | | /// | 0 | 1 | Index (6+) | |
76 | | /// +---+---+-----------------------+ |
77 | | /// | H | Value Length (7+) | |
78 | | /// +---+---------------------------+ |
79 | | /// | Value String (Length octets) | |
80 | | /// +-------------------------------+ |
81 | | /// ``` |
82 | | LiteralWithIndexing, |
83 | | |
84 | | /// Literal Header Field without Indexing |
85 | | /// |
86 | | /// A literal header field without indexing representation results in |
87 | | /// appending a header field to the decoded header list without altering the |
88 | | /// dynamic table. |
89 | | /// |
90 | | /// # Header encoding |
91 | | /// |
92 | | /// ```text |
93 | | /// 0 1 2 3 4 5 6 7 |
94 | | /// +---+---+---+---+---+---+---+---+ |
95 | | /// | 0 | 0 | 0 | 0 | Index (4+) | |
96 | | /// +---+---+-----------------------+ |
97 | | /// | H | Value Length (7+) | |
98 | | /// +---+---------------------------+ |
99 | | /// | Value String (Length octets) | |
100 | | /// +-------------------------------+ |
101 | | /// ``` |
102 | | LiteralWithoutIndexing, |
103 | | |
104 | | /// Literal Header Field Never Indexed |
105 | | /// |
106 | | /// A literal header field never-indexed representation results in appending |
107 | | /// a header field to the decoded header list without altering the dynamic |
108 | | /// table. Intermediaries MUST use the same representation for encoding this |
109 | | /// header field. |
110 | | /// |
111 | | /// ```text |
112 | | /// 0 1 2 3 4 5 6 7 |
113 | | /// +---+---+---+---+---+---+---+---+ |
114 | | /// | 0 | 0 | 0 | 1 | Index (4+) | |
115 | | /// +---+---+-----------------------+ |
116 | | /// | H | Value Length (7+) | |
117 | | /// +---+---------------------------+ |
118 | | /// | Value String (Length octets) | |
119 | | /// +-------------------------------+ |
120 | | /// ``` |
121 | | LiteralNeverIndexed, |
122 | | |
123 | | /// Dynamic Table Size Update |
124 | | /// |
125 | | /// A dynamic table size update signals a change to the size of the dynamic |
126 | | /// table. |
127 | | /// |
128 | | /// # Header encoding |
129 | | /// |
130 | | /// ```text |
131 | | /// 0 1 2 3 4 5 6 7 |
132 | | /// +---+---+---+---+---+---+---+---+ |
133 | | /// | 0 | 0 | 1 | Max size (5+) | |
134 | | /// +---+---------------------------+ |
135 | | /// ``` |
136 | | SizeUpdate, |
137 | | } |
138 | | |
139 | | #[derive(Debug)] |
140 | | struct Table { |
141 | | entries: VecDeque<Header>, |
142 | | size: usize, |
143 | | max_size: usize, |
144 | | } |
145 | | |
146 | | struct StringMarker { |
147 | | offset: usize, |
148 | | len: usize, |
149 | | string: Option<Bytes>, |
150 | | } |
151 | | |
152 | | // ===== impl Decoder ===== |
153 | | |
154 | | impl Decoder { |
155 | | /// Creates a new `Decoder` with all settings set to default values. |
156 | 28.2k | pub fn new(size: usize) -> Decoder { |
157 | 28.2k | Decoder { |
158 | 28.2k | max_size_update: None, |
159 | 28.2k | last_max_update: size, |
160 | 28.2k | table: Table::new(size), |
161 | 28.2k | buffer: BytesMut::with_capacity(4096), |
162 | 28.2k | } |
163 | 28.2k | } |
164 | | |
165 | | /// Queues a potential size update |
166 | | #[allow(dead_code)] |
167 | 0 | pub fn queue_size_update(&mut self, size: usize) { |
168 | 0 | let size = match self.max_size_update { |
169 | 0 | Some(v) => cmp::max(v, size), |
170 | 0 | None => size, |
171 | | }; |
172 | | |
173 | 0 | self.max_size_update = Some(size); |
174 | 0 | } |
175 | | |
176 | | /// Decodes the headers found in the given buffer. |
177 | 69.7k | pub fn decode<F>( |
178 | 69.7k | &mut self, |
179 | 69.7k | src: &mut Cursor<&mut BytesMut>, |
180 | 69.7k | mut f: F, |
181 | 69.7k | ) -> Result<(), DecoderError> |
182 | 69.7k | where |
183 | 69.7k | F: FnMut(Header) -> ControlFlow<()>, |
184 | | { |
185 | | use self::Representation::*; |
186 | | |
187 | 69.7k | let mut can_resize = true; |
188 | | |
189 | 69.7k | if let Some(size) = self.max_size_update.take() { |
190 | 0 | self.last_max_update = size; |
191 | 69.7k | } |
192 | | |
193 | 69.7k | let span = tracing::trace_span!("hpack::decode"); |
194 | 69.7k | let _e = span.enter(); |
195 | | |
196 | 69.7k | tracing::trace!("decode"); |
197 | | |
198 | 13.4M | while let Some(ty) = peek_u8(src) { |
199 | | // At this point we are always at the beginning of the next block |
200 | | // within the HPACK data. The type of the block can always be |
201 | | // determined from the first byte. |
202 | 13.3M | match Representation::load(ty)? { |
203 | | Indexed => { |
204 | 10.5M | tracing::trace!(rem = src.remaining(), kind = %"Indexed"); |
205 | 10.5M | can_resize = false; |
206 | 10.5M | let entry = self.decode_indexed(src)?; |
207 | 10.5M | consume(src); |
208 | 10.5M | if f(entry).is_break() { |
209 | 0 | break; |
210 | 10.5M | } |
211 | | } |
212 | | LiteralWithIndexing => { |
213 | 1.18M | tracing::trace!(rem = src.remaining(), kind = %"LiteralWithIndexing"); |
214 | 1.18M | can_resize = false; |
215 | 1.18M | let entry = self.decode_literal(src, true)?; |
216 | | |
217 | | // Insert the header into the table |
218 | 1.17M | self.table.insert(entry.clone()); |
219 | 1.17M | consume(src); |
220 | | |
221 | 1.17M | if f(entry).is_break() { |
222 | 0 | break; |
223 | 1.17M | } |
224 | | } |
225 | | LiteralWithoutIndexing => { |
226 | 1.41M | tracing::trace!(rem = src.remaining(), kind = %"LiteralWithoutIndexing"); |
227 | 1.41M | can_resize = false; |
228 | 1.41M | let entry = self.decode_literal(src, false)?; |
229 | 1.40M | consume(src); |
230 | 1.40M | if f(entry).is_break() { |
231 | 0 | break; |
232 | 1.40M | } |
233 | | } |
234 | | LiteralNeverIndexed => { |
235 | 272k | tracing::trace!(rem = src.remaining(), kind = %"LiteralNeverIndexed"); |
236 | 272k | can_resize = false; |
237 | 272k | let entry = self.decode_literal(src, false)?; |
238 | 270k | consume(src); |
239 | | |
240 | | // TODO: Track that this should never be indexed |
241 | | |
242 | 270k | if f(entry).is_break() { |
243 | 0 | break; |
244 | 270k | } |
245 | | } |
246 | | SizeUpdate => { |
247 | 12.2k | tracing::trace!(rem = src.remaining(), kind = %"SizeUpdate"); |
248 | 12.2k | if !can_resize { |
249 | 503 | return Err(DecoderError::InvalidMaxDynamicSize); |
250 | 11.7k | } |
251 | | |
252 | | // Handle the dynamic table size update |
253 | 11.7k | self.process_size_update(src)?; |
254 | 10.7k | consume(src); |
255 | | } |
256 | | } |
257 | | } |
258 | | |
259 | 49.9k | Ok(()) |
260 | 69.7k | } <h2::hpack::decoder::Decoder>::decode::<<h2::frame::headers::HeaderBlock>::load::{closure#0}>Line | Count | Source | 177 | 55.2k | pub fn decode<F>( | 178 | 55.2k | &mut self, | 179 | 55.2k | src: &mut Cursor<&mut BytesMut>, | 180 | 55.2k | mut f: F, | 181 | 55.2k | ) -> Result<(), DecoderError> | 182 | 55.2k | where | 183 | 55.2k | F: FnMut(Header) -> ControlFlow<()>, | 184 | | { | 185 | | use self::Representation::*; | 186 | | | 187 | 55.2k | let mut can_resize = true; | 188 | | | 189 | 55.2k | if let Some(size) = self.max_size_update.take() { | 190 | 0 | self.last_max_update = size; | 191 | 55.2k | } | 192 | | | 193 | 55.2k | let span = tracing::trace_span!("hpack::decode"); | 194 | 55.2k | let _e = span.enter(); | 195 | | | 196 | 55.2k | tracing::trace!("decode"); | 197 | | | 198 | 859k | while let Some(ty) = peek_u8(src) { | 199 | | // At this point we are always at the beginning of the next block | 200 | | // within the HPACK data. The type of the block can always be | 201 | | // determined from the first byte. | 202 | 817k | match Representation::load(ty)? { | 203 | | Indexed => { | 204 | 669k | tracing::trace!(rem = src.remaining(), kind = %"Indexed"); | 205 | 669k | can_resize = false; | 206 | 669k | let entry = self.decode_indexed(src)?; | 207 | 668k | consume(src); | 208 | 668k | if f(entry).is_break() { | 209 | 0 | break; | 210 | 668k | } | 211 | | } | 212 | | LiteralWithIndexing => { | 213 | 106k | tracing::trace!(rem = src.remaining(), kind = %"LiteralWithIndexing"); | 214 | 106k | can_resize = false; | 215 | 106k | let entry = self.decode_literal(src, true)?; | 216 | | | 217 | | // Insert the header into the table | 218 | 101k | self.table.insert(entry.clone()); | 219 | 101k | consume(src); | 220 | | | 221 | 101k | if f(entry).is_break() { | 222 | 0 | break; | 223 | 101k | } | 224 | | } | 225 | | LiteralWithoutIndexing => { | 226 | 30.1k | tracing::trace!(rem = src.remaining(), kind = %"LiteralWithoutIndexing"); | 227 | 30.1k | can_resize = false; | 228 | 30.1k | let entry = self.decode_literal(src, false)?; | 229 | 24.5k | consume(src); | 230 | 24.5k | if f(entry).is_break() { | 231 | 0 | break; | 232 | 24.5k | } | 233 | | } | 234 | | LiteralNeverIndexed => { | 235 | 2.61k | tracing::trace!(rem = src.remaining(), kind = %"LiteralNeverIndexed"); | 236 | 2.61k | can_resize = false; | 237 | 2.61k | let entry = self.decode_literal(src, false)?; | 238 | 1.37k | consume(src); | 239 | | | 240 | | // TODO: Track that this should never be indexed | 241 | | | 242 | 1.37k | if f(entry).is_break() { | 243 | 0 | break; | 244 | 1.37k | } | 245 | | } | 246 | | SizeUpdate => { | 247 | 9.41k | tracing::trace!(rem = src.remaining(), kind = %"SizeUpdate"); | 248 | 9.41k | if !can_resize { | 249 | 340 | return Err(DecoderError::InvalidMaxDynamicSize); | 250 | 9.07k | } | 251 | | | 252 | | // Handle the dynamic table size update | 253 | 9.07k | self.process_size_update(src)?; | 254 | 8.43k | consume(src); | 255 | | } | 256 | | } | 257 | | } | 258 | | | 259 | 41.7k | Ok(()) | 260 | 55.2k | } |
<h2::hpack::decoder::Decoder>::decode::<h2::fuzz_bridge::fuzz_logic::fuzz_hpack::{closure#0}>Line | Count | Source | 177 | 14.5k | pub fn decode<F>( | 178 | 14.5k | &mut self, | 179 | 14.5k | src: &mut Cursor<&mut BytesMut>, | 180 | 14.5k | mut f: F, | 181 | 14.5k | ) -> Result<(), DecoderError> | 182 | 14.5k | where | 183 | 14.5k | F: FnMut(Header) -> ControlFlow<()>, | 184 | | { | 185 | | use self::Representation::*; | 186 | | | 187 | 14.5k | let mut can_resize = true; | 188 | | | 189 | 14.5k | if let Some(size) = self.max_size_update.take() { | 190 | 0 | self.last_max_update = size; | 191 | 14.5k | } | 192 | | | 193 | 14.5k | let span = tracing::trace_span!("hpack::decode"); | 194 | 14.5k | let _e = span.enter(); | 195 | | | 196 | 14.5k | tracing::trace!("decode"); | 197 | | | 198 | 12.5M | while let Some(ty) = peek_u8(src) { | 199 | | // At this point we are always at the beginning of the next block | 200 | | // within the HPACK data. The type of the block can always be | 201 | | // determined from the first byte. | 202 | 12.5M | match Representation::load(ty)? { | 203 | | Indexed => { | 204 | 9.84M | tracing::trace!(rem = src.remaining(), kind = %"Indexed"); | 205 | 9.84M | can_resize = false; | 206 | 9.84M | let entry = self.decode_indexed(src)?; | 207 | 9.84M | consume(src); | 208 | 9.84M | if f(entry).is_break() { | 209 | 0 | break; | 210 | 9.84M | } | 211 | | } | 212 | | LiteralWithIndexing => { | 213 | 1.07M | tracing::trace!(rem = src.remaining(), kind = %"LiteralWithIndexing"); | 214 | 1.07M | can_resize = false; | 215 | 1.07M | let entry = self.decode_literal(src, true)?; | 216 | | | 217 | | // Insert the header into the table | 218 | 1.07M | self.table.insert(entry.clone()); | 219 | 1.07M | consume(src); | 220 | | | 221 | 1.07M | if f(entry).is_break() { | 222 | 0 | break; | 223 | 1.07M | } | 224 | | } | 225 | | LiteralWithoutIndexing => { | 226 | 1.38M | tracing::trace!(rem = src.remaining(), kind = %"LiteralWithoutIndexing"); | 227 | 1.38M | can_resize = false; | 228 | 1.38M | let entry = self.decode_literal(src, false)?; | 229 | 1.37M | consume(src); | 230 | 1.37M | if f(entry).is_break() { | 231 | 0 | break; | 232 | 1.37M | } | 233 | | } | 234 | | LiteralNeverIndexed => { | 235 | 270k | tracing::trace!(rem = src.remaining(), kind = %"LiteralNeverIndexed"); | 236 | 270k | can_resize = false; | 237 | 270k | let entry = self.decode_literal(src, false)?; | 238 | 269k | consume(src); | 239 | | | 240 | | // TODO: Track that this should never be indexed | 241 | | | 242 | 269k | if f(entry).is_break() { | 243 | 0 | break; | 244 | 269k | } | 245 | | } | 246 | | SizeUpdate => { | 247 | 2.79k | tracing::trace!(rem = src.remaining(), kind = %"SizeUpdate"); | 248 | 2.79k | if !can_resize { | 249 | 163 | return Err(DecoderError::InvalidMaxDynamicSize); | 250 | 2.63k | } | 251 | | | 252 | | // Handle the dynamic table size update | 253 | 2.63k | self.process_size_update(src)?; | 254 | 2.35k | consume(src); | 255 | | } | 256 | | } | 257 | | } | 258 | | | 259 | 8.21k | Ok(()) | 260 | 14.5k | } |
|
261 | | |
262 | 11.7k | fn process_size_update(&mut self, buf: &mut Cursor<&mut BytesMut>) -> Result<(), DecoderError> { |
263 | 11.7k | let new_size = decode_int(buf, 5)?; |
264 | | |
265 | 11.0k | if new_size > self.last_max_update { |
266 | 236 | return Err(DecoderError::InvalidMaxDynamicSize); |
267 | 10.7k | } |
268 | | |
269 | 10.7k | tracing::debug!( |
270 | 0 | from = self.table.size(), |
271 | | to = new_size, |
272 | 0 | "Decoder changed max table size" |
273 | | ); |
274 | | |
275 | 10.7k | self.table.set_max_size(new_size); |
276 | | |
277 | 10.7k | Ok(()) |
278 | 11.7k | } |
279 | | |
280 | 10.5M | fn decode_indexed(&self, buf: &mut Cursor<&mut BytesMut>) -> Result<Header, DecoderError> { |
281 | 10.5M | let index = decode_int(buf, 7)?; |
282 | 10.5M | self.table.get(index) |
283 | 10.5M | } |
284 | | |
285 | 2.87M | fn decode_literal( |
286 | 2.87M | &mut self, |
287 | 2.87M | buf: &mut Cursor<&mut BytesMut>, |
288 | 2.87M | index: bool, |
289 | 2.87M | ) -> Result<Header, DecoderError> { |
290 | 2.87M | let prefix = if index { 6 } else { 4 }; |
291 | | |
292 | | // Extract the table index for the name, or 0 if not indexed |
293 | 2.87M | let table_idx = decode_int(buf, prefix)?; |
294 | | |
295 | | // First, read the header name |
296 | 2.86M | if table_idx == 0 { |
297 | 954k | let old_pos = buf.position(); |
298 | 954k | let name_marker = self.try_decode_string(buf)?; |
299 | 952k | let value_marker = self.try_decode_string(buf)?; |
300 | 950k | buf.set_position(old_pos); |
301 | | // Read the name as a literal |
302 | 950k | let name = name_marker.consume(buf); |
303 | 950k | let value = value_marker.consume(buf); |
304 | 950k | Header::new(name, value) |
305 | | } else { |
306 | 1.91M | let e = self.table.get(table_idx)?; |
307 | 1.91M | let value = self.decode_string(buf)?; |
308 | | |
309 | 1.91M | e.name().into_entry(value) |
310 | | } |
311 | 2.87M | } |
312 | | |
313 | 3.82M | fn try_decode_string( |
314 | 3.82M | &mut self, |
315 | 3.82M | buf: &mut Cursor<&mut BytesMut>, |
316 | 3.82M | ) -> Result<StringMarker, DecoderError> { |
317 | 3.82M | let old_pos = buf.position(); |
318 | | const HUFF_FLAG: u8 = 0b1000_0000; |
319 | | |
320 | | // The first bit in the first byte contains the huffman encoded flag. |
321 | 3.82M | let huff = match peek_u8(buf) { |
322 | 3.82M | Some(hdr) => (hdr & HUFF_FLAG) == HUFF_FLAG, |
323 | 1.51k | None => return Err(DecoderError::NeedMore(NeedMore::UnexpectedEndOfStream)), |
324 | | }; |
325 | | |
326 | | // Decode the string length using 7 bit prefix |
327 | 3.82M | let len = decode_int(buf, 7)?; |
328 | | |
329 | 3.81M | if len > buf.remaining() { |
330 | 5.45k | tracing::trace!(len, remaining = buf.remaining(), "decode_string underflow",); |
331 | 5.45k | return Err(DecoderError::NeedMore(NeedMore::StringUnderflow)); |
332 | 3.81M | } |
333 | | |
334 | 3.81M | let offset = (buf.position() - old_pos) as usize; |
335 | 3.81M | if huff { |
336 | 2.83M | let ret = { |
337 | 2.83M | let raw = &buf.chunk()[..len]; |
338 | 2.83M | huffman::decode(raw, &mut self.buffer).map(|buf| StringMarker { |
339 | 2.83M | offset, |
340 | 2.83M | len, |
341 | 2.83M | string: Some(BytesMut::freeze(buf)), |
342 | 2.83M | }) |
343 | | }; |
344 | | |
345 | 2.83M | buf.advance(len); |
346 | 2.83M | ret |
347 | | } else { |
348 | 981k | buf.advance(len); |
349 | 981k | Ok(StringMarker { |
350 | 981k | offset, |
351 | 981k | len, |
352 | 981k | string: None, |
353 | 981k | }) |
354 | | } |
355 | 3.82M | } |
356 | | |
357 | 1.91M | fn decode_string(&mut self, buf: &mut Cursor<&mut BytesMut>) -> Result<Bytes, DecoderError> { |
358 | 1.91M | let old_pos = buf.position(); |
359 | 1.91M | let marker = self.try_decode_string(buf)?; |
360 | 1.91M | buf.set_position(old_pos); |
361 | 1.91M | Ok(marker.consume(buf)) |
362 | 1.91M | } |
363 | | } |
364 | | |
365 | | impl Default for Decoder { |
366 | 0 | fn default() -> Decoder { |
367 | 0 | Decoder::new(4096) |
368 | 0 | } |
369 | | } |
370 | | |
371 | | // ===== impl Representation ===== |
372 | | |
373 | | impl Representation { |
374 | 13.3M | pub fn load(byte: u8) -> Result<Representation, DecoderError> { |
375 | | const INDEXED: u8 = 0b1000_0000; |
376 | | const LITERAL_WITH_INDEXING: u8 = 0b0100_0000; |
377 | | const LITERAL_WITHOUT_INDEXING: u8 = 0b1111_0000; |
378 | | const LITERAL_NEVER_INDEXED: u8 = 0b0001_0000; |
379 | | const SIZE_UPDATE_MASK: u8 = 0b1110_0000; |
380 | | const SIZE_UPDATE: u8 = 0b0010_0000; |
381 | | |
382 | | // TODO: What did I even write here? |
383 | | |
384 | 13.3M | if byte & INDEXED == INDEXED { |
385 | 10.5M | Ok(Representation::Indexed) |
386 | 2.88M | } else if byte & LITERAL_WITH_INDEXING == LITERAL_WITH_INDEXING { |
387 | 1.18M | Ok(Representation::LiteralWithIndexing) |
388 | 1.69M | } else if byte & LITERAL_WITHOUT_INDEXING == 0 { |
389 | 1.41M | Ok(Representation::LiteralWithoutIndexing) |
390 | 284k | } else if byte & LITERAL_WITHOUT_INDEXING == LITERAL_NEVER_INDEXED { |
391 | 272k | Ok(Representation::LiteralNeverIndexed) |
392 | 12.2k | } else if byte & SIZE_UPDATE_MASK == SIZE_UPDATE { |
393 | 12.2k | Ok(Representation::SizeUpdate) |
394 | | } else { |
395 | 0 | Err(DecoderError::InvalidRepresentation) |
396 | | } |
397 | 13.3M | } |
398 | | } |
399 | | |
400 | 17.2M | fn decode_int<B: Buf>(buf: &mut B, prefix_size: u8) -> Result<usize, DecoderError> { |
401 | | // The octet limit is chosen such that the maximum allowed *value* can |
402 | | // never overflow an unsigned 32-bit integer. The maximum value of any |
403 | | // integer that can be encoded with 5 octets is ~2^28 |
404 | | const MAX_BYTES: usize = 5; |
405 | | const VARINT_MASK: u8 = 0b0111_1111; |
406 | | const VARINT_FLAG: u8 = 0b1000_0000; |
407 | | |
408 | 17.2M | if prefix_size < 1 || prefix_size > 8 { |
409 | 0 | return Err(DecoderError::InvalidIntegerPrefix); |
410 | 17.2M | } |
411 | | |
412 | 17.2M | if !buf.has_remaining() { |
413 | 0 | return Err(DecoderError::NeedMore(NeedMore::IntegerUnderflow)); |
414 | 17.2M | } |
415 | | |
416 | 17.2M | let mask = if prefix_size == 8 { |
417 | 0 | 0xFF |
418 | | } else { |
419 | 17.2M | (1u8 << prefix_size).wrapping_sub(1) |
420 | | }; |
421 | | |
422 | 17.2M | let mut ret = (buf.get_u8() & mask) as usize; |
423 | | |
424 | 17.2M | if ret < mask as usize { |
425 | | // Value fits in the prefix bits |
426 | 17.1M | return Ok(ret); |
427 | 26.5k | } |
428 | | |
429 | | // The int did not fit in the prefix bits, so continue reading. |
430 | | // |
431 | | // The total number of bytes used to represent the int. The first byte was |
432 | | // the prefix, so start at 1. |
433 | 26.5k | let mut bytes = 1; |
434 | | |
435 | | // The rest of the int is stored as a varint -- 7 bits for the value and 1 |
436 | | // bit to indicate if it is the last byte. |
437 | 26.5k | let mut shift = 0; |
438 | | |
439 | 52.0k | while buf.has_remaining() { |
440 | 49.7k | let b = buf.get_u8(); |
441 | | |
442 | 49.7k | bytes += 1; |
443 | 49.7k | ret += ((b & VARINT_MASK) as usize) << shift; |
444 | 49.7k | shift += 7; |
445 | | |
446 | 49.7k | if b & VARINT_FLAG == 0 { |
447 | 23.7k | return Ok(ret); |
448 | 25.9k | } |
449 | | |
450 | 25.9k | if bytes == MAX_BYTES { |
451 | | // The spec requires that this situation is an error |
452 | 419 | return Err(DecoderError::IntegerOverflow); |
453 | 25.5k | } |
454 | | } |
455 | | |
456 | 2.32k | Err(DecoderError::NeedMore(NeedMore::IntegerUnderflow)) |
457 | 17.2M | } |
458 | | |
459 | 17.2M | fn peek_u8<B: Buf>(buf: &B) -> Option<u8> { |
460 | 17.2M | if buf.has_remaining() { |
461 | 17.2M | Some(buf.chunk()[0]) |
462 | | } else { |
463 | 51.4k | None |
464 | | } |
465 | 17.2M | } |
466 | | |
467 | 14.3M | fn take(buf: &mut Cursor<&mut BytesMut>, n: usize) -> Bytes { |
468 | 14.3M | let pos = buf.position() as usize; |
469 | 14.3M | let mut head = buf.get_mut().split_to(pos + n); |
470 | 14.3M | buf.set_position(0); |
471 | 14.3M | head.advance(pos); |
472 | 14.3M | head.freeze() |
473 | 14.3M | } |
474 | | |
475 | | impl StringMarker { |
476 | 3.81M | fn consume(self, buf: &mut Cursor<&mut BytesMut>) -> Bytes { |
477 | 3.81M | buf.advance(self.offset); |
478 | 3.81M | match self.string { |
479 | 2.83M | Some(string) => { |
480 | 2.83M | buf.advance(self.len); |
481 | 2.83M | string |
482 | | } |
483 | 980k | None => take(buf, self.len), |
484 | | } |
485 | 3.81M | } |
486 | | } |
487 | | |
488 | 13.3M | fn consume(buf: &mut Cursor<&mut BytesMut>) { |
489 | | // remove bytes from the internal BytesMut when they have been successfully |
490 | | // decoded. This is a more permanent cursor position, which will be |
491 | | // used to resume if decoding was only partial. |
492 | 13.3M | take(buf, 0); |
493 | 13.3M | } |
494 | | |
495 | | // ===== impl Table ===== |
496 | | |
497 | | impl Table { |
498 | 28.2k | fn new(max_size: usize) -> Table { |
499 | 28.2k | Table { |
500 | 28.2k | entries: VecDeque::new(), |
501 | 28.2k | size: 0, |
502 | 28.2k | max_size, |
503 | 28.2k | } |
504 | 28.2k | } |
505 | | |
506 | 0 | fn size(&self) -> usize { |
507 | 0 | self.size |
508 | 0 | } |
509 | | |
510 | | /// Returns the entry located at the given index. |
511 | | /// |
512 | | /// The table is 1-indexed and constructed in such a way that the first |
513 | | /// entries belong to the static table, followed by entries in the dynamic |
514 | | /// table. They are merged into a single index address space, though. |
515 | | /// |
516 | | /// This is according to the [HPACK spec, section 2.3.3.] |
517 | | /// (http://http2.github.io/http2-spec/compression.html#index.address.space) |
518 | 12.4M | pub fn get(&self, index: usize) -> Result<Header, DecoderError> { |
519 | 12.4M | if index == 0 { |
520 | 41 | return Err(DecoderError::InvalidTableIndex); |
521 | 12.4M | } |
522 | | |
523 | 12.4M | if index <= 61 { |
524 | 12.3M | return Ok(get_static(index)); |
525 | 102k | } |
526 | | |
527 | | // Convert the index for lookup in the entries structure. |
528 | 102k | match self.entries.get(index - 62) { |
529 | 101k | Some(e) => Ok(e.clone()), |
530 | 830 | None => Err(DecoderError::InvalidTableIndex), |
531 | | } |
532 | 12.4M | } |
533 | | |
534 | 1.17M | fn insert(&mut self, entry: Header) { |
535 | 1.17M | let len = entry.len(); |
536 | | |
537 | 1.17M | self.reserve(len); |
538 | | |
539 | 1.17M | if self.size + len <= self.max_size { |
540 | 94.6k | self.size += len; |
541 | 94.6k | |
542 | 94.6k | // Track the entry |
543 | 94.6k | self.entries.push_front(entry); |
544 | 1.08M | } |
545 | 1.17M | } |
546 | | |
547 | 10.7k | fn set_max_size(&mut self, size: usize) { |
548 | 10.7k | self.max_size = size; |
549 | | // Make the table size fit within the new constraints. |
550 | 10.7k | self.consolidate(); |
551 | 10.7k | } |
552 | | |
553 | 1.17M | fn reserve(&mut self, size: usize) { |
554 | 1.20M | while self.size + size > self.max_size { |
555 | 1.11M | match self.entries.pop_back() { |
556 | 30.1k | Some(last) => { |
557 | 30.1k | self.size -= last.len(); |
558 | 30.1k | } |
559 | 1.08M | None => return, |
560 | | } |
561 | | } |
562 | 1.17M | } |
563 | | |
564 | 10.7k | fn consolidate(&mut self) { |
565 | 16.2k | while self.size > self.max_size { |
566 | | { |
567 | 5.46k | let last = match self.entries.back() { |
568 | 5.46k | Some(x) => x, |
569 | | None => { |
570 | | // Can never happen as the size of the table must reach |
571 | | // 0 by the time we've exhausted all elements. |
572 | 0 | panic!("Size of table != 0, but no headers left!"); |
573 | | } |
574 | | }; |
575 | | |
576 | 5.46k | self.size -= last.len(); |
577 | | } |
578 | | |
579 | 5.46k | self.entries.pop_back(); |
580 | | } |
581 | 10.7k | } |
582 | | } |
583 | | |
584 | | // ===== impl DecoderError ===== |
585 | | |
586 | | impl From<Utf8Error> for DecoderError { |
587 | 43 | fn from(_: Utf8Error) -> DecoderError { |
588 | | // TODO: Better error? |
589 | 43 | DecoderError::InvalidUtf8 |
590 | 43 | } |
591 | | } |
592 | | |
593 | | impl From<header::InvalidHeaderValue> for DecoderError { |
594 | 375 | fn from(_: header::InvalidHeaderValue) -> DecoderError { |
595 | | // TODO: Better error? |
596 | 375 | DecoderError::InvalidUtf8 |
597 | 375 | } |
598 | | } |
599 | | |
600 | | impl From<header::InvalidHeaderName> for DecoderError { |
601 | 4.07k | fn from(_: header::InvalidHeaderName) -> DecoderError { |
602 | | // TODO: Better error |
603 | 4.07k | DecoderError::InvalidUtf8 |
604 | 4.07k | } |
605 | | } |
606 | | |
607 | | impl From<method::InvalidMethod> for DecoderError { |
608 | 391 | fn from(_: method::InvalidMethod) -> DecoderError { |
609 | | // TODO: Better error |
610 | 391 | DecoderError::InvalidUtf8 |
611 | 391 | } |
612 | | } |
613 | | |
614 | | impl From<status::InvalidStatusCode> for DecoderError { |
615 | 6 | fn from(_: status::InvalidStatusCode) -> DecoderError { |
616 | | // TODO: Better error |
617 | 6 | DecoderError::InvalidUtf8 |
618 | 6 | } |
619 | | } |
620 | | |
621 | | impl From<DecoderError> for frame::Error { |
622 | 13.5k | fn from(src: DecoderError) -> Self { |
623 | 13.5k | frame::Error::Hpack(src) |
624 | 13.5k | } |
625 | | } |
626 | | |
627 | | /// Get an entry from the static table |
628 | 12.3M | pub fn get_static(idx: usize) -> Header { |
629 | | use http::header::HeaderValue; |
630 | | |
631 | 12.3M | match idx { |
632 | 126k | 1 => Header::Authority(BytesStr::from_static("")), |
633 | 657k | 2 => Header::Method(Method::GET), |
634 | 1.11M | 3 => Header::Method(Method::POST), |
635 | 2.03M | 4 => Header::Path(BytesStr::from_static("/")), |
636 | 33.8k | 5 => Header::Path(BytesStr::from_static("/index.html")), |
637 | 72.5k | 6 => Header::Scheme(BytesStr::from_static("http")), |
638 | 115k | 7 => Header::Scheme(BytesStr::from_static("https")), |
639 | 1.17M | 8 => Header::Status(StatusCode::OK), |
640 | 143k | 9 => Header::Status(StatusCode::NO_CONTENT), |
641 | 37.0k | 10 => Header::Status(StatusCode::PARTIAL_CONTENT), |
642 | 47.8k | 11 => Header::Status(StatusCode::NOT_MODIFIED), |
643 | 85.5k | 12 => Header::Status(StatusCode::BAD_REQUEST), |
644 | 24.5k | 13 => Header::Status(StatusCode::NOT_FOUND), |
645 | 454k | 14 => Header::Status(StatusCode::INTERNAL_SERVER_ERROR), |
646 | 1.25M | 15 => Header::Field { |
647 | 1.25M | name: header::ACCEPT_CHARSET, |
648 | 1.25M | value: HeaderValue::from_static(""), |
649 | 1.25M | }, |
650 | 145k | 16 => Header::Field { |
651 | 145k | name: header::ACCEPT_ENCODING, |
652 | 145k | value: HeaderValue::from_static("gzip, deflate"), |
653 | 145k | }, |
654 | 138k | 17 => Header::Field { |
655 | 138k | name: header::ACCEPT_LANGUAGE, |
656 | 138k | value: HeaderValue::from_static(""), |
657 | 138k | }, |
658 | 152k | 18 => Header::Field { |
659 | 152k | name: header::ACCEPT_RANGES, |
660 | 152k | value: HeaderValue::from_static(""), |
661 | 152k | }, |
662 | 93.1k | 19 => Header::Field { |
663 | 93.1k | name: header::ACCEPT, |
664 | 93.1k | value: HeaderValue::from_static(""), |
665 | 93.1k | }, |
666 | 126k | 20 => Header::Field { |
667 | 126k | name: header::ACCESS_CONTROL_ALLOW_ORIGIN, |
668 | 126k | value: HeaderValue::from_static(""), |
669 | 126k | }, |
670 | 177k | 21 => Header::Field { |
671 | 177k | name: header::AGE, |
672 | 177k | value: HeaderValue::from_static(""), |
673 | 177k | }, |
674 | 51.2k | 22 => Header::Field { |
675 | 51.2k | name: header::ALLOW, |
676 | 51.2k | value: HeaderValue::from_static(""), |
677 | 51.2k | }, |
678 | 329k | 23 => Header::Field { |
679 | 329k | name: header::AUTHORIZATION, |
680 | 329k | value: HeaderValue::from_static(""), |
681 | 329k | }, |
682 | 27.9k | 24 => Header::Field { |
683 | 27.9k | name: header::CACHE_CONTROL, |
684 | 27.9k | value: HeaderValue::from_static(""), |
685 | 27.9k | }, |
686 | 121k | 25 => Header::Field { |
687 | 121k | name: header::CONTENT_DISPOSITION, |
688 | 121k | value: HeaderValue::from_static(""), |
689 | 121k | }, |
690 | 31.1k | 26 => Header::Field { |
691 | 31.1k | name: header::CONTENT_ENCODING, |
692 | 31.1k | value: HeaderValue::from_static(""), |
693 | 31.1k | }, |
694 | 228k | 27 => Header::Field { |
695 | 228k | name: header::CONTENT_LANGUAGE, |
696 | 228k | value: HeaderValue::from_static(""), |
697 | 228k | }, |
698 | 35.6k | 28 => Header::Field { |
699 | 35.6k | name: header::CONTENT_LENGTH, |
700 | 35.6k | value: HeaderValue::from_static(""), |
701 | 35.6k | }, |
702 | 79.7k | 29 => Header::Field { |
703 | 79.7k | name: header::CONTENT_LOCATION, |
704 | 79.7k | value: HeaderValue::from_static(""), |
705 | 79.7k | }, |
706 | 27.4k | 30 => Header::Field { |
707 | 27.4k | name: header::CONTENT_RANGE, |
708 | 27.4k | value: HeaderValue::from_static(""), |
709 | 27.4k | }, |
710 | 604k | 31 => Header::Field { |
711 | 604k | name: header::CONTENT_TYPE, |
712 | 604k | value: HeaderValue::from_static(""), |
713 | 604k | }, |
714 | 655k | 32 => Header::Field { |
715 | 655k | name: header::COOKIE, |
716 | 655k | value: HeaderValue::from_static(""), |
717 | 655k | }, |
718 | 33.0k | 33 => Header::Field { |
719 | 33.0k | name: header::DATE, |
720 | 33.0k | value: HeaderValue::from_static(""), |
721 | 33.0k | }, |
722 | 28.2k | 34 => Header::Field { |
723 | 28.2k | name: header::ETAG, |
724 | 28.2k | value: HeaderValue::from_static(""), |
725 | 28.2k | }, |
726 | 123k | 35 => Header::Field { |
727 | 123k | name: header::EXPECT, |
728 | 123k | value: HeaderValue::from_static(""), |
729 | 123k | }, |
730 | 119k | 36 => Header::Field { |
731 | 119k | name: header::EXPIRES, |
732 | 119k | value: HeaderValue::from_static(""), |
733 | 119k | }, |
734 | 34.6k | 37 => Header::Field { |
735 | 34.6k | name: header::FROM, |
736 | 34.6k | value: HeaderValue::from_static(""), |
737 | 34.6k | }, |
738 | 40.3k | 38 => Header::Field { |
739 | 40.3k | name: header::HOST, |
740 | 40.3k | value: HeaderValue::from_static(""), |
741 | 40.3k | }, |
742 | 136k | 39 => Header::Field { |
743 | 136k | name: header::IF_MATCH, |
744 | 136k | value: HeaderValue::from_static(""), |
745 | 136k | }, |
746 | 55.8k | 40 => Header::Field { |
747 | 55.8k | name: header::IF_MODIFIED_SINCE, |
748 | 55.8k | value: HeaderValue::from_static(""), |
749 | 55.8k | }, |
750 | 136k | 41 => Header::Field { |
751 | 136k | name: header::IF_NONE_MATCH, |
752 | 136k | value: HeaderValue::from_static(""), |
753 | 136k | }, |
754 | 31.1k | 42 => Header::Field { |
755 | 31.1k | name: header::IF_RANGE, |
756 | 31.1k | value: HeaderValue::from_static(""), |
757 | 31.1k | }, |
758 | 30.4k | 43 => Header::Field { |
759 | 30.4k | name: header::IF_UNMODIFIED_SINCE, |
760 | 30.4k | value: HeaderValue::from_static(""), |
761 | 30.4k | }, |
762 | 112k | 44 => Header::Field { |
763 | 112k | name: header::LAST_MODIFIED, |
764 | 112k | value: HeaderValue::from_static(""), |
765 | 112k | }, |
766 | 36.1k | 45 => Header::Field { |
767 | 36.1k | name: header::LINK, |
768 | 36.1k | value: HeaderValue::from_static(""), |
769 | 36.1k | }, |
770 | 199k | 46 => Header::Field { |
771 | 199k | name: header::LOCATION, |
772 | 199k | value: HeaderValue::from_static(""), |
773 | 199k | }, |
774 | 48.2k | 47 => Header::Field { |
775 | 48.2k | name: header::MAX_FORWARDS, |
776 | 48.2k | value: HeaderValue::from_static(""), |
777 | 48.2k | }, |
778 | 28.8k | 48 => Header::Field { |
779 | 28.8k | name: header::PROXY_AUTHENTICATE, |
780 | 28.8k | value: HeaderValue::from_static(""), |
781 | 28.8k | }, |
782 | 34.9k | 49 => Header::Field { |
783 | 34.9k | name: header::PROXY_AUTHORIZATION, |
784 | 34.9k | value: HeaderValue::from_static(""), |
785 | 34.9k | }, |
786 | 43.1k | 50 => Header::Field { |
787 | 43.1k | name: header::RANGE, |
788 | 43.1k | value: HeaderValue::from_static(""), |
789 | 43.1k | }, |
790 | 199k | 51 => Header::Field { |
791 | 199k | name: header::REFERER, |
792 | 199k | value: HeaderValue::from_static(""), |
793 | 199k | }, |
794 | 44.6k | 52 => Header::Field { |
795 | 44.6k | name: header::REFRESH, |
796 | 44.6k | value: HeaderValue::from_static(""), |
797 | 44.6k | }, |
798 | 117k | 53 => Header::Field { |
799 | 117k | name: header::RETRY_AFTER, |
800 | 117k | value: HeaderValue::from_static(""), |
801 | 117k | }, |
802 | 15.1k | 54 => Header::Field { |
803 | 15.1k | name: header::SERVER, |
804 | 15.1k | value: HeaderValue::from_static(""), |
805 | 15.1k | }, |
806 | 26.1k | 55 => Header::Field { |
807 | 26.1k | name: header::SET_COOKIE, |
808 | 26.1k | value: HeaderValue::from_static(""), |
809 | 26.1k | }, |
810 | 70.5k | 56 => Header::Field { |
811 | 70.5k | name: header::STRICT_TRANSPORT_SECURITY, |
812 | 70.5k | value: HeaderValue::from_static(""), |
813 | 70.5k | }, |
814 | 30.5k | 57 => Header::Field { |
815 | 30.5k | name: header::TRANSFER_ENCODING, |
816 | 30.5k | value: HeaderValue::from_static(""), |
817 | 30.5k | }, |
818 | 47.8k | 58 => Header::Field { |
819 | 47.8k | name: header::USER_AGENT, |
820 | 47.8k | value: HeaderValue::from_static(""), |
821 | 47.8k | }, |
822 | 30.8k | 59 => Header::Field { |
823 | 30.8k | name: header::VARY, |
824 | 30.8k | value: HeaderValue::from_static(""), |
825 | 30.8k | }, |
826 | 33.4k | 60 => Header::Field { |
827 | 33.4k | name: header::VIA, |
828 | 33.4k | value: HeaderValue::from_static(""), |
829 | 33.4k | }, |
830 | 30.9k | 61 => Header::Field { |
831 | 30.9k | name: header::WWW_AUTHENTICATE, |
832 | 30.9k | value: HeaderValue::from_static(""), |
833 | 30.9k | }, |
834 | 0 | _ => unreachable!(), |
835 | | } |
836 | 12.3M | } |
837 | | |
838 | | #[cfg(test)] |
839 | | mod test { |
840 | | use super::*; |
841 | | |
842 | | #[test] |
843 | | fn test_peek_u8() { |
844 | | let b = 0xff; |
845 | | let mut buf = Cursor::new(vec![b]); |
846 | | assert_eq!(peek_u8(&buf), Some(b)); |
847 | | assert_eq!(buf.get_u8(), b); |
848 | | assert_eq!(peek_u8(&buf), None); |
849 | | } |
850 | | |
851 | | #[test] |
852 | | fn test_decode_string_empty() { |
853 | | let mut de = Decoder::new(0); |
854 | | let mut buf = BytesMut::new(); |
855 | | let err = de.decode_string(&mut Cursor::new(&mut buf)).unwrap_err(); |
856 | | assert_eq!(err, DecoderError::NeedMore(NeedMore::UnexpectedEndOfStream)); |
857 | | } |
858 | | |
859 | | #[test] |
860 | | fn test_decode_empty() { |
861 | | let mut de = Decoder::new(0); |
862 | | let mut buf = BytesMut::new(); |
863 | | de.decode(&mut Cursor::new(&mut buf), |_| ControlFlow::Continue(())) |
864 | | .unwrap(); |
865 | | } |
866 | | |
867 | | #[test] |
868 | | fn test_decode_indexed_larger_than_table() { |
869 | | let mut de = Decoder::new(0); |
870 | | |
871 | | let mut buf = BytesMut::new(); |
872 | | buf.extend([0b01000000, 0x80 | 2]); |
873 | | buf.extend(huff_encode(b"foo")); |
874 | | buf.extend([0x80 | 3]); |
875 | | buf.extend(huff_encode(b"bar")); |
876 | | |
877 | | let mut res = vec![]; |
878 | | de.decode(&mut Cursor::new(&mut buf), |h| { |
879 | | res.push(h); |
880 | | ControlFlow::Continue(()) |
881 | | }) |
882 | | .unwrap(); |
883 | | |
884 | | assert_eq!(res.len(), 1); |
885 | | assert_eq!(de.table.size(), 0); |
886 | | |
887 | | match res[0] { |
888 | | Header::Field { |
889 | | ref name, |
890 | | ref value, |
891 | | } => { |
892 | | assert_eq!(name, "foo"); |
893 | | assert_eq!(value, "bar"); |
894 | | } |
895 | | _ => panic!(), |
896 | | } |
897 | | } |
898 | | |
899 | | fn huff_encode(src: &[u8]) -> BytesMut { |
900 | | let mut buf = BytesMut::new(); |
901 | | huffman::encode(src, &mut buf); |
902 | | buf |
903 | | } |
904 | | |
905 | | #[test] |
906 | | fn test_decode_continuation_header_with_non_huff_encoded_name() { |
907 | | let mut de = Decoder::new(0); |
908 | | let value = huff_encode(b"bar"); |
909 | | let mut buf = BytesMut::new(); |
910 | | // header name is non_huff encoded |
911 | | buf.extend([0b01000000, 3]); |
912 | | buf.extend(b"foo"); |
913 | | // header value is partial |
914 | | buf.extend([0x80 | 3]); |
915 | | buf.extend(&value[0..1]); |
916 | | |
917 | | let mut res = vec![]; |
918 | | let e = de |
919 | | .decode(&mut Cursor::new(&mut buf), |h| { |
920 | | res.push(h); |
921 | | ControlFlow::Continue(()) |
922 | | }) |
923 | | .unwrap_err(); |
924 | | // decode error because the header value is partial |
925 | | assert_eq!(e, DecoderError::NeedMore(NeedMore::StringUnderflow)); |
926 | | |
927 | | // extend buf with the remaining header value |
928 | | buf.extend(&value[1..]); |
929 | | de.decode(&mut Cursor::new(&mut buf), |h| { |
930 | | res.push(h); |
931 | | ControlFlow::Continue(()) |
932 | | }) |
933 | | .unwrap(); |
934 | | |
935 | | assert_eq!(res.len(), 1); |
936 | | assert_eq!(de.table.size(), 0); |
937 | | |
938 | | match res[0] { |
939 | | Header::Field { |
940 | | ref name, |
941 | | ref value, |
942 | | } => { |
943 | | assert_eq!(name, "foo"); |
944 | | assert_eq!(value, "bar"); |
945 | | } |
946 | | _ => panic!(), |
947 | | } |
948 | | } |
949 | | } |