| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/spdy_header_block.h" | |
| 6 | |
| 7 #include <string.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 #include <utility> | |
| 11 | |
| 12 #include "base/logging.h" | |
| 13 #include "base/macros.h" | |
| 14 #include "base/values.h" | |
| 15 #include "net/base/arena.h" | |
| 16 #include "net/http/http_log_util.h" | |
| 17 #include "net/log/net_log_capture_mode.h" | |
| 18 #include "net/spdy/platform/api/spdy_estimate_memory_usage.h" | |
| 19 #include "net/spdy/platform/api/spdy_string_utils.h" | |
| 20 | |
| 21 namespace net { | |
| 22 namespace { | |
| 23 | |
| 24 // By default, linked_hash_map's internal map allocates space for 100 map | |
| 25 // buckets on construction, which is larger than necessary. Standard library | |
| 26 // unordered map implementations use a list of prime numbers to set the bucket | |
| 27 // count for a particular capacity. |kInitialMapBuckets| is chosen to reduce | |
| 28 // memory usage for small header blocks, at the cost of having to rehash for | |
| 29 // large header blocks. | |
| 30 const size_t kInitialMapBuckets = 11; | |
| 31 | |
| 32 // SpdyHeaderBlock::Storage allocates blocks of this size by default. | |
| 33 const size_t kDefaultStorageBlockSize = 2048; | |
| 34 | |
| 35 const char kCookieKey[] = "cookie"; | |
| 36 const char kNullSeparator = 0; | |
| 37 | |
| 38 SpdyStringPiece SeparatorForKey(SpdyStringPiece key) { | |
| 39 if (key == kCookieKey) { | |
| 40 static SpdyStringPiece cookie_separator = "; "; | |
| 41 return cookie_separator; | |
| 42 } else { | |
| 43 return SpdyStringPiece(&kNullSeparator, 1); | |
| 44 } | |
| 45 } | |
| 46 | |
| 47 } // namespace | |
| 48 | |
| 49 // This class provides a backing store for SpdyStringPieces. It previously used | |
| 50 // custom allocation logic, but now uses an UnsafeArena instead. It has the | |
| 51 // property that SpdyStringPieces that refer to data in Storage are never | |
| 52 // invalidated until the Storage is deleted or Clear() is called. | |
| 53 // | |
| 54 // Write operations always append to the last block. If there is not enough | |
| 55 // space to perform the write, a new block is allocated, and any unused space | |
| 56 // is wasted. | |
| 57 class SpdyHeaderBlock::Storage { | |
| 58 public: | |
| 59 Storage() : arena_(kDefaultStorageBlockSize) {} | |
| 60 ~Storage() { Clear(); } | |
| 61 | |
| 62 SpdyStringPiece Write(const SpdyStringPiece s) { | |
| 63 return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size()); | |
| 64 } | |
| 65 | |
| 66 // If |s| points to the most recent allocation from arena_, the arena will | |
| 67 // reclaim the memory. Otherwise, this method is a no-op. | |
| 68 void Rewind(const SpdyStringPiece s) { | |
| 69 arena_.Free(const_cast<char*>(s.data()), s.size()); | |
| 70 } | |
| 71 | |
| 72 void Clear() { arena_.Reset(); } | |
| 73 | |
| 74 // Given a list of fragments and a separator, writes the fragments joined by | |
| 75 // the separator to a contiguous region of memory. Returns a SpdyStringPiece | |
| 76 // pointing to the region of memory. | |
| 77 SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments, | |
| 78 SpdyStringPiece separator) { | |
| 79 if (fragments.empty()) { | |
| 80 return SpdyStringPiece(); | |
| 81 } | |
| 82 size_t total_size = separator.size() * (fragments.size() - 1); | |
| 83 for (const auto fragment : fragments) { | |
| 84 total_size += fragment.size(); | |
| 85 } | |
| 86 char* dst = arena_.Alloc(total_size); | |
| 87 size_t written = Join(dst, fragments, separator); | |
| 88 DCHECK_EQ(written, total_size); | |
| 89 return SpdyStringPiece(dst, total_size); | |
| 90 } | |
| 91 | |
| 92 size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } | |
| 93 | |
| 94 // TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated() | |
| 95 size_t EstimateMemoryUsage() const { | |
| 96 return arena_.status().bytes_allocated(); | |
| 97 } | |
| 98 | |
| 99 private: | |
| 100 UnsafeArena arena_; | |
| 101 | |
| 102 DISALLOW_COPY_AND_ASSIGN(Storage); | |
| 103 }; | |
| 104 | |
| 105 SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage, | |
| 106 SpdyStringPiece key, | |
| 107 SpdyStringPiece initial_value) | |
| 108 : storage_(storage), fragments_({initial_value}), pair_({key, {}}) {} | |
| 109 | |
| 110 SpdyHeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other) | |
| 111 : storage_(other.storage_), | |
| 112 fragments_(std::move(other.fragments_)), | |
| 113 pair_(std::move(other.pair_)) {} | |
| 114 | |
| 115 SpdyHeaderBlock::HeaderValue& SpdyHeaderBlock::HeaderValue::operator=( | |
| 116 HeaderValue&& other) { | |
| 117 storage_ = other.storage_; | |
| 118 fragments_ = std::move(other.fragments_); | |
| 119 pair_ = std::move(other.pair_); | |
| 120 return *this; | |
| 121 } | |
| 122 | |
| 123 SpdyHeaderBlock::HeaderValue::~HeaderValue() {} | |
| 124 | |
| 125 SpdyStringPiece SpdyHeaderBlock::HeaderValue::ConsolidatedValue() const { | |
| 126 if (fragments_.empty()) { | |
| 127 return SpdyStringPiece(); | |
| 128 } | |
| 129 if (fragments_.size() > 1) { | |
| 130 fragments_ = { | |
| 131 storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))}; | |
| 132 } | |
| 133 return fragments_[0]; | |
| 134 } | |
| 135 | |
| 136 void SpdyHeaderBlock::HeaderValue::Append(SpdyStringPiece fragment) { | |
| 137 fragments_.push_back(fragment); | |
| 138 } | |
| 139 | |
| 140 const std::pair<SpdyStringPiece, SpdyStringPiece>& | |
| 141 SpdyHeaderBlock::HeaderValue::as_pair() const { | |
| 142 pair_.second = ConsolidatedValue(); | |
| 143 return pair_; | |
| 144 } | |
| 145 | |
| 146 SpdyHeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {} | |
| 147 | |
| 148 SpdyHeaderBlock::iterator::iterator(const iterator& other) : it_(other.it_) {} | |
| 149 | |
| 150 SpdyHeaderBlock::iterator::~iterator() {} | |
| 151 | |
| 152 SpdyHeaderBlock::ValueProxy::ValueProxy( | |
| 153 SpdyHeaderBlock::MapType* block, | |
| 154 SpdyHeaderBlock::Storage* storage, | |
| 155 SpdyHeaderBlock::MapType::iterator lookup_result, | |
| 156 const SpdyStringPiece key) | |
| 157 : block_(block), | |
| 158 storage_(storage), | |
| 159 lookup_result_(lookup_result), | |
| 160 key_(key), | |
| 161 valid_(true) {} | |
| 162 | |
| 163 SpdyHeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other) | |
| 164 : block_(other.block_), | |
| 165 storage_(other.storage_), | |
| 166 lookup_result_(other.lookup_result_), | |
| 167 key_(other.key_), | |
| 168 valid_(true) { | |
| 169 other.valid_ = false; | |
| 170 } | |
| 171 | |
| 172 SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( | |
| 173 SpdyHeaderBlock::ValueProxy&& other) { | |
| 174 block_ = other.block_; | |
| 175 storage_ = other.storage_; | |
| 176 lookup_result_ = other.lookup_result_; | |
| 177 key_ = other.key_; | |
| 178 valid_ = true; | |
| 179 other.valid_ = false; | |
| 180 return *this; | |
| 181 } | |
| 182 | |
| 183 SpdyHeaderBlock::ValueProxy::~ValueProxy() { | |
| 184 // If the ValueProxy is destroyed while lookup_result_ == block_->end(), | |
| 185 // the assignment operator was never used, and the block's Storage can | |
| 186 // reclaim the memory used by the key. This makes lookup-only access to | |
| 187 // SpdyHeaderBlock through operator[] memory-neutral. | |
| 188 if (valid_ && lookup_result_ == block_->end()) { | |
| 189 storage_->Rewind(key_); | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( | |
| 194 const SpdyStringPiece value) { | |
| 195 if (lookup_result_ == block_->end()) { | |
| 196 DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")"; | |
| 197 lookup_result_ = | |
| 198 block_ | |
| 199 ->emplace(std::make_pair( | |
| 200 key_, HeaderValue(storage_, key_, storage_->Write(value)))) | |
| 201 .first; | |
| 202 } else { | |
| 203 DVLOG(1) << "Updating key: " << key_ << " with value: " << value; | |
| 204 lookup_result_->second = | |
| 205 HeaderValue(storage_, key_, storage_->Write(value)); | |
| 206 } | |
| 207 return *this; | |
| 208 } | |
| 209 | |
| 210 SpdyString SpdyHeaderBlock::ValueProxy::as_string() const { | |
| 211 if (lookup_result_ == block_->end()) { | |
| 212 return ""; | |
| 213 } else { | |
| 214 return SpdyString(lookup_result_->second.value()); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 SpdyHeaderBlock::SpdyHeaderBlock() : block_(kInitialMapBuckets) {} | |
| 219 | |
| 220 SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other) = default; | |
| 221 | |
| 222 SpdyHeaderBlock::~SpdyHeaderBlock() {} | |
| 223 | |
| 224 SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) { | |
| 225 block_.swap(other.block_); | |
| 226 storage_.swap(other.storage_); | |
| 227 return *this; | |
| 228 } | |
| 229 | |
| 230 SpdyHeaderBlock SpdyHeaderBlock::Clone() const { | |
| 231 SpdyHeaderBlock copy; | |
| 232 for (const auto& p : *this) { | |
| 233 copy.AppendHeader(p.first, p.second); | |
| 234 } | |
| 235 return copy; | |
| 236 } | |
| 237 | |
| 238 bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const { | |
| 239 return size() == other.size() && std::equal(begin(), end(), other.begin()); | |
| 240 } | |
| 241 | |
| 242 bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const { | |
| 243 return !(operator==(other)); | |
| 244 } | |
| 245 | |
| 246 SpdyString SpdyHeaderBlock::DebugString() const { | |
| 247 if (empty()) { | |
| 248 return "{}"; | |
| 249 } | |
| 250 | |
| 251 SpdyString output = "\n{\n"; | |
| 252 for (auto it = begin(); it != end(); ++it) { | |
| 253 SpdyStrAppend(&output, " ", it->first, " ", it->second, "\n"); | |
| 254 } | |
| 255 SpdyStrAppend(&output, "}\n"); | |
| 256 return output; | |
| 257 } | |
| 258 | |
| 259 void SpdyHeaderBlock::clear() { | |
| 260 block_.clear(); | |
| 261 storage_.reset(); | |
| 262 } | |
| 263 | |
| 264 void SpdyHeaderBlock::insert(const SpdyHeaderBlock::value_type& value) { | |
| 265 // TODO(birenroy): Write new value in place of old value, if it fits. | |
| 266 auto iter = block_.find(value.first); | |
| 267 if (iter == block_.end()) { | |
| 268 DVLOG(1) << "Inserting: (" << value.first << ", " << value.second << ")"; | |
| 269 AppendHeader(value.first, value.second); | |
| 270 } else { | |
| 271 DVLOG(1) << "Updating key: " << iter->first | |
| 272 << " with value: " << value.second; | |
| 273 auto* storage = GetStorage(); | |
| 274 iter->second = | |
| 275 HeaderValue(storage, iter->first, storage->Write(value.second)); | |
| 276 } | |
| 277 } | |
| 278 | |
| 279 SpdyHeaderBlock::ValueProxy SpdyHeaderBlock::operator[]( | |
| 280 const SpdyStringPiece key) { | |
| 281 DVLOG(2) << "Operator[] saw key: " << key; | |
| 282 SpdyStringPiece out_key; | |
| 283 auto iter = block_.find(key); | |
| 284 if (iter == block_.end()) { | |
| 285 // We write the key first, to assure that the ValueProxy has a | |
| 286 // reference to a valid SpdyStringPiece in its operator=. | |
| 287 out_key = GetStorage()->Write(key); | |
| 288 DVLOG(2) << "Key written as: " << std::hex | |
| 289 << static_cast<const void*>(key.data()) << ", " << std::dec | |
| 290 << key.size(); | |
| 291 } else { | |
| 292 out_key = iter->first; | |
| 293 } | |
| 294 return ValueProxy(&block_, GetStorage(), iter, out_key); | |
| 295 } | |
| 296 | |
| 297 void SpdyHeaderBlock::AppendValueOrAddHeader(const SpdyStringPiece key, | |
| 298 const SpdyStringPiece value) { | |
| 299 auto iter = block_.find(key); | |
| 300 if (iter == block_.end()) { | |
| 301 DVLOG(1) << "Inserting: (" << key << ", " << value << ")"; | |
| 302 AppendHeader(key, value); | |
| 303 return; | |
| 304 } | |
| 305 DVLOG(1) << "Updating key: " << iter->first << "; appending value: " << value; | |
| 306 iter->second.Append(GetStorage()->Write(value)); | |
| 307 } | |
| 308 | |
| 309 size_t SpdyHeaderBlock::EstimateMemoryUsage() const { | |
| 310 // TODO(xunjieli): https://crbug.com/669108. Also include |block_| when EMU() | |
| 311 // supports linked_hash_map. | |
| 312 return SpdyEstimateMemoryUsage(storage_); | |
| 313 } | |
| 314 | |
| 315 void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key, | |
| 316 const SpdyStringPiece value) { | |
| 317 auto* storage = GetStorage(); | |
| 318 auto backed_key = storage->Write(key); | |
| 319 block_.emplace(std::make_pair( | |
| 320 backed_key, HeaderValue(storage, backed_key, storage->Write(value)))); | |
| 321 } | |
| 322 | |
| 323 SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() { | |
| 324 if (!storage_) { | |
| 325 storage_.reset(new Storage); | |
| 326 } | |
| 327 return storage_.get(); | |
| 328 } | |
| 329 | |
| 330 std::unique_ptr<base::Value> SpdyHeaderBlockNetLogCallback( | |
| 331 const SpdyHeaderBlock* headers, | |
| 332 NetLogCaptureMode capture_mode) { | |
| 333 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
| 334 base::DictionaryValue* headers_dict = new base::DictionaryValue(); | |
| 335 for (SpdyHeaderBlock::const_iterator it = headers->begin(); | |
| 336 it != headers->end(); ++it) { | |
| 337 headers_dict->SetWithoutPathExpansion( | |
| 338 it->first.as_string(), | |
| 339 new base::Value(ElideHeaderValueForNetLog( | |
| 340 capture_mode, it->first.as_string(), it->second.as_string()))); | |
| 341 } | |
| 342 dict->Set("headers", headers_dict); | |
| 343 return std::move(dict); | |
| 344 } | |
| 345 | |
| 346 bool SpdyHeaderBlockFromNetLogParam( | |
| 347 const base::Value* event_param, | |
| 348 SpdyHeaderBlock* headers) { | |
| 349 headers->clear(); | |
| 350 | |
| 351 const base::DictionaryValue* dict = NULL; | |
| 352 const base::DictionaryValue* header_dict = NULL; | |
| 353 | |
| 354 if (!event_param || | |
| 355 !event_param->GetAsDictionary(&dict) || | |
| 356 !dict->GetDictionary("headers", &header_dict)) { | |
| 357 return false; | |
| 358 } | |
| 359 | |
| 360 for (base::DictionaryValue::Iterator it(*header_dict); !it.IsAtEnd(); | |
| 361 it.Advance()) { | |
| 362 SpdyString value; | |
| 363 if (!it.value().GetAsString(&value)) { | |
| 364 headers->clear(); | |
| 365 return false; | |
| 366 } | |
| 367 (*headers)[it.key()] = value; | |
| 368 } | |
| 369 return true; | |
| 370 } | |
| 371 | |
| 372 size_t SpdyHeaderBlock::bytes_allocated() const { | |
| 373 if (storage_ == nullptr) { | |
| 374 return 0; | |
| 375 } else { | |
| 376 return storage_->bytes_allocated(); | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 size_t Join(char* dst, | |
| 381 const std::vector<SpdyStringPiece>& fragments, | |
| 382 SpdyStringPiece separator) { | |
| 383 if (fragments.empty()) { | |
| 384 return 0; | |
| 385 } | |
| 386 auto* original_dst = dst; | |
| 387 auto it = fragments.begin(); | |
| 388 memcpy(dst, it->data(), it->size()); | |
| 389 dst += it->size(); | |
| 390 for (++it; it != fragments.end(); ++it) { | |
| 391 memcpy(dst, separator.data(), separator.size()); | |
| 392 dst += separator.size(); | |
| 393 memcpy(dst, it->data(), it->size()); | |
| 394 dst += it->size(); | |
| 395 } | |
| 396 return dst - original_dst; | |
| 397 } | |
| 398 | |
| 399 } // namespace net | |
| OLD | NEW |