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;
280 uint32_t index;
281 };
282
283 class TimeoutCompressorImpl {
284 public:
285 void EncodeWith(absl::string_view key, Timestamp deadline, Encoder* encoder);
286
287 private:
288 std::vector<PreviousTimeout> previous_timeouts_;
289 };
290
291 template <typename MetadataTrait>
292 class Compressor<MetadataTrait, TimeoutCompressor>
293 : public TimeoutCompressorImpl {
294 public:
EncodeWith(MetadataTrait,const typename MetadataTrait::ValueType & value,Encoder * encoder)295 void EncodeWith(MetadataTrait, const typename MetadataTrait::ValueType& value,
296 Encoder* encoder) {
297 TimeoutCompressorImpl::EncodeWith(MetadataTrait::key(), value, encoder);
298 }
299 };
300
301 template <>
302 class Compressor<HttpStatusMetadata, HttpStatusCompressor> {
303 public:
304 void EncodeWith(HttpStatusMetadata, uint32_t status, Encoder* encoder);
305 };
306
307 template <>
308 class Compressor<HttpMethodMetadata, HttpMethodCompressor> {
309 public:
310 void EncodeWith(HttpMethodMetadata, HttpMethodMetadata::ValueType method,
311 Encoder* encoder);
312 };
313
314 template <>
315 class Compressor<HttpSchemeMetadata, HttpSchemeCompressor> {
316 public:
317 void EncodeWith(HttpSchemeMetadata, HttpSchemeMetadata::ValueType value,
318 Encoder* encoder);
319 };
320
321 } // namespace hpack_encoder_detail
322
323 class HPackCompressor {
324 class SliceIndex;
325
326 public:
327 HPackCompressor() = default;
328 ~HPackCompressor() = default;
329
330 // Maximum table size we'll actually use.
331 static constexpr uint32_t kMaxTableSize = 1024 * 1024;
332
333 void SetMaxTableSize(uint32_t max_table_size);
334 void SetMaxUsableSize(uint32_t max_table_size);
335
test_only_table_size()336 uint32_t test_only_table_size() const {
337 return table_.test_only_table_size();
338 }
339
340 struct EncodeHeaderOptions {
341 uint32_t stream_id;
342 bool is_end_of_stream;
343 bool use_true_binary_metadata;
344 size_t max_frame_size;
345 grpc_transport_one_way_stats* stats;
346 };
347
348 template <typename HeaderSet>
EncodeHeaders(const EncodeHeaderOptions & options,const HeaderSet & headers,grpc_slice_buffer * output)349 void EncodeHeaders(const EncodeHeaderOptions& options,
350 const HeaderSet& headers, grpc_slice_buffer* output) {
351 SliceBuffer raw;
352 hpack_encoder_detail::Encoder encoder(
353 this, options.use_true_binary_metadata, raw);
354 headers.Encode(&encoder);
355 Frame(options, raw, output);
356 }
357
358 template <typename HeaderSet>
EncodeRawHeaders(const HeaderSet & headers,SliceBuffer & output)359 void EncodeRawHeaders(const HeaderSet& headers, SliceBuffer& output) {
360 hpack_encoder_detail::Encoder encoder(this, true, output);
361 headers.Encode(&encoder);
362 }
363
364 private:
365 static constexpr size_t kNumFilterValues = 64;
366 static constexpr uint32_t kNumCachedGrpcStatusValues = 16;
367 friend class hpack_encoder_detail::Encoder;
368
369 void Frame(const EncodeHeaderOptions& options, SliceBuffer& raw,
370 grpc_slice_buffer* output);
371
372 // maximum number of bytes we'll use for the decode table (to guard against
373 // peers ooming us by setting decode table size high)
374 uint32_t max_usable_size_ = hpack_constants::kInitialTableSize;
375 // if non-zero, advertise to the decoder that we'll start using a table
376 // of this size
377 bool advertise_table_size_change_ = false;
378 HPackEncoderTable table_;
379
380 grpc_metadata_batch::StatefulCompressor<hpack_encoder_detail::Compressor>
381 compression_state_;
382 };
383
384 namespace hpack_encoder_detail {
385
386 template <typename MetadataTrait>
Encode(MetadataTrait,const typename MetadataTrait::ValueType & value)387 void Encoder::Encode(MetadataTrait,
388 const typename MetadataTrait::ValueType& value) {
389 compressor_->compression_state_
390 .Compressor<MetadataTrait, typename MetadataTrait::CompressionTraits>::
391 EncodeWith(MetadataTrait(), value, this);
392 }
393
hpack_table()394 inline HPackEncoderTable& Encoder::hpack_table() { return compressor_->table_; }
395
396 } // namespace hpack_encoder_detail
397
398 } // namespace grpc_core
399
400 #endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_H
401