-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
hpack_encoder.h
400 lines (340 loc) · 13.1 KB
/
hpack_encoder.h
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
//
//
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
#ifndef GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
#define GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
#include <grpc/support/port_platform.h>
#include <stddef.h>
#include <cstdint>
#include <utility>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include <grpc/slice.h>
#include <grpc/support/log.h>
#include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/slice/slice.h"
#include "src/core/lib/slice/slice_buffer.h"
#include "src/core/lib/transport/metadata_batch.h"
#include "src/core/lib/transport/metadata_compression_traits.h"
#include "src/core/lib/transport/timeout_encoding.h"
#include "src/core/lib/transport/transport.h"
namespace grpc_core {
// Forward decl for encoder
class HPackCompressor;
namespace hpack_encoder_detail {
class Encoder {
public:
Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
SliceBuffer& output);
void Encode(const Slice& key, const Slice& value);
template <typename MetadataTrait>
void Encode(MetadataTrait, const typename MetadataTrait::ValueType& value);
void AdvertiseTableSizeChange();
void EmitIndexed(uint32_t index);
GRPC_MUST_USE_RESULT
uint32_t EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
Slice value_slice);
GRPC_MUST_USE_RESULT
uint32_t EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
Slice value_slice);
void EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice, Slice value_slice);
void EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
Slice value_slice);
void EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,
Slice value_slice);
void EncodeAlwaysIndexed(uint32_t* index, absl::string_view key, Slice value,
size_t transport_length);
void EncodeIndexedKeyWithBinaryValue(uint32_t* index, absl::string_view key,
Slice value);
void EncodeRepeatingSliceValue(const absl::string_view& key,
const Slice& slice, uint32_t* index,
size_t max_compression_size);
HPackEncoderTable& hpack_table();
private:
const bool use_true_binary_metadata_;
HPackCompressor* const compressor_;
SliceBuffer& output_;
};
// Compressor is partially specialized on CompressionTraits, but leaves
// MetadataTrait as variable.
// Via MetadataMap::StatefulCompressor it builds compression state for
// HPackCompressor.
// Each trait compressor gets to have some persistent state across the channel
// (declared as Compressor member variables).
// The compressors expose a single method:
// void EncodeWith(MetadataTrait, const MetadataTrait::ValueType, Encoder*);
// This method figures out how to encode the value, and then delegates to
// Encoder to perform the encoding.
template <typename MetadataTrait, typename CompressonTraits>
class Compressor;
// No compression encoder: just emit the key and value as literals.
template <typename MetadataTrait>
class Compressor<MetadataTrait, NoCompressionCompressor> {
public:
void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
Encoder* encoder) {
const Slice& slice = MetadataValueAsSlice<MetadataTrait>(value);
if (absl::EndsWith(MetadataTrait::key(), "-bin")) {
encoder->EmitLitHdrWithBinaryStringKeyNotIdx(
Slice::FromStaticString(MetadataTrait::key()), slice.Ref());
} else {
encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
Slice::FromStaticString(MetadataTrait::key()), slice.Ref());
}
}
};
// Frequent key with no value compression encoder
template <typename MetadataTrait>
class Compressor<MetadataTrait, FrequentKeyWithNoValueCompressionCompressor> {
public:
void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
Encoder* encoder) {
const Slice& slice = MetadataValueAsSlice<MetadataTrait>(value);
encoder->EncodeRepeatingSliceValue(MetadataTrait::key(), slice,
&some_sent_value_,
HPackEncoderTable::MaxEntrySize());
}
private:
// Some previously sent value with this tag.
uint32_t some_sent_value_ = 0;
};
// Helper to determine if two objects have the same identity.
// Equivalent here => equality, but equality does not imply equivalency.
// For example, two slices with the same contents are equal, but not
// equivalent.
// Used as a much faster check for equality than the full equality check,
// since many metadatum that are stable have the same root object in metadata
// maps.
template <typename T>
static bool IsEquivalent(T a, T b) {
return a == b;
}
template <typename T>
static bool IsEquivalent(const Slice& a, const Slice& b) {
return a.is_equivalent(b);
}
template <typename T>
static void SaveCopyTo(const T& value, T& copy) {
copy = value;
}
static inline void SaveCopyTo(const Slice& value, Slice& copy) {
copy = value.Ref();
}
template <typename MetadataTrait>
class Compressor<MetadataTrait, StableValueCompressor> {
public:
void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
Encoder* encoder) {
auto& table = encoder->hpack_table();
if (previously_sent_value_ == value &&
table.ConvertableToDynamicIndex(previously_sent_index_)) {
encoder->EmitIndexed(table.DynamicIndex(previously_sent_index_));
return;
}
previously_sent_index_ = 0;
auto key = MetadataTrait::key();
const Slice& value_slice = MetadataValueAsSlice<MetadataTrait>(value);
if (hpack_constants::SizeForEntry(key.size(), value_slice.size()) >
HPackEncoderTable::MaxEntrySize()) {
encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
Slice::FromStaticString(key), value_slice.Ref());
return;
}
encoder->EncodeAlwaysIndexed(
&previously_sent_index_, key, value_slice.Ref(),
hpack_constants::SizeForEntry(key.size(), value_slice.size()));
SaveCopyTo(value, previously_sent_value_);
}
private:
// Previously sent value
typename MetadataTrait::ValueType previously_sent_value_{};
// And its index in the table
uint32_t previously_sent_index_ = 0;
};
template <typename MetadataTrait, typename MetadataTrait::ValueType known_value>
class Compressor<
MetadataTrait,
KnownValueCompressor<typename MetadataTrait::ValueType, known_value>> {
public:
void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
Encoder* encoder) {
if (value != known_value) {
gpr_log(GPR_ERROR, "%s",
absl::StrCat("Not encoding bad ", MetadataTrait::key(), " header")
.c_str());
return;
}
Slice encoded(MetadataTrait::Encode(known_value));
const auto encoded_length = encoded.length();
encoder->EncodeAlwaysIndexed(&previously_sent_index_, MetadataTrait::key(),
std::move(encoded),
MetadataTrait::key().size() + encoded_length +
hpack_constants::kEntryOverhead);
}
private:
uint32_t previously_sent_index_ = 0;
};
template <typename MetadataTrait, size_t N>
class Compressor<MetadataTrait, SmallIntegralValuesCompressor<N>> {
public:
void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
Encoder* encoder) {
uint32_t* index = nullptr;
auto& table = encoder->hpack_table();
if (static_cast<size_t>(value) < N) {
index = &previously_sent_[static_cast<uint32_t>(value)];
if (table.ConvertableToDynamicIndex(*index)) {
encoder->EmitIndexed(table.DynamicIndex(*index));
return;
}
}
auto key = Slice::FromStaticString(MetadataTrait::key());
auto encoded_value = MetadataTrait::Encode(value);
if (index != nullptr) {
*index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
std::move(key), std::move(encoded_value));
} else {
encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key),
std::move(encoded_value));
}
}
private:
uint32_t previously_sent_[N] = {};
};
class SliceIndex {
public:
void EmitTo(absl::string_view key, const Slice& value, Encoder* encoder);
private:
struct ValueIndex {
ValueIndex(Slice value, uint32_t index)
: value(std::move(value)), index(index) {}
Slice value;
uint32_t index;
};
std::vector<ValueIndex> values_;
};
template <typename MetadataTrait>
class Compressor<MetadataTrait, SmallSetOfValuesCompressor> {
public:
void EncodeWith(MetadataTrait, const Slice& value, Encoder* encoder) {
index_.EmitTo(MetadataTrait::key(), value, encoder);
}
private:
SliceIndex index_;
};
struct PreviousTimeout {
Timeout timeout;
uint32_t index;
};
class TimeoutCompressorImpl {
public:
void EncodeWith(absl::string_view key, Timestamp deadline, Encoder* encoder);
private:
std::vector<PreviousTimeout> previous_timeouts_;
};
template <typename MetadataTrait>
class Compressor<MetadataTrait, TimeoutCompressor>
: public TimeoutCompressorImpl {
public:
void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
Encoder* encoder) {
TimeoutCompressorImpl::EncodeWith(MetadataTrait::key(), value, encoder);
}
};
template <>
class Compressor<HttpStatusMetadata, HttpStatusCompressor> {
public:
void EncodeWith(HttpStatusMetadata, uint32_t status, Encoder* encoder);
};
template <>
class Compressor<HttpMethodMetadata, HttpMethodCompressor> {
public:
void EncodeWith(HttpMethodMetadata, HttpMethodMetadata::ValueType method,
Encoder* encoder);
};
template <>
class Compressor<HttpSchemeMetadata, HttpSchemeCompressor> {
public:
void EncodeWith(HttpSchemeMetadata, HttpSchemeMetadata::ValueType value,
Encoder* encoder);
};
} // namespace hpack_encoder_detail
class HPackCompressor {
class SliceIndex;
public:
HPackCompressor() = default;
~HPackCompressor() = default;
// Maximum table size we'll actually use.
static constexpr uint32_t kMaxTableSize = 1024 * 1024;
void SetMaxTableSize(uint32_t max_table_size);
void SetMaxUsableSize(uint32_t max_table_size);
uint32_t test_only_table_size() const {
return table_.test_only_table_size();
}
struct EncodeHeaderOptions {
uint32_t stream_id;
bool is_end_of_stream;
bool use_true_binary_metadata;
size_t max_frame_size;
grpc_transport_one_way_stats* stats;
};
template <typename HeaderSet>
void EncodeHeaders(const EncodeHeaderOptions& options,
const HeaderSet& headers, grpc_slice_buffer* output) {
SliceBuffer raw;
hpack_encoder_detail::Encoder encoder(
this, options.use_true_binary_metadata, raw);
headers.Encode(&encoder);
Frame(options, raw, output);
}
template <typename HeaderSet>
void EncodeRawHeaders(const HeaderSet& headers, SliceBuffer& output) {
hpack_encoder_detail::Encoder encoder(this, true, output);
headers.Encode(&encoder);
}
private:
static constexpr size_t kNumFilterValues = 64;
static constexpr uint32_t kNumCachedGrpcStatusValues = 16;
friend class hpack_encoder_detail::Encoder;
void Frame(const EncodeHeaderOptions& options, SliceBuffer& raw,
grpc_slice_buffer* output);
// maximum number of bytes we'll use for the decode table (to guard against
// peers ooming us by setting decode table size high)
uint32_t max_usable_size_ = hpack_constants::kInitialTableSize;
// if non-zero, advertise to the decoder that we'll start using a table
// of this size
bool advertise_table_size_change_ = false;
HPackEncoderTable table_;
grpc_metadata_batch::StatefulCompressor<hpack_encoder_detail::Compressor>
compression_state_;
};
namespace hpack_encoder_detail {
template <typename MetadataTrait>
void Encoder::Encode(MetadataTrait,
const typename MetadataTrait::ValueType& value) {
compressor_->compression_state_
.Compressor<MetadataTrait, typename MetadataTrait::CompressionTraits>::
EncodeWith(MetadataTrait(), value, this);
}
inline HPackEncoderTable& Encoder::hpack_table() { return compressor_->table_; }
} // namespace hpack_encoder_detail
} // namespace grpc_core
#endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H