| 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 |