blob: 8a1b65f04f9aaaeaa31ede8d0df683988916b236 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
#include <string.h>
#include <algorithm>
#include <utility>
#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h"
namespace spdy {
namespace {
// By default, linked_hash_map's internal map allocates space for 100 map
// buckets on construction, which is larger than necessary. Standard library
// unordered map implementations use a list of prime numbers to set the bucket
// count for a particular capacity. |kInitialMapBuckets| is chosen to reduce
// memory usage for small header blocks, at the cost of having to rehash for
// large header blocks.
const size_t kInitialMapBuckets = 11;
// SpdyHeaderBlock::Storage allocates blocks of this size by default.
const size_t kDefaultStorageBlockSize = 2048;
const char kCookieKey[] = "cookie";
const char kNullSeparator = 0;
SpdyStringPiece SeparatorForKey(SpdyStringPiece key) {
if (key == kCookieKey) {
static SpdyStringPiece cookie_separator = "; ";
return cookie_separator;
} else {
return SpdyStringPiece(&kNullSeparator, 1);
}
}
} // namespace
// This class provides a backing store for SpdyStringPieces. It previously used
// custom allocation logic, but now uses an UnsafeArena instead. It has the
// property that SpdyStringPieces that refer to data in Storage are never
// invalidated until the Storage is deleted or Clear() is called.
//
// Write operations always append to the last block. If there is not enough
// space to perform the write, a new block is allocated, and any unused space
// is wasted.
class SpdyHeaderBlock::Storage {
public:
Storage() : arena_(kDefaultStorageBlockSize) {}
Storage(const Storage&) = delete;
Storage& operator=(const Storage&) = delete;
SpdyStringPiece Write(const SpdyStringPiece s) {
return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size());
}
// If |s| points to the most recent allocation from arena_, the arena will
// reclaim the memory. Otherwise, this method is a no-op.
void Rewind(const SpdyStringPiece s) {
arena_.Free(const_cast<char*>(s.data()), s.size());
}
void Clear() { arena_.Reset(); }
// Given a list of fragments and a separator, writes the fragments joined by
// the separator to a contiguous region of memory. Returns a SpdyStringPiece
// pointing to the region of memory.
SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments,
SpdyStringPiece separator) {
if (fragments.empty()) {
return SpdyStringPiece();
}
size_t total_size = separator.size() * (fragments.size() - 1);
for (const auto fragment : fragments) {
total_size += fragment.size();
}
char* dst = arena_.Alloc(total_size);
size_t written = Join(dst, fragments, separator);
DCHECK_EQ(written, total_size);
return SpdyStringPiece(dst, total_size);
}
size_t bytes_allocated() const { return arena_.status().bytes_allocated(); }
// TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated()
size_t EstimateMemoryUsage() const {
return arena_.status().bytes_allocated();
}
private:
SpdyUnsafeArena arena_;
};
SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage,
SpdyStringPiece key,
SpdyStringPiece initial_value)
: storage_(storage),
fragments_({initial_value}),
pair_({key, {}}),
size_(initial_value.size()),
separator_size_(SeparatorForKey(key).size()) {}
SpdyHeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other)
: storage_(other.storage_),
fragments_(std::move(other.fragments_)),
pair_(std::move(other.pair_)),
size_(other.size_),
separator_size_(other.separator_size_) {}
SpdyHeaderBlock::HeaderValue& SpdyHeaderBlock::HeaderValue::operator=(
HeaderValue&& other) {
storage_ = other.storage_;
fragments_ = std::move(other.fragments_);
pair_ = std::move(other.pair_);
size_ = other.size_;
separator_size_ = other.separator_size_;
return *this;
}
SpdyHeaderBlock::HeaderValue::~HeaderValue() = default;
SpdyStringPiece SpdyHeaderBlock::HeaderValue::ConsolidatedValue() const {
if (fragments_.empty()) {
return SpdyStringPiece();
}
if (fragments_.size() > 1) {
fragments_ = {
storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))};
}
return fragments_[0];
}
void SpdyHeaderBlock::HeaderValue::Append(SpdyStringPiece fragment) {
size_ += (fragment.size() + separator_size_);
fragments_.push_back(fragment);
}
const std::pair<SpdyStringPiece, SpdyStringPiece>&
SpdyHeaderBlock::HeaderValue::as_pair() const {
pair_.second = ConsolidatedValue();
return pair_;
}
SpdyHeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {}
SpdyHeaderBlock::iterator::iterator(const iterator& other) = default;
SpdyHeaderBlock::iterator::~iterator() = default;
SpdyHeaderBlock::ValueProxy::ValueProxy(
SpdyHeaderBlock::MapType* block,
SpdyHeaderBlock::Storage* storage,
SpdyHeaderBlock::MapType::iterator lookup_result,
const SpdyStringPiece key,
size_t* spdy_header_block_value_size)
: block_(block),
storage_(storage),
lookup_result_(lookup_result),
key_(key),
spdy_header_block_value_size_(spdy_header_block_value_size),
valid_(true) {}
SpdyHeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other)
: block_(other.block_),
storage_(other.storage_),
lookup_result_(other.lookup_result_),
key_(other.key_),
spdy_header_block_value_size_(other.spdy_header_block_value_size_),
valid_(true) {
other.valid_ = false;
}
SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=(
SpdyHeaderBlock::ValueProxy&& other) {
block_ = other.block_;
storage_ = other.storage_;
lookup_result_ = other.lookup_result_;
key_ = other.key_;
valid_ = true;
other.valid_ = false;
spdy_header_block_value_size_ = other.spdy_header_block_value_size_;
return *this;
}
SpdyHeaderBlock::ValueProxy::~ValueProxy() {
// If the ValueProxy is destroyed while lookup_result_ == block_->end(),
// the assignment operator was never used, and the block's Storage can
// reclaim the memory used by the key. This makes lookup-only access to
// SpdyHeaderBlock through operator[] memory-neutral.
if (valid_ && lookup_result_ == block_->end()) {
storage_->Rewind(key_);
}
}
SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=(
const SpdyStringPiece value) {
*spdy_header_block_value_size_ += value.size();
if (lookup_result_ == block_->end()) {
SPDY_DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")";
lookup_result_ =
block_
->emplace(std::make_pair(
key_, HeaderValue(storage_, key_, storage_->Write(value))))
.first;
} else {
SPDY_DVLOG(1) << "Updating key: " << key_ << " with value: " << value;
*spdy_header_block_value_size_ -= lookup_result_->second.SizeEstimate();
lookup_result_->second =
HeaderValue(storage_, key_, storage_->Write(value));
}
return *this;
}
std::string SpdyHeaderBlock::ValueProxy::as_string() const {
if (lookup_result_ == block_->end()) {
return "";
} else {
return std::string(lookup_result_->second.value());
}
}
SpdyHeaderBlock::SpdyHeaderBlock() : block_(kInitialMapBuckets) {}
SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other)
: block_(kInitialMapBuckets) {
block_.swap(other.block_);
storage_.swap(other.storage_);
key_size_ = other.key_size_;
value_size_ = other.value_size_;
}
SpdyHeaderBlock::~SpdyHeaderBlock() = default;
SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) {
block_.swap(other.block_);
storage_.swap(other.storage_);
key_size_ = other.key_size_;
value_size_ = other.value_size_;
return *this;
}
SpdyHeaderBlock SpdyHeaderBlock::Clone() const {
SpdyHeaderBlock copy;
for (const auto& p : *this) {
copy.AppendHeader(p.first, p.second);
}
return copy;
}
bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const {
return size() == other.size() && std::equal(begin(), end(), other.begin());
}
bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const {
return !(operator==(other));
}
std::string SpdyHeaderBlock::DebugString() const {
if (empty()) {
return "{}";
}
std::string output = "\n{\n";
for (auto it = begin(); it != end(); ++it) {
SpdyStrAppend(&output, " ", it->first, " ", it->second, "\n");
}
SpdyStrAppend(&output, "}\n");
return output;
}
void SpdyHeaderBlock::erase(SpdyStringPiece key) {
auto iter = block_.find(key);
if (iter != block_.end()) {
SPDY_DVLOG(1) << "Erasing header with name: " << key;
key_size_ -= key.size();
value_size_ -= iter->second.SizeEstimate();
block_.erase(iter);
}
}
void SpdyHeaderBlock::clear() {
key_size_ = 0;
value_size_ = 0;
block_.clear();
storage_.reset();
}
void SpdyHeaderBlock::insert(const SpdyHeaderBlock::value_type& value) {
// TODO(birenroy): Write new value in place of old value, if it fits.
value_size_ += value.second.size();
auto iter = block_.find(value.first);
if (iter == block_.end()) {
SPDY_DVLOG(1) << "Inserting: (" << value.first << ", " << value.second
<< ")";
AppendHeader(value.first, value.second);
} else {
SPDY_DVLOG(1) << "Updating key: " << iter->first
<< " with value: " << value.second;
value_size_ -= iter->second.SizeEstimate();
auto* storage = GetStorage();
iter->second =
HeaderValue(storage, iter->first, storage->Write(value.second));
}
}
SpdyHeaderBlock::ValueProxy SpdyHeaderBlock::operator[](
const SpdyStringPiece key) {
SPDY_DVLOG(2) << "Operator[] saw key: " << key;
SpdyStringPiece out_key;
auto iter = block_.find(key);
if (iter == block_.end()) {
// We write the key first, to assure that the ValueProxy has a
// reference to a valid SpdyStringPiece in its operator=.
out_key = WriteKey(key);
SPDY_DVLOG(2) << "Key written as: " << std::hex
<< static_cast<const void*>(key.data()) << ", " << std::dec
<< key.size();
} else {
out_key = iter->first;
}
return ValueProxy(&block_, GetStorage(), iter, out_key, &value_size_);
}
void SpdyHeaderBlock::AppendValueOrAddHeader(const SpdyStringPiece key,
const SpdyStringPiece value) {
value_size_ += value.size();
auto iter = block_.find(key);
if (iter == block_.end()) {
SPDY_DVLOG(1) << "Inserting: (" << key << ", " << value << ")";
AppendHeader(key, value);
return;
}
SPDY_DVLOG(1) << "Updating key: " << iter->first
<< "; appending value: " << value;
value_size_ += SeparatorForKey(key).size();
iter->second.Append(GetStorage()->Write(value));
}
size_t SpdyHeaderBlock::EstimateMemoryUsage() const {
// TODO(xunjieli): https://crbug.com/669108. Also include |block_| when EMU()
// supports linked_hash_map.
return SpdyEstimateMemoryUsage(storage_);
}
void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key,
const SpdyStringPiece value) {
auto backed_key = WriteKey(key);
auto* storage = GetStorage();
block_.emplace(std::make_pair(
backed_key, HeaderValue(storage, backed_key, storage->Write(value))));
}
SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() {
if (storage_ == nullptr) {
storage_ = std::make_unique<Storage>();
}
return storage_.get();
}
SpdyStringPiece SpdyHeaderBlock::WriteKey(const SpdyStringPiece key) {
key_size_ += key.size();
return GetStorage()->Write(key);
}
size_t SpdyHeaderBlock::bytes_allocated() const {
if (storage_ == nullptr) {
return 0;
} else {
return storage_->bytes_allocated();
}
}
size_t Join(char* dst,
const std::vector<SpdyStringPiece>& fragments,
SpdyStringPiece separator) {
if (fragments.empty()) {
return 0;
}
auto* original_dst = dst;
auto it = fragments.begin();
memcpy(dst, it->data(), it->size());
dst += it->size();
for (++it; it != fragments.end(); ++it) {
memcpy(dst, separator.data(), separator.size());
dst += separator.size();
memcpy(dst, it->data(), it->size());
dst += it->size();
}
return dst - original_dst;
}
} // namespace spdy