blob: ff7ef48d4fc62752c151169011aba51e3c220f7f [file] [log] [blame]
// Copyright (c) 2018 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/quic/core/qpack/qpack_header_table.h"
#include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
using ::testing::Mock;
using ::testing::StrictMock;
namespace quic {
namespace test {
namespace {
const uint64_t kMaximumDynamicTableCapacityForTesting = 1024 * 1024;
class MockObserver : public QpackHeaderTable::Observer {
public:
~MockObserver() override = default;
MOCK_METHOD0(OnInsertCountReachedThreshold, void());
};
class QpackHeaderTableTest : public QuicTest {
protected:
QpackHeaderTableTest() {
table_.SetMaximumDynamicTableCapacity(
kMaximumDynamicTableCapacityForTesting);
}
~QpackHeaderTableTest() override = default;
void ExpectEntryAtIndex(bool is_static,
uint64_t index,
QuicStringPiece expected_name,
QuicStringPiece expected_value) const {
const auto* entry = table_.LookupEntry(is_static, index);
ASSERT_TRUE(entry);
EXPECT_EQ(expected_name, entry->name());
EXPECT_EQ(expected_value, entry->value());
}
void ExpectNoEntryAtIndex(bool is_static, uint64_t index) const {
EXPECT_FALSE(table_.LookupEntry(is_static, index));
}
void ExpectMatch(QuicStringPiece name,
QuicStringPiece value,
QpackHeaderTable::MatchType expected_match_type,
bool expected_is_static,
uint64_t expected_index) const {
// Initialize outparams to a value different from the expected to ensure
// that FindHeaderField() sets them.
bool is_static = !expected_is_static;
uint64_t index = expected_index + 1;
QpackHeaderTable::MatchType matchtype =
table_.FindHeaderField(name, value, &is_static, &index);
EXPECT_EQ(expected_match_type, matchtype) << name << ": " << value;
EXPECT_EQ(expected_is_static, is_static) << name << ": " << value;
EXPECT_EQ(expected_index, index) << name << ": " << value;
}
void ExpectNoMatch(QuicStringPiece name, QuicStringPiece value) const {
bool is_static = false;
uint64_t index = 0;
QpackHeaderTable::MatchType matchtype =
table_.FindHeaderField(name, value, &is_static, &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNoMatch, matchtype)
<< name << ": " << value;
}
void InsertEntry(QuicStringPiece name, QuicStringPiece value) {
EXPECT_TRUE(table_.InsertEntry(name, value));
}
void ExpectToFailInsertingEntry(QuicStringPiece name, QuicStringPiece value) {
EXPECT_FALSE(table_.InsertEntry(name, value));
}
bool SetDynamicTableCapacity(uint64_t capacity) {
return table_.SetDynamicTableCapacity(capacity);
}
void RegisterObserver(QpackHeaderTable::Observer* observer,
uint64_t required_insert_count) {
table_.RegisterObserver(observer, required_insert_count);
}
uint64_t max_entries() const { return table_.max_entries(); }
uint64_t inserted_entry_count() const {
return table_.inserted_entry_count();
}
uint64_t dropped_entry_count() const { return table_.dropped_entry_count(); }
private:
QpackHeaderTable table_;
};
TEST_F(QpackHeaderTableTest, LookupStaticEntry) {
ExpectEntryAtIndex(/* is_static = */ true, 0, ":authority", "");
ExpectEntryAtIndex(/* is_static = */ true, 1, ":path", "/");
// 98 is the last entry.
ExpectEntryAtIndex(/* is_static = */ true, 98, "x-frame-options",
"sameorigin");
ASSERT_EQ(99u, QpackStaticTableVector().size());
ExpectNoEntryAtIndex(/* is_static = */ true, 99);
}
TEST_F(QpackHeaderTableTest, InsertAndLookupDynamicEntry) {
// Dynamic table is initially entry.
ExpectNoEntryAtIndex(/* is_static = */ false, 0);
ExpectNoEntryAtIndex(/* is_static = */ false, 1);
ExpectNoEntryAtIndex(/* is_static = */ false, 2);
ExpectNoEntryAtIndex(/* is_static = */ false, 3);
// Insert one entry.
InsertEntry("foo", "bar");
ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
ExpectNoEntryAtIndex(/* is_static = */ false, 1);
ExpectNoEntryAtIndex(/* is_static = */ false, 2);
ExpectNoEntryAtIndex(/* is_static = */ false, 3);
// Insert a different entry.
InsertEntry("baz", "bing");
ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing");
ExpectNoEntryAtIndex(/* is_static = */ false, 2);
ExpectNoEntryAtIndex(/* is_static = */ false, 3);
// Insert an entry identical to the most recently inserted one.
InsertEntry("baz", "bing");
ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar");
ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing");
ExpectEntryAtIndex(/* is_static = */ false, 2, "baz", "bing");
ExpectNoEntryAtIndex(/* is_static = */ false, 3);
}
TEST_F(QpackHeaderTableTest, FindStaticHeaderField) {
// A header name that has multiple entries with different values.
ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue,
true, 17u);
ExpectMatch(":method", "POST", QpackHeaderTable::MatchType::kNameAndValue,
true, 20u);
ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true,
15u);
// A header name that has a single entry with non-empty value.
ExpectMatch("accept-encoding", "gzip, deflate, br",
QpackHeaderTable::MatchType::kNameAndValue, true, 31u);
ExpectMatch("accept-encoding", "compress", QpackHeaderTable::MatchType::kName,
true, 31u);
ExpectMatch("accept-encoding", "", QpackHeaderTable::MatchType::kName, true,
31u);
// A header name that has a single entry with empty value.
ExpectMatch("location", "", QpackHeaderTable::MatchType::kNameAndValue, true,
12u);
ExpectMatch("location", "foo", QpackHeaderTable::MatchType::kName, true, 12u);
// No matching header name.
ExpectNoMatch("foo", "");
ExpectNoMatch("foo", "bar");
}
TEST_F(QpackHeaderTableTest, FindDynamicHeaderField) {
// Dynamic table is initially entry.
ExpectNoMatch("foo", "bar");
ExpectNoMatch("foo", "baz");
// Insert one entry.
InsertEntry("foo", "bar");
// Match name and value.
ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false,
0u);
// Match name only.
ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 0u);
// Insert an identical entry. FindHeaderField() should return the index of
// the most recently inserted matching entry.
InsertEntry("foo", "bar");
// Match name and value.
ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false,
1u);
// Match name only.
ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 1u);
}
TEST_F(QpackHeaderTableTest, FindHeaderFieldPrefersStaticTable) {
// Insert an entry to the dynamic table that exists in the static table.
InsertEntry(":method", "GET");
// Insertion works.
ExpectEntryAtIndex(/* is_static = */ false, 0, ":method", "GET");
// FindHeaderField() prefers static table if both have name-and-value match.
ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue,
true, 17u);
// FindHeaderField() prefers static table if both have name match but no value
// match, and prefers the first entry with matching name.
ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true,
15u);
// Add new entry to the dynamic table.
InsertEntry(":method", "TRACE");
// FindHeaderField prefers name-and-value match in dynamic table over name
// only match in static table.
ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kNameAndValue,
false, 1u);
}
// MaxEntries is determined by maximum dynamic table capacity,
// which is set at construction time.
TEST_F(QpackHeaderTableTest, MaxEntries) {
QpackHeaderTable table1;
table1.SetMaximumDynamicTableCapacity(1024);
EXPECT_EQ(32u, table1.max_entries());
QpackHeaderTable table2;
table2.SetMaximumDynamicTableCapacity(500);
EXPECT_EQ(15u, table2.max_entries());
}
TEST_F(QpackHeaderTableTest, SetDynamicTableCapacity) {
// Dynamic table capacity does not affect MaxEntries.
EXPECT_TRUE(SetDynamicTableCapacity(1024));
EXPECT_EQ(32u * 1024, max_entries());
EXPECT_TRUE(SetDynamicTableCapacity(500));
EXPECT_EQ(32u * 1024, max_entries());
// Dynamic table capacity cannot exceed maximum dynamic table capacity.
EXPECT_FALSE(
SetDynamicTableCapacity(2 * kMaximumDynamicTableCapacityForTesting));
}
TEST_F(QpackHeaderTableTest, EvictByInsertion) {
EXPECT_TRUE(SetDynamicTableCapacity(40));
// Entry size is 3 + 3 + 32 = 38.
InsertEntry("foo", "bar");
EXPECT_EQ(1u, inserted_entry_count());
EXPECT_EQ(0u, dropped_entry_count());
ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 0u);
// Inserting second entry evicts the first one.
InsertEntry("baz", "qux");
EXPECT_EQ(2u, inserted_entry_count());
EXPECT_EQ(1u, dropped_entry_count());
ExpectNoMatch("foo", "bar");
ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 1u);
// Inserting an entry that does not fit results in error.
ExpectToFailInsertingEntry("foobar", "foobar");
}
TEST_F(QpackHeaderTableTest, EvictByUpdateTableSize) {
// Entry size is 3 + 3 + 32 = 38.
InsertEntry("foo", "bar");
InsertEntry("baz", "qux");
EXPECT_EQ(2u, inserted_entry_count());
EXPECT_EQ(0u, dropped_entry_count());
ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 0u);
ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 1u);
EXPECT_TRUE(SetDynamicTableCapacity(40));
EXPECT_EQ(2u, inserted_entry_count());
EXPECT_EQ(1u, dropped_entry_count());
ExpectNoMatch("foo", "bar");
ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 1u);
EXPECT_TRUE(SetDynamicTableCapacity(20));
EXPECT_EQ(2u, inserted_entry_count());
EXPECT_EQ(2u, dropped_entry_count());
ExpectNoMatch("foo", "bar");
ExpectNoMatch("baz", "qux");
}
TEST_F(QpackHeaderTableTest, EvictOldestOfIdentical) {
EXPECT_TRUE(SetDynamicTableCapacity(80));
// Entry size is 3 + 3 + 32 = 38.
// Insert same entry twice.
InsertEntry("foo", "bar");
InsertEntry("foo", "bar");
EXPECT_EQ(2u, inserted_entry_count());
EXPECT_EQ(0u, dropped_entry_count());
// Find most recently inserted entry.
ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 1u);
// Inserting third entry evicts the first one, not the second.
InsertEntry("baz", "qux");
EXPECT_EQ(3u, inserted_entry_count());
EXPECT_EQ(1u, dropped_entry_count());
ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 1u);
ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 2u);
}
TEST_F(QpackHeaderTableTest, EvictOldestOfSameName) {
EXPECT_TRUE(SetDynamicTableCapacity(80));
// Entry size is 3 + 3 + 32 = 38.
// Insert two entries with same name but different values.
InsertEntry("foo", "bar");
InsertEntry("foo", "baz");
EXPECT_EQ(2u, inserted_entry_count());
EXPECT_EQ(0u, dropped_entry_count());
// Find most recently inserted entry with matching name.
ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName,
/* expected_is_static = */ false, 1u);
// Inserting third entry evicts the first one, not the second.
InsertEntry("baz", "qux");
EXPECT_EQ(3u, inserted_entry_count());
EXPECT_EQ(1u, dropped_entry_count());
ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName,
/* expected_is_static = */ false, 1u);
ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue,
/* expected_is_static = */ false, 2u);
}
TEST_F(QpackHeaderTableTest, Observer) {
StrictMock<MockObserver> observer1;
RegisterObserver(&observer1, 1);
EXPECT_CALL(observer1, OnInsertCountReachedThreshold);
InsertEntry("foo", "bar");
EXPECT_EQ(1u, inserted_entry_count());
Mock::VerifyAndClearExpectations(&observer1);
// Registration order does not matter.
StrictMock<MockObserver> observer2;
StrictMock<MockObserver> observer3;
RegisterObserver(&observer3, 3);
RegisterObserver(&observer2, 2);
EXPECT_CALL(observer2, OnInsertCountReachedThreshold);
InsertEntry("foo", "bar");
EXPECT_EQ(2u, inserted_entry_count());
Mock::VerifyAndClearExpectations(&observer3);
EXPECT_CALL(observer3, OnInsertCountReachedThreshold);
InsertEntry("foo", "bar");
EXPECT_EQ(3u, inserted_entry_count());
Mock::VerifyAndClearExpectations(&observer2);
// Multiple observers with identical |required_insert_count| should all be
// notified.
StrictMock<MockObserver> observer4;
StrictMock<MockObserver> observer5;
RegisterObserver(&observer4, 4);
RegisterObserver(&observer5, 4);
EXPECT_CALL(observer4, OnInsertCountReachedThreshold);
EXPECT_CALL(observer5, OnInsertCountReachedThreshold);
InsertEntry("foo", "bar");
EXPECT_EQ(4u, inserted_entry_count());
Mock::VerifyAndClearExpectations(&observer4);
Mock::VerifyAndClearExpectations(&observer5);
}
} // namespace
} // namespace test
} // namespace quic