summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc
blob: a3e4ee097a09f6126db35a5a72a360eec4497fe9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2016 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 "http2/hpack/decoder/hpack_decoder_tables.h"

#include <algorithm>
#include <string>
#include <tuple>
#include <vector>

#include "http2/hpack/http2_hpack_constants.h"
#include "http2/platform/api/http2_logging.h"
#include "http2/platform/api/http2_test_helpers.h"
#include "http2/test_tools/http2_random.h"
#include "http2/tools/random_util.h"
#include "common/platform/api/quiche_test.h"

using ::testing::AssertionResult;
using ::testing::AssertionSuccess;

namespace http2 {
namespace test {
class HpackDecoderTablesPeer {
 public:
  static size_t num_dynamic_entries(const HpackDecoderTables& tables) {
    return tables.dynamic_table_.table_.size();
  }
};

namespace {
struct StaticEntry {
  const char* name;
  const char* value;
  size_t index;
};

std::vector<StaticEntry> MakeSpecStaticEntries() {
  std::vector<StaticEntry> static_entries;

#define STATIC_TABLE_ENTRY(name, value, index)                             \
  QUICHE_DCHECK_EQ(static_entries.size() + 1, static_cast<size_t>(index)); \
  static_entries.push_back({name, value, index});

#include "http2/hpack/hpack_static_table_entries.inc"

#undef STATIC_TABLE_ENTRY

  return static_entries;
}

template <class C>
void ShuffleCollection(C* collection, Http2Random* r) {
  std::shuffle(collection->begin(), collection->end(), *r);
}

class HpackDecoderStaticTableTest : public QuicheTest {
 protected:
  HpackDecoderStaticTableTest() = default;

  std::vector<StaticEntry> shuffled_static_entries() {
    std::vector<StaticEntry> entries = MakeSpecStaticEntries();
    ShuffleCollection(&entries, &random_);
    return entries;
  }

  // This test is in a function so that it can be applied to both the static
  // table and the combined static+dynamic tables.
  AssertionResult VerifyStaticTableContents() {
    for (const auto& expected : shuffled_static_entries()) {
      const HpackStringPair* found = Lookup(expected.index);
      VERIFY_NE(found, nullptr);
      VERIFY_EQ(expected.name, found->name) << expected.index;
      VERIFY_EQ(expected.value, found->value) << expected.index;
    }

    // There should be no entry with index 0.
    VERIFY_EQ(nullptr, Lookup(0));
    return AssertionSuccess();
  }

  virtual const HpackStringPair* Lookup(size_t index) {
    return static_table_.Lookup(index);
  }

  Http2Random* RandomPtr() { return &random_; }

  Http2Random random_;

 private:
  HpackDecoderStaticTable static_table_;
};

TEST_F(HpackDecoderStaticTableTest, StaticTableContents) {
  EXPECT_TRUE(VerifyStaticTableContents());
}

size_t Size(const std::string& name, const std::string& value) {
  return name.size() + value.size() + 32;
}

// To support tests with more than a few of hand crafted changes to the dynamic
// table, we have another, exceedingly simple, implementation of the HPACK
// dynamic table containing FakeHpackEntry instances. We can thus compare the
// contents of the actual table with those in fake_dynamic_table_.

typedef std::tuple<std::string, std::string, size_t> FakeHpackEntry;
const std::string& Name(const FakeHpackEntry& entry) {
  return std::get<0>(entry);
}
const std::string& Value(const FakeHpackEntry& entry) {
  return std::get<1>(entry);
}
size_t Size(const FakeHpackEntry& entry) {
  return std::get<2>(entry);
}

class HpackDecoderTablesTest : public HpackDecoderStaticTableTest {
 protected:
  const HpackStringPair* Lookup(size_t index) override {
    return tables_.Lookup(index);
  }

  size_t dynamic_size_limit() const {
    return tables_.header_table_size_limit();
  }
  size_t current_dynamic_size() const {
    return tables_.current_header_table_size();
  }
  size_t num_dynamic_entries() const {
    return HpackDecoderTablesPeer::num_dynamic_entries(tables_);
  }

  // Insert the name and value into fake_dynamic_table_.
  void FakeInsert(const std::string& name, const std::string& value) {
    FakeHpackEntry entry(name, value, Size(name, value));
    fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry);
  }

