1use bytes::Bytes;
4use rustc_hash::FxHashMap;
5use std::collections::VecDeque;
6
7static STATIC_EXACT: phf::Map<(&[u8], &[u8]), usize> = phf::phf_map! {
11 (b":authority", b"") => 1,
12 (b":method", b"GET") => 2,
13 (b":method", b"POST") => 3,
14 (b":path", b"/") => 4,
15 (b":path", b"/index.html") => 5,
16 (b":scheme", b"http") => 6,
17 (b":scheme", b"https") => 7,
18 (b":status", b"200") => 8,
19 (b":status", b"204") => 9,
20 (b":status", b"206") => 10,
21 (b":status", b"304") => 11,
22 (b":status", b"400") => 12,
23 (b":status", b"404") => 13,
24 (b":status", b"500") => 14,
25 (b"accept-charset", b"") => 15,
26 (b"accept-encoding", b"gzip, deflate") => 16,
27 (b"accept-language", b"") => 17,
28 (b"accept-ranges", b"") => 18,
29 (b"accept", b"") => 19,
30 (b"access-control-allow-origin", b"") => 20,
31 (b"age", b"") => 21,
32 (b"allow", b"") => 22,
33 (b"authorization", b"") => 23,
34 (b"cache-control", b"") => 24,
35 (b"content-disposition", b"") => 25,
36 (b"content-encoding", b"") => 26,
37 (b"content-language", b"") => 27,
38 (b"content-length", b"") => 28,
39 (b"content-location", b"") => 29,
40 (b"content-range", b"") => 30,
41 (b"content-type", b"") => 31,
42 (b"cookie", b"") => 32,
43 (b"date", b"") => 33,
44 (b"etag", b"") => 34,
45 (b"expect", b"") => 35,
46 (b"expires", b"") => 36,
47 (b"from", b"") => 37,
48 (b"host", b"") => 38,
49 (b"if-match", b"") => 39,
50 (b"if-modified-since", b"") => 40,
51 (b"if-none-match", b"") => 41,
52 (b"if-range", b"") => 42,
53 (b"if-unmodified-since", b"") => 43,
54 (b"last-modified", b"") => 44,
55 (b"link", b"") => 45,
56 (b"location", b"") => 46,
57 (b"max-forwards", b"") => 47,
58 (b"proxy-authenticate", b"") => 48,
59 (b"proxy-authorization", b"") => 49,
60 (b"range", b"") => 50,
61 (b"referer", b"") => 51,
62 (b"refresh", b"") => 52,
63 (b"retry-after", b"") => 53,
64 (b"server", b"") => 54,
65 (b"set-cookie", b"") => 55,
66 (b"strict-transport-security", b"") => 56,
67 (b"transfer-encoding", b"") => 57,
68 (b"user-agent", b"") => 58,
69 (b"vary", b"") => 59,
70 (b"via", b"") => 60,
71 (b"www-authenticate", b"") => 61,
72};
73
74static STATIC_NAME: phf::Map<&[u8], usize> = phf::phf_map! {
77 b":authority" => 1,
78 b":method" => 2,
79 b":path" => 4,
80 b":scheme" => 6,
81 b":status" => 8,
82 b"accept-charset" => 15,
83 b"accept-encoding" => 16,
84 b"accept-language" => 17,
85 b"accept-ranges" => 18,
86 b"accept" => 19,
87 b"access-control-allow-origin" => 20,
88 b"age" => 21,
89 b"allow" => 22,
90 b"authorization" => 23,
91 b"cache-control" => 24,
92 b"content-disposition" => 25,
93 b"content-encoding" => 26,
94 b"content-language" => 27,
95 b"content-length" => 28,
96 b"content-location" => 29,
97 b"content-range" => 30,
98 b"content-type" => 31,
99 b"cookie" => 32,
100 b"date" => 33,
101 b"etag" => 34,
102 b"expect" => 35,
103 b"expires" => 36,
104 b"from" => 37,
105 b"host" => 38,
106 b"if-match" => 39,
107 b"if-modified-since" => 40,
108 b"if-none-match" => 41,
109 b"if-range" => 42,
110 b"if-unmodified-since" => 43,
111 b"last-modified" => 44,
112 b"link" => 45,
113 b"location" => 46,
114 b"max-forwards" => 47,
115 b"proxy-authenticate" => 48,
116 b"proxy-authorization" => 49,
117 b"range" => 50,
118 b"referer" => 51,
119 b"refresh" => 52,
120 b"retry-after" => 53,
121 b"server" => 54,
122 b"set-cookie" => 55,
123 b"strict-transport-security" => 56,
124 b"transfer-encoding" => 57,
125 b"user-agent" => 58,
126 b"vary" => 59,
127 b"via" => 60,
128 b"www-authenticate" => 61,
129};
130
131#[derive(Clone, Debug, Hash, PartialEq, Eq)]
134struct NameValue(Bytes, Bytes);
135
136const STATIC_TABLE: [(&[u8], &[u8]); 61] = [
139 (b":authority", b""),
140 (b":method", b"GET"),
141 (b":method", b"POST"),
142 (b":path", b"/"),
143 (b":path", b"/index.html"),
144 (b":scheme", b"http"),
145 (b":scheme", b"https"),
146 (b":status", b"200"),
147 (b":status", b"204"),
148 (b":status", b"206"),
149 (b":status", b"304"),
150 (b":status", b"400"),
151 (b":status", b"404"),
152 (b":status", b"500"),
153 (b"accept-charset", b""),
154 (b"accept-encoding", b"gzip, deflate"),
155 (b"accept-language", b""),
156 (b"accept-ranges", b""),
157 (b"accept", b""),
158 (b"access-control-allow-origin", b""),
159 (b"age", b""),
160 (b"allow", b""),
161 (b"authorization", b""),
162 (b"cache-control", b""),
163 (b"content-disposition", b""),
164 (b"content-encoding", b""),
165 (b"content-language", b""),
166 (b"content-length", b""),
167 (b"content-location", b""),
168 (b"content-range", b""),
169 (b"content-type", b""),
170 (b"cookie", b""),
171 (b"date", b""),
172 (b"etag", b""),
173 (b"expect", b""),
174 (b"expires", b""),
175 (b"from", b""),
176 (b"host", b""),
177 (b"if-match", b""),
178 (b"if-modified-since", b""),
179 (b"if-none-match", b""),
180 (b"if-range", b""),
181 (b"if-unmodified-since", b""),
182 (b"last-modified", b""),
183 (b"link", b""),
184 (b"location", b""),
185 (b"max-forwards", b""),
186 (b"proxy-authenticate", b""),
187 (b"proxy-authorization", b""),
188 (b"range", b""),
189 (b"referer", b""),
190 (b"refresh", b""),
191 (b"retry-after", b""),
192 (b"server", b""),
193 (b"set-cookie", b""),
194 (b"strict-transport-security", b""),
195 (b"transfer-encoding", b""),
196 (b"user-agent", b""),
197 (b"vary", b""),
198 (b"via", b""),
199 (b"www-authenticate", b""),
200];
201
202pub(crate) const STATIC_LEN: usize = 61;
204
205const HYBRID_THRESHOLD: usize = 32;
210
211const ENTRY_OVERHEAD: usize = 32;
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct Header {
217 name: Bytes,
218 value: Bytes,
219 size: usize,
222}
223
224impl Header {
225 #[inline]
229 pub fn new(name: impl Into<Bytes>, value: impl Into<Bytes>) -> Self {
230 let name = name.into();
231 let value = value.into();
232 let size = ENTRY_OVERHEAD + name.len() + value.len();
233 Header { name, value, size }
234 }
235
236 #[inline]
239 pub(crate) fn size(&self) -> usize {
240 self.size
241 }
242
243 #[inline]
244 pub fn name(&self) -> &[u8] {
245 &self.name
246 }
247
248 #[inline]
249 pub fn value(&self) -> &[u8] {
250 &self.value
251 }
252
253 #[inline]
255 pub(crate) fn name_bytes(&self) -> &Bytes {
256 &self.name
257 }
258
259 #[inline]
261 pub(crate) fn value_bytes(&self) -> &Bytes {
262 &self.value
263 }
264}
265
266#[derive(Debug)]
269pub(crate) struct Table {
270 entries: VecDeque<Header>,
273 size: usize,
275 max_size: usize,
277 exact: FxHashMap<NameValue, usize>,
280 name: FxHashMap<Bytes, usize>,
283 maintain_maps: bool,
287}
288
289impl Table {
290 #[inline]
291 pub(crate) fn new() -> Self {
292 Table::with_max_size(DEFAULT_MAX_SIZE)
293 }
294
295 #[inline]
298 pub(crate) fn with_max_size(max_size: usize) -> Self {
299 Table {
300 entries: VecDeque::new(),
301 size: 0,
302 max_size,
303 exact: FxHashMap::default(),
304 name: FxHashMap::default(),
305 maintain_maps: true,
306 }
307 }
308
309 #[inline]
313 pub(crate) fn with_max_size_no_maps(max_size: usize) -> Self {
314 Table {
315 entries: VecDeque::new(),
316 size: 0,
317 max_size,
318 exact: FxHashMap::default(),
319 name: FxHashMap::default(),
320 maintain_maps: false,
321 }
322 }
323
324 #[inline]
327 pub(crate) fn get(&self, index: usize) -> Option<Header> {
328 if index == 0 {
329 return None;
330 }
331 if index <= STATIC_LEN {
332 let (name, value) = STATIC_TABLE[index - 1];
333 return Some(Header::new(
335 Bytes::from_static(name),
336 Bytes::from_static(value),
337 ));
338 }
339 self.entries.get(index - STATIC_LEN - 1).cloned()
340 }
341
342 #[cfg(test)]
344 #[inline]
345 pub(crate) fn dynamic_len(&self) -> usize {
346 self.entries.len()
347 }
348
349 #[inline]
353 pub(crate) fn find(&self, name: &Bytes, value: &Bytes) -> Option<usize> {
354 if self.maintain_maps && self.entries.len() > HYBRID_THRESHOLD {
355 if let Some(&index) = STATIC_EXACT.get(&(name.as_ref(), value.as_ref())) {
356 return Some(index);
357 }
358 return self
359 .exact
360 .get(&NameValue(name.clone(), value.clone()))
361 .copied();
362 }
363 let name_ref = name.as_ref();
366 let value_ref = value.as_ref();
367 for (i, entry) in STATIC_TABLE.iter().enumerate() {
368 if entry.0 == name_ref && entry.1 == value_ref {
369 return Some(i + 1);
370 }
371 }
372 for (i, header) in self.entries.iter().enumerate() {
373 if header.name() == name_ref && header.value() == value_ref {
374 return Some(STATIC_LEN + 1 + i);
375 }
376 }
377 None
378 }
379
380 #[inline]
384 pub(crate) fn find_name(&self, name: &Bytes) -> Option<usize> {
385 if self.maintain_maps && self.entries.len() > HYBRID_THRESHOLD {
386 if let Some(&index) = STATIC_NAME.get(name.as_ref()) {
387 return Some(index);
388 }
389 return self.name.get(name.as_ref()).copied();
390 }
391 let name_ref = name.as_ref();
392 for (i, entry) in STATIC_TABLE.iter().enumerate() {
393 if entry.0 == name_ref {
394 return Some(i + 1);
395 }
396 }
397 for (i, header) in self.entries.iter().enumerate() {
398 if header.name() == name_ref {
399 return Some(STATIC_LEN + 1 + i);
400 }
401 }
402 None
403 }
404
405 #[cfg(test)]
407 #[inline]
408 pub(crate) fn len(&self) -> usize {
409 STATIC_LEN + self.entries.len()
410 }
411
412 #[cfg(test)]
414 #[inline]
415 pub(crate) fn size(&self) -> usize {
416 self.size
417 }
418
419 #[cfg(test)]
420 #[inline]
421 pub(crate) fn max_size(&self) -> usize {
422 self.max_size
423 }
424
425 #[inline]
429 pub(crate) fn set_max_size(&mut self, max_size: usize) {
430 self.max_size = max_size;
431 while self.size > self.max_size {
432 match self.entries.pop_back() {
433 Some(entry) => {
434 self.size -= entry.size();
435 let combined = STATIC_LEN + self.entries.len() + 1;
436 self.evict_entry(&entry, combined);
437 }
438 None => break,
439 }
440 }
441 }
442
443 #[inline]
448 fn evict_entry(&mut self, entry: &Header, combined: usize) {
449 if !self.maintain_maps {
450 return;
451 }
452 if self.name.get(entry.name()).copied() == Some(combined) {
453 self.name.remove(entry.name());
454 }
455 let key = NameValue(entry.name_bytes().clone(), entry.value_bytes().clone());
456 if self.exact.get(&key).copied() == Some(combined) {
457 self.exact.remove(&key);
458 }
459 }
460
461 #[inline]
465 pub(crate) fn add(&mut self, header: Header) {
466 if header.size() > self.max_size {
467 self.entries.clear();
468 self.size = 0;
469 if self.maintain_maps {
470 self.exact.clear();
471 self.name.clear();
472 }
473 return;
474 }
475 while self.size + header.size() > self.max_size {
476 match self.entries.pop_back() {
477 Some(entry) => {
478 self.size -= entry.size();
479 let combined = STATIC_LEN + self.entries.len() + 1;
480 self.evict_entry(&entry, combined);
481 }
482 None => break,
483 }
484 }
485 if self.maintain_maps {
486 for v in self.exact.values_mut() {
489 *v += 1;
490 }
491 for v in self.name.values_mut() {
492 *v += 1;
493 }
494 }
495
496 let combined = STATIC_LEN + 1;
497 self.size += header.size();
498 if self.maintain_maps {
499 let name = header.name_bytes().clone();
500 let value = header.value_bytes().clone();
501 self.entries.push_front(header);
502 self.exact
503 .insert(NameValue(name.clone(), value.clone()), combined);
504 self.name.insert(name, combined);
505 } else {
506 self.entries.push_front(header);
507 }
508 }
509}
510
511impl Default for Table {
512 #[inline]
513 fn default() -> Self {
514 Self::new()
515 }
516}
517
518const DEFAULT_MAX_SIZE: usize = 4096;
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 fn header(name: &str, value: &str) -> Header {
527 Header::new(
528 Bytes::copy_from_slice(name.as_bytes()),
529 Bytes::copy_from_slice(value.as_bytes()),
530 )
531 }
532
533 #[test]
537 fn find_tracks_eviction_and_duplicates() {
538 let mut table = Table::with_max_size(200);
539 table.add(header("a", "1")); table.add(header("b", "2")); table.add(header("a", "3")); assert_eq!(table.find_name(header("a", "").name_bytes()), Some(62));
544 assert_eq!(
545 table.find(
546 header("a", "3").name_bytes(),
547 header("a", "3").value_bytes()
548 ),
549 Some(62)
550 );
551 assert_eq!(
552 table.find(
553 header("a", "1").name_bytes(),
554 header("a", "1").value_bytes()
555 ),
556 Some(64)
557 );
558
559 table.set_max_size(70); assert_eq!(
563 table.find(
564 header("a", "1").name_bytes(),
565 header("a", "1").value_bytes()
566 ),
567 None
568 );
569 assert_eq!(table.find_name(header("a", "").name_bytes()), Some(62));
570 assert_eq!(
571 table.find(
572 header("a", "3").name_bytes(),
573 header("a", "3").value_bytes()
574 ),
575 Some(62)
576 );
577 assert_eq!(table.find_name(header("b", "").name_bytes()), Some(63));
578 }
579
580 #[test]
581 fn static_table_contents() {
582 assert_eq!(
583 Table::new().get(1),
584 Some(Header::new(
585 Bytes::from_static(b":authority"),
586 Bytes::from_static(b"")
587 ))
588 );
589 assert_eq!(
590 Table::new().get(2),
591 Some(Header::new(
592 Bytes::from_static(b":method"),
593 Bytes::from_static(b"GET")
594 ))
595 );
596 assert_eq!(
597 Table::new().get(16),
598 Some(Header::new(
599 Bytes::from_static(b"accept-encoding"),
600 Bytes::from_static(b"gzip, deflate")
601 ))
602 );
603 assert_eq!(
604 Table::new().get(61),
605 Some(Header::new(
606 Bytes::from_static(b"www-authenticate"),
607 Bytes::from_static(b"")
608 ))
609 );
610 assert_eq!(Table::new().get(0), None);
611 }
612
613 #[test]
614 fn entry_size_math() {
615 assert_eq!(header("test", "abc").size(), 39);
617 assert_eq!(header("", "").size(), 32);
618 }
619
620 #[test]
621 fn add_and_fetch_order() {
622 let mut table = Table::with_max_size(200);
623 table.add(header("a", "1"));
624 table.add(header("b", "2"));
625 table.add(header("c", "3"));
626
627 assert_eq!(table.get(62), Some(header("c", "3")));
629 assert_eq!(table.get(63), Some(header("b", "2")));
630 assert_eq!(table.get(64), Some(header("a", "1")));
631 assert_eq!(table.get(65), None);
632 assert_eq!(table.dynamic_len(), 3);
633 assert_eq!(table.len(), STATIC_LEN + 3);
634 assert_eq!(table.size(), 34 * 3);
635 }
636
637 #[test]
638 fn eviction_from_end() {
639 let mut table = Table::with_max_size(70);
641 table.add(header("a", "1"));
642 table.add(header("b", "2"));
643 table.add(header("c", "3"));
644
645 assert_eq!(table.dynamic_len(), 2);
646 assert_eq!(table.get(62), Some(header("c", "3")));
647 assert_eq!(table.get(63), Some(header("b", "2")));
648 assert_eq!(table.get(64), None);
649 assert_eq!(table.size(), 68);
650 }
651
652 #[test]
653 fn entry_larger_than_max_empties_table() {
654 let mut table = Table::with_max_size(100);
655 table.add(header("x", "1"));
656 table.add(header("y", "2"));
657 assert_eq!(table.dynamic_len(), 2);
658
659 let big = header(&"v".repeat(60), &"v".repeat(9));
661 assert_eq!(big.size(), 101);
662 table.add(big);
663
664 assert_eq!(table.dynamic_len(), 0);
665 assert_eq!(table.size(), 0);
666 }
667
668 #[test]
669 fn entry_exactly_max_size_is_added() {
670 let mut table = Table::with_max_size(101);
671 let big = header(&"v".repeat(60), &"v".repeat(9));
672 assert_eq!(big.size(), 101);
673 table.add(big);
674 assert_eq!(table.dynamic_len(), 1);
675 }
676
677 #[test]
678 fn size_update_evicts() {
679 let mut table = Table::with_max_size(200);
680 table.add(header("a", "1"));
681 table.add(header("b", "2"));
682 table.add(header("c", "3"));
683
684 table.set_max_size(70);
685 assert_eq!(table.dynamic_len(), 2);
686 assert_eq!(table.get(62), Some(header("c", "3")));
687 assert_eq!(table.size(), 68);
688
689 table.set_max_size(0);
690 assert_eq!(table.dynamic_len(), 0);
691 assert_eq!(table.size(), 0);
692
693 table.set_max_size(200);
696 assert_eq!(table.dynamic_len(), 0);
697 table.add(header("d", "4"));
698 assert_eq!(table.get(62), Some(header("d", "4")));
699 }
700
701 #[test]
702 fn max_size_tracks_requests() {
703 let mut table = Table::with_max_size(128);
704 assert_eq!(table.max_size(), 128);
705 table.set_max_size(256);
706 assert_eq!(table.max_size(), 256);
707 }
708
709 #[test]
710 fn default_max_size() {
711 assert_eq!(Table::new().max_size(), DEFAULT_MAX_SIZE);
712 assert_eq!(Table::default().max_size(), DEFAULT_MAX_SIZE);
713 }
714
715 #[test]
716 fn grows_and_reuses_slots() {
717 let mut table = Table::with_max_size(200);
720 for i in 0..100 {
721 let entry = header(&format!("k{i}"), "v");
722 table.add(entry.clone());
723 assert!(table.size() <= table.max_size(), "iteration {i}");
724 assert_eq!(table.get(62), Some(entry), "iteration {i}");
725 }
726 assert_eq!(table.get(62), Some(header("k99", "v")));
727 assert_eq!(
728 table.size(),
729 table.dynamic_len() * table.get(62).unwrap().size()
730 );
731 }
732}