loona_hpack/
lib.rs

1//! A module implementing HPACK functionality. Exposes a simple API for
2//! performing the encoding and decoding of header sets, according to the
3//! HPACK spec.
4
5use std::collections::VecDeque;
6use std::fmt;
7
8use tracing::debug;
9
10// Re-export the main HPACK API entry points.
11pub use self::decoder::Decoder;
12pub use self::encoder::Encoder;
13
14pub mod decoder;
15pub mod encoder;
16pub mod huffman;
17
18/// A struct representing the dynamic table that needs to be maintained by the
19/// coder.
20///
21/// The dynamic table contains a number of recently used headers. The size of
22/// the table is constrained to a certain number of octets. If on insertion of
23/// a new header into the table, the table would exceed the maximum size,
24/// headers are evicted in a FIFO fashion until there is enough room for the
25/// new header to be inserted. (Therefore, it is possible that though all
26/// elements end up being evicted, there is still not enough space for the new
27/// header: when the size of this individual header exceeds the maximum size of
28/// the table.)
29///
30/// The current size of the table is calculated, based on the IETF definition,
31/// as the sum of sizes of each header stored within the table, where the size
32/// of an individual header is
33/// `len_in_octets(header_name) + len_in_octets(header_value) + 32`.
34///
35/// Note: the maximum size of the dynamic table does not have to be equal to
36/// the maximum header table size as defined by a "higher level" protocol
37/// (such as the `SETTINGS_HEADER_TABLE_SIZE` setting in HTTP/2), since HPACK
38/// can choose to modify the dynamic table size on the fly (as long as it keeps
39/// it below the maximum value set by the protocol). So, the `DynamicTable`
40/// only cares about the maximum size as set by the HPACK {en,de}coder and lets
41/// *it* worry about making certain that the changes are valid according to
42/// the (current) constraints of the protocol.
43struct DynamicTable {
44    table: VecDeque<(Vec<u8>, Vec<u8>)>,
45    size: usize,
46    max_size: usize,
47}
48
49impl DynamicTable {
50    /// Creates a new empty dynamic table with a default size.
51    fn new() -> DynamicTable {
52        // The default maximum size corresponds to the default HTTP/2
53        // setting
54        DynamicTable::with_size(4096)
55    }
56
57    /// Creates a new empty dynamic table with the given maximum size.
58    fn with_size(max_size: usize) -> DynamicTable {
59        DynamicTable {
60            table: VecDeque::new(),
61            size: 0,
62            max_size,
63        }
64    }
65
66    /// Returns the current size of the table in octets, as defined by the IETF
67    /// HPACK spec.
68    fn get_size(&self) -> usize {
69        self.size
70    }
71
72    /// Returns an `Iterator` through the headers stored in the `DynamicTable`.
73    ///
74    /// The iterator will yield elements of type `(&[u8], &[u8])`,
75    /// corresponding to a single header name and value. The name and value
76    /// slices are borrowed from their representations in the `DynamicTable`
77    /// internal implementation, which means that it is possible only to
78    /// iterate through the headers, not mutate them.
79    fn iter(&self) -> impl Iterator<Item = (&[u8], &[u8])> {
80        self.table.iter().map(|(a, b)| (&a[..], &b[..]))
81    }
82
83    /// Sets the new maximum table size.
84    ///
85    /// If the current size of the table is larger than the new maximum size,
86    /// existing headers are evicted in a FIFO fashion until the size drops
87    /// below the new maximum.
88    fn set_max_table_size(&mut self, new_max_size: usize) {
89        self.max_size = new_max_size;
90        // Make the table size fit within the new constraints.
91        self.consolidate_table();
92    }
93
94    /// Returns the maximum size of the table in octets.
95    #[cfg(test)]
96    fn get_max_table_size(&self) -> usize {
97        self.max_size
98    }
99
100    /// Add a new header to the dynamic table.
101    ///
102    /// The table automatically gets resized, if necessary.
103    ///
104    /// Do note that, under the HPACK rules, it is possible the given header
105    /// is not found in the dynamic table after this operation finishes, in
106    /// case the total size of the given header exceeds the maximum size of the
107    /// dynamic table.
108    fn add_header(&mut self, name: Vec<u8>, value: Vec<u8>) {
109        // This is how the HPACK spec makes us calculate the size.  The 32 is
110        // a magic number determined by them (under reasonable assumptions of
111        // how the table is stored).
112        self.size += name.len() + value.len() + 32;
113        debug!("New dynamic table size {}", self.size);
114        // Now add it to the internal buffer
115        self.table.push_front((name, value));
116        // ...and make sure we're not over the maximum size.
117        self.consolidate_table();
118        debug!("After consolidation dynamic table size {}", self.size);
119    }
120
121    /// Consolidates the table entries so that the table size is below the
122    /// maximum allowed size, by evicting headers from the table in a FIFO
123    /// fashion.
124    fn consolidate_table(&mut self) {
125        while self.size > self.max_size {
126            {
127                let last_header = match self.table.back() {
128                    Some(x) => x,
129                    None => {
130                        // Can never happen as the size of the table must reach
131                        // 0 by the time we've exhausted all elements.
132                        panic!("Size of table != 0, but no headers left!");
133                    }
134                };
135                self.size -= last_header.0.len() + last_header.1.len() + 32;
136            }
137            self.table.pop_back();
138        }
139    }
140
141    /// Returns the number of headers in the dynamic table.
142    ///
143    /// This is different than the size of the dynamic table.
144    fn len(&self) -> usize {
145        self.table.len()
146    }
147
148    /// Converts the current state of the table to a `Vec`
149    #[cfg(test)]
150    fn to_vec(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
151        let mut ret: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
152        for elem in self.table.iter() {
153            ret.push(elem.clone());
154        }
155
156        ret
157    }
158
159    /// Returns a reference to the header at the given index, if found in the
160    /// dynamic table.
161    fn get(&self, index: usize) -> Option<&(Vec<u8>, Vec<u8>)> {
162        self.table.get(index)
163    }
164}
165
166impl fmt::Debug for DynamicTable {
167    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
168        write!(formatter, "{:?}", self.table)
169    }
170}
171
172/// Represents the type of the static table, as defined by the HPACK spec.
173type StaticTable<'a> = &'a [(&'a [u8], &'a [u8])];
174
175/// The struct represents the header table obtained by merging the static and
176/// dynamic tables into a single index address space, as described in section
177/// `2.3.3.` of the HPACK spec.
178struct HeaderTable<'a> {
179    static_table: StaticTable<'a>,
180    dynamic_table: DynamicTable,
181}
182
183impl<'a> HeaderTable<'a> {
184    /// Creates a new header table where the static part is initialized with
185    /// the given static table.
186    pub fn with_static_table(static_table: StaticTable<'a>) -> HeaderTable<'a> {
187        HeaderTable {
188            static_table,
189            dynamic_table: DynamicTable::new(),
190        }
191    }
192
193    /// Returns an iterator through *all* headers stored in the header table,
194    /// i.e. it includes both the ones found in the static table and the
195    /// dynamic table, in the order of their indices in the single address
196    /// space (first the headers in the static table, followed by headers in
197    /// the dynamic table).
198    ///
199    /// The type yielded by the iterator is `(&[u8], &[u8])`, where the tuple
200    /// corresponds to the header name, value pairs in the described order.
201    pub fn iter(&self) -> impl Iterator<Item = (&[u8], &[u8])> + '_ {
202        self.static_table
203            .iter()
204            .copied()
205            .chain(self.dynamic_table.iter())
206    }
207
208    /// Adds the given header to the table. Of course, this means that the new
209    /// header is added to the dynamic part of the table.
210    ///
211    /// If the size of the new header is larger than the current maximum table
212    /// size of the dynamic table, the effect will be that the dynamic table
213    /// gets emptied and the new header does *not* get inserted into it.
214    #[inline]
215    pub fn add_header(&mut self, name: Vec<u8>, value: Vec<u8>) {
216        self.dynamic_table.add_header(name, value);
217    }
218
219    /// Returns a reference to the header (a `(name, value)` pair) with the
220    /// given index in the table.
221    ///
222    /// The table is 1-indexed and constructed in such a way that the first
223    /// entries belong to the static table, followed by entries in the dynamic
224    /// table. They are merged into a single index address space, though.
225    ///
226    /// This is according to the [HPACK spec, section 2.3.3.]
227    /// <http://http2.github.io/http2-spec/compression.html#index.address.space>
228    pub fn get_from_table(&self, index: usize) -> Option<(&[u8], &[u8])> {
229        // The IETF defined table indexing as 1-based.
230        // So, before starting, make sure the given index is within the proper
231        // bounds.
232        let real_index = if index > 0 { index - 1 } else { return None };
233
234        if real_index < self.static_table.len() {
235            // It is in the static table so just return that...
236            Some(self.static_table[real_index])
237        } else {
238            // Maybe it's in the dynamic table then?
239            let dynamic_index = real_index - self.static_table.len();
240            if dynamic_index < self.dynamic_table.len() {
241                self.dynamic_table
242                    .get(dynamic_index)
243                    .map(|(name, value)| (&name[..], &value[..]))
244            } else {
245                // Index out of bounds!
246                None
247            }
248        }
249    }
250
251    /// Finds the given header in the header table. Tries to match both the
252    /// header name and value to one of the headers in the table. If no such
253    /// header exists, then falls back to returning one that matched only the
254    /// name.
255    ///
256    /// # Returns
257    ///
258    /// An `Option`, where `Some` corresponds to a tuple representing the index
259    /// of the header in the header tables (the 1-based index that HPACK uses)
260    /// and a `bool` indicating whether the value of the header also matched.
261    pub fn find_header(&self, header: (&[u8], &[u8])) -> Option<(usize, bool)> {
262        // Just does a simple scan of the entire table, searching for a header
263        // that matches both the name and the value of the given header.
264        // If no such header is found, then any one of the headers that had a
265        // matching name is returned, with the appropriate return flag set.
266        //
267        // The tables are so small that it is unlikely that the linear scan
268        // would be a major performance bottlneck. If it does prove to be,
269        // though, a more efficient lookup/header representation method could
270        // be devised.
271        let mut matching_name: Option<usize> = None;
272        for (i, h) in self.iter().enumerate() {
273            if header.0 == h.0 {
274                if header.1 == h.1 {
275                    // Both name and value matched: returns it immediately
276                    return Some((i + 1, true));
277                }
278                // If only the name was valid, we continue scanning, hoping to
279                // find one where both the name and value match. We remember
280                // this one, in case such a header isn't found after all.
281                matching_name = Some(i + 1);
282            }
283        }
284
285        // Finally, if there's no header with a matching name and value,
286        // return one that matched only the name, if that *was* found.
287        matching_name.map(|i| (i, false))
288    }
289}
290
291/// The table represents the static header table defined by the HPACK spec.
292/// (HPACK, Appendix A)
293static STATIC_TABLE: &[(&[u8], &[u8])] = &[
294    (b":authority", b""),
295    (b":method", b"GET"),
296    (b":method", b"POST"),
297    (b":path", b"/"),
298    (b":path", b"/index.html"),
299    (b":scheme", b"http"),
300    (b":scheme", b"https"),
301    (b":status", b"200"),
302    (b":status", b"204"),
303    (b":status", b"206"),
304    (b":status", b"304"),
305    (b":status", b"400"),
306    (b":status", b"404"),
307    (b":status", b"500"),
308    (b"accept-charset", b""),
309    (b"accept-encoding", b"gzip, deflate"),
310    (b"accept-language", b""),
311    (b"accept-ranges", b""),
312    (b"accept", b""),
313    (b"access-control-allow-origin", b""),
314    (b"age", b""),
315    (b"allow", b""),
316    (b"authorization", b""),
317    (b"cache-control", b""),
318    (b"content-disposition", b""),
319    (b"content-encoding", b""),
320    (b"content-language", b""),
321    (b"content-length", b""),
322    (b"content-location", b""),
323    (b"content-range", b""),
324    (b"content-type", b""),
325    (b"cookie", b""),
326    (b"date", b""),
327    (b"etag", b""),
328    (b"expect", b""),
329    (b"expires", b""),
330    (b"from", b""),
331    (b"host", b""),
332    (b"if-match", b""),
333    (b"if-modified-since", b""),
334    (b"if-none-match", b""),
335    (b"if-range", b""),
336    (b"if-unmodified-since", b""),
337    (b"last-modified", b""),
338    (b"link", b""),
339    (b"location", b""),
340    (b"max-forwards", b""),
341    (b"proxy-authenticate", b""),
342    (b"proxy-authorization", b""),
343    (b"range", b""),
344    (b"referer", b""),
345    (b"refresh", b""),
346    (b"retry-after", b""),
347    (b"server", b""),
348    (b"set-cookie", b""),
349    (b"strict-transport-security", b""),
350    (b"transfer-encoding", b""),
351    (b"user-agent", b""),
352    (b"vary", b""),
353    (b"via", b""),
354    (b"www-authenticate", b""),
355];
356
357#[cfg(test)]
358mod tests {
359    use super::DynamicTable;
360    use super::HeaderTable;
361    use super::STATIC_TABLE;
362
363    #[test]
364    fn test_dynamic_table_size_calculation_simple() {
365        let mut table = DynamicTable::new();
366        // Sanity check
367        assert_eq!(0, table.get_size());
368
369        table.add_header(b"a".to_vec(), b"b".to_vec());
370
371        assert_eq!(32 + 2, table.get_size());
372    }
373
374    #[test]
375    fn test_dynamic_table_size_calculation() {
376        let mut table = DynamicTable::new();
377
378        table.add_header(b"a".to_vec(), b"b".to_vec());
379        table.add_header(b"123".to_vec(), b"456".to_vec());
380        table.add_header(b"a".to_vec(), b"b".to_vec());
381
382        assert_eq!(3 * 32 + 2 + 6 + 2, table.get_size());
383    }
384
385    /// Tests that the `DynamicTable` gets correctly resized (by evicting old
386    /// headers) if it exceeds the maximum size on an insertion.
387    #[test]
388    fn test_dynamic_table_auto_resize() {
389        let mut table = DynamicTable::with_size(38);
390        table.add_header(b"a".to_vec(), b"b".to_vec());
391        assert_eq!(32 + 2, table.get_size());
392
393        table.add_header(b"123".to_vec(), b"456".to_vec());
394
395        // Resized?
396        assert_eq!(32 + 6, table.get_size());
397        // Only has the second header?
398        assert_eq!(table.to_vec(), vec![(b"123".to_vec(), b"456".to_vec())]);
399    }
400
401    /// Tests that when inserting a new header whose size is larger than the
402    /// size of the entire table, the table is fully emptied.
403    #[test]
404    fn test_dynamic_table_auto_resize_into_empty() {
405        let mut table = DynamicTable::with_size(38);
406        table.add_header(b"a".to_vec(), b"b".to_vec());
407        assert_eq!(32 + 2, table.get_size());
408
409        table.add_header(b"123".to_vec(), b"4567".to_vec());
410
411        // Resized and empty?
412        assert_eq!(0, table.get_size());
413        assert_eq!(0, table.to_vec().len());
414    }
415
416    /// Tests that when changing the maximum size of the `DynamicTable`, the
417    /// headers are correctly evicted in order to keep its size below the new
418    /// max.
419    #[test]
420    fn test_dynamic_table_change_max_size() {
421        let mut table = DynamicTable::new();
422        table.add_header(b"a".to_vec(), b"b".to_vec());
423        table.add_header(b"123".to_vec(), b"456".to_vec());
424        table.add_header(b"c".to_vec(), b"d".to_vec());
425        assert_eq!(3 * 32 + 2 + 6 + 2, table.get_size());
426
427        table.set_max_table_size(38);
428
429        assert_eq!(32 + 2, table.get_size());
430        assert_eq!(table.to_vec(), vec![(b"c".to_vec(), b"d".to_vec())]);
431    }
432
433    /// Tests that setting the maximum table size to 0 clears the dynamic
434    /// table.
435    #[test]
436    fn test_dynamic_table_clear() {
437        let mut table = DynamicTable::new();
438        table.add_header(b"a".to_vec(), b"b".to_vec());
439        table.add_header(b"123".to_vec(), b"456".to_vec());
440        table.add_header(b"c".to_vec(), b"d".to_vec());
441        assert_eq!(3 * 32 + 2 + 6 + 2, table.get_size());
442
443        table.set_max_table_size(0);
444
445        assert_eq!(0, table.len());
446        assert_eq!(0, table.to_vec().len());
447        assert_eq!(0, table.get_size());
448        assert_eq!(0, table.get_max_table_size());
449    }
450
451    /// Tests that when the initial max size of the table is 0, nothing
452    /// can be added to the table.
453    #[test]
454    fn test_dynamic_table_max_size_zero() {
455        let mut table = DynamicTable::with_size(0);
456
457        table.add_header(b"a".to_vec(), b"b".to_vec());
458
459        assert_eq!(0, table.len());
460        assert_eq!(0, table.to_vec().len());
461        assert_eq!(0, table.get_size());
462        assert_eq!(0, table.get_max_table_size());
463    }
464
465    /// Tests that the iterator through the `DynamicTable` works when there are
466    /// some elements in the dynamic table.
467    #[test]
468    fn test_dynamic_table_iter_with_elems() {
469        let mut table = DynamicTable::new();
470        table.add_header(b"a".to_vec(), b"b".to_vec());
471        table.add_header(b"123".to_vec(), b"456".to_vec());
472        table.add_header(b"c".to_vec(), b"d".to_vec());
473
474        let iter_res: Vec<(&[u8], &[u8])> = table.iter().collect();
475
476        let expected: Vec<(&[u8], &[u8])> = vec![(b"c", b"d"), (b"123", b"456"), (b"a", b"b")];
477        assert_eq!(iter_res, expected);
478    }
479
480    /// Tests that the iterator through the `DynamicTable` works when there are
481    /// no elements in the dynamic table.
482    #[test]
483    fn test_dynamic_table_iter_no_elems() {
484        let table = DynamicTable::new();
485
486        let iter_res: Vec<(&[u8], &[u8])> = table.iter().collect();
487
488        let expected = vec![];
489        assert_eq!(iter_res, expected);
490    }
491
492    /// Tests that indexing the header table with indices that correspond to
493    /// entries found in the static table works.
494    #[test]
495    fn test_header_table_index_static() {
496        let table = HeaderTable::with_static_table(STATIC_TABLE);
497
498        for (index, entry) in STATIC_TABLE.iter().enumerate() {
499            assert_eq!(table.get_from_table(index + 1).unwrap(), *entry);
500        }
501    }
502
503    /// Tests that when the given index is out of bounds, the `HeaderTable`
504    /// returns a `None`
505    #[test]
506    fn test_header_table_index_out_of_bounds() {
507        let table = HeaderTable::with_static_table(STATIC_TABLE);
508
509        assert!(table.get_from_table(0).is_none());
510        assert!(table.get_from_table(STATIC_TABLE.len() + 1).is_none());
511    }
512
513    /// Tests that adding entries to the dynamic table through the
514    /// `HeaderTable` interface works.
515    #[test]
516    fn test_header_table_add_to_dynamic() {
517        let mut table = HeaderTable::with_static_table(STATIC_TABLE);
518        let header = (b"a".to_vec(), b"b".to_vec());
519
520        table.add_header(header.0.clone(), header.1.clone());
521
522        assert_eq!(table.dynamic_table.to_vec(), vec![header]);
523    }
524
525    /// Tests that indexing the header table with indices that correspond to
526    /// entries found in the dynamic table works.
527    #[test]
528    fn test_header_table_index_dynamic() {
529        let mut table = HeaderTable::with_static_table(STATIC_TABLE);
530        let header = (b"a".to_vec(), b"b".to_vec());
531
532        table.add_header(header.0.clone(), header.1.clone());
533
534        assert_eq!(
535            table.get_from_table(STATIC_TABLE.len() + 1).unwrap(),
536            (&header.0[..], &header.1[..])
537        );
538    }
539
540    /// Tests that the `iter` method of the `HeaderTable` returns an iterator
541    /// through *all* the headers found in the header table (static and dynamic
542    /// tables both included)
543    #[test]
544    fn test_header_table_iter() {
545        let mut table = HeaderTable::with_static_table(STATIC_TABLE);
546        let headers: [(&[u8], &[u8]); 2] = [(b"a", b"b"), (b"c", b"d")];
547        for header in headers.iter() {
548            table.add_header(header.0.to_vec(), header.1.to_vec());
549        }
550
551        let iterated: Vec<(&[u8], &[u8])> = table.iter().collect();
552
553        assert_eq!(iterated.len(), headers.len() + STATIC_TABLE.len());
554        // Part of the static table correctly iterated through
555        for (h1, h2) in iterated.iter().zip(STATIC_TABLE.iter()) {
556            assert_eq!(h1, h2);
557        }
558        // Part of the dynamic table correctly iterated through: the elements
559        // are in reversed order of insertion in the dynamic table.
560        for (h1, h2) in iterated
561            .iter()
562            .skip(STATIC_TABLE.len())
563            .zip(headers.iter().rev())
564        {
565            assert_eq!(h1, h2);
566        }
567    }
568
569    /// Tests that searching for an entry in the header table, which should be
570    /// fully in the static table (both name and value), works correctly.
571    #[test]
572    fn test_find_header_static_full() {
573        let table = HeaderTable::with_static_table(STATIC_TABLE);
574
575        for (i, h) in STATIC_TABLE.iter().enumerate() {
576            assert_eq!(table.find_header(*h).unwrap(), (i + 1, true));
577        }
578    }
579
580    /// Tests that searching for an entry in the header table, which should be
581    /// only partially in the static table (only the name), works correctly.
582    #[test]
583    fn test_find_header_static_partial() {
584        {
585            let table = HeaderTable::with_static_table(STATIC_TABLE);
586            let h: (&[u8], &[u8]) = (b":method", b"PUT");
587
588            if let (index, false) = table.find_header(h).unwrap() {
589                assert_eq!(h.0, STATIC_TABLE[index - 1].0);
590                // The index is the last one with the corresponding name
591                assert_eq!(3, index);
592            } else {
593                panic!("The header should have matched only partially");
594            }
595        }
596        {
597            let table = HeaderTable::with_static_table(STATIC_TABLE);
598            let h: (&[u8], &[u8]) = (b":status", b"333");
599
600            if let (index, false) = table.find_header(h).unwrap() {
601                assert_eq!(h.0, STATIC_TABLE[index - 1].0);
602                // The index is the last one with the corresponding name
603                assert_eq!(14, index);
604            } else {
605                panic!("The header should have matched only partially");
606            }
607        }
608        {
609            let table = HeaderTable::with_static_table(STATIC_TABLE);
610            let h: (&[u8], &[u8]) = (b":authority", b"example.com");
611
612            if let (index, false) = table.find_header(h).unwrap() {
613                assert_eq!(h.0, STATIC_TABLE[index - 1].0);
614            } else {
615                panic!("The header should have matched only partially");
616            }
617        }
618        {
619            let table = HeaderTable::with_static_table(STATIC_TABLE);
620            let h: (&[u8], &[u8]) = (b"www-authenticate", b"asdf");
621
622            if let (index, false) = table.find_header(h).unwrap() {
623                assert_eq!(h.0, STATIC_TABLE[index - 1].0);
624            } else {
625                panic!("The header should have matched only partially");
626            }
627        }
628    }
629
630    /// Tests that searching for an entry in the header table, which should be
631    /// fully in the dynamic table (both name and value), works correctly.
632    #[test]
633    fn test_find_header_dynamic_full() {
634        let mut table = HeaderTable::with_static_table(STATIC_TABLE);
635        let h: (&[u8], &[u8]) = (b":method", b"PUT");
636        table.add_header(h.0.to_vec(), h.1.to_vec());
637
638        if let (index, true) = table.find_header(h).unwrap() {
639            assert_eq!(index, STATIC_TABLE.len() + 1);
640        } else {
641            panic!("The header should have matched fully");
642        }
643    }
644
645    /// Tests that searching for an entry in the header table, which should be
646    /// only partially in the dynamic table (only the name), works correctly.
647    #[test]
648    fn test_find_header_dynamic_partial() {
649        let mut table = HeaderTable::with_static_table(STATIC_TABLE);
650        // First add it to the dynamic table
651        {
652            let h = (b"X-Custom-Header", b"stuff");
653            table.add_header(h.0.to_vec(), h.1.to_vec());
654        }
655        // Prepare a search
656        let h: (&[u8], &[u8]) = (b"X-Custom-Header", b"different-stuff");
657
658        // It must match only partially
659        if let (index, false) = table.find_header(h).unwrap() {
660            // The index must be the first one in the dynamic table
661            // segment of the header table.
662            assert_eq!(index, STATIC_TABLE.len() + 1);
663        } else {
664            panic!("The header should have matched only partially");
665        }
666    }
667}