OLD | NEW |
| (Empty) |
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 | |
5 #include "net/spdy/hpack/hpack_huffman_table.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cmath> | |
9 #include <memory> | |
10 | |
11 #include "base/logging.h" | |
12 #include "base/numerics/safe_conversions.h" | |
13 #include "net/spdy/hpack/hpack_input_stream.h" | |
14 #include "net/spdy/hpack/hpack_output_stream.h" | |
15 #include "net/spdy/platform/api/spdy_estimate_memory_usage.h" | |
16 | |
17 namespace net { | |
18 | |
19 namespace { | |
20 | |
21 // How many bits to index in the root decode table. | |
22 const uint8_t kDecodeTableRootBits = 9; | |
23 // Maximum number of bits to index in successive decode tables. | |
24 const uint8_t kDecodeTableBranchBits = 6; | |
25 | |
26 bool SymbolLengthAndIdCompare(const HpackHuffmanSymbol& a, | |
27 const HpackHuffmanSymbol& b) { | |
28 if (a.length == b.length) { | |
29 return a.id < b.id; | |
30 } | |
31 return a.length < b.length; | |
32 } | |
33 bool SymbolIdCompare(const HpackHuffmanSymbol& a, const HpackHuffmanSymbol& b) { | |
34 return a.id < b.id; | |
35 } | |
36 | |
37 } // namespace | |
38 | |
39 HpackHuffmanTable::DecodeEntry::DecodeEntry() | |
40 : next_table_index(0), length(0), symbol_id(0) {} | |
41 HpackHuffmanTable::DecodeEntry::DecodeEntry(uint8_t next_table_index, | |
42 uint8_t length, | |
43 uint16_t symbol_id) | |
44 : next_table_index(next_table_index), | |
45 length(length), | |
46 symbol_id(symbol_id) {} | |
47 size_t HpackHuffmanTable::DecodeTable::size() const { | |
48 return size_t(1) << indexed_length; | |
49 } | |
50 | |
51 HpackHuffmanTable::HpackHuffmanTable() : pad_bits_(0), failed_symbol_id_(0) {} | |
52 | |
53 HpackHuffmanTable::~HpackHuffmanTable() {} | |
54 | |
55 bool HpackHuffmanTable::Initialize(const HpackHuffmanSymbol* input_symbols, | |
56 size_t symbol_count) { | |
57 CHECK(!IsInitialized()); | |
58 DCHECK(base::IsValueInRangeForNumericType<uint16_t>(symbol_count)); | |
59 | |
60 std::vector<Symbol> symbols(symbol_count); | |
61 // Validate symbol id sequence, and copy into |symbols|. | |
62 for (uint16_t i = 0; i < symbol_count; i++) { | |
63 if (i != input_symbols[i].id) { | |
64 failed_symbol_id_ = i; | |
65 return false; | |
66 } | |
67 symbols[i] = input_symbols[i]; | |
68 } | |
69 // Order on length and ID ascending, to verify symbol codes are canonical. | |
70 std::sort(symbols.begin(), symbols.end(), SymbolLengthAndIdCompare); | |
71 if (symbols[0].code != 0) { | |
72 failed_symbol_id_ = 0; | |
73 return false; | |
74 } | |
75 for (size_t i = 1; i != symbols.size(); i++) { | |
76 unsigned code_shift = 32 - symbols[i - 1].length; | |
77 uint32_t code = symbols[i - 1].code + (1 << code_shift); | |
78 | |
79 if (code != symbols[i].code) { | |
80 failed_symbol_id_ = symbols[i].id; | |
81 return false; | |
82 } | |
83 if (code < symbols[i - 1].code) { | |
84 // An integer overflow occurred. This implies the input | |
85 // lengths do not represent a valid Huffman code. | |
86 failed_symbol_id_ = symbols[i].id; | |
87 return false; | |
88 } | |
89 } | |
90 if (symbols.back().length < 8) { | |
91 // At least one code (such as an EOS symbol) must be 8 bits or longer. | |
92 // Without this, some inputs will not be encodable in a whole number | |
93 // of bytes. | |
94 return false; | |
95 } | |
96 pad_bits_ = static_cast<uint8_t>(symbols.back().code >> 24); | |
97 | |
98 BuildDecodeTables(symbols); | |
99 // Order on symbol ID ascending. | |
100 std::sort(symbols.begin(), symbols.end(), SymbolIdCompare); | |
101 BuildEncodeTable(symbols); | |
102 return true; | |
103 } | |
104 | |
105 void HpackHuffmanTable::BuildEncodeTable(const std::vector<Symbol>& symbols) { | |
106 for (size_t i = 0; i != symbols.size(); i++) { | |
107 const Symbol& symbol = symbols[i]; | |
108 CHECK_EQ(i, symbol.id); | |
109 code_by_id_.push_back(symbol.code); | |
110 length_by_id_.push_back(symbol.length); | |
111 } | |
112 } | |
113 | |
114 void HpackHuffmanTable::BuildDecodeTables(const std::vector<Symbol>& symbols) { | |
115 AddDecodeTable(0, kDecodeTableRootBits); | |
116 // We wish to maximize the flatness of the DecodeTable hierarchy (subject to | |
117 // the |kDecodeTableBranchBits| constraint), and to minimize the size of | |
118 // child tables. To achieve this, we iterate in order of descending code | |
119 // length. This ensures that child tables are visited with their longest | |
120 // entry first, and that the child can therefore be minimally sized to hold | |
121 // that entry without fear of introducing unneccesary branches later. | |
122 for (std::vector<Symbol>::const_reverse_iterator it = symbols.rbegin(); | |
123 it != symbols.rend(); ++it) { | |
124 uint8_t table_index = 0; | |
125 while (true) { | |
126 const DecodeTable table = decode_tables_[table_index]; | |
127 | |
128 // Mask and shift the portion of the code being indexed into low bits. | |
129 uint32_t index = (it->code << table.prefix_length); | |
130 index = index >> (32 - table.indexed_length); | |
131 | |
132 CHECK_LT(index, table.size()); | |
133 DecodeEntry entry = Entry(table, index); | |
134 | |
135 uint8_t total_indexed = table.prefix_length + table.indexed_length; | |
136 if (total_indexed >= it->length) { | |
137 // We're writing a terminal entry. | |
138 entry.length = it->length; | |
139 entry.symbol_id = it->id; | |
140 entry.next_table_index = table_index; | |
141 SetEntry(table, index, entry); | |
142 break; | |
143 } | |
144 | |
145 if (entry.length == 0) { | |
146 // First visit to this placeholder. We need to create a new table. | |
147 CHECK_EQ(entry.next_table_index, 0); | |
148 entry.length = it->length; | |
149 entry.next_table_index = | |
150 AddDecodeTable(total_indexed, // Becomes the new table prefix. | |
151 std::min<uint8_t>(kDecodeTableBranchBits, | |
152 entry.length - total_indexed)); | |
153 SetEntry(table, index, entry); | |
154 } | |
155 CHECK_NE(entry.next_table_index, table_index); | |
156 table_index = entry.next_table_index; | |
157 } | |
158 } | |
159 // Fill shorter table entries into the additional entry spots they map to. | |
160 for (size_t i = 0; i != decode_tables_.size(); i++) { | |
161 const DecodeTable& table = decode_tables_[i]; | |
162 uint8_t total_indexed = table.prefix_length + table.indexed_length; | |
163 | |
164 size_t j = 0; | |
165 while (j != table.size()) { | |
166 const DecodeEntry& entry = Entry(table, j); | |
167 if (entry.length != 0 && entry.length < total_indexed) { | |
168 // The difference between entry & table bit counts tells us how | |
169 // many additional entries map to this one. | |
170 size_t fill_count = static_cast<size_t>(1) | |
171 << (total_indexed - entry.length); | |
172 CHECK_LE(j + fill_count, table.size()); | |
173 | |
174 for (size_t k = 1; k != fill_count; k++) { | |
175 CHECK_EQ(Entry(table, j + k).length, 0); | |
176 SetEntry(table, j + k, entry); | |
177 } | |
178 j += fill_count; | |
179 } else { | |
180 j++; | |
181 } | |
182 } | |
183 } | |
184 } | |
185 | |
186 uint8_t HpackHuffmanTable::AddDecodeTable(uint8_t prefix, uint8_t indexed) { | |
187 CHECK_LT(decode_tables_.size(), 255u); | |
188 { | |
189 DecodeTable table; | |
190 table.prefix_length = prefix; | |
191 table.indexed_length = indexed; | |
192 table.entries_offset = decode_entries_.size(); | |
193 decode_tables_.push_back(table); | |
194 } | |
195 decode_entries_.resize(decode_entries_.size() + (size_t(1) << indexed)); | |
196 return static_cast<uint8_t>(decode_tables_.size() - 1); | |
197 } | |
198 | |
199 const HpackHuffmanTable::DecodeEntry& HpackHuffmanTable::Entry( | |
200 const DecodeTable& table, | |
201 uint32_t index) const { | |
202 DCHECK_LT(index, table.size()); | |
203 DCHECK_LT(table.entries_offset + index, decode_entries_.size()); | |
204 return decode_entries_[table.entries_offset + index]; | |
205 } | |
206 | |
207 void HpackHuffmanTable::SetEntry(const DecodeTable& table, | |
208 uint32_t index, | |
209 const DecodeEntry& entry) { | |
210 CHECK_LT(index, table.size()); | |
211 CHECK_LT(table.entries_offset + index, decode_entries_.size()); | |
212 decode_entries_[table.entries_offset + index] = entry; | |
213 } | |
214 | |
215 bool HpackHuffmanTable::IsInitialized() const { | |
216 return !code_by_id_.empty(); | |
217 } | |
218 | |
219 void HpackHuffmanTable::EncodeString(SpdyStringPiece in, | |
220 HpackOutputStream* out) const { | |
221 size_t bit_remnant = 0; | |
222 for (size_t i = 0; i != in.size(); i++) { | |
223 uint16_t symbol_id = static_cast<uint8_t>(in[i]); | |
224 CHECK_GT(code_by_id_.size(), symbol_id); | |
225 | |
226 // Load, and shift code to low bits. | |
227 unsigned length = length_by_id_[symbol_id]; | |
228 uint32_t code = code_by_id_[symbol_id] >> (32 - length); | |
229 | |
230 bit_remnant = (bit_remnant + length) % 8; | |
231 | |
232 if (length > 24) { | |
233 out->AppendBits(static_cast<uint8_t>(code >> 24), length - 24); | |
234 length = 24; | |
235 } | |
236 if (length > 16) { | |
237 out->AppendBits(static_cast<uint8_t>(code >> 16), length - 16); | |
238 length = 16; | |
239 } | |
240 if (length > 8) { | |
241 out->AppendBits(static_cast<uint8_t>(code >> 8), length - 8); | |
242 length = 8; | |
243 } | |
244 out->AppendBits(static_cast<uint8_t>(code), length); | |
245 } | |
246 if (bit_remnant != 0) { | |
247 // Pad current byte as required. | |
248 out->AppendBits(pad_bits_ >> bit_remnant, 8 - bit_remnant); | |
249 } | |
250 } | |
251 | |
252 size_t HpackHuffmanTable::EncodedSize(SpdyStringPiece in) const { | |
253 size_t bit_count = 0; | |
254 for (size_t i = 0; i != in.size(); i++) { | |
255 uint16_t symbol_id = static_cast<uint8_t>(in[i]); | |
256 CHECK_GT(code_by_id_.size(), symbol_id); | |
257 | |
258 bit_count += length_by_id_[symbol_id]; | |
259 } | |
260 if (bit_count % 8 != 0) { | |
261 bit_count += 8 - bit_count % 8; | |
262 } | |
263 return bit_count / 8; | |
264 } | |
265 | |
266 bool HpackHuffmanTable::GenericDecodeString(HpackInputStream* in, | |
267 SpdyString* out) const { | |
268 // Number of decode iterations required for a 32-bit code. | |
269 const int kDecodeIterations = static_cast<int>( | |
270 std::ceil((32.f - kDecodeTableRootBits) / kDecodeTableBranchBits)); | |
271 | |
272 out->clear(); | |
273 | |
274 // Current input, stored in the high |bits_available| bits of |bits|. | |
275 uint32_t bits = 0; | |
276 size_t bits_available = 0; | |
277 bool peeked_success = in->PeekBits(&bits_available, &bits); | |
278 | |
279 while (true) { | |
280 const DecodeTable* table = &decode_tables_[0]; | |
281 uint32_t index = bits >> (32 - kDecodeTableRootBits); | |
282 | |
283 for (int i = 0; i != kDecodeIterations; i++) { | |
284 DCHECK_LT(index, table->size()); | |
285 DCHECK_LT(Entry(*table, index).next_table_index, decode_tables_.size()); | |
286 | |
287 table = &decode_tables_[Entry(*table, index).next_table_index]; | |
288 // Mask and shift the portion of the code being indexed into low bits. | |
289 index = (bits << table->prefix_length) >> (32 - table->indexed_length); | |
290 } | |
291 const DecodeEntry& entry = Entry(*table, index); | |
292 | |
293 if (entry.length > bits_available) { | |
294 if (!peeked_success) { | |
295 // Unable to read enough input for a match. If only a portion of | |
296 // the last byte remains, this is a successful EOF condition. | |
297 in->ConsumeByteRemainder(); | |
298 return !in->HasMoreData(); | |
299 } | |
300 } else if (entry.length == 0) { | |
301 // The input is an invalid prefix, larger than any prefix in the table. | |
302 return false; | |
303 } else { | |
304 if (entry.symbol_id < 256) { | |
305 // Assume symbols >= 256 are used for padding. | |
306 out->push_back(static_cast<char>(entry.symbol_id)); | |
307 } | |
308 | |
309 in->ConsumeBits(entry.length); | |
310 bits = bits << entry.length; | |
311 bits_available -= entry.length; | |
312 } | |
313 peeked_success = in->PeekBits(&bits_available, &bits); | |
314 } | |
315 NOTREACHED(); | |
316 return false; | |
317 } | |
318 | |
319 size_t HpackHuffmanTable::EstimateMemoryUsage() const { | |
320 return SpdyEstimateMemoryUsage(decode_tables_) + | |
321 SpdyEstimateMemoryUsage(decode_entries_) + | |
322 SpdyEstimateMemoryUsage(code_by_id_) + | |
323 SpdyEstimateMemoryUsage(length_by_id_); | |
324 } | |
325 | |
326 } // namespace net | |
OLD | NEW |