• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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