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
10{
11 namespace
12 {
13 // HPACK static table (RFC 7541 Appendix A)
14 const http_header static_table_entries[] = {
15 {"", ""}, // Index 0 (unused)
16 {":authority", ""}, // 1
17 {":method", "GET"}, // 2
18 {":method", "POST"}, // 3
19 {":path", "/"}, // 4
20 {":path", "/index.html"}, // 5
21 {":scheme", "http"}, // 6
22 {":scheme", "https"}, // 7
23 {":status", "200"}, // 8
24 {":status", "204"}, // 9
25 {":status", "206"}, // 10
26 {":status", "304"}, // 11
27 {":status", "400"}, // 12
28 {":status", "404"}, // 13
29 {":status", "500"}, // 14
30 {"accept-charset", ""}, // 15
31 {"accept-encoding", "gzip, deflate"}, // 16
32 {"accept-language", ""}, // 17
33 {"accept-ranges", ""}, // 18
34 {"accept", ""}, // 19
35 {"access-control-allow-origin", ""}, // 20
36 {"age", ""}, // 21
37 {"allow", ""}, // 22
38 {"authorization", ""}, // 23
39 {"cache-control", ""}, // 24
40 {"content-disposition", ""}, // 25
41 {"content-encoding", ""}, // 26
42 {"content-language", ""}, // 27
43 {"content-length", ""}, // 28
44 {"content-location", ""}, // 29
45 {"content-range", ""}, // 30
46 {"content-type", ""}, // 31
47 {"cookie", ""}, // 32
48 {"date", ""}, // 33
49 {"etag", ""}, // 34
50 {"expect", ""}, // 35
51 {"expires", ""}, // 36
52 {"from", ""}, // 37
53 {"host", ""}, // 38
54 {"if-match", ""}, // 39
55 {"if-modified-since", ""}, // 40
56 {"if-none-match", ""}, // 41
57 {"if-range", ""}, // 42
58 {"if-unmodified-since", ""}, // 43
59 {"last-modified", ""}, // 44
60 {"link", ""}, // 45
61 {"location", ""}, // 46
62 {"max-forwards", ""}, // 47
63 {"proxy-authenticate", ""}, // 48
64 {"proxy-authorization", ""}, // 49
65 {"range", ""}, // 50
66 {"referer", ""}, // 51
67 {"refresh", ""}, // 52
68 {"retry-after", ""}, // 53
69 {"server", ""}, // 54
70 {"set-cookie", ""}, // 55
71 {"strict-transport-security", ""}, // 56
72 {"transfer-encoding", ""}, // 57
73 {"user-agent", ""}, // 58
74 {"vary", ""}, // 59
75 {"via", ""}, // 60
76 {"www-authenticate", ""} // 61
77 };
78
79 constexpr size_t static_table_size = 61;
80 }
81
82 // Static table implementation
83 auto static_table::get(size_t index) -> std::optional<http_header>
84 {
85 if (index == 0 || index > static_table_size)
86 {
87 return std::nullopt;
88 }
89 return static_table_entries[index];
90 }
91
92 auto static_table::find(std::string_view name, std::string_view value)
93 -> size_t
94 {
95 for (size_t i = 1; i <= static_table_size; ++i)
96 {
97 const auto& entry = static_table_entries[i];
98 if (entry.name == name)
99 {
100 if (value.empty() || entry.value == value)
101 {
102 return i;
103 }
104 }
105 }
106 return 0;
107 }
108
109 // Dynamic table implementation
111 : max_size_(max_size)
112 {
113 }
114
115 auto dynamic_table::insert(std::string_view name, std::string_view value) -> void
116 {
117 http_header header{std::string(name), std::string(value)};
118 size_t entry_size = header.size();
119
120 // Evict entries if needed
121 evict_to_size(max_size_ - entry_size);
122
123 // Insert at beginning
124 entries_.push_front(std::move(header));
125 current_size_ += entry_size;
126 }
127
128 auto dynamic_table::get(size_t index) const -> std::optional<http_header>
129 {
130 if (index >= entries_.size())
131 {
132 return std::nullopt;
133 }
134 return entries_[index];
135 }
136
137 auto dynamic_table::find(std::string_view name, std::string_view value) const
138 -> std::optional<size_t>
139 {
140 for (size_t i = 0; i < entries_.size(); ++i)
141 {
142 const auto& entry = entries_[i];
143 if (entry.name == name)
144 {
145 if (value.empty() || entry.value == value)
146 {
147 return i;
148 }
149 }
150 }
151 return std::nullopt;
152 }
153
154 auto dynamic_table::set_max_size(size_t size) -> void
155 {
156 max_size_ = size;
157 evict_to_size(max_size_);
158 }
159
160 auto dynamic_table::current_size() const -> size_t
161 {
162 return current_size_;
163 }
164
165 auto dynamic_table::max_size() const -> size_t
166 {
167 return max_size_;
168 }
169
170 auto dynamic_table::entry_count() const -> size_t
171 {
172 return entries_.size();
173 }
174
175 auto dynamic_table::clear() -> void
176 {
177 entries_.clear();
178 current_size_ = 0;
179 }
180
181 auto dynamic_table::evict_to_size(size_t target_size) -> void
182 {
183 while (current_size_ > target_size && !entries_.empty())
184 {
185 current_size_ -= entries_.back().size();
186 entries_.pop_back();
187 }
188 }
189
190 // HPACK encoder implementation
191 hpack_encoder::hpack_encoder(size_t max_table_size)
192 : table_(max_table_size)
193 {
194 }
195
196 auto hpack_encoder::encode(const std::vector<http_header>& headers)
197 -> std::vector<uint8_t>
198 {
199 std::vector<uint8_t> result;
200
201 for (const auto& header : headers)
202 {
203 // Try to find in static table first
204 size_t static_index = static_table::find(header.name, header.value);
205 if (static_index > 0)
206 {
207 // Indexed header field representation
208 auto encoded = encode_indexed(static_index);
209 result.insert(result.end(), encoded.begin(), encoded.end());
210 continue;
211 }
212
213 // Try to find in dynamic table
214 auto dynamic_index = table_.find(header.name, header.value);
215 if (dynamic_index.has_value())
216 {
217 // Indexed header field representation
218 size_t index = static_table::size() + 1 + dynamic_index.value();
219 auto encoded = encode_indexed(index);
220 result.insert(result.end(), encoded.begin(), encoded.end());
221 continue;
222 }
223
224 // Check if name is in static table
225 size_t name_index = static_table::find(header.name, "");
226 if (name_index > 0)
227 {
228 // Literal with incremental indexing - indexed name
229 auto encoded = encode_literal_with_indexing(name_index, header.value);
230 result.insert(result.end(), encoded.begin(), encoded.end());
231 table_.insert(header.name, header.value);
232 }
233 else
234 {
235 // Check if name is in dynamic table
236 auto dynamic_name_index = table_.find(header.name, "");
237 if (dynamic_name_index.has_value())
238 {
239 size_t index = static_table::size() + 1 + dynamic_name_index.value();
240 auto encoded = encode_literal_with_indexing(index, header.value);
241 result.insert(result.end(), encoded.begin(), encoded.end());
242 table_.insert(header.name, header.value);
243 }
244 else
245 {
246 // Literal with incremental indexing - new name
247 auto encoded = encode_literal_with_indexing(header.name, header.value);
248 result.insert(result.end(), encoded.begin(), encoded.end());
249 table_.insert(header.name, header.value);
250 }
251 }
252 }
253
254 return result;
255 }
256
257 auto hpack_encoder::set_max_table_size(size_t size) -> void
258 {
259 table_.set_max_size(size);
260 }
261
262 auto hpack_encoder::table_size() const -> size_t
263 {
264 return table_.current_size();
265 }
266
267 auto hpack_encoder::encode_integer(uint64_t value, uint8_t prefix_bits)
268 -> std::vector<uint8_t>
269 {
270 std::vector<uint8_t> result;
271 uint8_t max_prefix = (1 << prefix_bits) - 1;
272
273 if (value < max_prefix)
274 {
275 result.push_back(static_cast<uint8_t>(value));
276 }
277 else
278 {
279 result.push_back(max_prefix);
280 value -= max_prefix;
281
282 while (value >= 128)
283 {
284 result.push_back(static_cast<uint8_t>((value % 128) + 128));
285 value /= 128;
286 }
287 result.push_back(static_cast<uint8_t>(value));
288 }
289
290 return result;
291 }
292
293 auto hpack_encoder::encode_string(std::string_view str, bool huffman)
294 -> std::vector<uint8_t>
295 {
296 std::vector<uint8_t> result;
297
298 // String length (with Huffman bit)
299 auto length_bytes = encode_integer(str.size(), 7);
300 if (!huffman)
301 {
302 result.insert(result.end(), length_bytes.begin(), length_bytes.end());
303 }
304 else
305 {
306 // Huffman encoding not implemented yet
307 // Set H bit in first byte
308 length_bytes[0] |= 0x80;
309 result.insert(result.end(), length_bytes.begin(), length_bytes.end());
310 }
311
312 // String data
313 result.insert(result.end(), str.begin(), str.end());
314
315 return result;
316 }
317
318 auto hpack_encoder::encode_indexed(size_t index) -> std::vector<uint8_t>
319 {
320 auto result = encode_integer(index, 7);
321 result[0] |= 0x80; // Set indexed bit
322 return result;
323 }
324
326 std::string_view value)
327 -> std::vector<uint8_t>
328 {
329 std::vector<uint8_t> result;
330
331 // First byte: 01xxxxxx (literal with incremental indexing, new name)
332 result.push_back(0x40);
333
334 // Encode name
335 auto name_bytes = encode_string(name);
336 result.insert(result.end(), name_bytes.begin(), name_bytes.end());
337
338 // Encode value
339 auto value_bytes = encode_string(value);
340 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
341
342 return result;
343 }
344
346 std::string_view value)
347 -> std::vector<uint8_t>
348 {
349 std::vector<uint8_t> result;
350
351 // Encode name index with 6-bit prefix
352 auto index_bytes = encode_integer(name_index, 6);
353 index_bytes[0] |= 0x40; // Set literal with indexing bit
354 result.insert(result.end(), index_bytes.begin(), index_bytes.end());
355
356 // Encode value
357 auto value_bytes = encode_string(value);
358 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
359
360 return result;
361 }
362
364 std::string_view value)
365 -> std::vector<uint8_t>
366 {
367 std::vector<uint8_t> result;
368
369 // First byte: 0000xxxx (literal without indexing, new name)
370 result.push_back(0x00);
371
372 // Encode name
373 auto name_bytes = encode_string(name);
374 result.insert(result.end(), name_bytes.begin(), name_bytes.end());
375
376 // Encode value
377 auto value_bytes = encode_string(value);
378 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
379
380 return result;
381 }
382
384 std::string_view value)
385 -> std::vector<uint8_t>
386 {
387 std::vector<uint8_t> result;
388
389 // Encode name index with 4-bit prefix
390 auto index_bytes = encode_integer(name_index, 4);
391 // First byte already has 0000 prefix for literal without indexing
392 result.insert(result.end(), index_bytes.begin(), index_bytes.end());
393
394 // Encode value
395 auto value_bytes = encode_string(value);
396 result.insert(result.end(), value_bytes.begin(), value_bytes.end());
397
398 return result;
399 }
400
401 // HPACK decoder implementation
402 hpack_decoder::hpack_decoder(size_t max_table_size)
403 : table_(max_table_size)
404 {
405 }
406
407 auto hpack_decoder::decode(std::span<const uint8_t> data)
409 {
410 std::vector<http_header> headers;
411 auto remaining = data;
412
413 while (!remaining.empty())
414 {
415 uint8_t first_byte = remaining[0];
416
417 if (first_byte & 0x80)
418 {
419 // Indexed header field representation
420 auto index_result = decode_integer(remaining, 7);
421 if (index_result.is_err())
422 {
423 return index_result.error();
424 }
425 size_t index = index_result.value();
426
427 auto header_result = get_indexed_header(index);
428 if (header_result.is_err())
429 {
430 return header_result.error();
431 }
432
433 headers.push_back(header_result.value());
434 }
435 else if (first_byte & 0x40)
436 {
437 // Literal with incremental indexing
438 auto name_index_result = decode_integer(remaining, 6);
439 if (name_index_result.is_err())
440 {
441 return name_index_result.error();
442 }
443 size_t name_index = name_index_result.value();
444
445 std::string name;
446 if (name_index == 0)
447 {
448 // New name
449 auto name_result = decode_string(remaining);
450 if (name_result.is_err())
451 {
452 return name_result.error();
453 }
454 name = name_result.value();
455 }
456 else
457 {
458 // Indexed name
459 auto header_result = get_indexed_header(name_index);
460 if (header_result.is_err())
461 {
462 return header_result.error();
463 }
464 name = header_result.value().name;
465 }
466
467 // Decode value
468 auto value_result = decode_string(remaining);
469 if (value_result.is_err())
470 {
471 return value_result.error();
472 }
473 std::string value = value_result.value();
474
475 headers.emplace_back(name, value);
476 table_.insert(name, value);
477 }
478 else
479 {
480 // Literal without indexing or never indexed
481 uint8_t prefix_bits = (first_byte & 0x10) ? 4 : 4;
482
483 auto name_index_result = decode_integer(remaining, prefix_bits);
484 if (name_index_result.is_err())
485 {
486 return name_index_result.error();
487 }
488 size_t name_index = name_index_result.value();
489
490 std::string name;
491 if (name_index == 0)
492 {
493 // New name
494 auto name_result = decode_string(remaining);
495 if (name_result.is_err())
496 {
497 return name_result.error();
498 }
499 name = name_result.value();
500 }
501 else
502 {
503 // Indexed name
504 auto header_result = get_indexed_header(name_index);
505 if (header_result.is_err())
506 {
507 return header_result.error();
508 }
509 name = header_result.value().name;
510 }
511
512 // Decode value
513 auto value_result = decode_string(remaining);
514 if (value_result.is_err())
515 {
516 return value_result.error();
517 }
518
519 headers.emplace_back(name, value_result.value());
520 }
521 }
522
523 return headers;
524 }
525
526 auto hpack_decoder::set_max_table_size(size_t size) -> void
527 {
528 table_.set_max_size(size);
529 }
530
531 auto hpack_decoder::table_size() const -> size_t
532 {
533 return table_.current_size();
534 }
535
536 auto hpack_decoder::decode_integer(std::span<const uint8_t>& data,
537 uint8_t prefix_bits)
539 {
540 if (data.empty())
541 {
542 return error_info(100, "Insufficient data for integer", "hpack");
543 }
544
545 uint8_t prefix_mask = (1 << prefix_bits) - 1;
546 uint64_t value = data[0] & prefix_mask;
547 data = data.subspan(1);
548
549 if (value < prefix_mask)
550 {
551 return value;
552 }
553
554 // Multi-byte integer
555 uint64_t m = 0;
556 do
557 {
558 if (data.empty())
559 {
560 return error_info(101, "Incomplete integer encoding", "hpack");
561 }
562
563 uint8_t byte = data[0];
564 data = data.subspan(1);
565
566 value += (byte & 0x7F) << m;
567 m += 7;
568
569 if (m >= 64)
570 {
571 return error_info(102, "Integer overflow", "hpack");
572 }
573
574 if ((byte & 0x80) == 0)
575 {
576 break;
577 }
578 } while (true);
579
580 return value;
581 }
582
583 auto hpack_decoder::decode_string(std::span<const uint8_t>& data)
585 {
586 if (data.empty())
587 {
588 return error_info(103, "Insufficient data for string", "hpack");
589 }
590
591 bool huffman = (data[0] & 0x80) != 0;
592
593 auto length_result = decode_integer(data, 7);
594 if (length_result.is_err())
595 {
596 return length_result.error();
597 }
598
599 size_t length = length_result.value();
600
601 if (data.size() < length)
602 {
603 return error_info(104, "Insufficient data for string value", "hpack");
604 }
605
606 std::string result;
607 if (huffman)
608 {
609 // Huffman decoding not implemented yet - just return raw for now
610 result.assign(data.begin(), data.begin() + length);
611 }
612 else
613 {
614 result.assign(data.begin(), data.begin() + length);
615 }
616
617 data = data.subspan(length);
618 return result;
619 }
620
621 auto hpack_decoder::get_indexed_header(size_t index) const
623 {
624 if (index == 0)
625 {
626 return error_info(105, "Invalid index 0", "hpack");
627 }
628
629 // Check static table
630 if (index <= static_table::size())
631 {
632 auto header = static_table::get(index);
633 if (header.has_value())
634 {
635 return std::move(header.value());
636 }
637 return error_info(106, "Invalid static table index", "hpack");
638 }
639
640 // Check dynamic table
641 size_t dynamic_index = index - static_table::size() - 1;
642 auto header = table_.get(dynamic_index);
643 if (header.has_value())
644 {
645 return std::move(header.value());
646 }
647
648 return error_info(107, "Invalid dynamic table index", "hpack");
649 }
650
651 // Huffman coding (basic stub implementation)
652 namespace huffman
653 {
654 auto encode(std::string_view input) -> std::vector<uint8_t>
655 {
656 // Stub: just return the input as-is
657 return std::vector<uint8_t>(input.begin(), input.end());
658 }
659
660 auto decode(std::span<const uint8_t> data) -> Result<std::string>
661 {
662 // Stub: just return the data as-is
663 return std::string(data.begin(), data.end());
664 }
665
666 auto encoded_size(std::string_view input) -> size_t
667 {
668 // Stub: return input size
669 return input.size();
670 }
671 }
672
673} // namespace kcenon::network::protocols::http2
auto entry_count() const -> size_t
Get number of entries.
Definition hpack.cpp:170
auto max_size() const -> size_t
Get maximum table size.
Definition hpack.cpp:165
auto clear() -> void
Clear all entries.
Definition hpack.cpp:175
auto set_max_size(size_t size) -> void
Set maximum table size.
Definition hpack.cpp:154
auto evict_to_size(size_t target_size) -> void
Definition hpack.cpp:181
auto get(size_t index) const -> std::optional< http_header >
Get header by dynamic table index.
Definition hpack.cpp:128
auto find(std::string_view name, std::string_view value="") const -> std::optional< size_t >
Find header in dynamic table.
Definition hpack.cpp:137
auto insert(std::string_view name, std::string_view value) -> void
Insert header at beginning of table.
Definition hpack.cpp:115
auto current_size() const -> size_t
Get current table size.
Definition hpack.cpp:160
dynamic_table(size_t max_size=4096)
Construct dynamic table with max size.
Definition hpack.cpp:110
auto decode(std::span< const uint8_t > data) -> Result< std::vector< http_header > >
Decode HPACK binary to headers.
Definition hpack.cpp:407
auto decode_string(std::span< const uint8_t > &data) -> Result< std::string >
Definition hpack.cpp:583
auto set_max_table_size(size_t size) -> void
Set maximum dynamic table size.
Definition hpack.cpp:526
auto table_size() const -> size_t
Get current dynamic table size.
Definition hpack.cpp:531
auto decode_integer(std::span< const uint8_t > &data, uint8_t prefix_bits) -> Result< uint64_t >
Definition hpack.cpp:536
auto get_indexed_header(size_t index) const -> Result< http_header >
Definition hpack.cpp:621
hpack_decoder(size_t max_table_size=4096)
Construct decoder with max table size.
Definition hpack.cpp:402
auto encode_literal_with_indexing(std::string_view name, std::string_view value) -> std::vector< uint8_t >
Definition hpack.cpp:325
auto set_max_table_size(size_t size) -> void
Set maximum dynamic table size.
Definition hpack.cpp:257
auto encode(const std::vector< http_header > &headers) -> std::vector< uint8_t >
Encode headers to HPACK binary format.
Definition hpack.cpp:196
hpack_encoder(size_t max_table_size=4096)
Construct encoder with max table size.
Definition hpack.cpp:191
auto table_size() const -> size_t
Get current dynamic table size.
Definition hpack.cpp:262
auto encode_indexed(size_t index) -> std::vector< uint8_t >
Definition hpack.cpp:318
auto encode_literal_without_indexing(std::string_view name, std::string_view value) -> std::vector< uint8_t >
Definition hpack.cpp:363
auto encode_integer(uint64_t value, uint8_t prefix_bits) -> std::vector< uint8_t >
Definition hpack.cpp:267
auto encode_string(std::string_view str, bool huffman=false) -> std::vector< uint8_t >
Definition hpack.cpp:293
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:83
static auto find(std::string_view name, std::string_view value="") -> size_t
Find index of header in static table.
Definition hpack.cpp:92
Huffman coding for HPACK string compression.
auto decode(std::span< const uint8_t > data) -> Result< std::string >
Decode Huffman encoded string.
Definition hpack.cpp:660
auto encode(std::string_view input) -> std::vector< uint8_t >
Encode string using Huffman coding.
Definition hpack.cpp:654
auto encoded_size(std::string_view input) -> size_t
Get encoded size for string.
Definition hpack.cpp:666
simple_error error_info
HTTP header name-value pair.
Definition hpack.h:24