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_encoder.h" | |
6 | |
7 #include <algorithm> | |
8 #include <limits> | |
9 | |
10 #include "base/logging.h" | |
11 #include "base/memory/ptr_util.h" | |
12 #include "net/spdy/hpack/hpack_constants.h" | |
13 #include "net/spdy/hpack/hpack_header_table.h" | |
14 #include "net/spdy/hpack/hpack_huffman_table.h" | |
15 #include "net/spdy/hpack/hpack_output_stream.h" | |
16 #include "net/spdy/platform/api/spdy_estimate_memory_usage.h" | |
17 | |
18 namespace net { | |
19 | |
20 class HpackEncoder::RepresentationIterator { | |
21 public: | |
22 // |pseudo_headers| and |regular_headers| must outlive the iterator. | |
23 RepresentationIterator(const Representations& pseudo_headers, | |
24 const Representations& regular_headers) | |
25 : pseudo_begin_(pseudo_headers.begin()), | |
26 pseudo_end_(pseudo_headers.end()), | |
27 regular_begin_(regular_headers.begin()), | |
28 regular_end_(regular_headers.end()) {} | |
29 | |
30 // |headers| must outlive the iterator. | |
31 explicit RepresentationIterator(const Representations& headers) | |
32 : pseudo_begin_(headers.begin()), | |
33 pseudo_end_(headers.end()), | |
34 regular_begin_(headers.end()), | |
35 regular_end_(headers.end()) {} | |
36 | |
37 bool HasNext() { | |
38 return pseudo_begin_ != pseudo_end_ || regular_begin_ != regular_end_; | |
39 } | |
40 | |
41 const Representation Next() { | |
42 if (pseudo_begin_ != pseudo_end_) { | |
43 return *pseudo_begin_++; | |
44 } else { | |
45 return *regular_begin_++; | |
46 } | |
47 } | |
48 | |
49 private: | |
50 Representations::const_iterator pseudo_begin_; | |
51 Representations::const_iterator pseudo_end_; | |
52 Representations::const_iterator regular_begin_; | |
53 Representations::const_iterator regular_end_; | |
54 }; | |
55 | |
56 namespace { | |
57 | |
58 // The default header listener. | |
59 void NoOpListener(SpdyStringPiece /*name*/, SpdyStringPiece /*value*/) {} | |
60 | |
61 // The default HPACK indexing policy. | |
62 bool DefaultPolicy(SpdyStringPiece name, SpdyStringPiece /* value */) { | |
63 if (name.empty()) { | |
64 return false; | |
65 } | |
66 // :authority is always present and rarely changes, and has moderate | |
67 // length, therefore it makes a lot of sense to index (insert in the | |
68 // dynamic table). | |
69 if (name[0] == kPseudoHeaderPrefix) { | |
70 return name == ":authority"; | |
71 } | |
72 return true; | |
73 } | |
74 | |
75 } // namespace | |
76 | |
77 HpackEncoder::HpackEncoder(const HpackHuffmanTable& table) | |
78 : output_stream_(), | |
79 huffman_table_(table), | |
80 min_table_size_setting_received_(std::numeric_limits<size_t>::max()), | |
81 listener_(NoOpListener), | |
82 should_index_(DefaultPolicy), | |
83 enable_compression_(true), | |
84 should_emit_table_size_(false) {} | |
85 | |
86 HpackEncoder::~HpackEncoder() {} | |
87 | |
88 void HpackEncoder::EncodeHeaderSet(const Representations& representations, | |
89 SpdyString* output) { | |
90 RepresentationIterator iter(representations); | |
91 EncodeRepresentations(&iter, output); | |
92 } | |
93 | |
94 bool HpackEncoder::EncodeHeaderSet(const SpdyHeaderBlock& header_set, | |
95 SpdyString* output) { | |
96 // Separate header set into pseudo-headers and regular headers. | |
97 Representations pseudo_headers; | |
98 Representations regular_headers; | |
99 bool found_cookie = false; | |
100 for (const auto& header : header_set) { | |
101 if (!found_cookie && header.first == "cookie") { | |
102 // Note that there can only be one "cookie" header, because header_set is | |
103 // a map. | |
104 found_cookie = true; | |
105 CookieToCrumbs(header, ®ular_headers); | |
106 } else if (!header.first.empty() && | |
107 header.first[0] == kPseudoHeaderPrefix) { | |
108 DecomposeRepresentation(header, &pseudo_headers); | |
109 } else { | |
110 DecomposeRepresentation(header, ®ular_headers); | |
111 } | |
112 } | |
113 | |
114 { | |
115 RepresentationIterator iter(pseudo_headers, regular_headers); | |
116 EncodeRepresentations(&iter, output); | |
117 } | |
118 return true; | |
119 } | |
120 | |
121 void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) { | |
122 if (size_setting == header_table_.settings_size_bound()) { | |
123 return; | |
124 } | |
125 if (size_setting < header_table_.settings_size_bound()) { | |
126 min_table_size_setting_received_ = | |
127 std::min(size_setting, min_table_size_setting_received_); | |
128 } | |
129 header_table_.SetSettingsHeaderTableSize(size_setting); | |
130 should_emit_table_size_ = true; | |
131 } | |
132 | |
133 size_t HpackEncoder::EstimateMemoryUsage() const { | |
134 // |huffman_table_| is a singleton. It's accounted for in spdy_session_pool.cc | |
135 return SpdyEstimateMemoryUsage(header_table_) + | |
136 SpdyEstimateMemoryUsage(output_stream_); | |
137 } | |
138 | |
139 void HpackEncoder::EncodeRepresentations(RepresentationIterator* iter, | |
140 SpdyString* output) { | |
141 MaybeEmitTableSize(); | |
142 while (iter->HasNext()) { | |
143 const auto header = iter->Next(); | |
144 listener_(header.first, header.second); | |
145 if (enable_compression_) { | |
146 const HpackEntry* entry = | |
147 header_table_.GetByNameAndValue(header.first, header.second); | |
148 if (entry != nullptr) { | |
149 EmitIndex(entry); | |
150 } else if (should_index_(header.first, header.second)) { | |
151 EmitIndexedLiteral(header); | |
152 } else { | |
153 EmitNonIndexedLiteral(header); | |
154 } | |
155 } else { | |
156 EmitNonIndexedLiteral(header); | |
157 } | |
158 } | |
159 | |
160 output_stream_.TakeString(output); | |
161 } | |
162 | |
163 void HpackEncoder::EmitIndex(const HpackEntry* entry) { | |
164 DVLOG(2) << "Emitting index " << header_table_.IndexOf(entry); | |
165 output_stream_.AppendPrefix(kIndexedOpcode); | |
166 output_stream_.AppendUint32(header_table_.IndexOf(entry)); | |
167 } | |
168 | |
169 void HpackEncoder::EmitIndexedLiteral(const Representation& representation) { | |
170 DVLOG(2) << "Emitting indexed literal: (" << representation.first << ", " | |
171 << representation.second << ")"; | |
172 output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode); | |
173 EmitLiteral(representation); | |
174 header_table_.TryAddEntry(representation.first, representation.second); | |
175 } | |
176 | |
177 void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation) { | |
178 DVLOG(2) << "Emitting nonindexed literal: (" << representation.first << ", " | |
179 << representation.second << ")"; | |
180 output_stream_.AppendPrefix(kLiteralNoIndexOpcode); | |
181 output_stream_.AppendUint32(0); | |
182 EmitString(representation.first); | |
183 EmitString(representation.second); | |
184 } | |
185 | |
186 void HpackEncoder::EmitLiteral(const Representation& representation) { | |
187 const HpackEntry* name_entry = header_table_.GetByName(representation.first); | |
188 if (name_entry != nullptr) { | |
189 output_stream_.AppendUint32(header_table_.IndexOf(name_entry)); | |
190 } else { | |
191 output_stream_.AppendUint32(0); | |
192 EmitString(representation.first); | |
193 } | |
194 EmitString(representation.second); | |
195 } | |
196 | |
197 void HpackEncoder::EmitString(SpdyStringPiece str) { | |
198 size_t encoded_size = | |
199 enable_compression_ ? huffman_table_.EncodedSize(str) : str.size(); | |
200 if (encoded_size < str.size()) { | |
201 DVLOG(2) << "Emitted Huffman-encoded string of length " << encoded_size; | |
202 output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded); | |
203 output_stream_.AppendUint32(encoded_size); | |
204 huffman_table_.EncodeString(str, &output_stream_); | |
205 } else { | |
206 DVLOG(2) << "Emitted literal string of length " << str.size(); | |
207 output_stream_.AppendPrefix(kStringLiteralIdentityEncoded); | |
208 output_stream_.AppendUint32(str.size()); | |
209 output_stream_.AppendBytes(str); | |
210 } | |
211 } | |
212 | |
213 void HpackEncoder::MaybeEmitTableSize() { | |
214 if (!should_emit_table_size_) { | |
215 return; | |
216 } | |
217 const size_t current_size = CurrentHeaderTableSizeSetting(); | |
218 DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size; | |
219 DVLOG(1) << "MaybeEmitTableSize min_table_size_setting_received_=" | |
220 << min_table_size_setting_received_; | |
221 if (min_table_size_setting_received_ < current_size) { | |
222 output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode); | |
223 output_stream_.AppendUint32(min_table_size_setting_received_); | |
224 } | |
225 output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode); | |
226 output_stream_.AppendUint32(current_size); | |
227 min_table_size_setting_received_ = std::numeric_limits<size_t>::max(); | |
228 should_emit_table_size_ = false; | |
229 } | |
230 | |
231 // static | |
232 void HpackEncoder::CookieToCrumbs(const Representation& cookie, | |
233 Representations* out) { | |
234 // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2 | |
235 // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14. | |
236 // Cookie values are split into individually-encoded HPACK representations. | |
237 SpdyStringPiece cookie_value = cookie.second; | |
238 // Consume leading and trailing whitespace if present. | |
239 SpdyStringPiece::size_type first = cookie_value.find_first_not_of(" \t"); | |
240 SpdyStringPiece::size_type last = cookie_value.find_last_not_of(" \t"); | |
241 if (first == SpdyStringPiece::npos) { | |
242 cookie_value = SpdyStringPiece(); | |
243 } else { | |
244 cookie_value = cookie_value.substr(first, (last - first) + 1); | |
245 } | |
246 for (size_t pos = 0;;) { | |
247 size_t end = cookie_value.find(";", pos); | |
248 | |
249 if (end == SpdyStringPiece::npos) { | |
250 out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos))); | |
251 break; | |
252 } | |
253 out->push_back( | |
254 std::make_pair(cookie.first, cookie_value.substr(pos, end - pos))); | |
255 | |
256 // Consume next space if present. | |
257 pos = end + 1; | |
258 if (pos != cookie_value.size() && cookie_value[pos] == ' ') { | |
259 pos++; | |
260 } | |
261 } | |
262 } | |
263 | |
264 // static | |
265 void HpackEncoder::DecomposeRepresentation(const Representation& header_field, | |
266 Representations* out) { | |
267 size_t pos = 0; | |
268 size_t end = 0; | |
269 while (end != SpdyStringPiece::npos) { | |
270 end = header_field.second.find('\0', pos); | |
271 out->push_back(std::make_pair( | |
272 header_field.first, | |
273 header_field.second.substr( | |
274 pos, end == SpdyStringPiece::npos ? end : end - pos))); | |
275 pos = end + 1; | |
276 } | |
277 } | |
278 | |
279 // static | |
280 void HpackEncoder::GatherRepresentation(const Representation& header_field, | |
281 Representations* out) { | |
282 out->push_back(std::make_pair(header_field.first, header_field.second)); | |
283 } | |
284 | |
285 // Iteratively encodes a SpdyHeaderBlock. | |
286 class HpackEncoder::Encoderator : public ProgressiveEncoder { | |
287 public: | |
288 Encoderator(const SpdyHeaderBlock& header_set, HpackEncoder* encoder); | |
289 | |
290 // Encoderator is neither copyable nor movable. | |
291 Encoderator(const Encoderator&) = delete; | |
292 Encoderator& operator=(const Encoderator&) = delete; | |
293 | |
294 // Returns true iff more remains to encode. | |
295 bool HasNext() const override { return has_next_; } | |
296 | |
297 // Encodes up to max_encoded_bytes of the current header block into the | |
298 // given output string. | |
299 void Next(size_t max_encoded_bytes, SpdyString* output) override; | |
300 | |
301 private: | |
302 HpackEncoder* encoder_; | |
303 std::unique_ptr<RepresentationIterator> header_it_; | |
304 Representations pseudo_headers_; | |
305 Representations regular_headers_; | |
306 bool has_next_; | |
307 }; | |
308 | |
309 HpackEncoder::Encoderator::Encoderator(const SpdyHeaderBlock& header_set, | |
310 HpackEncoder* encoder) | |
311 : encoder_(encoder), has_next_(true) { | |
312 // Separate header set into pseudo-headers and regular headers. | |
313 const bool use_compression = encoder_->enable_compression_; | |
314 bool found_cookie = false; | |
315 for (const auto& header : header_set) { | |
316 if (!found_cookie && header.first == "cookie") { | |
317 // Note that there can only be one "cookie" header, because header_set | |
318 // is a map. | |
319 found_cookie = true; | |
320 CookieToCrumbs(header, ®ular_headers_); | |
321 } else if (!header.first.empty() && | |
322 header.first[0] == kPseudoHeaderPrefix) { | |
323 use_compression ? DecomposeRepresentation(header, &pseudo_headers_) | |
324 : GatherRepresentation(header, &pseudo_headers_); | |
325 } else { | |
326 use_compression ? DecomposeRepresentation(header, ®ular_headers_) | |
327 : GatherRepresentation(header, ®ular_headers_); | |
328 } | |
329 } | |
330 header_it_ = base::MakeUnique<RepresentationIterator>(pseudo_headers_, | |
331 regular_headers_); | |
332 | |
333 encoder_->MaybeEmitTableSize(); | |
334 } | |
335 | |
336 void HpackEncoder::Encoderator::Next(size_t max_encoded_bytes, | |
337 SpdyString* output) { | |
338 SPDY_BUG_IF(!has_next_) | |
339 << "Encoderator::Next called with nothing left to encode."; | |
340 const bool use_compression = encoder_->enable_compression_; | |
341 | |
342 // Encode up to max_encoded_bytes of headers. | |
343 while (header_it_->HasNext() && | |
344 encoder_->output_stream_.size() <= max_encoded_bytes) { | |
345 const Representation header = header_it_->Next(); | |
346 encoder_->listener_(header.first, header.second); | |
347 if (use_compression) { | |
348 const HpackEntry* entry = encoder_->header_table_.GetByNameAndValue( | |
349 header.first, header.second); | |
350 if (entry != nullptr) { | |
351 encoder_->EmitIndex(entry); | |
352 } else if (encoder_->should_index_(header.first, header.second)) { | |
353 encoder_->EmitIndexedLiteral(header); | |
354 } else { | |
355 encoder_->EmitNonIndexedLiteral(header); | |
356 } | |
357 } else { | |
358 encoder_->EmitNonIndexedLiteral(header); | |
359 } | |
360 } | |
361 | |
362 has_next_ = encoder_->output_stream_.size() > max_encoded_bytes; | |
363 encoder_->output_stream_.BoundedTakeString(max_encoded_bytes, output); | |
364 } | |
365 | |
366 std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet( | |
367 const SpdyHeaderBlock& header_set) { | |
368 return base::MakeUnique<Encoderator>(header_set, this); | |
369 } | |
370 | |
371 } // namespace net | |
OLD | NEW |