1use super::{
15 integer, string,
16 table::{Header, Table},
17};
18
19const INDEXED: u8 = 0b1000_0000;
21const LITERAL_WITH_INDEXING: u8 = 0b0100_0000;
22const LITERAL_NEVER_INDEXED: u8 = 0b0001_0000;
23const SIZE_UPDATE: u8 = 0b0010_0000;
24
25const NEVER_INDEXED: [&[u8]; 3] = [b"authorization", b"proxy-authorization", b"cookie"];
28
29#[derive(Debug)]
31pub struct Encoder {
32 table: Table,
34 queued_size_update: Option<usize>,
37 use_huffman: bool,
39}
40
41impl Encoder {
42 #[inline]
46 pub fn new(max_table_size: usize) -> Self {
47 Encoder {
48 table: Table::with_max_size(max_table_size),
49 queued_size_update: None,
50 use_huffman: true,
51 }
52 }
53
54 #[inline]
57 pub fn queue_size_update(&mut self, size: usize) {
58 self.queued_size_update = Some(match self.queued_size_update {
59 Some(current) => current.max(size),
60 None => size,
61 });
62 }
63
64 #[inline]
67 pub fn set_use_huffman(&mut self, use_huffman: bool) {
68 self.use_huffman = use_huffman;
69 }
70
71 #[cfg(test)]
73 #[inline]
74 pub(crate) fn table(&self) -> &Table {
75 &self.table
76 }
77
78 #[inline]
86 pub fn encode(&mut self, headers: &[Header], out: &mut Vec<u8>) {
87 if let Some(size) = self.queued_size_update.take() {
88 self.table.set_max_size(size);
89 integer::encode(out, size as u64, 5, SIZE_UPDATE);
90 }
91
92 for header in headers {
93 let name = header.name();
94 if NEVER_INDEXED.contains(&name) {
95 match self.table.find_name(name) {
96 Some(index) => integer::encode(out, index as u64, 4, LITERAL_NEVER_INDEXED),
97 None => {
98 out.push(LITERAL_NEVER_INDEXED);
99 self.encode_string(out, name);
100 }
101 }
102 self.encode_string(out, header.value());
103 continue;
104 }
105
106 match self.table.find(name, header.value()) {
107 Some(index) => {
108 integer::encode(out, index as u64, 7, INDEXED);
109 }
110 None => {
111 match self.table.find_name(name) {
112 Some(index) => integer::encode(out, index as u64, 6, LITERAL_WITH_INDEXING),
113 None => {
114 out.push(LITERAL_WITH_INDEXING);
115 self.encode_string(out, name);
116 }
117 }
118 self.encode_string(out, header.value());
119 self.table.add(header.clone());
123 }
124 }
125 }
126 }
127
128 #[inline]
129 fn encode_string(&self, out: &mut Vec<u8>, value: &[u8]) {
130 let huffman_len = self
131 .use_huffman
132 .then(|| string::huffman_encoded_len_if_shorter(value))
133 .flatten();
134 string::encode(out, value, huffman_len);
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::h2::hpack::decode::Decoder;
142
143 #[inline]
145 fn headers(list: &[(&str, &str)]) -> Vec<Header> {
146 list.iter()
147 .map(|(name, value)| Header::new(name.as_bytes().to_vec(), value.as_bytes().to_vec()))
148 .collect()
149 }
150
151 #[inline]
152 fn encode(encoder: &mut Encoder, list: &[(&str, &str)]) -> Vec<u8> {
153 let list = headers(list);
154 let mut out = Vec::new();
155 encoder.encode(&list, &mut out);
156 out
157 }
158
159 #[inline]
160 fn decode(encoder: &Encoder, wire: &[u8]) -> Vec<(String, String)> {
161 let mut decoder = Decoder::new(encoder.table().max_size());
162 decoder
163 .decode(wire, &mut 0)
164 .unwrap()
165 .into_iter()
166 .map(|h| {
167 (
168 String::from_utf8(h.name().to_vec()).unwrap(),
169 String::from_utf8(h.value().to_vec()).unwrap(),
170 )
171 })
172 .collect()
173 }
174
175 #[inline]
176 fn hex_to_bytes(hex: &str) -> Vec<u8> {
177 hex.as_bytes()
178 .chunks_exact(2)
179 .map(|pair| {
180 let hi = (pair[0] as char).to_digit(16).unwrap() as u8;
181 let lo = (pair[1] as char).to_digit(16).unwrap() as u8;
182 (hi << 4) | lo
183 })
184 .collect()
185 }
186
187 #[test]
189 fn literal_new_name_indexed() {
190 let mut encoder = Encoder::new(4096);
191 encoder.set_use_huffman(false);
192 let out = encode(&mut encoder, &[("custom-key", "custom-header")]);
193 assert_eq!(
194 out,
195 hex_to_bytes("400a637573746f6d2d6b65790d637573746f6d2d686561646572")
196 );
197 assert_eq!(encoder.table().dynamic_len(), 1);
198 }
199
200 #[test]
202 fn c3_request_walkthrough() {
203 let mut encoder = Encoder::new(4096);
204 encoder.set_use_huffman(false);
205
206 assert_eq!(
208 encode(
209 &mut encoder,
210 &[
211 (":method", "GET"),
212 (":scheme", "http"),
213 (":path", "/"),
214 (":authority", "www.example.com"),
215 ]
216 ),
217 hex_to_bytes("828684410f7777772e6578616d706c652e636f6d")
218 );
219
220 assert_eq!(
222 encode(
223 &mut encoder,
224 &[
225 (":method", "GET"),
226 (":scheme", "http"),
227 (":path", "/"),
228 (":authority", "www.example.com"),
229 ("cache-control", "no-cache"),
230 ]
231 ),
232 hex_to_bytes("828684be58086e6f2d6361636865")
233 );
234
235 assert_eq!(
237 encode(
238 &mut encoder,
239 &[
240 (":method", "GET"),
241 (":scheme", "https"),
242 (":path", "/index.html"),
243 (":authority", "www.example.com"),
244 ("custom-key", "custom-value"),
245 ]
246 ),
247 hex_to_bytes("828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565")
248 );
249 }
250
251 #[test]
253 fn c4_request_walkthrough() {
254 let mut encoder = Encoder::new(4096);
255
256 assert_eq!(
258 encode(
259 &mut encoder,
260 &[
261 (":method", "GET"),
262 (":scheme", "http"),
263 (":path", "/"),
264 (":authority", "www.example.com"),
265 ]
266 ),
267 hex_to_bytes("828684418cf1e3c2e5f23a6ba0ab90f4ff")
268 );
269
270 assert_eq!(
272 encode(
273 &mut encoder,
274 &[
275 (":method", "GET"),
276 (":scheme", "http"),
277 (":path", "/"),
278 (":authority", "www.example.com"),
279 ("cache-control", "no-cache"),
280 ]
281 ),
282 hex_to_bytes("828684be5886a8eb10649cbf")
283 );
284
285 assert_eq!(
287 encode(
288 &mut encoder,
289 &[
290 (":method", "GET"),
291 (":scheme", "https"),
292 (":path", "/index.html"),
293 (":authority", "www.example.com"),
294 ("custom-key", "custom-value"),
295 ]
296 ),
297 hex_to_bytes("828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf")
298 );
299 }
300
301 #[test]
304 fn c5_response_walkthrough() {
305 let mut encoder = Encoder::new(256);
306 encoder.set_use_huffman(false);
307
308 assert_eq!(
310 encode(
311 &mut encoder,
312 &[
313 (":status", "302"),
314 ("cache-control", "private"),
315 ("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
316 ("location", "https://www.example.com"),
317 ]
318 ),
319 hex_to_bytes(
320 "4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d"
321 )
322 );
323 assert_eq!(encoder.table().dynamic_len(), 4);
324
325 assert_eq!(
327 encode(
328 &mut encoder,
329 &[
330 (":status", "307"),
331 ("cache-control", "private"),
332 ("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
333 ("location", "https://www.example.com"),
334 ]
335 ),
336 hex_to_bytes("4803333037c1c0bf")
337 );
338 assert_eq!(encoder.table().dynamic_len(), 4);
339
340 assert_eq!(
342 encode(
343 &mut encoder,
344 &[
345 (":status", "200"),
346 ("cache-control", "private"),
347 ("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
348 ("location", "https://www.example.com"),
349 ("content-encoding", "gzip"),
350 (
351 "set-cookie",
352 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
353 ),
354 ]
355 ),
356 hex_to_bytes(
357 "88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31"
358 )
359 );
360 assert_eq!(encoder.table().dynamic_len(), 3);
361 }
362
363 #[test]
366 fn c6_response_walkthrough() {
367 let mut encoder = Encoder::new(256);
368
369 assert_eq!(
371 encode(
372 &mut encoder,
373 &[
374 (":status", "302"),
375 ("cache-control", "private"),
376 ("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
377 ("location", "https://www.example.com"),
378 ]
379 ),
380 hex_to_bytes(
381 "488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3"
382 )
383 );
384 assert_eq!(encoder.table().dynamic_len(), 4);
385
386 assert_eq!(
388 encode(
389 &mut encoder,
390 &[
391 (":status", "307"),
392 ("cache-control", "private"),
393 ("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
394 ("location", "https://www.example.com"),
395 ]
396 ),
397 hex_to_bytes("4883640effc1c0bf")
398 );
399 assert_eq!(encoder.table().dynamic_len(), 4);
400
401 assert_eq!(
403 encode(
404 &mut encoder,
405 &[
406 (":status", "200"),
407 ("cache-control", "private"),
408 ("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
409 ("location", "https://www.example.com"),
410 ("content-encoding", "gzip"),
411 (
412 "set-cookie",
413 "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
414 ),
415 ]
416 ),
417 hex_to_bytes(
418 "88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007"
419 )
420 );
421 assert_eq!(encoder.table().dynamic_len(), 3);
422 }
423
424 #[test]
427 fn non_sensitive_is_indexed() {
428 let mut encoder = Encoder::new(4096);
429 encoder.set_use_huffman(false);
430 let out = encode(&mut encoder, &[("password", "secret")]);
431 assert_eq!(out, hex_to_bytes("400870617373776f726406736563726574"));
433 assert_eq!(encoder.table().dynamic_len(), 1);
434 }
435
436 #[test]
439 fn sensitive_headers_never_indexed() {
440 for name in ["authorization", "proxy-authorization", "cookie"] {
441 let mut encoder = Encoder::new(4096);
442 encoder.set_use_huffman(false);
443 let out = encode(&mut encoder, &[(name, "secret-value")]);
444
445 assert_eq!(out[0], 0x1f, "{name}");
449 let decoded = decode(&encoder, &out);
450 assert_eq!(
451 decoded,
452 vec![(name.to_string(), "secret-value".into())],
453 "{name}"
454 );
455 assert_eq!(encoder.table().dynamic_len(), 0, "{name}");
457 }
458 }
459
460 #[test]
463 fn sensitive_uses_static_name_index() {
464 let mut encoder = Encoder::new(4096);
465 encoder.set_use_huffman(false);
466 let out = encode(&mut encoder, &[("authorization", "Bearer xyz")]);
467 assert_eq!(out, hex_to_bytes("1f080a4265617265722078797a"));
468 assert_eq!(encoder.table().dynamic_len(), 0);
469 }
470
471 #[test]
474 fn size_update_emitted_at_block_start() {
475 let mut encoder = Encoder::new(4096);
476 encoder.set_use_huffman(false);
477 encoder.queue_size_update(0);
478 let out = encode(&mut encoder, &[(":method", "GET")]);
479 assert_eq!(out, hex_to_bytes("2082"));
480 assert_eq!(encoder.table().max_size(), 0);
481
482 let mut encoder = Encoder::new(4096);
484 encoder.set_use_huffman(false);
485 let _ = encode(&mut encoder, &[("custom-key", "custom-value")]);
486 assert_eq!(encoder.table().dynamic_len(), 1);
487 encoder.queue_size_update(32);
488 let _ = encode(&mut encoder, &[(":method", "GET")]);
489 assert_eq!(encoder.table().dynamic_len(), 0);
490 }
491
492 #[test]
494 fn size_update_grow_reindexes() {
495 let mut encoder = Encoder::new(4096);
496 encoder.set_use_huffman(false);
497 encoder.queue_size_update(0);
498 let _ = encode(&mut encoder, &[("a", "1")]);
499 encoder.queue_size_update(4096);
500 let _ = encode(&mut encoder, &[("b", "2")]);
501 assert_eq!(encoder.table().get(62).unwrap().name(), b"b");
502 assert_eq!(encoder.table().get(63), None);
503 }
504
505 #[test]
508 fn round_trip() {
509 let lists: [&[(&str, &str)]; 5] = [
510 &[(":method", "GET"), (":scheme", "https")],
511 &[
512 (":status", "302"),
513 ("cache-control", "private"),
514 ("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
515 ("location", "https://www.example.com"),
516 ("content-encoding", "gzip"),
517 ("authorization", "Bearer sekrit"),
518 ("x-empty", ""),
519 ],
520 &[("x-long", &"v".repeat(300))],
521 &[("x-huffman-ok", "custom-value"), ("cookie", "a=b; c=d")],
522 &[("accept-encoding", "gzip, deflate"), ("te", "trailers")],
523 ];
524
525 for (i, list) in lists.iter().enumerate() {
526 for table_size in [0, 16, 256, 4096] {
527 for use_huffman in [false, true] {
528 let mut encoder = Encoder::new(table_size);
529 encoder.set_use_huffman(use_huffman);
530 let wire = encode(&mut encoder, list);
531 let decoded = decode(&encoder, &wire);
532 let expected = list
533 .iter()
534 .map(|(name, value)| (name.to_string(), value.to_string()))
535 .collect::<Vec<_>>();
536 assert_eq!(
537 decoded, expected,
538 "list {i}, table {table_size}, huffman {use_huffman}"
539 );
540 }
541 }
542 }
543 }
544
545 #[test]
547 fn empty_value_round_trip() {
548 let mut encoder = Encoder::new(4096);
549 let out = encode(&mut encoder, &[("x-empty", "")]);
550 assert_eq!(decode(&encoder, &out), vec![("x-empty".into(), "".into())]);
551 }
552}