1use crate::table::{static_name, DynTable, Name, STATIC_TABLE};
4use crate::{bytes_eq, encode_integer, encode_string, field_size, DEFAULT_TABLE_SIZE};
5
6pub fn is_sensitive(name: &[u8]) -> bool {
9 matches!(name, b"authorization" | b"proxy-authorization" | b"cookie" | b"set-cookie")
10}
11
12const SENSITIVE: u64 = 1 << 23 | 1 << 32 | 1 << 49 | 1 << 55;
15
16const RARE: u64 = 1 << 21 | 1 << 28 | 1 << 30 | 1 << 34 | 1 << 39 | 1 << 40 | 1 << 41 | 1 << 44 | 1 << 46;
20
21fn rarely_repeats(idx: usize, name: &[u8], value: &[u8]) -> bool {
24 match idx {
25 4 => value.len() > 64 || value.contains(&b'?'), 0 => matches!(name, b"x-request-id" | b"traceparent" | b"tracestate"),
27 i => RARE >> i & 1 != 0,
28 }
29}
30
31#[derive(Debug, Clone)]
38pub struct Encoder {
39 table: DynTable,
40 limit: usize,
42 pending_min: usize,
44 pending: bool,
45 pub huffman: bool,
47 pub indexing: bool,
49}
50
51impl Default for Encoder {
52 fn default() -> Self {
53 Encoder::new(DEFAULT_TABLE_SIZE)
54 }
55}
56
57impl Encoder {
58 pub fn new(limit: usize) -> Self {
61 let size = limit.min(DEFAULT_TABLE_SIZE);
62 Encoder {
63 table: DynTable::new(size),
64 limit,
65 pending_min: size,
66 pending: size != DEFAULT_TABLE_SIZE,
67 huffman: true,
68 indexing: true,
69 }
70 }
71
72 pub fn set_peer_max_table_size(&mut self, peer: usize) {
75 let size = peer.min(self.limit);
76 if size != self.table.max() {
77 self.pending_min = self.pending_min.min(size);
78 self.pending = true;
79 self.table.set_max(size);
80 }
81 }
82
83 pub fn table_size(&self) -> usize {
85 self.table.size()
86 }
87
88 pub fn table_len(&self) -> usize {
90 self.table.len()
91 }
92
93 pub fn begin_block(&mut self, out: &mut Vec<u8>) {
95 if self.pending {
96 let max = self.table.max();
97 if self.pending_min < max {
98 encode_integer(self.pending_min, 5, 0x20, out);
99 }
100 encode_integer(max, 5, 0x20, out);
101 self.pending = false;
102 self.pending_min = max;
103 }
104 }
105
106 pub fn encode(&mut self, fields: &[(&[u8], &[u8])], out: &mut Vec<u8>) {
108 self.begin_block(out);
109 for (n, v) in fields {
110 self.field(n, v, out);
111 }
112 }
113
114 pub fn field(&mut self, name: &[u8], value: &[u8], out: &mut Vec<u8>) {
116 let mut name_idx = 0usize;
117 if let Some((first, count)) = static_name(name) {
118 name_idx = first;
119 for i in first..first + count {
120 if bytes_eq(STATIC_TABLE[i - 1].1, value) {
121 encode_integer(i, 7, 0x80, out);
122 return;
123 }
124 }
125 }
126 let static_idx = name_idx;
127 let sensitive = SENSITIVE >> static_idx & 1 != 0;
129 let (full, by_name) = self.table.find(name, value);
130 if let (Some(i), false) = (full, sensitive) {
131 encode_integer(STATIC_TABLE.len() + 1 + i, 7, 0x80, out);
132 return;
133 }
134 if let (0, Some(i)) = (name_idx, by_name) {
135 name_idx = STATIC_TABLE.len() + 1 + i;
136 }
137 let index = self.indexing
138 && !sensitive
139 && !rarely_repeats(static_idx, name, value)
140 && field_size(name, value) <= self.table.max() / 2;
141 let (prefix, first) = if sensitive {
142 (4, 0x10)
143 } else if index {
144 (6, 0x40)
145 } else {
146 (4, 0x00)
147 };
148 encode_integer(name_idx, prefix, first, out);
149 if name_idx == 0 {
150 encode_string(name, self.huffman, out);
151 }
152 encode_string(value, self.huffman, out);
153 if index {
154 self.table.insert(Name::Bytes(name), value);
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
165 fn masks_match_the_names() {
166 let rare: [&[u8]; 9] = [b"content-length", b"content-range", b"etag", b"if-none-match", b"if-modified-since", b"if-match", b"last-modified", b"age", b"location"];
167 for (i, (n, _)) in STATIC_TABLE.iter().enumerate() {
168 let first = static_name(n).unwrap().0;
169 if first != i + 1 {
170 continue;
171 }
172 assert_eq!(SENSITIVE >> first & 1 != 0, is_sensitive(n), "{}", String::from_utf8_lossy(n));
173 if first != 4 {
174 assert_eq!(rarely_repeats(first, n, b"x"), rare.contains(n), "{}", String::from_utf8_lossy(n));
175 }
176 }
177 assert!(rarely_repeats(4, b":path", b"/a?b=1"));
178 assert!(!rarely_repeats(4, b":path", b"/a"));
179 for n in [&b"x-request-id"[..], b"traceparent", b"tracestate"] {
180 assert!(rarely_repeats(0, n, b"1"));
181 }
182 assert!(!rarely_repeats(0, b"x-other", b"1"));
183 }
184}