QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
QUICHE team | 5be974e | 2020-12-29 18:35:24 -0500 | [diff] [blame] | 5 | #include "spdy/core/hpack/hpack_huffman_table.h" |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 6 | |
| 7 | #include <algorithm> |
| 8 | #include <cmath> |
| 9 | #include <limits> |
| 10 | #include <memory> |
| 11 | |
QUICHE team | 5be974e | 2020-12-29 18:35:24 -0500 | [diff] [blame] | 12 | #include "spdy/core/hpack/hpack_output_stream.h" |
| 13 | #include "spdy/platform/api/spdy_estimate_memory_usage.h" |
| 14 | #include "spdy/platform/api/spdy_logging.h" |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 15 | |
| 16 | namespace spdy { |
| 17 | |
| 18 | namespace { |
| 19 | |
| 20 | bool SymbolLengthAndIdCompare(const HpackHuffmanSymbol& a, |
| 21 | const HpackHuffmanSymbol& b) { |
| 22 | if (a.length == b.length) { |
| 23 | return a.id < b.id; |
| 24 | } |
| 25 | return a.length < b.length; |
| 26 | } |
| 27 | bool SymbolIdCompare(const HpackHuffmanSymbol& a, const HpackHuffmanSymbol& b) { |
| 28 | return a.id < b.id; |
| 29 | } |
| 30 | |
| 31 | } // namespace |
| 32 | |
| 33 | HpackHuffmanTable::HpackHuffmanTable() : pad_bits_(0), failed_symbol_id_(0) {} |
| 34 | |
| 35 | HpackHuffmanTable::~HpackHuffmanTable() = default; |
| 36 | |
| 37 | bool HpackHuffmanTable::Initialize(const HpackHuffmanSymbol* input_symbols, |
| 38 | size_t symbol_count) { |
| 39 | CHECK(!IsInitialized()); |
| 40 | DCHECK_LE(symbol_count, std::numeric_limits<uint16_t>::max()); |
| 41 | |
| 42 | std::vector<Symbol> symbols(symbol_count); |
| 43 | // Validate symbol id sequence, and copy into |symbols|. |
| 44 | for (uint16_t i = 0; i < symbol_count; i++) { |
| 45 | if (i != input_symbols[i].id) { |
| 46 | failed_symbol_id_ = i; |
| 47 | return false; |
| 48 | } |
| 49 | symbols[i] = input_symbols[i]; |
| 50 | } |
| 51 | // Order on length and ID ascending, to verify symbol codes are canonical. |
| 52 | std::sort(symbols.begin(), symbols.end(), SymbolLengthAndIdCompare); |
| 53 | if (symbols[0].code != 0) { |
| 54 | failed_symbol_id_ = 0; |
| 55 | return false; |
| 56 | } |
| 57 | for (size_t i = 1; i != symbols.size(); i++) { |
| 58 | unsigned code_shift = 32 - symbols[i - 1].length; |
| 59 | uint32_t code = symbols[i - 1].code + (1 << code_shift); |
| 60 | |
| 61 | if (code != symbols[i].code) { |
| 62 | failed_symbol_id_ = symbols[i].id; |
| 63 | return false; |
| 64 | } |
| 65 | if (code < symbols[i - 1].code) { |
| 66 | // An integer overflow occurred. This implies the input |
| 67 | // lengths do not represent a valid Huffman code. |
| 68 | failed_symbol_id_ = symbols[i].id; |
| 69 | return false; |
| 70 | } |
| 71 | } |
| 72 | if (symbols.back().length < 8) { |
| 73 | // At least one code (such as an EOS symbol) must be 8 bits or longer. |
| 74 | // Without this, some inputs will not be encodable in a whole number |
| 75 | // of bytes. |
| 76 | return false; |
| 77 | } |
| 78 | pad_bits_ = static_cast<uint8_t>(symbols.back().code >> 24); |
| 79 | |
| 80 | // Order on symbol ID ascending. |
| 81 | std::sort(symbols.begin(), symbols.end(), SymbolIdCompare); |
| 82 | BuildEncodeTable(symbols); |
| 83 | return true; |
| 84 | } |
| 85 | |
| 86 | void HpackHuffmanTable::BuildEncodeTable(const std::vector<Symbol>& symbols) { |
| 87 | for (size_t i = 0; i != symbols.size(); i++) { |
| 88 | const Symbol& symbol = symbols[i]; |
| 89 | CHECK_EQ(i, symbol.id); |
| 90 | code_by_id_.push_back(symbol.code); |
| 91 | length_by_id_.push_back(symbol.length); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | bool HpackHuffmanTable::IsInitialized() const { |
| 96 | return !code_by_id_.empty(); |
| 97 | } |
| 98 | |
vasilvv | a89e7fa | 2020-10-12 21:35:05 -0700 | [diff] [blame] | 99 | void HpackHuffmanTable::EncodeString(absl::string_view in, |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 100 | HpackOutputStream* out) const { |
| 101 | size_t bit_remnant = 0; |
| 102 | for (size_t i = 0; i != in.size(); i++) { |
| 103 | uint16_t symbol_id = static_cast<uint8_t>(in[i]); |
| 104 | CHECK_GT(code_by_id_.size(), symbol_id); |
| 105 | |
| 106 | // Load, and shift code to low bits. |
| 107 | unsigned length = length_by_id_[symbol_id]; |
| 108 | uint32_t code = code_by_id_[symbol_id] >> (32 - length); |
| 109 | |
| 110 | bit_remnant = (bit_remnant + length) % 8; |
| 111 | |
| 112 | if (length > 24) { |
| 113 | out->AppendBits(static_cast<uint8_t>(code >> 24), length - 24); |
| 114 | length = 24; |
| 115 | } |
| 116 | if (length > 16) { |
| 117 | out->AppendBits(static_cast<uint8_t>(code >> 16), length - 16); |
| 118 | length = 16; |
| 119 | } |
| 120 | if (length > 8) { |
| 121 | out->AppendBits(static_cast<uint8_t>(code >> 8), length - 8); |
| 122 | length = 8; |
| 123 | } |
| 124 | out->AppendBits(static_cast<uint8_t>(code), length); |
| 125 | } |
| 126 | if (bit_remnant != 0) { |
| 127 | // Pad current byte as required. |
| 128 | out->AppendBits(pad_bits_ >> bit_remnant, 8 - bit_remnant); |
| 129 | } |
| 130 | } |
| 131 | |
vasilvv | a89e7fa | 2020-10-12 21:35:05 -0700 | [diff] [blame] | 132 | size_t HpackHuffmanTable::EncodedSize(absl::string_view in) const { |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 133 | size_t bit_count = 0; |
| 134 | for (size_t i = 0; i != in.size(); i++) { |
| 135 | uint16_t symbol_id = static_cast<uint8_t>(in[i]); |
| 136 | CHECK_GT(code_by_id_.size(), symbol_id); |
| 137 | |
| 138 | bit_count += length_by_id_[symbol_id]; |
| 139 | } |
| 140 | if (bit_count % 8 != 0) { |
| 141 | bit_count += 8 - bit_count % 8; |
| 142 | } |
| 143 | return bit_count / 8; |
| 144 | } |
| 145 | |
| 146 | size_t HpackHuffmanTable::EstimateMemoryUsage() const { |
| 147 | return SpdyEstimateMemoryUsage(code_by_id_) + |
| 148 | SpdyEstimateMemoryUsage(length_by_id_); |
| 149 | } |
| 150 | |
| 151 | } // namespace spdy |