xref: /aosp_15_r20/external/grpc-grpc/src/core/ext/transport/chttp2/transport/hpack_encoder.h (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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