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