1 //
2 //
3 // Copyright 2015 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
20
21 #include <grpc/support/port_platform.h>
22 #include <stdlib.h>
23
24 #include <algorithm>
25 #include <cstddef>
26 #include <cstring>
27 #include <utility>
28
29 #include "absl/log/check.h"
30 #include "absl/log/log.h"
31 #include "absl/status/status.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/string_view.h"
34 #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
35 #include "src/core/ext/transport/chttp2/transport/hpack_parse_result.h"
36 #include "src/core/lib/debug/trace.h"
37 #include "src/core/lib/slice/slice.h"
38 #include "src/core/telemetry/stats.h"
39
40 namespace grpc_core {
41
Put(Memento m)42 void HPackTable::MementoRingBuffer::Put(Memento m) {
43 CHECK_LT(num_entries_, max_entries_);
44 if (entries_.size() < max_entries_) {
45 ++num_entries_;
46 return entries_.push_back(std::move(m));
47 }
48 size_t index = (first_entry_ + num_entries_) % max_entries_;
49 if (timestamp_index_ == kNoTimestamp) {
50 timestamp_index_ = index;
51 timestamp_ = Timestamp::Now();
52 }
53 entries_[index] = std::move(m);
54 ++num_entries_;
55 }
56
PopOne()57 auto HPackTable::MementoRingBuffer::PopOne() -> Memento {
58 CHECK_GT(num_entries_, 0u);
59 size_t index = first_entry_ % max_entries_;
60 if (index == timestamp_index_) {
61 global_stats().IncrementHttp2HpackEntryLifetime(
62 (Timestamp::Now() - timestamp_).millis());
63 timestamp_index_ = kNoTimestamp;
64 }
65 ++first_entry_;
66 --num_entries_;
67 auto& entry = entries_[index];
68 if (!entry.parse_status.TestBit(Memento::kUsedBit)) {
69 global_stats().IncrementHttp2HpackMisses();
70 }
71 return std::move(entry);
72 }
73
Lookup(uint32_t index)74 auto HPackTable::MementoRingBuffer::Lookup(uint32_t index) -> const Memento* {
75 if (index >= num_entries_) return nullptr;
76 uint32_t offset = (num_entries_ - 1u - index + first_entry_) % max_entries_;
77 auto& entry = entries_[offset];
78 const bool was_used = entry.parse_status.TestBit(Memento::kUsedBit);
79 entry.parse_status.SetBit(Memento::kUsedBit);
80 if (!was_used) global_stats().IncrementHttp2HpackHits();
81 return &entry;
82 }
83
Peek(uint32_t index) const84 auto HPackTable::MementoRingBuffer::Peek(uint32_t index) const
85 -> const Memento* {
86 if (index >= num_entries_) return nullptr;
87 uint32_t offset = (num_entries_ - 1u - index + first_entry_) % max_entries_;
88 return &entries_[offset];
89 }
90
Rebuild(uint32_t max_entries)91 void HPackTable::MementoRingBuffer::Rebuild(uint32_t max_entries) {
92 if (max_entries == max_entries_) return;
93 max_entries_ = max_entries;
94 std::vector<Memento> entries;
95 entries.reserve(num_entries_);
96 for (size_t i = 0; i < num_entries_; i++) {
97 entries.push_back(
98 std::move(entries_[(first_entry_ + i) % entries_.size()]));
99 }
100 first_entry_ = 0;
101 entries_.swap(entries);
102 }
103
104 template <typename F>
ForEach(F f) const105 void HPackTable::MementoRingBuffer::ForEach(F f) const {
106 uint32_t index = 0;
107 while (auto* m = Peek(index++)) {
108 f(index, *m);
109 }
110 }
111
~MementoRingBuffer()112 HPackTable::MementoRingBuffer::~MementoRingBuffer() {
113 ForEach([](uint32_t, const Memento& m) {
114 if (!m.parse_status.TestBit(Memento::kUsedBit)) {
115 global_stats().IncrementHttp2HpackMisses();
116 }
117 });
118 }
119
120 // Evict one element from the table
EvictOne()121 void HPackTable::EvictOne() {
122 auto first_entry = entries_.PopOne();
123 CHECK(first_entry.md.transport_size() <= mem_used_);
124 mem_used_ -= first_entry.md.transport_size();
125 }
126
SetMaxBytes(uint32_t max_bytes)127 void HPackTable::SetMaxBytes(uint32_t max_bytes) {
128 if (max_bytes_ == max_bytes) {
129 return;
130 }
131 GRPC_TRACE_LOG(http, INFO) << "Update hpack parser max size to " << max_bytes;
132 while (mem_used_ > max_bytes) {
133 EvictOne();
134 }
135 max_bytes_ = max_bytes;
136 }
137
SetCurrentTableSize(uint32_t bytes)138 bool HPackTable::SetCurrentTableSize(uint32_t bytes) {
139 if (current_table_bytes_ == bytes) return true;
140 if (bytes > max_bytes_) return false;
141 GRPC_TRACE_LOG(http, INFO) << "Update hpack parser table size to " << bytes;
142 while (mem_used_ > bytes) {
143 EvictOne();
144 }
145 current_table_bytes_ = bytes;
146 uint32_t new_cap = std::max(hpack_constants::EntriesForBytes(bytes),
147 hpack_constants::kInitialTableEntries);
148 entries_.Rebuild(new_cap);
149 return true;
150 }
151
Add(Memento md)152 bool HPackTable::Add(Memento md) {
153 if (current_table_bytes_ > max_bytes_) return false;
154
155 // we can't add elements bigger than the max table size
156 if (md.md.transport_size() > current_table_bytes_) {
157 AddLargerThanCurrentTableSize();
158 return true;
159 }
160
161 // evict entries to ensure no overflow
162 while (md.md.transport_size() >
163 static_cast<size_t>(current_table_bytes_) - mem_used_) {
164 EvictOne();
165 }
166
167 // copy the finalized entry in
168 mem_used_ += md.md.transport_size();
169 entries_.Put(std::move(md));
170 return true;
171 }
172
AddLargerThanCurrentTableSize()173 void HPackTable::AddLargerThanCurrentTableSize() {
174 // HPACK draft 10 section 4.4 states:
175 // If the size of the new entry is less than or equal to the maximum
176 // size, that entry is added to the table. It is not an error to
177 // attempt to add an entry that is larger than the maximum size; an
178 // attempt to add an entry larger than the entire table causes
179 // the table to be emptied of all existing entries, and results in an
180 // empty table.
181 while (entries_.num_entries()) {
182 EvictOne();
183 }
184 }
185
TestOnlyDynamicTableAsString() const186 std::string HPackTable::TestOnlyDynamicTableAsString() const {
187 std::string out;
188 entries_.ForEach([&out](uint32_t i, const Memento& m) {
189 if (m.parse_status == nullptr) {
190 absl::StrAppend(&out, i, ": ", m.md.DebugString(), "\n");
191 } else {
192 absl::StrAppend(&out, i, ": ", m.parse_status->Materialize().ToString(),
193 "\n");
194 }
195 });
196 return out;
197 }
198
199 namespace {
200 struct StaticTableEntry {
201 const char* key;
202 const char* value;
203 };
204
205 const StaticTableEntry kStaticTable[hpack_constants::kLastStaticEntry] = {
206 {":authority", ""},
207 {":method", "GET"},
208 {":method", "POST"},
209 {":path", "/"},
210 {":path", "/index.html"},
211 {":scheme", "http"},
212 {":scheme", "https"},
213 {":status", "200"},
214 {":status", "204"},
215 {":status", "206"},
216 {":status", "304"},
217 {":status", "400"},
218 {":status", "404"},
219 {":status", "500"},
220 {"accept-charset", ""},
221 {"accept-encoding", "gzip, deflate"},
222 {"accept-language", ""},
223 {"accept-ranges", ""},
224 {"accept", ""},
225 {"access-control-allow-origin", ""},
226 {"age", ""},
227 {"allow", ""},
228 {"authorization", ""},
229 {"cache-control", ""},
230 {"content-disposition", ""},
231 {"content-encoding", ""},
232 {"content-language", ""},
233 {"content-length", ""},
234 {"content-location", ""},
235 {"content-range", ""},
236 {"content-type", ""},
237 {"cookie", ""},
238 {"date", ""},
239 {"etag", ""},
240 {"expect", ""},
241 {"expires", ""},
242 {"from", ""},
243 {"host", ""},
244 {"if-match", ""},
245 {"if-modified-since", ""},
246 {"if-none-match", ""},
247 {"if-range", ""},
248 {"if-unmodified-since", ""},
249 {"last-modified", ""},
250 {"link", ""},
251 {"location", ""},
252 {"max-forwards", ""},
253 {"proxy-authenticate", ""},
254 {"proxy-authorization", ""},
255 {"range", ""},
256 {"referer", ""},
257 {"refresh", ""},
258 {"retry-after", ""},
259 {"server", ""},
260 {"set-cookie", ""},
261 {"strict-transport-security", ""},
262 {"transfer-encoding", ""},
263 {"user-agent", ""},
264 {"vary", ""},
265 {"via", ""},
266 {"www-authenticate", ""},
267 };
268
MakeMemento(size_t i)269 HPackTable::Memento MakeMemento(size_t i) {
270 auto sm = kStaticTable[i];
271 return HPackTable::Memento{
272 grpc_metadata_batch::Parse(
273 sm.key, Slice::FromStaticString(sm.value), true,
274 strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
275 [](absl::string_view, const Slice&) {
276 abort(); // not expecting to see this
277 }),
278 nullptr};
279 }
280
281 } // namespace
282
StaticMementos()283 HPackTable::StaticMementos::StaticMementos() {
284 for (uint32_t i = 0; i < hpack_constants::kLastStaticEntry; i++) {
285 memento[i] = MakeMemento(i);
286 }
287 }
288
289 } // namespace grpc_core
290