Commit d4ec6008 authored by bnc's avatar bnc Committed by Commit bot

Make HPACK static table static and immutable.

Create HpackStaticTable singleton class, so that every HPACK context can share
a constant instance of the static table.

This lands server change 74862865 by bnc.

BUG=400336

Review URL: https://codereview.chromium.org/549583003

Cr-Commit-Position: refs/heads/master@{#293710}
parent cb6d57d6
......@@ -1029,6 +1029,8 @@
'spdy/hpack_input_stream.h',
'spdy/hpack_output_stream.cc',
'spdy/hpack_output_stream.h',
'spdy/hpack_static_table.cc',
'spdy/hpack_static_table.h',
'spdy/hpack_string_util.cc',
'spdy/hpack_string_util.h',
'spdy/spdy_bitmasks.h',
......@@ -1598,6 +1600,7 @@
'spdy/hpack_input_stream_test.cc',
'spdy/hpack_output_stream_test.cc',
'spdy/hpack_round_trip_test.cc',
'spdy/hpack_static_table_test.cc',
'spdy/hpack_string_util_test.cc',
'spdy/mock_spdy_framer_visitor.cc',
'spdy/mock_spdy_framer_visitor.h',
......
......@@ -4,10 +4,13 @@
#include "net/spdy/hpack_constants.h"
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "net/spdy/hpack_huffman_table.h"
#include "net/spdy/hpack_static_table.h"
namespace net {
......@@ -33,6 +36,25 @@ struct SharedHpackHuffmanTable {
scoped_ptr<const HpackHuffmanTable> table;
};
// SharedHpackStaticTable is a Singleton wrapping a HpackStaticTable
// instance initialized with |kHpackStaticTable|.
struct SharedHpackStaticTable {
public:
SharedHpackStaticTable() {
std::vector<HpackStaticEntry> static_table = HpackStaticTableVector();
scoped_ptr<HpackStaticTable> mutable_table(new HpackStaticTable());
mutable_table->Initialize(&static_table[0], static_table.size());
CHECK(mutable_table->IsInitialized());
table.reset(mutable_table.release());
}
static SharedHpackStaticTable* GetInstance() {
return Singleton<SharedHpackStaticTable>::get();
}
scoped_ptr<const HpackStaticTable> table;
};
} // namespace
// Produced by applying the python program [1] with tables
......@@ -309,8 +331,88 @@ std::vector<HpackHuffmanSymbol> HpackHuffmanCode() {
kHpackHuffmanCode + arraysize(kHpackHuffmanCode));
}
// The "constructor" for a HpackStaticEntry that computes the lengths at
// compile time.
#define STATIC_ENTRY(name, value) \
{ name, arraysize(name) - 1, value, arraysize(value) - 1 }
std::vector<HpackStaticEntry> HpackStaticTableVector() {
static const HpackStaticEntry kHpackStaticTable[] = {
STATIC_ENTRY(":authority", ""), // 1
STATIC_ENTRY(":method", "GET"), // 2
STATIC_ENTRY(":method", "POST"), // 3
STATIC_ENTRY(":path", "/"), // 4
STATIC_ENTRY(":path", "/index.html"), // 5
STATIC_ENTRY(":scheme", "http"), // 6
STATIC_ENTRY(":scheme", "https"), // 7
STATIC_ENTRY(":status", "200"), // 8
STATIC_ENTRY(":status", "204"), // 9
STATIC_ENTRY(":status", "206"), // 10
STATIC_ENTRY(":status", "304"), // 11
STATIC_ENTRY(":status", "400"), // 12
STATIC_ENTRY(":status", "404"), // 13
STATIC_ENTRY(":status", "500"), // 14
STATIC_ENTRY("accept-charset", ""), // 15
STATIC_ENTRY("accept-encoding", "gzip, deflate"), // 16
STATIC_ENTRY("accept-language", ""), // 17
STATIC_ENTRY("accept-ranges", ""), // 18
STATIC_ENTRY("accept", ""), // 19
STATIC_ENTRY("access-control-allow-origin", ""), // 20
STATIC_ENTRY("age", ""), // 21
STATIC_ENTRY("allow", ""), // 22
STATIC_ENTRY("authorization", ""), // 23
STATIC_ENTRY("cache-control", ""), // 24
STATIC_ENTRY("content-disposition", ""), // 25
STATIC_ENTRY("content-encoding", ""), // 26
STATIC_ENTRY("content-language", ""), // 27
STATIC_ENTRY("content-length", ""), // 28
STATIC_ENTRY("content-location", ""), // 29
STATIC_ENTRY("content-range", ""), // 30
STATIC_ENTRY("content-type", ""), // 31
STATIC_ENTRY("cookie", ""), // 32
STATIC_ENTRY("date", ""), // 33
STATIC_ENTRY("etag", ""), // 34
STATIC_ENTRY("expect", ""), // 35
STATIC_ENTRY("expires", ""), // 36
STATIC_ENTRY("from", ""), // 37
STATIC_ENTRY("host", ""), // 38
STATIC_ENTRY("if-match", ""), // 39
STATIC_ENTRY("if-modified-since", ""), // 40
STATIC_ENTRY("if-none-match", ""), // 41
STATIC_ENTRY("if-range", ""), // 42
STATIC_ENTRY("if-unmodified-since", ""), // 43
STATIC_ENTRY("last-modified", ""), // 44
STATIC_ENTRY("link", ""), // 45
STATIC_ENTRY("location", ""), // 46
STATIC_ENTRY("max-forwards", ""), // 47
STATIC_ENTRY("proxy-authenticate", ""), // 48
STATIC_ENTRY("proxy-authorization", ""), // 49
STATIC_ENTRY("range", ""), // 50
STATIC_ENTRY("referer", ""), // 51
STATIC_ENTRY("refresh", ""), // 52
STATIC_ENTRY("retry-after", ""), // 53
STATIC_ENTRY("server", ""), // 54
STATIC_ENTRY("set-cookie", ""), // 55
STATIC_ENTRY("strict-transport-security", ""), // 56
STATIC_ENTRY("transfer-encoding", ""), // 57
STATIC_ENTRY("user-agent", ""), // 58
STATIC_ENTRY("vary", ""), // 59
STATIC_ENTRY("via", ""), // 60
STATIC_ENTRY("www-authenticate", ""), // 61
};
return std::vector<HpackStaticEntry>(
kHpackStaticTable,
kHpackStaticTable + arraysize(kHpackStaticTable));
}
#undef STATIC_ENTRY
const HpackHuffmanTable& ObtainHpackHuffmanTable() {
return *SharedHpackHuffmanTable::GetInstance()->table;
}
const HpackStaticTable& ObtainHpackStaticTable() {
return *SharedHpackStaticTable::GetInstance()->table;
}
} // namespace net
......@@ -29,7 +29,17 @@ struct HpackHuffmanSymbol {
uint16 id;
};
// An entry in the static table. Must be a POD in order to avoid static
// initializers, i.e. no user-defined constructors or destructors.
struct HpackStaticEntry {
const char* const name;
const size_t name_len;
const char* const value;
const size_t value_len;
};
class HpackHuffmanTable;
class HpackStaticTable;
const uint32 kDefaultHeaderTableSizeSetting = 4096;
......@@ -65,14 +75,22 @@ const HpackPrefix kLiteralNeverIndexOpcode = { 0x1, 4 };
// table size with a 5-bit prefix.
const HpackPrefix kHeaderTableSizeUpdateOpcode = { 0x1, 3 };
// Returns symbol code table from "Appendix C. Huffman Codes".
// Returns symbol code table from "Appendix C. Huffman Code".
NET_EXPORT_PRIVATE std::vector<HpackHuffmanSymbol> HpackHuffmanCode();
// Returns static table from "Appendix B. Static Table Definition".
NET_EXPORT_PRIVATE std::vector<HpackStaticEntry> HpackStaticTableVector();
// Returns a HpackHuffmanTable instance initialized with |kHpackHuffmanCode|.
// The instance is read-only, has static lifetime, and is safe to share amoung
// threads. This function is thread-safe.
NET_EXPORT_PRIVATE const HpackHuffmanTable& ObtainHpackHuffmanTable();
// Returns a HpackStaticTable instance initialized with |kHpackStaticTable|.
// The instance is read-only, has static lifetime, and is safe to share amoung
// threads. This function is thread-safe.
NET_EXPORT_PRIVATE const HpackStaticTable& ObtainHpackStaticTable();
// Pseudo-headers start with a colon. (HTTP2 8.1.2.1., HPACK 3.1.)
const char kPseudoHeaderPrefix = ':';
......
......@@ -139,7 +139,7 @@ bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream) {
if (!input_stream->DecodeNextUint32(&index))
return false;
HpackEntry* entry = header_table_.GetByIndex(index);
const HpackEntry* entry = header_table_.GetByIndex(index);
if (entry == NULL)
return false;
......
......@@ -92,7 +92,7 @@ class HpackDecoderTest : public ::testing::Test {
void expectEntry(size_t index, size_t size, const string& name,
const string& value) {
HpackEntry* entry = decoder_peer_.header_table()->GetByIndex(index);
const HpackEntry* entry = decoder_peer_.header_table()->GetByIndex(index);
EXPECT_EQ(name, entry->name()) << "index " << index;
EXPECT_EQ(value, entry->value());
EXPECT_EQ(size, entry->Size());
......
......@@ -46,7 +46,8 @@ bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set,
// Encode pseudo-headers.
for (Representations::const_iterator it = pseudo_headers.begin();
it != pseudo_headers.end(); ++it) {
HpackEntry* entry = header_table_.GetByNameAndValue(it->first, it->second);
const HpackEntry* entry =
header_table_.GetByNameAndValue(it->first, it->second);
if (entry != NULL) {
EmitIndex(entry);
} else {
......@@ -69,7 +70,8 @@ bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set,
Representations literal_headers;
for (Representations::const_iterator it = regular_headers.begin();
it != regular_headers.end(); ++it) {
HpackEntry* entry = header_table_.GetByNameAndValue(it->first, it->second);
const HpackEntry* entry =
header_table_.GetByNameAndValue(it->first, it->second);
if (entry != NULL) {
EmitIndex(entry);
} else {
......@@ -103,7 +105,7 @@ bool HpackEncoder::EncodeHeaderSetWithoutCompression(
return true;
}
void HpackEncoder::EmitIndex(HpackEntry* entry) {
void HpackEncoder::EmitIndex(const HpackEntry* entry) {
output_stream_.AppendPrefix(kIndexedOpcode);
output_stream_.AppendUint32(header_table_.IndexOf(entry));
}
......
......@@ -67,7 +67,7 @@ class NET_EXPORT_PRIVATE HpackEncoder {
typedef std::vector<Representation> Representations;
// Emits a static/dynamic indexed representation (Section 7.1).
void EmitIndex(HpackEntry* entry);
void EmitIndex(const HpackEntry* entry);
// Emits a literal representation (Section 7.2).
void EmitIndexedLiteral(const Representation& representation);
......
......@@ -98,10 +98,10 @@ class HpackEncoderTest : public ::testing::Test {
HpackEncoderTest()
: encoder_(ObtainHpackHuffmanTable()),
peer_(&encoder_) {}
peer_(&encoder_),
static_(peer_.table()->GetByIndex(1)) {}
virtual void SetUp() {
static_ = peer_.table()->GetByIndex(1);
// Populate dynamic entries into the table fixture. For simplicity each
// entry has name.size() + value.size() == 10.
key_1_ = peer_.table()->TryAddEntry("key1", "value1");
......@@ -120,7 +120,7 @@ class HpackEncoderTest : public ::testing::Test {
expected_.AppendPrefix(kIndexedOpcode);
expected_.AppendUint32(index);
}
void ExpectIndexedLiteral(HpackEntry* key_entry, StringPiece value) {
void ExpectIndexedLiteral(const HpackEntry* key_entry, StringPiece value) {
expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
expected_.AppendUint32(IndexOf(key_entry));
expected_.AppendPrefix(kStringLiteralIdentityEncoded);
......@@ -156,15 +156,18 @@ class HpackEncoderTest : public ::testing::Test {
size_t IndexOf(HpackEntry* entry) {
return peer_.table()->IndexOf(entry);
}
size_t IndexOf(const HpackEntry* entry) {
return peer_.table()->IndexOf(entry);
}
HpackEncoder encoder_;
test::HpackEncoderPeer peer_;
HpackEntry* static_;
HpackEntry* key_1_;
HpackEntry* key_2_;
HpackEntry* cookie_a_;
HpackEntry* cookie_c_;
const HpackEntry* static_;
const HpackEntry* key_1_;
const HpackEntry* key_2_;
const HpackEntry* cookie_a_;
const HpackEntry* cookie_c_;
HpackOutputStream expected_;
};
......
......@@ -8,97 +8,13 @@
#include "base/logging.h"
#include "net/spdy/hpack_constants.h"
#include "net/spdy/hpack_static_table.h"
#include "net/spdy/hpack_string_util.h"
namespace net {
using base::StringPiece;
namespace {
// An entry in the static table. Must be a POD in order to avoid
// static initializers, i.e. no user-defined constructors or
// destructors.
struct StaticEntry {
const char* const name;
const size_t name_len;
const char* const value;
const size_t value_len;
};
// The "constructor" for a StaticEntry that computes the lengths at
// compile time.
#define STATIC_ENTRY(name, value) \
{ name, arraysize(name) - 1, value, arraysize(value) - 1 }
const StaticEntry kStaticTable[] = {
STATIC_ENTRY(":authority" , ""), // 1
STATIC_ENTRY(":method" , "GET"), // 2
STATIC_ENTRY(":method" , "POST"), // 3
STATIC_ENTRY(":path" , "/"), // 4
STATIC_ENTRY(":path" , "/index.html"), // 5
STATIC_ENTRY(":scheme" , "http"), // 6
STATIC_ENTRY(":scheme" , "https"), // 7
STATIC_ENTRY(":status" , "200"), // 8
STATIC_ENTRY(":status" , "204"), // 9
STATIC_ENTRY(":status" , "206"), // 10
STATIC_ENTRY(":status" , "304"), // 11
STATIC_ENTRY(":status" , "400"), // 12
STATIC_ENTRY(":status" , "404"), // 13
STATIC_ENTRY(":status" , "500"), // 14
STATIC_ENTRY("accept-charset" , ""), // 15
STATIC_ENTRY("accept-encoding" , "gzip, deflate"), // 16
STATIC_ENTRY("accept-language" , ""), // 17
STATIC_ENTRY("accept-ranges" , ""), // 18
STATIC_ENTRY("accept" , ""), // 19
STATIC_ENTRY("access-control-allow-origin", ""), // 20
STATIC_ENTRY("age" , ""), // 21
STATIC_ENTRY("allow" , ""), // 22
STATIC_ENTRY("authorization" , ""), // 23
STATIC_ENTRY("cache-control" , ""), // 24
STATIC_ENTRY("content-disposition" , ""), // 25
STATIC_ENTRY("content-encoding" , ""), // 26
STATIC_ENTRY("content-language" , ""), // 27
STATIC_ENTRY("content-length" , ""), // 28
STATIC_ENTRY("content-location" , ""), // 29
STATIC_ENTRY("content-range" , ""), // 30
STATIC_ENTRY("content-type" , ""), // 31
STATIC_ENTRY("cookie" , ""), // 32
STATIC_ENTRY("date" , ""), // 33
STATIC_ENTRY("etag" , ""), // 34
STATIC_ENTRY("expect" , ""), // 35
STATIC_ENTRY("expires" , ""), // 36
STATIC_ENTRY("from" , ""), // 37
STATIC_ENTRY("host" , ""), // 38
STATIC_ENTRY("if-match" , ""), // 39
STATIC_ENTRY("if-modified-since" , ""), // 40
STATIC_ENTRY("if-none-match" , ""), // 41
STATIC_ENTRY("if-range" , ""), // 42
STATIC_ENTRY("if-unmodified-since" , ""), // 43
STATIC_ENTRY("last-modified" , ""), // 44
STATIC_ENTRY("link" , ""), // 45
STATIC_ENTRY("location" , ""), // 46
STATIC_ENTRY("max-forwards" , ""), // 47
STATIC_ENTRY("proxy-authenticate" , ""), // 48
STATIC_ENTRY("proxy-authorization" , ""), // 49
STATIC_ENTRY("range" , ""), // 50
STATIC_ENTRY("referer" , ""), // 51
STATIC_ENTRY("refresh" , ""), // 52
STATIC_ENTRY("retry-after" , ""), // 53
STATIC_ENTRY("server" , ""), // 54
STATIC_ENTRY("set-cookie" , ""), // 55
STATIC_ENTRY("strict-transport-security" , ""), // 56
STATIC_ENTRY("transfer-encoding" , ""), // 57
STATIC_ENTRY("user-agent" , ""), // 58
STATIC_ENTRY("vary" , ""), // 59
STATIC_ENTRY("via" , ""), // 60
STATIC_ENTRY("www-authenticate" , ""), // 61
};
#undef STATIC_ENTRY
} // namespace
bool HpackHeaderTable::EntryComparator::operator() (
const HpackEntry* lhs, const HpackEntry* rhs) const {
int result = lhs->name().compare(rhs->name());
......@@ -107,8 +23,8 @@ bool HpackHeaderTable::EntryComparator::operator() (
result = lhs->value().compare(rhs->value());
if (result != 0)
return result < 0;
const size_t lhs_index = table_->IndexOf(lhs);
const size_t rhs_index = table_->IndexOf(rhs);
const size_t lhs_index = lhs->IsLookup() ? 0 : 1 + lhs->InsertionIndex();
const size_t rhs_index = rhs->IsLookup() ? 0 : 1 + rhs->InsertionIndex();
DCHECK(lhs == rhs || lhs_index != rhs_index)
<< "lhs: (" << lhs->name() << ", " << rhs->value() << ") rhs: ("
<< rhs->name() << ", " << rhs->value() << ")"
......@@ -117,27 +33,16 @@ bool HpackHeaderTable::EntryComparator::operator() (
}
HpackHeaderTable::HpackHeaderTable()
: index_(EntryComparator(this)),
: static_entries_(ObtainHpackStaticTable().GetStaticEntries()),
static_index_(ObtainHpackStaticTable().GetStaticIndex()),
settings_size_bound_(kDefaultHeaderTableSizeSetting),
size_(0),
max_size_(kDefaultHeaderTableSizeSetting),
total_insertions_(0) {
for (const StaticEntry* it = kStaticTable;
it != kStaticTable + arraysize(kStaticTable); ++it) {
static_entries_.push_back(
HpackEntry(StringPiece(it->name, it->name_len),
StringPiece(it->value, it->value_len),
true, // is_static
total_insertions_));
CHECK(index_.insert(&static_entries_.back()).second);
++total_insertions_;
}
}
total_insertions_(static_entries_.size()) {}
HpackHeaderTable::~HpackHeaderTable() {}
HpackEntry* HpackHeaderTable::GetByIndex(size_t index) {
const HpackEntry* HpackHeaderTable::GetByIndex(size_t index) {
if (index == 0) {
return NULL;
}
......@@ -152,21 +57,41 @@ HpackEntry* HpackHeaderTable::GetByIndex(size_t index) {
return NULL;
}
HpackEntry* HpackHeaderTable::GetByName(StringPiece name) {
const HpackEntry* HpackHeaderTable::GetByName(StringPiece name) {
HpackEntry query(name, "");
OrderedEntrySet::const_iterator it = index_.lower_bound(&query);
if (it != index_.end() && (*it)->name() == name) {
return *it;
{
OrderedEntrySet::const_iterator it = static_index_.lower_bound(&query);
if (it != static_index_.end() && (*it)->name() == name) {
return *it;
}
}
{
OrderedEntrySet::const_iterator it = dynamic_index_.lower_bound(&query);
if (it != dynamic_index_.end() && (*it)->name() == name) {
return *it;
}
}
return NULL;
}
HpackEntry* HpackHeaderTable::GetByNameAndValue(StringPiece name,
StringPiece value) {
const HpackEntry* HpackHeaderTable::GetByNameAndValue(StringPiece name,
StringPiece value) {
HpackEntry query(name, value);
OrderedEntrySet::const_iterator it = index_.lower_bound(&query);
if (it != index_.end() && (*it)->name() == name && (*it)->value() == value) {
return *it;
{
OrderedEntrySet::const_iterator it = static_index_.lower_bound(&query);
if (it != static_index_.end() &&
(*it)->name() == name &&
(*it)->value() == value) {
return *it;
}
}
{
OrderedEntrySet::const_iterator it = dynamic_index_.lower_bound(&query);
if (it != dynamic_index_.end() &&
(*it)->name() == name &&
(*it)->value() == value) {
return *it;
}
}
return NULL;
}
......@@ -234,12 +159,13 @@ void HpackHeaderTable::Evict(size_t count) {
HpackEntry* entry = &dynamic_entries_.back();
size_ -= entry->Size();
CHECK_EQ(1u, index_.erase(entry));
CHECK_EQ(1u, dynamic_index_.erase(entry));
dynamic_entries_.pop_back();
}
}
HpackEntry* HpackHeaderTable::TryAddEntry(StringPiece name, StringPiece value) {
const HpackEntry* HpackHeaderTable::TryAddEntry(StringPiece name,
StringPiece value) {
Evict(EvictionCountForEntry(name, value));
size_t entry_size = HpackEntry::Size(name, value);
......@@ -253,7 +179,7 @@ HpackEntry* HpackHeaderTable::TryAddEntry(StringPiece name, StringPiece value) {
value,
false, // is_static
total_insertions_));
CHECK(index_.insert(&dynamic_entries_.front()).second);
CHECK(dynamic_index_.insert(&dynamic_entries_.front()).second);
size_ += entry_size;
++total_insertions_;
......@@ -267,9 +193,14 @@ void HpackHeaderTable::DebugLogTableState() const {
it != dynamic_entries_.end(); ++it) {
DVLOG(2) << " " << it->GetDebugString();
}
DVLOG(2) << "Full Index:";
for (OrderedEntrySet::const_iterator it = index_.begin();
it != index_.end(); ++it) {
DVLOG(2) << "Full Static Index:";
for (OrderedEntrySet::const_iterator it = static_index_.begin();
it != static_index_.end(); ++it) {
DVLOG(2) << " " << (*it)->GetDebugString();
}
DVLOG(2) << "Full Dynamic Index:";
for (OrderedEntrySet::const_iterator it = dynamic_index_.begin();
it != dynamic_index_.end(); ++it) {
DVLOG(2) << " " << (*it)->GetDebugString();
}
}
......
......@@ -19,6 +19,8 @@
namespace net {
using base::StringPiece;
namespace test {
class HpackHeaderTablePeer;
} // namespace test
......@@ -31,7 +33,7 @@ class NET_EXPORT_PRIVATE HpackHeaderTable {
// HpackHeaderTable takes advantage of the deque property that references
// remain valid, so long as insertions & deletions are at the head & tail.
// If this changes (eg we start to drop entries from the middle of the table),
// this needs to be a std::list, in which case |index_| can be trivially
// this needs to be a std::list, in which case |*_index_| can be trivially
// extended to map to list iterators.
typedef std::deque<HpackEntry> EntryTable;
......@@ -41,12 +43,7 @@ class NET_EXPORT_PRIVATE HpackHeaderTable {
// composed with the 'lookup' HpackEntry constructor to allow for efficient
// lower-bounding of matching entries.
struct NET_EXPORT_PRIVATE EntryComparator {
explicit EntryComparator(HpackHeaderTable* table) : table_(table) {}
bool operator() (const HpackEntry* lhs, const HpackEntry* rhs) const;
private:
HpackHeaderTable* table_;
};
typedef std::set<HpackEntry*, EntryComparator> OrderedEntrySet;
......@@ -63,14 +60,13 @@ class NET_EXPORT_PRIVATE HpackHeaderTable {
size_t max_size() const { return max_size_; }
// Returns the entry matching the index, or NULL.
HpackEntry* GetByIndex(size_t index);
const HpackEntry* GetByIndex(size_t index);
// Returns the lowest-value entry having |name|, or NULL.
HpackEntry* GetByName(base::StringPiece name);
const HpackEntry* GetByName(StringPiece name);
// Returns the lowest-index matching entry, or NULL.
HpackEntry* GetByNameAndValue(base::StringPiece name,
base::StringPiece value);
const HpackEntry* GetByNameAndValue(StringPiece name, StringPiece value);
// Returns the index of an entry within this header table.
size_t IndexOf(const HpackEntry* entry) const;
......@@ -86,7 +82,8 @@ class NET_EXPORT_PRIVATE HpackHeaderTable {
// Determine the set of entries which would be evicted by the insertion
// of |name| & |value| into the table, as per section 3.3.3. No eviction
// actually occurs. The set is returned via range [begin_out, end_out).
void EvictionSet(base::StringPiece name, base::StringPiece value,
void EvictionSet(StringPiece name,
StringPiece value,
EntryTable::iterator* begin_out,
EntryTable::iterator* end_out);
......@@ -94,14 +91,13 @@ class NET_EXPORT_PRIVATE HpackHeaderTable {
// and |value| must not be owned by an entry which could be evicted. The
// added HpackEntry is returned, or NULL is returned if all entries were
// evicted and the empty table is of insufficent size for the representation.
HpackEntry* TryAddEntry(base::StringPiece name, base::StringPiece value);
const HpackEntry* TryAddEntry(StringPiece name, StringPiece value);
void DebugLogTableState() const;
private:
// Returns number of evictions required to enter |name| & |value|.
size_t EvictionCountForEntry(base::StringPiece name,
base::StringPiece value) const;
size_t EvictionCountForEntry(StringPiece name, StringPiece value) const;
// Returns number of evictions required to reclaim |reclaim_size| table size.
size_t EvictionCountToReclaim(size_t reclaim_size) const;
......@@ -109,11 +105,13 @@ class NET_EXPORT_PRIVATE HpackHeaderTable {
// Evicts |count| oldest entries from the table.
void Evict(size_t count);
// |static_entries_| and |static_index_| are owned by HpackStaticTable
// singleton.
const EntryTable& static_entries_;
EntryTable dynamic_entries_;
EntryTable static_entries_;
// Full table index, over |dynamic_entries_| and |static_entries_|.
OrderedEntrySet index_;
const OrderedEntrySet& static_index_;
OrderedEntrySet dynamic_index_;
// Last acknowledged value for SETTINGS_HEADER_TABLE_SIZE.
size_t settings_size_bound_;
......
......@@ -34,8 +34,8 @@ class HpackHeaderTablePeer {
const HpackHeaderTable::EntryTable& static_entries() {
return table_->static_entries_;
}
const HpackHeaderTable::OrderedEntrySet& index() {
return table_->index_;
size_t index_size() {
return table_->static_index_.size() + table_->dynamic_index_.size();
}
std::vector<HpackEntry*> EvictionSet(StringPiece name, StringPiece value) {
HpackHeaderTable::EntryTable::iterator begin, end;
......@@ -62,11 +62,6 @@ class HpackHeaderTablePeer {
return table_->Evict(count);
}
void AddStaticEntry(StringPiece name, StringPiece value) {
table_->static_entries_.push_back(
HpackEntry(name, value, true, table_->total_insertions_++));
}
void AddDynamicEntry(StringPiece name, StringPiece value) {
table_->dynamic_entries_.push_back(
HpackEntry(name, value, false, table_->total_insertions_++));
......@@ -84,11 +79,7 @@ class HpackHeaderTableTest : public ::testing::Test {
protected:
typedef std::vector<HpackEntry> HpackEntryVector;
HpackHeaderTableTest()
: table_(),
peer_(&table_),
name_("header-name"),
value_("header value") {}
HpackHeaderTableTest() : table_(), peer_(&table_) {}
// Returns an entry whose Size() is equal to the given one.
static HpackEntry MakeEntryOfSize(uint32 size) {
......@@ -126,32 +117,27 @@ class HpackHeaderTableTest : public ::testing::Test {
table_.EvictionSet(it->name(), it->value(), &begin, &end);
EXPECT_EQ(0, distance(begin, end));
HpackEntry* entry = table_.TryAddEntry(it->name(), it->value());
const HpackEntry* entry = table_.TryAddEntry(it->name(), it->value());
EXPECT_NE(entry, static_cast<HpackEntry*>(NULL));
}
for (size_t i = 0; i != entries.size(); ++i) {
// Static table has 61 entries, dynamic entries follow those.
size_t index = 61 + entries.size() - i;
HpackEntry* entry = table_.GetByIndex(index);
const HpackEntry* entry = table_.GetByIndex(index);
EXPECT_EQ(entries[i].name(), entry->name());
EXPECT_EQ(entries[i].value(), entry->value());
EXPECT_EQ(index, table_.IndexOf(entry));
}
}
HpackEntry StaticEntry() {
peer_.AddStaticEntry(name_, value_);
return peer_.static_entries().back();
}
HpackEntry DynamicEntry() {
peer_.AddDynamicEntry(name_, value_);
HpackEntry DynamicEntry(string name, string value) {
peer_.AddDynamicEntry(name, value);
return peer_.dynamic_entries().back();
}
HpackHeaderTable table_;
test::HpackHeaderTablePeer peer_;
string name_, value_;
};
TEST_F(HpackHeaderTableTest, StaticTableInitialization) {
......@@ -164,7 +150,7 @@ TEST_F(HpackHeaderTableTest, StaticTableInitialization) {
// Static entries have been populated and inserted into the table & index.
EXPECT_NE(0u, peer_.static_entries().size());
EXPECT_EQ(peer_.index().size(), peer_.static_entries().size());
EXPECT_EQ(peer_.index_size(), peer_.static_entries().size());
for (size_t i = 0; i != peer_.static_entries().size(); ++i) {
const HpackEntry* entry = &peer_.static_entries()[i];
......@@ -176,11 +162,11 @@ TEST_F(HpackHeaderTableTest, StaticTableInitialization) {
TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) {
size_t static_count = peer_.total_insertions();
HpackEntry* first_static_entry = table_.GetByIndex(1);
const HpackEntry* first_static_entry = table_.GetByIndex(1);
EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
HpackEntry* entry = table_.TryAddEntry("header-key", "Header Value");
const HpackEntry* entry = table_.TryAddEntry("header-key", "Header Value");
EXPECT_EQ("header-key", entry->name());
EXPECT_EQ("Header Value", entry->value());
EXPECT_FALSE(entry->IsStatic());
......@@ -190,7 +176,7 @@ TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) {
EXPECT_EQ(1u, peer_.dynamic_entries_count());
EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
EXPECT_EQ(static_count + 1, peer_.total_insertions());
EXPECT_EQ(static_count + 1, peer_.index().size());
EXPECT_EQ(static_count + 1, peer_.index_size());
// Index() of entries reflects the insertion.
EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
......@@ -205,7 +191,7 @@ TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) {
EXPECT_EQ(0u, peer_.dynamic_entries_count());
EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
EXPECT_EQ(static_count + 1, peer_.total_insertions());
EXPECT_EQ(static_count, peer_.index().size());
EXPECT_EQ(static_count, peer_.index_size());
// Index() of |first_static_entry| reflects the eviction.
EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
......@@ -213,7 +199,7 @@ TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) {
}
TEST_F(HpackHeaderTableTest, EntryIndexing) {
HpackEntry* first_static_entry = table_.GetByIndex(1);
const HpackEntry* first_static_entry = table_.GetByIndex(1);
// Static entries are queryable by name & value.
EXPECT_EQ(first_static_entry, table_.GetByName(first_static_entry->name()));
......@@ -222,15 +208,15 @@ TEST_F(HpackHeaderTableTest, EntryIndexing) {
// Create a mix of entries which duplicate names, and names & values of both
// dynamic and static entries.
HpackEntry* entry1 = table_.TryAddEntry(first_static_entry->name(),
first_static_entry->value());
HpackEntry* entry2 = table_.TryAddEntry(first_static_entry->name(),
"Value Four");
HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One");
HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three");
HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two");
HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three");
HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four");
const HpackEntry* entry1 = table_.TryAddEntry(first_static_entry->name(),
first_static_entry->value());
const HpackEntry* entry2 =
table_.TryAddEntry(first_static_entry->name(), "Value Four");
const HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One");
const HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three");
const HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two");
const HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three");
const HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four");
// Entries are queryable under their current index.
EXPECT_EQ(entry7, table_.GetByIndex(62));
......@@ -249,10 +235,11 @@ TEST_F(HpackHeaderTableTest, EntryIndexing) {
table_.GetByName(first_static_entry->name())->name());
EXPECT_EQ(NULL, table_.GetByName("not-present"));
// Querying by name & value returns the lowest-index matching entry.
// Querying by name & value returns the lowest-index matching entry among
// static entries, and the highest-index one among dynamic entries.
EXPECT_EQ(entry3, table_.GetByNameAndValue("key-1", "Value One"));
EXPECT_EQ(entry5, table_.GetByNameAndValue("key-1", "Value Two"));
EXPECT_EQ(entry6, table_.GetByNameAndValue("key-2", "Value Three"));
EXPECT_EQ(entry4, table_.GetByNameAndValue("key-2", "Value Three"));
EXPECT_EQ(entry7, table_.GetByNameAndValue("key-2", "Value Four"));
EXPECT_EQ(first_static_entry,
table_.GetByNameAndValue(first_static_entry->name(),
......@@ -279,9 +266,9 @@ TEST_F(HpackHeaderTableTest, EntryIndexing) {
TEST_F(HpackHeaderTableTest, SetSizes) {
string key = "key", value = "value";
HpackEntry* entry1 = table_.TryAddEntry(key, value);
HpackEntry* entry2 = table_.TryAddEntry(key, value);
HpackEntry* entry3 = table_.TryAddEntry(key, value);
const HpackEntry* entry1 = table_.TryAddEntry(key, value);
const HpackEntry* entry2 = table_.TryAddEntry(key, value);
const HpackEntry* entry3 = table_.TryAddEntry(key, value);
// Set exactly large enough. No Evictions.
size_t max_size = entry1->Size() + entry2->Size() + entry3->Size();
......@@ -313,8 +300,8 @@ TEST_F(HpackHeaderTableTest, SetSizes) {
TEST_F(HpackHeaderTableTest, EvictionCountForEntry) {
string key = "key", value = "value";
HpackEntry* entry1 = table_.TryAddEntry(key, value);
HpackEntry* entry2 = table_.TryAddEntry(key, value);
const HpackEntry* entry1 = table_.TryAddEntry(key, value);
const HpackEntry* entry2 = table_.TryAddEntry(key, value);
size_t entry3_size = HpackEntry::Size(key, value);
// Just enough capacity for third entry.
......@@ -330,8 +317,8 @@ TEST_F(HpackHeaderTableTest, EvictionCountForEntry) {
TEST_F(HpackHeaderTableTest, EvictionCountToReclaim) {
string key = "key", value = "value";
HpackEntry* entry1 = table_.TryAddEntry(key, value);
HpackEntry* entry2 = table_.TryAddEntry(key, value);
const HpackEntry* entry1 = table_.TryAddEntry(key, value);
const HpackEntry* entry2 = table_.TryAddEntry(key, value);
EXPECT_EQ(1u, peer_.EvictionCountToReclaim(1));
EXPECT_EQ(1u, peer_.EvictionCountToReclaim(entry1->Size()));
......@@ -387,7 +374,7 @@ TEST_F(HpackHeaderTableTest, TryAddEntryEviction) {
HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
AddEntriesExpectNoEviction(entries);
HpackEntry* survivor_entry = table_.GetByIndex(61 + 1);
const HpackEntry* survivor_entry = table_.GetByIndex(61 + 1);
HpackEntry long_entry =
MakeEntryOfSize(table_.max_size() - survivor_entry->Size());
......@@ -395,8 +382,8 @@ TEST_F(HpackHeaderTableTest, TryAddEntryEviction) {
EXPECT_EQ(peer_.dynamic_entries().size() - 1, peer_.EvictionSet(
long_entry.name(), long_entry.value()).size());
HpackEntry* new_entry = table_.TryAddEntry(long_entry.name(),
long_entry.value());
const HpackEntry* new_entry =
table_.TryAddEntry(long_entry.name(), long_entry.value());
EXPECT_EQ(62u, table_.IndexOf(new_entry));
EXPECT_EQ(2u, peer_.dynamic_entries().size());
EXPECT_EQ(table_.GetByIndex(63), survivor_entry);
......@@ -409,67 +396,51 @@ TEST_F(HpackHeaderTableTest, TryAddTooLargeEntry) {
HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
AddEntriesExpectNoEviction(entries);
HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1);
const HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1);
// All entries are to be evicted.
EXPECT_EQ(peer_.dynamic_entries().size(), peer_.EvictionSet(
long_entry.name(), long_entry.value()).size());
HpackEntry* new_entry = table_.TryAddEntry(long_entry.name(),
long_entry.value());
const HpackEntry* new_entry =
table_.TryAddEntry(long_entry.name(), long_entry.value());
EXPECT_EQ(new_entry, static_cast<HpackEntry*>(NULL));
EXPECT_EQ(0u, peer_.dynamic_entries().size());
}
TEST_F(HpackHeaderTableTest, ComparatorNameOrdering) {
HpackEntry entry1(StaticEntry());
name_[0]--;
HpackEntry entry2(StaticEntry());
HpackEntry entry1("header", "value");
HpackEntry entry2("HEADER", "value");
HpackHeaderTable::EntryComparator comparator(&table_);
HpackHeaderTable::EntryComparator comparator;
EXPECT_FALSE(comparator(&entry1, &entry2));
EXPECT_TRUE(comparator(&entry2, &entry1));
}
TEST_F(HpackHeaderTableTest, ComparatorValueOrdering) {
HpackEntry entry1(StaticEntry());
value_[0]--;
HpackEntry entry2(StaticEntry());
HpackEntry entry1("header", "value");
HpackEntry entry2("header", "VALUE");
HpackHeaderTable::EntryComparator comparator(&table_);
HpackHeaderTable::EntryComparator comparator;
EXPECT_FALSE(comparator(&entry1, &entry2));
EXPECT_TRUE(comparator(&entry2, &entry1));
}
TEST_F(HpackHeaderTableTest, ComparatorIndexOrdering) {
HpackEntry entry1(StaticEntry());
HpackEntry entry2(StaticEntry());
HpackHeaderTable::EntryComparator comparator(&table_);
EXPECT_TRUE(comparator(&entry1, &entry2));
EXPECT_FALSE(comparator(&entry2, &entry1));
HpackEntry entry3(DynamicEntry());
HpackEntry entry4(DynamicEntry());
// |entry4| has lower index than |entry3|.
EXPECT_TRUE(comparator(&entry4, &entry3));
EXPECT_FALSE(comparator(&entry3, &entry4));
// |entry1| has lower index than |entry3|.
EXPECT_TRUE(comparator(&entry1, &entry3));
EXPECT_FALSE(comparator(&entry3, &entry1));
HpackHeaderTable::EntryComparator comparator;
HpackEntry entry1(DynamicEntry("name", "value"));
HpackEntry entry2(DynamicEntry("name", "value"));
// |entry1| & |entry2| ordering is preserved, though each Index() has changed.
// |entry1| has lower insertion index than |entry2|.
EXPECT_TRUE(comparator(&entry1, &entry2));
EXPECT_FALSE(comparator(&entry2, &entry1));
}
TEST_F(HpackHeaderTableTest, ComparatorEqualityOrdering) {
HpackEntry entry1(StaticEntry());
HpackEntry entry2(DynamicEntry());
HpackEntry entry1("name", "value");
HpackEntry entry2(DynamicEntry("name", "value"));
HpackHeaderTable::EntryComparator comparator(&table_);
HpackHeaderTable::EntryComparator comparator;
EXPECT_FALSE(comparator(&entry1, &entry1));
EXPECT_FALSE(comparator(&entry2, &entry2));
}
......
// Copyright 2014 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/spdy/hpack_static_table.h"
#include "base/logging.h"
#include "net/spdy/hpack_constants.h"
#include "net/spdy/hpack_entry.h"
namespace net {
HpackStaticTable::HpackStaticTable() {}
HpackStaticTable::~HpackStaticTable() {}
void HpackStaticTable::Initialize(const HpackStaticEntry* static_entry_table,
size_t static_entry_count) {
CHECK(!IsInitialized());
int total_insertions = 0;
for (const HpackStaticEntry* it = static_entry_table;
it != static_entry_table + static_entry_count; ++it) {
static_entries_.push_back(HpackEntry(StringPiece(it->name, it->name_len),
StringPiece(it->value, it->value_len),
true, // is_static
total_insertions));
CHECK(static_index_.insert(&static_entries_.back()).second);
++total_insertions;
}
}
bool HpackStaticTable::IsInitialized() const {
return !static_entries_.empty();
}
} // namespace net
// Copyright 2014 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.
#ifndef NET_SPDY_HPACK_STATIC_TABLE_H_
#define NET_SPDY_HPACK_STATIC_TABLE_H_
#include "net/spdy/hpack_header_table.h"
namespace net {
struct HpackStaticEntry;
// HpackStaticTable provides |static_entries_| and |static_index_| for HPACK
// encoding and decoding contexts. Once initialized, an instance is read only
// and may be accessed only through its const interface. Such an instance may
// be shared accross multiple HPACK contexts.
class NET_EXPORT_PRIVATE HpackStaticTable {
public:
HpackStaticTable();
~HpackStaticTable();
// Prepares HpackStaticTable by filling up static_entries_ and static_index_
// from an array of struct HpackStaticEntry. Must be called exactly once.
void Initialize(const HpackStaticEntry* static_entry_table,
size_t static_entry_count);
// Returns whether Initialize() has been called.
bool IsInitialized() const;
// Accessors.
const HpackHeaderTable::EntryTable& GetStaticEntries() const {
return static_entries_;
}
const HpackHeaderTable::OrderedEntrySet& GetStaticIndex() const {
return static_index_;
}
private:
HpackHeaderTable::EntryTable static_entries_;
HpackHeaderTable::OrderedEntrySet static_index_;
};
} // namespace net
#endif // NET_SPDY_HPACK_STATIC_TABLE_H_
// Copyright 2014 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/spdy/hpack_static_table.h"
#include <vector>
#include "net/base/net_export.h"
#include "net/spdy/hpack_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace test {
namespace {
class HpackStaticTableTest : public ::testing::Test {
protected:
HpackStaticTableTest() : table_() {}
HpackStaticTable table_;
};
// Check that an initialized instance has the right number of entries.
TEST_F(HpackStaticTableTest, Initialize) {
EXPECT_FALSE(table_.IsInitialized());
std::vector<HpackStaticEntry> static_table = HpackStaticTableVector();
table_.Initialize(&static_table[0], static_table.size());
EXPECT_TRUE(table_.IsInitialized());
HpackHeaderTable::EntryTable static_entries = table_.GetStaticEntries();
EXPECT_EQ(static_table.size(), static_entries.size());
HpackHeaderTable::OrderedEntrySet static_index = table_.GetStaticIndex();
EXPECT_EQ(static_table.size(), static_index.size());
}
// Test that ObtainHpackStaticTable returns the same instance every time.
TEST_F(HpackStaticTableTest, IsSingleton) {
const HpackStaticTable* static_table_one = &ObtainHpackStaticTable();
const HpackStaticTable* static_table_two = &ObtainHpackStaticTable();
EXPECT_EQ(static_table_one, static_table_two);
}
} // namespace
} // namespace test
} // namespace net
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment