Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
hpack.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
6#include <algorithm>
7#include <cstring>
8#include <limits>
9
11{
12 namespace
13 {
14 // HPACK static table (RFC 7541 Appendix A)
15 const http_header static_table_entries[] = {
16 {"", ""}, // Index 0 (unused)
17 {":authority", ""}, // 1
18 {":method", "GET"}, // 2
19 {":method", "POST"}, // 3
20 {":path", "/"}, // 4
21 {":path", "/index.html"}, // 5
22 {":scheme", "http"}, // 6
23 {":scheme", "https"}, // 7
24 {":status", "200"}, // 8
25 {":status", "204"}, // 9
26 {":status", "206"}, // 10
27 {":status", "304"}, // 11
28 {":status", "400"}, // 12
29 {":status", "404"}, // 13
30 {":status", "500"}, // 14
31 {"accept-charset", ""}, // 15
32 {"accept-encoding", "gzip, deflate"}, // 16
33 {"accept-language", ""}, // 17
34 {"accept-ranges", ""}, // 18
35 {"accept", ""}, // 19
36 {"access-control-allow-origin", ""}, // 20
37 {"age", ""}, // 21
38 {"allow", ""}, // 22
39 {"authorization", ""}, // 23
40 {"cache-control", ""}, // 24
41 {"content-disposition", ""}, // 25
42 {"content-encoding", ""}, // 26
43 {"content-language", ""}, // 27
44 {"content-length", ""}, // 28
45 {"content-location", ""}, // 29
46 {"content-range", ""}, // 30
47 {"content-type", ""}, // 31
48 {"cookie", ""}, // 32
49 {"date", ""}, // 33
50 {"etag", ""}, // 34
51 {"expect", ""}, // 35
52 {"expires", ""}, // 36
53 {"from", ""}, // 37
54 {"host", ""}, // 38
55 {"if-match", ""}, // 39
56 {"if-modified-since", ""}, // 40
57 {"if-none-match", ""}, // 41
58 {"if-range", ""}, // 42
59 {"if-unmodified-since", ""}, // 43
60 {"last-modified", ""}, // 44
61 {"link", ""}, // 45
62 {"location", ""}, // 46
63 {"max-forwards", ""}, // 47
64 {"proxy-authenticate", ""}, // 48
65 {"proxy-authorization", ""}, // 49
66 {"range", ""}, // 50
67 {"referer", ""}, // 51
68 {"refresh", ""}, // 52
69 {"retry-after", ""}, // 53
70 {"server", ""}, // 54
71 {"set-cookie", ""}, // 55
72 {"strict-transport-security", ""}, // 56
73 {"transfer-encoding", ""}, // 57
74 {"user-agent", ""}, // 58
75 {"vary", ""}, // 59
76 {"via", ""}, // 60
77 {"www-authenticate", ""} // 61
78 };
79
80 constexpr size_t static_table_size = 61;
81 }
82
83 // Static table implementation
84 auto static_table::get(size_t index) -> std::optional<http_header>
85 {
86 if (index == 0 || index > static_table_size)
87 {
88 return std::nullopt;
89 }
90 return static_table_entries[index];
91 }
92
93 auto static_table::find(std::string_view name, std::string_view value)
94 -> size_t
95 {
96 for (size_t i = 1; i <= static_table_size; ++i)
97 {
98 const auto& entry = static_table_entries[i];
99 if (entry.name == name)
100 {
101 if (value.empty() || entry.value == value)
102 {
103 return i;
104 }
105 }
106 }
107 return 0;
108 }
109
110 // Dynamic table implementation
112 : max_size_(max_size)
113 {
114 }
115
116 auto dynamic_table::insert(std::string_view name, std::string_view value) -> void
117 {
118 http_header header{std::string(name), std::string(value)};
119 size_t entry_size = header.size();
120
121 // Evict entries if needed
122 evict_to_size(max_size_ - entry_size);
123
124 // Insert at beginning
125 entries_.push_front(std::move(header));
126 current_size_ += entry_size;
127 }
128
129 auto dynamic_table::get(size_t index) const -> std::optional<http_header>
130 {
131 if (index >= entries_.size())
132 {
133 return std::nullopt;
134 }
135 return entries_[index];
136 }
137
138 auto dynamic_table::find(std::string_view name, std::string_view value) const
139 -> std::optional<size_t>
140 {
141 for (size_t i = 0; i < entries_.size(); ++i)
142 {
143 const auto& entry = entries_[i];
144 if (entry.name == name)
145 {
146 if (value.empty() || entry.value == value)
147 {
148 return i;
149 }
150 }
151 }
152 return std::nullopt;
153 }
154
155 auto dynamic_table::set_max_size(size_t size) -> void
156 {
157 max_size_ = size;
158 evict_to_size(max_size_);
159 }
160
161 auto dynamic_table::current_size() const -> size_t
162 {
163 return current_size_;
164 }
165
166 auto dynamic_table::max_size() const -> size_t
167 {
168 return max_size_;
169 }
170
171 auto dynamic_table::entry_count() const -> size_t
172 {
173 return entries_.size();
174 }
175
176 auto dynamic_table::clear() -> void
177 {
178 entries_.clear();
179 current_size_ = 0;
180 }
181
182 auto dynamic_table::evict_to_size(size_t target_size) -> void
183 {
184 while (current_size_ > target_size && !entries_.empty())
185 {
186 current_size_ -= entries_.back().size();
187 entries_.pop_back();
188 }
189 }
190
191 // HPACK encoder implementation
192 hpack_encoder::hpack_encoder(size_t max_table_size)
193 : table_(max_table_size)
194 {
195 }
196
197 auto hpack_encoder::encode(const std::vector<http_header>& headers)
198 -> std::vector<uint8_t>
199 {
200 std::vector<uint8_t> result;
201
202 for (const auto& header : headers)
203 {
204 // Try to find in static table first
205 size_t static_index = static_table::find(header.name, header.value);
206 if (static_index > 0)
207 {
208 // Indexed header field representation
209 auto encoded = encode_indexed(static_index);
210 result.insert(result.end(), encoded.begin(), encoded.end());
211 continue;
212 }
213
214 // Try to find in dynamic table
215 auto dynamic_index = table_.find(header.name, header.value);
216 if (dynamic_index.has_value())
217 {
218 // Indexed header field representation
219 size_t index = static_table::size() + 1 + dynamic_index.value();
220 auto encoded = encode_indexed(index);
221 result.insert(result.end(), encoded.begin(), encoded.end());
222 continue;
223 }
224
225 // Check if name is in static table
226 size_t name_index = static_table::find(header.name, "");
227 if (name_index > 0)
228 {
229 // Literal with incremental indexing - indexed name
230 auto encoded = encode_literal_with_indexing(name_index, header.value);
231 result.insert(result.end(), encoded.begin(), encoded.end());
232 table_.insert(header.name, header.value);
233 }
234 else
235 {
236 // Check if name is in dynamic table
237 auto dynamic_name_index = table_.find(header.name, "");
238 if (dynamic_name_index.has_value())
239 {
240 size_t index = static_table::size() + 1 + dynamic_name_index.value();
241 auto encoded = encode_literal_with_indexing(index, header.value);
242 result.insert(result.end(), encoded.begin(), encoded.end());
243 table_.insert(header.name, header.value);
244 }
245 else
246 {
247 // Literal with incremental indexing - new name
248 auto encoded = encode_literal_with_indexing(header.name, header.value);
249 result.insert(result.end(), encoded.begin(), encoded.end());
250 table_.insert(header.name, header.value);
251 }
252 }
253 }
254
255 return result;
256 }
257
258 auto hpack_encoder::set_max_table_size(size_t size) -> void
259 {
260 table_.set_max_size(size);
261 }
262
263 auto hpack_encoder::table_size() const -> size_t
264 {
265 return table_.current_size();
266 }
267
268 auto hpack_encoder::encode_integer(uint64_t value, uint8_t prefix_bits)
269 -> std::vector<uint8_t>
270 {
271 std::vector<uint8_t> result;
272 uint8_t max_prefix = (1 << prefix_bits) - 1;
273
274 if (value < max_prefix)
275 {
276 result.push_back(static_cast<uint8_t>(value));
277 }
278 else
279 {
280 result.push_back(max_prefix);
281 value -= max_prefix;
282
283 while (value >= 128)
284 {
285 result.push_back(static_cast<uint8_t>((value % 128) + 128));
286 value /= 128;
287 }
288 result.push_back(static_cast<uint8_t>(value));
289 }
290
291 return result;
292 }
293
294 auto hpack_encoder::encode_string(std::string_view str, bool huffman)
295 -> std::vector<uint8_t>
296 {
297 std::vector<uint8_t> result;
298
299 if (huffman)
300 {
301 // Only use Huffman coding when it actually shrinks the string;
302 // RFC 7541 5.2 lets the encoder choose per string.
303 auto encoded = huffman::encode(str);
304 if (encoded.size() < str.size())
305 {
306 auto length_bytes = encode_integer(encoded.size(), 7);
307 length_bytes[0] |= 0x80; // Set H bit
308 result.insert(result.end(), length_bytes.begin(), length_bytes.end());
309 result.insert(result.end(), encoded.begin(), encoded.end());
310 return result;
311 }
312 }
313
314 // Raw literal: length with the H bit clear, then the octets verbatim.
315 auto length_bytes = encode_integer(str.size(), 7);
316 result.insert(result.end(), length_bytes.begin(), length_bytes.end());
317 result.insert(result.end(), str.begin(), str.end());
318
319 return result;
320 }
321
322 auto hpack_encoder::encode_indexed(size_t index) -> std::vector<uint8_t>
323 {
324 auto result = encode_integer(index, 7);
325 result[0] |= 0x80; // Set indexed bit
326 return result;
327 }
328
330 std::string_view value)
331 -> std::vector<uint8_t>
332 {
333 std::vector<uint8_t> result;
334
335 // First byte: 01xxxxxx (literal with incremental indexing, new name)
336 result.push_back(0x40);
337
338 // Encode name
339 auto name_bytes = encode_string(name);
340 result.insert(result.end(), name_bytes.begin(), name_bytes.end());
341
342 // Encode value
343 auto value_bytes = encode_string(value);
344 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
345
346 return result;
347 }
348
350 std::string_view value)
351 -> std::vector<uint8_t>
352 {
353 std::vector<uint8_t> result;
354
355 // Encode name index with 6-bit prefix
356 auto index_bytes = encode_integer(name_index, 6);
357 index_bytes[0] |= 0x40; // Set literal with indexing bit
358 result.insert(result.end(), index_bytes.begin(), index_bytes.end());
359
360 // Encode value
361 auto value_bytes = encode_string(value);
362 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
363
364 return result;
365 }
366
368 std::string_view value)
369 -> std::vector<uint8_t>
370 {
371 std::vector<uint8_t> result;
372
373 // First byte: 0000xxxx (literal without indexing, new name)
374 result.push_back(0x00);
375
376 // Encode name
377 auto name_bytes = encode_string(name);
378 result.insert(result.end(), name_bytes.begin(), name_bytes.end());
379
380 // Encode value
381 auto value_bytes = encode_string(value);
382 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
383
384 return result;
385 }
386
388 std::string_view value)
389 -> std::vector<uint8_t>
390 {
391 std::vector<uint8_t> result;
392
393 // Encode name index with 4-bit prefix
394 auto index_bytes = encode_integer(name_index, 4);
395 // First byte already has 0000 prefix for literal without indexing
396 result.insert(result.end(), index_bytes.begin(), index_bytes.end());
397
398 // Encode value
399 auto value_bytes = encode_string(value);
400 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
401
402 return result;
403 }
404
405 // HPACK decoder implementation
406 hpack_decoder::hpack_decoder(size_t max_table_size)
407 : table_(max_table_size)
408 {
409 }
410
411 auto hpack_decoder::decode(std::span<const uint8_t> data)
413 {
414 std::vector<http_header> headers;
415 auto remaining = data;
416
417 while (!remaining.empty())
418 {
419 uint8_t first_byte = remaining[0];
420
421 if (first_byte & 0x80)
422 {
423 // Indexed header field representation
424 auto index_result = decode_integer(remaining, 7);
425 if (index_result.is_err())
426 {
427 return index_result.error();
428 }
429 size_t index = index_result.value();
430
431 auto header_result = get_indexed_header(index);
432 if (header_result.is_err())
433 {
434 return header_result.error();
435 }
436
437 headers.push_back(header_result.value());
438 }
439 else if (first_byte & 0x40)
440 {
441 // Literal with incremental indexing
442 auto name_index_result = decode_integer(remaining, 6);
443 if (name_index_result.is_err())
444 {
445 return name_index_result.error();
446 }
447 size_t name_index = name_index_result.value();
448
449 std::string name;
450 if (name_index == 0)
451 {
452 // New name
453 auto name_result = decode_string(remaining);
454 if (name_result.is_err())
455 {
456 return name_result.error();
457 }
458 name = name_result.value();
459 }
460 else
461 {
462 // Indexed name
463 auto header_result = get_indexed_header(name_index);
464 if (header_result.is_err())
465 {
466 return header_result.error();
467 }
468 name = header_result.value().name;
469 }
470
471 // Decode value
472 auto value_result = decode_string(remaining);
473 if (value_result.is_err())
474 {
475 return value_result.error();
476 }
477 std::string value = value_result.value();
478
479 headers.emplace_back(name, value);
480 table_.insert(name, value);
481 }
482 else
483 {
484 // Literal without indexing or never indexed
485 uint8_t prefix_bits = (first_byte & 0x10) ? 4 : 4;
486
487 auto name_index_result = decode_integer(remaining, prefix_bits);
488 if (name_index_result.is_err())
489 {
490 return name_index_result.error();
491 }
492 size_t name_index = name_index_result.value();
493
494 std::string name;
495 if (name_index == 0)
496 {
497 // New name
498 auto name_result = decode_string(remaining);
499 if (name_result.is_err())
500 {
501 return name_result.error();
502 }
503 name = name_result.value();
504 }
505 else
506 {
507 // Indexed name
508 auto header_result = get_indexed_header(name_index);
509 if (header_result.is_err())
510 {
511 return header_result.error();
512 }
513 name = header_result.value().name;
514 }
515
516 // Decode value
517 auto value_result = decode_string(remaining);
518 if (value_result.is_err())
519 {
520 return value_result.error();
521 }
522
523 headers.emplace_back(name, value_result.value());
524 }
525 }
526
527 return headers;
528 }
529
530 auto hpack_decoder::set_max_table_size(size_t size) -> void
531 {
532 table_.set_max_size(size);
533 }
534
535 auto hpack_decoder::table_size() const -> size_t
536 {
537 return table_.current_size();
538 }
539
540 auto hpack_decoder::decode_integer(std::span<const uint8_t>& data,
541 uint8_t prefix_bits)
543 {
544 if (data.empty())
545 {
546 return error_info(100, "Insufficient data for integer", "hpack");
547 }
548
549 uint8_t prefix_mask = (1 << prefix_bits) - 1;
550 uint64_t value = data[0] & prefix_mask;
551 data = data.subspan(1);
552
553 if (value < prefix_mask)
554 {
555 return value;
556 }
557
558 // Multi-byte integer
559 uint64_t m = 0;
560 do
561 {
562 if (data.empty())
563 {
564 return error_info(101, "Incomplete integer encoding", "hpack");
565 }
566
567 uint8_t byte = data[0];
568 data = data.subspan(1);
569
570 const uint64_t payload = byte & 0x7F;
571 if (m >= 64 || payload > ((std::numeric_limits<uint64_t>::max() - value) >> m))
572 {
573 return error_info(102, "Integer overflow", "hpack");
574 }
575 value += payload << m;
576
577 if ((byte & 0x80) == 0)
578 {
579 break;
580 }
581 m += 7;
582 } while (true);
583
584 return value;
585 }
586
587 auto hpack_decoder::decode_string(std::span<const uint8_t>& data)
589 {
590 if (data.empty())
591 {
592 return error_info(103, "Insufficient data for string", "hpack");
593 }
594
595 bool huffman = (data[0] & 0x80) != 0;
596
597 auto length_result = decode_integer(data, 7);
598 if (length_result.is_err())
599 {
600 return length_result.error();
601 }
602
603 size_t length = length_result.value();
604
605 if (data.size() < length)
606 {
607 return error_info(104, "Insufficient data for string value", "hpack");
608 }
609
610 std::string result;
611 if (huffman)
612 {
613 auto decoded = huffman::decode(data.subspan(0, length));
614 if (decoded.is_err())
615 {
616 return decoded.error();
617 }
618 result = std::move(decoded.value());
619 }
620 else
621 {
622 result.assign(data.begin(), data.begin() + length);
623 }
624
625 data = data.subspan(length);
626 return result;
627 }
628
629 auto hpack_decoder::get_indexed_header(size_t index) const
631 {
632 if (index == 0)
633 {
634 return error_info(105, "Invalid index 0", "hpack");
635 }
636
637 // Check static table
638 if (index <= static_table::size())
639 {
640 auto header = static_table::get(index);
641 if (header.has_value())
642 {
643 return std::move(header.value());
644 }
645 return error_info(106, "Invalid static table index", "hpack");
646 }
647
648 // Check dynamic table
649 size_t dynamic_index = index - static_table::size() - 1;
650 auto header = table_.get(dynamic_index);
651 if (header.has_value())
652 {
653 return std::move(header.value());
654 }
655
656 return error_info(107, "Invalid dynamic table index", "hpack");
657 }
658
659 // Huffman coding (RFC 7541 Appendix B)
660 namespace huffman
661 {
662 namespace
663 {
664 // RFC 7541 Appendix B static Huffman code: {code, bit length} per
665 // symbol. Index 0..255 are octet values; index 256 is the EOS symbol.
666 struct huffman_symbol
667 {
668 uint32_t code;
669 uint8_t bits;
670 };
671
672 constexpr huffman_symbol kHuffmanTable[257] = {
673 {0x1ff8u, 13}, {0x7fffd8u, 23}, {0xfffffe2u, 28}, {0xfffffe3u, 28},
674 {0xfffffe4u, 28}, {0xfffffe5u, 28}, {0xfffffe6u, 28}, {0xfffffe7u, 28},
675 {0xfffffe8u, 28}, {0xffffeau, 24}, {0x3ffffffcu, 30},{0xfffffe9u, 28},
676 {0xfffffeau, 28}, {0x3ffffffdu, 30},{0xfffffebu, 28}, {0xfffffecu, 28},
677 {0xfffffedu, 28}, {0xfffffeeu, 28}, {0xfffffefu, 28}, {0xffffff0u, 28},
678 {0xffffff1u, 28}, {0xffffff2u, 28}, {0x3ffffffeu, 30},{0xffffff3u, 28},
679 {0xffffff4u, 28}, {0xffffff5u, 28}, {0xffffff6u, 28}, {0xffffff7u, 28},
680 {0xffffff8u, 28}, {0xffffff9u, 28}, {0xffffffau, 28}, {0xffffffbu, 28},
681 {0x14u, 6}, {0x3f8u, 10}, {0x3f9u, 10}, {0xffau, 12},
682 {0x1ff9u, 13}, {0x15u, 6}, {0xf8u, 8}, {0x7fau, 11},
683 {0x3fau, 10}, {0x3fbu, 10}, {0xf9u, 8}, {0x7fbu, 11},
684 {0xfau, 8}, {0x16u, 6}, {0x17u, 6}, {0x18u, 6},
685 {0x0u, 5}, {0x1u, 5}, {0x2u, 5}, {0x19u, 6},
686 {0x1au, 6}, {0x1bu, 6}, {0x1cu, 6}, {0x1du, 6},
687 {0x1eu, 6}, {0x1fu, 6}, {0x5cu, 7}, {0xfbu, 8},
688 {0x7ffcu, 15}, {0x20u, 6}, {0xffbu, 12}, {0x3fcu, 10},
689 {0x1ffau, 13}, {0x21u, 6}, {0x5du, 7}, {0x5eu, 7},
690 {0x5fu, 7}, {0x60u, 7}, {0x61u, 7}, {0x62u, 7},
691 {0x63u, 7}, {0x64u, 7}, {0x65u, 7}, {0x66u, 7},
692 {0x67u, 7}, {0x68u, 7}, {0x69u, 7}, {0x6au, 7},
693 {0x6bu, 7}, {0x6cu, 7}, {0x6du, 7}, {0x6eu, 7},
694 {0x6fu, 7}, {0x70u, 7}, {0x71u, 7}, {0x72u, 7},
695 {0xfcu, 8}, {0x73u, 7}, {0xfdu, 8}, {0x1ffbu, 13},
696 {0x7fff0u, 19}, {0x1ffcu, 13}, {0x3ffcu, 14}, {0x22u, 6},
697 {0x7ffdu, 15}, {0x3u, 5}, {0x23u, 6}, {0x4u, 5},
698 {0x24u, 6}, {0x5u, 5}, {0x25u, 6}, {0x26u, 6},
699 {0x27u, 6}, {0x6u, 5}, {0x74u, 7}, {0x75u, 7},
700 {0x28u, 6}, {0x29u, 6}, {0x2au, 6}, {0x7u, 5},
701 {0x2bu, 6}, {0x76u, 7}, {0x2cu, 6}, {0x8u, 5},
702 {0x9u, 5}, {0x2du, 6}, {0x77u, 7}, {0x78u, 7},
703 {0x79u, 7}, {0x7au, 7}, {0x7bu, 7}, {0x7ffeu, 15},
704 {0x7fcu, 11}, {0x3ffdu, 14}, {0x1ffdu, 13}, {0xffffffcu, 28},
705 {0xfffe6u, 20}, {0x3fffd2u, 22}, {0xfffe7u, 20}, {0xfffe8u, 20},
706 {0x3fffd3u, 22}, {0x3fffd4u, 22}, {0x3fffd5u, 22}, {0x7fffd9u, 23},
707 {0x3fffd6u, 22}, {0x7fffdau, 23}, {0x7fffdbu, 23}, {0x7fffdcu, 23},
708 {0x7fffddu, 23}, {0x7fffdeu, 23}, {0xffffebu, 24}, {0x7fffdfu, 23},
709 {0xffffecu, 24}, {0xffffedu, 24}, {0x3fffd7u, 22}, {0x7fffe0u, 23},
710 {0xffffeeu, 24}, {0x7fffe1u, 23}, {0x7fffe2u, 23}, {0x7fffe3u, 23},
711 {0x7fffe4u, 23}, {0x1fffdcu, 21}, {0x3fffd8u, 22}, {0x7fffe5u, 23},
712 {0x3fffd9u, 22}, {0x7fffe6u, 23}, {0x7fffe7u, 23}, {0xffffefu, 24},
713 {0x3fffdau, 22}, {0x1fffddu, 21}, {0xfffe9u, 20}, {0x3fffdbu, 22},
714 {0x3fffdcu, 22}, {0x7fffe8u, 23}, {0x7fffe9u, 23}, {0x1fffdeu, 21},
715 {0x7fffeau, 23}, {0x3fffddu, 22}, {0x3fffdeu, 22}, {0xfffff0u, 24},
716 {0x1fffdfu, 21}, {0x3fffdfu, 22}, {0x7fffebu, 23}, {0x7fffecu, 23},
717 {0x1fffe0u, 21}, {0x1fffe1u, 21}, {0x3fffe0u, 22}, {0x1fffe2u, 21},
718 {0x7fffedu, 23}, {0x3fffe1u, 22}, {0x7fffeeu, 23}, {0x7fffefu, 23},
719 {0xfffeau, 20}, {0x3fffe2u, 22}, {0x3fffe3u, 22}, {0x3fffe4u, 22},
720 {0x7ffff0u, 23}, {0x3fffe5u, 22}, {0x3fffe6u, 22}, {0x7ffff1u, 23},
721 {0x3ffffe0u, 26}, {0x3ffffe1u, 26}, {0xfffebu, 20}, {0x7fff1u, 19},
722 {0x3fffe7u, 22}, {0x7ffff2u, 23}, {0x3fffe8u, 22}, {0x1ffffecu, 25},
723 {0x3ffffe2u, 26}, {0x3ffffe3u, 26}, {0x3ffffe4u, 26}, {0x7ffffdeu, 27},
724 {0x7ffffdfu, 27}, {0x3ffffe5u, 26}, {0xfffff1u, 24}, {0x1ffffedu, 25},
725 {0x7fff2u, 19}, {0x1fffe3u, 21}, {0x3ffffe6u, 26}, {0x7ffffe0u, 27},
726 {0x7ffffe1u, 27}, {0x3ffffe7u, 26}, {0x7ffffe2u, 27}, {0xfffff2u, 24},
727 {0x1fffe4u, 21}, {0x1fffe5u, 21}, {0x3ffffe8u, 26}, {0x3ffffe9u, 26},
728 {0xffffffdu, 28}, {0x7ffffe3u, 27}, {0x7ffffe4u, 27}, {0x7ffffe5u, 27},
729 {0xfffecu, 20}, {0xfffff3u, 24}, {0xfffedu, 20}, {0x1fffe6u, 21},
730 {0x3fffe9u, 22}, {0x1fffe7u, 21}, {0x1fffe8u, 21}, {0x7ffff3u, 23},
731 {0x3fffeau, 22}, {0x3fffebu, 22}, {0x1ffffeeu, 25}, {0x1ffffefu, 25},
732 {0xfffff4u, 24}, {0xfffff5u, 24}, {0x3ffffeau, 26}, {0x7ffff4u, 23},
733 {0x3ffffebu, 26}, {0x7ffffe6u, 27}, {0x3ffffecu, 26}, {0x3ffffedu, 26},
734 {0x7ffffe7u, 27}, {0x7ffffe8u, 27}, {0x7ffffe9u, 27}, {0x7ffffeau, 27},
735 {0x7ffffebu, 27}, {0xffffffeu, 28}, {0x7ffffecu, 27}, {0x7ffffedu, 27},
736 {0x7ffffeeu, 27}, {0x7ffffefu, 27}, {0x7fffff0u, 27}, {0x3ffffeeu, 26},
737 {0x3fffffffu, 30}, // 256: EOS
738 };
739
740 constexpr int kEosSymbol = 256;
741
742 // Decoding trie node: child indices for bit 0 / bit 1 (-1 if absent)
743 // and the decoded symbol at a leaf (-1 for internal nodes).
744 struct decode_node
745 {
746 int children[2] = {-1, -1};
747 int symbol = -1;
748 };
749
750 auto build_decode_tree() -> std::vector<decode_node>
751 {
752 std::vector<decode_node> nodes(1); // root at index 0
753 for (int sym = 0; sym < 257; ++sym)
754 {
755 const auto& entry = kHuffmanTable[sym];
756 int cur = 0;
757 for (int b = static_cast<int>(entry.bits) - 1; b >= 0; --b)
758 {
759 int bit = static_cast<int>((entry.code >> b) & 1u);
760 int next = nodes[cur].children[bit];
761 if (next == -1)
762 {
763 nodes.push_back(decode_node{});
764 next = static_cast<int>(nodes.size()) - 1;
765 nodes[cur].children[bit] = next;
766 }
767 cur = next;
768 }
769 nodes[cur].symbol = sym;
770 }
771 return nodes;
772 }
773 } // namespace
774
775 auto encode(std::string_view input) -> std::vector<uint8_t>
776 {
777 std::vector<uint8_t> out;
778 out.reserve(input.size());
779
780 uint64_t buffer = 0;
781 int buffer_bits = 0;
782
783 for (unsigned char ch : input)
784 {
785 const auto& entry = kHuffmanTable[ch];
786 buffer = (buffer << entry.bits) | entry.code;
787 buffer_bits += entry.bits;
788
789 while (buffer_bits >= 8)
790 {
791 buffer_bits -= 8;
792 out.push_back(static_cast<uint8_t>(buffer >> buffer_bits));
793 }
794 // Keep only the still-pending low bits to avoid emitting stale data.
795 buffer &= (1ull << buffer_bits) - 1;
796 }
797
798 if (buffer_bits > 0)
799 {
800 // Pad the final byte with the most significant bits of EOS (all 1s).
801 int pad = 8 - buffer_bits;
802 out.push_back(static_cast<uint8_t>((buffer << pad) | ((1u << pad) - 1)));
803 }
804
805 return out;
806 }
807
808 auto decode(std::span<const uint8_t> data) -> Result<std::string>
809 {
810 static const std::vector<decode_node> tree = build_decode_tree();
811
812 std::string out;
813 int node = 0;
814 int partial_bits = 0;
815 bool partial_all_ones = true;
816
817 for (uint8_t byte : data)
818 {
819 for (int i = 7; i >= 0; --i)
820 {
821 int bit = (byte >> i) & 1;
822 node = tree[node].children[bit];
823 if (node == -1)
824 {
825 return error_info(108, "Invalid Huffman code", "hpack");
826 }
827 ++partial_bits;
828 if (bit == 0)
829 {
830 partial_all_ones = false;
831 }
832
833 if (tree[node].symbol >= 0)
834 {
835 if (tree[node].symbol == kEosSymbol)
836 {
837 // RFC 7541 5.2: EOS in the encoded data is an error.
838 return error_info(108, "EOS symbol in Huffman-encoded data",
839 "hpack");
840 }
841 out.push_back(static_cast<char>(tree[node].symbol));
842 node = 0;
843 partial_bits = 0;
844 partial_all_ones = true;
845 }
846 }
847 }
848
849 // RFC 7541 5.2: any trailing bits must be valid EOS padding —
850 // at most 7 bits, all set to 1.
851 if (node != 0 && (partial_bits > 7 || !partial_all_ones))
852 {
853 return error_info(108, "Invalid Huffman padding", "hpack");
854 }
855
856 return out;
857 }
858
859 auto encoded_size(std::string_view input) -> size_t
860 {
861 size_t bits = 0;
862 for (unsigned char ch : input)
863 {
864 bits += kHuffmanTable[ch].bits;
865 }
866 return (bits + 7) / 8;
867 }
868 }
869
870} // namespace kcenon::network::protocols::http2
auto entry_count() const -> size_t
Get number of entries.
Definition hpack.cpp:171
auto max_size() const -> size_t
Get maximum table size.
Definition hpack.cpp:166
auto clear() -> void
Clear all entries.
Definition hpack.cpp:176
auto set_max_size(size_t size) -> void
Set maximum table size.
Definition hpack.cpp:155
auto evict_to_size(size_t target_size) -> void
Definition hpack.cpp:182
auto get(size_t index) const -> std::optional< http_header >
Get header by dynamic table index.
Definition hpack.cpp:129
auto find(std::string_view name, std::string_view value="") const -> std::optional< size_t >
Find header in dynamic table.
Definition hpack.cpp:138
auto insert(std::string_view name, std::string_view value) -> void
Insert header at beginning of table.
Definition hpack.cpp:116
auto current_size() const -> size_t
Get current table size.
Definition hpack.cpp:161
dynamic_table(size_t max_size=4096)
Construct dynamic table with max size.
Definition hpack.cpp:111
auto decode(std::span< const uint8_t > data) -> Result< std::vector< http_header > >
Decode HPACK binary to headers.
Definition hpack.cpp:411
auto decode_string(std::span< const uint8_t > &data) -> Result< std::string >
Definition hpack.cpp:587
auto set_max_table_size(size_t size) -> void
Set maximum dynamic table size.
Definition hpack.cpp:530
auto table_size() const -> size_t
Get current dynamic table size.
Definition hpack.cpp:535
auto decode_integer(std::span< const uint8_t > &data, uint8_t prefix_bits) -> Result< uint64_t >
Definition hpack.cpp:540
auto get_indexed_header(size_t index) const -> Result< http_header >
Definition hpack.cpp:629
hpack_decoder(size_t max_table_size=4096)
Construct decoder with max table size.
Definition hpack.cpp:406
auto encode_literal_with_indexing(std::string_view name, std::string_view value) -> std::vector< uint8_t >
Definition hpack.cpp:329
auto set_max_table_size(size_t size) -> void
Set maximum dynamic table size.
Definition hpack.cpp:258
auto encode(const std::vector< http_header > &headers) -> std::vector< uint8_t >
Encode headers to HPACK binary format.
Definition hpack.cpp:197
hpack_encoder(size_t max_table_size=4096)
Construct encoder with max table size.
Definition hpack.cpp:192
auto table_size() const -> size_t
Get current dynamic table size.
Definition hpack.cpp:263
auto encode_indexed(size_t index) -> std::vector< uint8_t >
Definition hpack.cpp:322
auto encode_literal_without_indexing(std::string_view name, std::string_view value) -> std::vector< uint8_t >
Definition hpack.cpp:367
auto encode_integer(uint64_t value, uint8_t prefix_bits) -> std::vector< uint8_t >
Definition hpack.cpp:268
auto encode_string(std::string_view str, bool huffman=false) -> std::vector< uint8_t >
Definition hpack.cpp:294
static constexpr auto size() -> size_t
Get static table size.
Definition hpack.h:72
static auto get(size_t index) -> std::optional< http_header >
Get static table entry by index.
Definition hpack.cpp:84
static auto find(std::string_view name, std::string_view value="") -> size_t
Find index of header in static table.
Definition hpack.cpp:93
int children[2]
Definition hpack.cpp:746
uint8_t bits
Definition hpack.cpp:669
int symbol
Definition hpack.cpp:747
uint32_t code
Definition hpack.cpp:668
Huffman coding for HPACK string compression.
auto decode(std::span< const uint8_t > data) -> Result< std::string >
Decode Huffman encoded string.
Definition hpack.cpp:808
auto encode(std::string_view input) -> std::vector< uint8_t >
Encode string using Huffman coding.
Definition hpack.cpp:775
auto encoded_size(std::string_view input) -> size_t
Get encoded size for string.
Definition hpack.cpp:859
simple_error error_info
HTTP header name-value pair.
Definition hpack.h:24