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 #ifndef GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
20 #define GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
21
22 #include <grpc/support/port_platform.h>
23
24 #include <stddef.h>
25
26 #include <cstdint>
27 #include <utility>
28 #include <vector>
29
30 #include "absl/strings/match.h"
31 #include "absl/strings/str_cat.h"
32 #include "absl/strings/string_view.h"
33
34 #include <grpc/slice.h>
35 #include <grpc/support/log.h>
36
37 #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
38 #include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
39 #include "src/core/lib/gprpp/time.h"
40 #include "src/core/lib/slice/slice.h"
41 #include "src/core/lib/slice/slice_buffer.h"
42 #include "src/core/lib/transport/metadata_batch.h"
43 #include "src/core/lib/transport/metadata_compression_traits.h"
44 #include "src/core/lib/transport/timeout_encoding.h"
45 #include "src/core/lib/transport/transport.h"
46
47 namespace grpc_core {
48
49 // Forward decl for encoder
50 class HPackCompressor;
51
52 namespace hpack_encoder_detail {
53
54 class Encoder {
55 public:
56 Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
57 SliceBuffer& output);
58
59 void Encode(const Slice& key, const Slice& value);
60 template <typename MetadataTrait>
61 void Encode(MetadataTrait, const typename MetadataTrait::ValueType& value);
62
63 void AdvertiseTableSizeChange();
64 void EmitIndexed(uint32_t index);
65 GRPC_MUST_USE_RESULT
66 uint32_t EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
67 Slice value_slice);
68 GRPC_MUST_USE_RESULT
69 uint32_t EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
70 Slice value_slice);
71 void EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice, Slice value_slice);
72 void EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
73 Slice value_slice);
74 void EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,
75 Slice value_slice);
76
77 void EncodeAlwaysIndexed(uint32_t* index, absl::string_view key, Slice value,
78 size_t transport_length);
79 void EncodeIndexedKeyWithBinaryValue(uint32_t* index, absl::string_view key,
80 Slice value);
81
82 void EncodeRepeatingSliceValue(const absl::string_view& key,
83 const Slice& slice, uint32_t* index,
84 size_t max_compression_size);
85
86 HPackEncoderTable& hpack_table();
87
88 private:
89 const bool use_true_binary_metadata_;
90 HPackCompressor* const compressor_;
91 SliceBuffer& output_;
92 };
93
94 // Compressor is partially specialized on CompressionTraits, but leaves
95 // MetadataTrait as variable.
96 // Via MetadataMap::StatefulCompressor it builds compression state for
97 // HPackCompressor.
98 // Each trait compressor gets to have some persistent state across the channel
99 // (declared as Compressor member variables).
100 // The compressors expose a single method:
101 // void EncodeWith(MetadataTrait, const MetadataTrait::ValueType, Encoder*);
102 // This method figures out how to encode the value, and then delegates to
103 // Encoder to perform the encoding.
104 template <typename MetadataTrait, typename CompressonTraits>
105 class Compressor;
106
107 // No compression encoder: just emit the key and value as literals.
108 template <typename MetadataTrait>
109 class Compressor<MetadataTrait, NoCompressionCompressor> {
110 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)111 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
112 Encoder* encoder) {
113 const Slice& slice = MetadataValueAsSlice<MetadataTrait>(value);
114 if (absl::EndsWith(MetadataTrait::key(), "-bin")) {
115 encoder->EmitLitHdrWithBinaryStringKeyNotIdx(
116 Slice::FromStaticString(MetadataTrait::key()), slice.Ref());
117 } else {
118 encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
119 Slice::FromStaticString(MetadataTrait::key()), slice.Ref());
120 }
121 }
122 };
123
124 // Frequent key with no value compression encoder
125 template <typename MetadataTrait>
126 class Compressor<MetadataTrait, FrequentKeyWithNoValueCompressionCompressor> {
127 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)128 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
129 Encoder* encoder) {
130 const Slice& slice = MetadataValueAsSlice<MetadataTrait>(value);
131 encoder->EncodeRepeatingSliceValue(MetadataTrait::key(), slice,
132 &some_sent_value_,
133 HPackEncoderTable::MaxEntrySize());
134 }
135
136 private:
137 // Some previously sent value with this tag.
138 uint32_t some_sent_value_ = 0;
139 };
140
141 // Helper to determine if two objects have the same identity.
142 // Equivalent here => equality, but equality does not imply equivalency.
143 // For example, two slices with the same contents are equal, but not
144 // equivalent.
145 // Used as a much faster check for equality than the full equality check,
146 // since many metadatum that are stable have the same root object in metadata
147 // maps.
148 template <typename T>
IsEquivalent(T a,T b)149 static bool IsEquivalent(T a, T b) {
150 return a == b;
151 }
152
153 template <typename T>
IsEquivalent(const Slice & a,const Slice & b)154 static bool IsEquivalent(const Slice& a, const Slice& b) {
155 return a.is_equivalent(b);
156 }
157
158 template <typename T>
SaveCopyTo(const T & value,T & copy)159 static void SaveCopyTo(const T& value, T& copy) {
160 copy = value;
161 }
162
SaveCopyTo(const Slice & value,Slice & copy)163 static inline void SaveCopyTo(const Slice& value, Slice& copy) {
164 copy = value.Ref();
165 }
166
167 template <typename MetadataTrait>
168 class Compressor<MetadataTrait, StableValueCompressor> {
169 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)170 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
171 Encoder* encoder) {
172 auto& table = encoder->hpack_table();
173 if (previously_sent_value_ == value &&
174 table.ConvertableToDynamicIndex(previously_sent_index_)) {
175 encoder->EmitIndexed(table.DynamicIndex(previously_sent_index_));
176 return;
177 }
178 previously_sent_index_ = 0;
179 auto key = MetadataTrait::key();
180 const Slice& value_slice = MetadataValueAsSlice<MetadataTrait>(value);
181 if (hpack_constants::SizeForEntry(key.size(), value_slice.size()) >
182 HPackEncoderTable::MaxEntrySize()) {
183 encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
184 Slice::FromStaticString(key), value_slice.Ref());
185 return;
186 }
187 encoder->EncodeAlwaysIndexed(
188 &previously_sent_index_, key, value_slice.Ref(),
189 hpack_constants::SizeForEntry(key.size(), value_slice.size()));
190 SaveCopyTo(value, previously_sent_value_);
191 }
192
193 private:
194 // Previously sent value
195 typename MetadataTrait::ValueType previously_sent_value_{};
196 // And its index in the table
197 uint32_t previously_sent_index_ = 0;
198 };
199
200 template <typename MetadataTrait, typename MetadataTrait::ValueType known_value>
201 class Compressor<
202 MetadataTrait,
203 KnownValueCompressor<typename MetadataTrait::ValueType, known_value>> {
204 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)205 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
206 Encoder* encoder) {
207 if (value != known_value) {
208 gpr_log(GPR_ERROR, "%s",
209 absl::StrCat("Not encoding bad ", MetadataTrait::key(), " header")
210 .c_str());
211 return;
212 }
213 Slice encoded(MetadataTrait::Encode(known_value));
214 const auto encoded_length = encoded.length();
215 encoder->EncodeAlwaysIndexed(&previously_sent_index_, MetadataTrait::key(),
216 std::move(encoded),
217 MetadataTrait::key().size() + encoded_length +
218 hpack_constants::kEntryOverhead);
219 }
220
221 private:
222 uint32_t previously_sent_index_ = 0;
223 };
224 template <typename MetadataTrait, size_t N>
225 class Compressor<MetadataTrait, SmallIntegralValuesCompressor<N>> {
226 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)227 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
228 Encoder* encoder) {
229 uint32_t* index = nullptr;
230 auto& table = encoder->hpack_table();
231 if (static_cast<size_t>(value) < N) {
232 index = &previously_sent_[static_cast<uint32_t>(value)];
233 if (table.ConvertableToDynamicIndex(*index)) {
234 encoder->EmitIndexed(table.DynamicIndex(*index));
235 return;
236 }
237 }
238 auto key = Slice::FromStaticString(MetadataTrait::key());
239 auto encoded_value = MetadataTrait::Encode(value);
240 if (index != nullptr) {
241 *index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
242 std::move(key), std::move(encoded_value));
243 } else {
244 encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key),
245 std::move(encoded_value));
246 }
247 }
248
249 private:
250 uint32_t previously_sent_[N] = {};
251 };
252
253 class SliceIndex {
254 public:
255 void EmitTo(absl::string_view key, const Slice& value, Encoder* encoder);
256
257 private:
258 struct ValueIndex {
ValueIndexValueIndex259 ValueIndex(Slice value, uint32_t index)
260 : value(std::move(value)), index(index) {}
261 Slice value;
262 uint32_t index;
263 };
264 std::vector<ValueIndex> values_;
265 };
266
267 template <typename MetadataTrait>
268 class Compressor<MetadataTrait, SmallSetOfValuesCompressor> {
269 public:
EncodeWith(MetadataTrait,const Slice & value,Encoder * encoder)270 void EncodeWith(MetadataTrait, const Slice& value, Encoder* encoder) {
271 index_.EmitTo(MetadataTrait::key(), value, encoder);
272 }
273
274 private:
275 SliceIndex index_;
276 };
277
278 struct PreviousTimeout {
279 Timeout timeout = Timeout::FromDuration(Duration::Zero());
280 // Dynamic table index of a previously sent timeout
281 // 0 is guaranteed not in the dynamic table so is a safe initializer
282 uint32_t index = 0;
283 };
284
285 class TimeoutCompressorImpl {
286 public:
287 void EncodeWith(absl::string_view key, Timestamp deadline, Encoder* encoder);
288
289 private:
290 static constexpr const size_t kNumPreviousValues = 5;
291 PreviousTimeout previous_timeouts_[kNumPreviousValues];
292 uint32_t next_previous_value_ = 0;
293 };
294
295 template <typename MetadataTrait>
296 class Compressor<MetadataTrait, TimeoutCompressor>
297 : public TimeoutCompressorImpl {
298 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)299 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
300 Encoder* encoder) {
301 TimeoutCompressorImpl::EncodeWith(MetadataTrait::key(), value, encoder);
302 }
303 };
304
305 template <>
306 class Compressor<HttpStatusMetadata, HttpStatusCompressor> {
307 public:
308 void EncodeWith(HttpStatusMetadata, uint32_t status, Encoder* encoder);
309 };
310
311 template <>
312 class Compressor<HttpMethodMetadata, HttpMethodCompressor> {
313 public:
314 void EncodeWith(HttpMethodMetadata, HttpMethodMetadata::ValueType method,
315 Encoder* encoder);
316 };
317
318 template <>
319 class Compressor<HttpSchemeMetadata, HttpSchemeCompressor> {
320 public:
321 void EncodeWith(HttpSchemeMetadata, HttpSchemeMetadata::ValueType value,
322 Encoder* encoder);
323 };
324
325 } // namespace hpack_encoder_detail
326
327 class HPackCompressor {
328 class SliceIndex;
329
330 public:
331 HPackCompressor() = default;
332 ~HPackCompressor() = default;
333
334 HPackCompressor(const HPackCompressor&) = delete;
335 HPackCompressor& operator=(const HPackCompressor&) = delete;
336 HPackCompressor(HPackCompressor&&) = default;
337 HPackCompressor& operator=(HPackCompressor&&) = default;
338
339 // Maximum table size we'll actually use.
340 static constexpr uint32_t kMaxTableSize = 1024 * 1024;
341
342 void SetMaxTableSize(uint32_t max_table_size);
343 void SetMaxUsableSize(uint32_t max_table_size);
344
test_only_table_size()345 uint32_t test_only_table_size() const {
346 return table_.test_only_table_size();
347 }
348
349 struct EncodeHeaderOptions {
350 uint32_t stream_id;
351 bool is_end_of_stream;
352 bool use_true_binary_metadata;
353 size_t max_frame_size;
354 grpc_transport_one_way_stats* stats;
355 };
356
357 template <typename HeaderSet>
EncodeHeaders(const EncodeHeaderOptions & options,const HeaderSet & headers,grpc_slice_buffer * output)358 void EncodeHeaders(const EncodeHeaderOptions& options,
359 const HeaderSet& headers, grpc_slice_buffer* output) {
360 SliceBuffer raw;
361 hpack_encoder_detail::Encoder encoder(
362 this, options.use_true_binary_metadata, raw);
363 headers.Encode(&encoder);
364 Frame(options, raw, output);
365 }
366
367 template <typename HeaderSet>
EncodeRawHeaders(const HeaderSet & headers,SliceBuffer & output)368 void EncodeRawHeaders(const HeaderSet& headers, SliceBuffer& output) {
369 hpack_encoder_detail::Encoder encoder(this, true, output);
370 headers.Encode(&encoder);
371 }
372
373 private:
374 static constexpr size_t kNumFilterValues = 64;
375 static constexpr uint32_t kNumCachedGrpcStatusValues = 16;
376 friend class hpack_encoder_detail::Encoder;
377
378 void Frame(const EncodeHeaderOptions& options, SliceBuffer& raw,
379 grpc_slice_buffer* output);
380
381 // maximum number of bytes we'll use for the decode table (to guard against
382 // peers ooming us by setting decode table size high)
383 uint32_t max_usable_size_ = hpack_constants::kInitialTableSize;
384 // if non-zero, advertise to the decoder that we'll start using a table
385 // of this size
386 bool advertise_table_size_change_ = false;
387 HPackEncoderTable table_;
388
389 grpc_metadata_batch::StatefulCompressor<hpack_encoder_detail::Compressor>
390 compression_state_;
391 };
392
393 namespace hpack_encoder_detail {
394
395 template <typename MetadataTrait>
Encode(MetadataTrait,const typename MetadataTrait::ValueType & value)396 void Encoder::Encode(MetadataTrait,
397 const typename MetadataTrait::ValueType& value) {
398 compressor_->compression_state_
399 .Compressor<MetadataTrait, typename MetadataTrait::CompressionTraits>::
400 EncodeWith(MetadataTrait(), value, this);
401 }
402
hpack_table()403 inline HPackEncoderTable& Encoder::hpack_table() { return compressor_->table_; }
404
405 } // namespace hpack_encoder_detail
406
407 } // namespace grpc_core
408
409 #endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
410