  // Add up the size of all entries in fake_dynamic_table_.
  size_t FakeSize() {
    size_t sz = 0;
    for (const auto& entry : fake_dynamic_table_) {
      sz += Size(entry);
    }
    return sz;
  }

  // If the total size of the fake_dynamic_table_ is greater than limit,
  // keep the first N entries such that those N entries have a size not
  // greater than limit, and such that keeping entry N+1 would have a size
  // greater than limit. Returns the count of removed bytes.
  size_t FakeTrim(size_t limit) {
    size_t original_size = FakeSize();
    size_t total_size = 0;
    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
      total_size += Size(fake_dynamic_table_[ndx]);
      if (total_size > limit) {
        // Need to get rid of ndx and all following entries.
        fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx,
                                  fake_dynamic_table_.end());
        return original_size - FakeSize();
      }
    }
    return 0;
  }

  // Verify that the contents of the actual dynamic table match those in
  // fake_dynamic_table_.
  AssertionResult VerifyDynamicTableContents() {
    VERIFY_EQ(current_dynamic_size(), FakeSize());
    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());

    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
      const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex);
      VERIFY_NE(found, nullptr);

      const auto& expected = fake_dynamic_table_[ndx];
      VERIFY_EQ(Name(expected), found->name);
      VERIFY_EQ(Value(expected), found->value);
    }

    // Make sure there are no more entries.
    VERIFY_EQ(nullptr,
              Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex));
    return AssertionSuccess();
  }

  // Apply an update to the limit on the maximum size of the dynamic table.
  AssertionResult DynamicTableSizeUpdate(size_t size_limit) {
    VERIFY_EQ(current_dynamic_size(), FakeSize());
    if (size_limit < current_dynamic_size()) {
      // Will need to trim the dynamic table's oldest entries.
      tables_.DynamicTableSizeUpdate(size_limit);
      FakeTrim(size_limit);
      return VerifyDynamicTableContents();
    }
    // Shouldn't change the size.
    tables_.DynamicTableSizeUpdate(size_limit);
    return VerifyDynamicTableContents();
  }

  // Insert an entry into the dynamic table, confirming that trimming of entries
  // occurs if the total size is greater than the limit, and that older entries
  // move up by 1 index.
  AssertionResult Insert(const std::string& name, const std::string& value) {
    size_t old_count = num_dynamic_entries();
    tables_.Insert(HpackString(name), HpackString(value));
    FakeInsert(name, value);
    VERIFY_EQ(old_count + 1, fake_dynamic_table_.size());
    FakeTrim(dynamic_size_limit());
    VERIFY_EQ(current_dynamic_size(), FakeSize());
    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
    return VerifyDynamicTableContents();
  }

 private:
  HpackDecoderTables tables_;

  std::vector<FakeHpackEntry> fake_dynamic_table_;
};

TEST_F(HpackDecoderTablesTest, StaticTableContents) {
  EXPECT_TRUE(VerifyStaticTableContents());
}

// Generate a bunch of random header entries, insert them, and confirm they
// present, as required by the RFC, using VerifyDynamicTableContents above on
// each Insert. Also apply various resizings of the dynamic table.
TEST_F(HpackDecoderTablesTest, RandomDynamicTable) {
  EXPECT_EQ(0u, current_dynamic_size());
  EXPECT_TRUE(VerifyStaticTableContents());
  EXPECT_TRUE(VerifyDynamicTableContents());

  std::vector<size_t> table_sizes;
  table_sizes.push_back(dynamic_size_limit());
  table_sizes.push_back(0);
  table_sizes.push_back(dynamic_size_limit() / 2);
  table_sizes.push_back(dynamic_size_limit());
  table_sizes.push_back(dynamic_size_limit() / 2);
  table_sizes.push_back(0);
  table_sizes.push_back(dynamic_size_limit());

  for (size_t limit : table_sizes) {
    ASSERT_TRUE(DynamicTableSizeUpdate(limit));
    for (int insert_count = 0; insert_count < 100; ++insert_count) {
      std::string name =
          GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr());
      std::string value =
          GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr());
      ASSERT_TRUE(Insert(name, value));
    }
    EXPECT_TRUE(VerifyStaticTableContents());
  }
}

}  // namespace
}  // namespace test
}  // namespace http2