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 #include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
20
21 #include <grpc/slice.h>
22 #include <grpc/slice_buffer.h>
23 #include <grpc/support/port_platform.h>
24
25 #include <algorithm>
26 #include <cstdint>
27
28 #include "absl/log/check.h"
29 #include "absl/log/log.h"
30 #include "src/core/ext/transport/chttp2/transport/bin_encoder.h"
31 #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
32 #include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
33 #include "src/core/ext/transport/chttp2/transport/legacy_frame.h"
34 #include "src/core/ext/transport/chttp2/transport/varint.h"
35 #include "src/core/lib/debug/trace.h"
36 #include "src/core/lib/surface/validate_metadata.h"
37 #include "src/core/lib/transport/timeout_encoding.h"
38 #include "src/core/util/crash.h"
39
40 namespace grpc_core {
41
42 namespace {
43
44 constexpr size_t kHeadersFrameHeaderSize = 9;
45
46 } // namespace
47
48 // fills p (which is expected to be kHeadersFrameHeaderSize bytes long)
49 // with a headers frame header
FillHeader(uint8_t * p,uint8_t type,uint32_t id,size_t len,uint8_t flags)50 static void FillHeader(uint8_t* p, uint8_t type, uint32_t id, size_t len,
51 uint8_t flags) {
52 // len is the current frame size (i.e. for the frame we're finishing).
53 // We finish a frame if:
54 // 1) We called ensure_space(), (i.e. add_tiny_header_data()) and adding
55 // 'need_bytes' to the frame would cause us to exceed max_frame_size.
56 // 2) We called add_header_data, and adding the slice would cause us to exceed
57 // max_frame_size.
58 // 3) We're done encoding the header.
59
60 // Thus, len is always <= max_frame_size.
61 // max_frame_size is derived from GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE,
62 // which has a max allowable value of 16777215 (see chttp_transport.cc).
63 // Thus, the following assert can be a debug assert.
64 DCHECK_LE(len, 16777216u);
65 *p++ = static_cast<uint8_t>(len >> 16);
66 *p++ = static_cast<uint8_t>(len >> 8);
67 *p++ = static_cast<uint8_t>(len);
68 *p++ = type;
69 *p++ = flags;
70 *p++ = static_cast<uint8_t>(id >> 24);
71 *p++ = static_cast<uint8_t>(id >> 16);
72 *p++ = static_cast<uint8_t>(id >> 8);
73 *p++ = static_cast<uint8_t>(id);
74 }
75
Frame(const EncodeHeaderOptions & options,SliceBuffer & raw,grpc_slice_buffer * output)76 void HPackCompressor::Frame(const EncodeHeaderOptions& options,
77 SliceBuffer& raw, grpc_slice_buffer* output) {
78 uint8_t frame_type = GRPC_CHTTP2_FRAME_HEADER;
79 uint8_t flags = 0;
80 // per the HTTP/2 spec:
81 // A HEADERS frame carries the END_STREAM flag that signals the end of a
82 // stream. However, a HEADERS frame with the END_STREAM flag set can be
83 // followed by CONTINUATION frames on the same stream. Logically, the
84 // CONTINUATION frames are part of the HEADERS frame.
85 // Thus, we add the END_STREAM flag to the HEADER frame (the first frame).
86 if (options.is_end_of_stream) {
87 flags |= GRPC_CHTTP2_DATA_FLAG_END_STREAM;
88 }
89 options.call_tracer->RecordOutgoingBytes({0, 0, raw.Length()});
90 while (frame_type == GRPC_CHTTP2_FRAME_HEADER || raw.Length() > 0) {
91 // per the HTTP/2 spec:
92 // A HEADERS frame without the END_HEADERS flag set MUST be followed by
93 // a CONTINUATION frame for the same stream.
94 // Thus, we add the END_HEADER flag to the last frame.
95 size_t len = raw.Length();
96 if (len <= options.max_frame_size) {
97 flags |= GRPC_CHTTP2_DATA_FLAG_END_HEADERS;
98 } else {
99 len = options.max_frame_size;
100 }
101 FillHeader(grpc_slice_buffer_tiny_add(output, kHeadersFrameHeaderSize),
102 frame_type, options.stream_id, len, flags);
103 options.call_tracer->RecordOutgoingBytes({kHeadersFrameHeaderSize, 0, 0});
104 grpc_slice_buffer_move_first(raw.c_slice_buffer(), len, output);
105
106 frame_type = GRPC_CHTTP2_FRAME_CONTINUATION;
107 flags = 0;
108 }
109 }
110
SetMaxUsableSize(uint32_t max_table_size)111 void HPackCompressor::SetMaxUsableSize(uint32_t max_table_size) {
112 max_usable_size_ = max_table_size;
113 SetMaxTableSize(std::min(table_.max_size(), max_table_size));
114 }
115
SetMaxTableSize(uint32_t max_table_size)116 void HPackCompressor::SetMaxTableSize(uint32_t max_table_size) {
117 if (table_.SetMaxSize(std::min(max_usable_size_, max_table_size))) {
118 advertise_table_size_change_ = true;
119 GRPC_TRACE_LOG(http, INFO)
120 << "set max table size from encoder to " << max_table_size;
121 }
122 }
123
124 namespace {
125 struct WireValue {
WireValuegrpc_core::__anon4dc18ba40211::WireValue126 WireValue(uint8_t huffman_prefix, bool insert_null_before_wire_value,
127 Slice slice)
128 : data(std::move(slice)),
129 huffman_prefix(huffman_prefix),
130 insert_null_before_wire_value(insert_null_before_wire_value),
131 length(data.length() + (insert_null_before_wire_value ? 1 : 0)),
132 hpack_length(length) {}
WireValuegrpc_core::__anon4dc18ba40211::WireValue133 WireValue(uint8_t huffman_prefix, bool insert_null_before_wire_value,
134 Slice slice, size_t hpack_length)
135 : data(std::move(slice)),
136 huffman_prefix(huffman_prefix),
137 insert_null_before_wire_value(insert_null_before_wire_value),
138 length(data.length() + (insert_null_before_wire_value ? 1 : 0)),
139 hpack_length(hpack_length + (insert_null_before_wire_value ? 1 : 0)) {}
140 Slice data;
141 const uint8_t huffman_prefix;
142 const bool insert_null_before_wire_value;
143 const size_t length;
144 const size_t hpack_length;
145 };
146
147 // Construct a wire value from a slice.
148 // true_binary_enabled => use the true binary system
149 // is_bin_hdr => the header is -bin suffixed
GetWireValue(Slice value,bool true_binary_enabled,bool is_bin_hdr)150 WireValue GetWireValue(Slice value, bool true_binary_enabled, bool is_bin_hdr) {
151 if (is_bin_hdr) {
152 if (true_binary_enabled) {
153 return WireValue(0x00, true, std::move(value));
154 } else {
155 uint32_t hpack_length;
156 Slice output(grpc_chttp2_base64_encode_and_huffman_compress(
157 value.c_slice(), &hpack_length));
158 return WireValue(0x80, false, std::move(output), hpack_length);
159 }
160 } else {
161 // TODO(ctiller): opportunistically compress non-binary headers
162 return WireValue(0x00, false, std::move(value));
163 }
164 }
165
166 struct DefinitelyInterned {
IsBinarygrpc_core::__anon4dc18ba40211::DefinitelyInterned167 static bool IsBinary(grpc_slice key) {
168 return grpc_is_refcounted_slice_binary_header(key);
169 }
170 };
171 struct UnsureIfInterned {
IsBinarygrpc_core::__anon4dc18ba40211::UnsureIfInterned172 static bool IsBinary(grpc_slice key) {
173 return grpc_is_binary_header_internal(key);
174 }
175 };
176
177 class BinaryStringValue {
178 public:
BinaryStringValue(Slice value,bool use_true_binary_metadata)179 explicit BinaryStringValue(Slice value, bool use_true_binary_metadata)
180 : wire_value_(
181 GetWireValue(std::move(value), use_true_binary_metadata, true)),
182 len_val_(wire_value_.length) {}
183
prefix_length() const184 size_t prefix_length() const {
185 return len_val_.length() +
186 (wire_value_.insert_null_before_wire_value ? 1 : 0);
187 }
188
WritePrefix(uint8_t * prefix_data)189 void WritePrefix(uint8_t* prefix_data) {
190 len_val_.Write(wire_value_.huffman_prefix, prefix_data);
191 if (wire_value_.insert_null_before_wire_value) {
192 prefix_data[len_val_.length()] = 0;
193 }
194 }
195
data()196 Slice data() { return std::move(wire_value_.data); }
197
hpack_length()198 uint32_t hpack_length() { return wire_value_.hpack_length; }
199
200 private:
201 WireValue wire_value_;
202 VarintWriter<1> len_val_;
203 };
204
205 class NonBinaryStringValue {
206 public:
NonBinaryStringValue(Slice value)207 explicit NonBinaryStringValue(Slice value)
208 : value_(std::move(value)), len_val_(value_.length()) {}
209
prefix_length() const210 size_t prefix_length() const { return len_val_.length(); }
211
WritePrefix(uint8_t * prefix_data)212 void WritePrefix(uint8_t* prefix_data) { len_val_.Write(0x00, prefix_data); }
213
data()214 Slice data() { return std::move(value_); }
215
216 private:
217 Slice value_;
218 VarintWriter<1> len_val_;
219 };
220
221 class StringKey {
222 public:
StringKey(Slice key)223 explicit StringKey(Slice key)
224 : key_(std::move(key)), len_key_(key_.length()) {}
225
prefix_length() const226 size_t prefix_length() const { return 1 + len_key_.length(); }
227
WritePrefix(uint8_t type,uint8_t * data)228 void WritePrefix(uint8_t type, uint8_t* data) {
229 data[0] = type;
230 len_key_.Write(0x00, data + 1);
231 }
232
key()233 Slice key() { return std::move(key_); }
234
235 private:
236 Slice key_;
237 VarintWriter<1> len_key_;
238 };
239 } // namespace
240
241 namespace hpack_encoder_detail {
EmitIndexed(uint32_t elem_index)242 void Encoder::EmitIndexed(uint32_t elem_index) {
243 VarintWriter<1> w(elem_index);
244 w.Write(0x80, output_.AddTiny(w.length()));
245 }
246
EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,Slice value_slice)247 uint32_t Encoder::EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
248 Slice value_slice) {
249 auto key_len = key_slice.length();
250 auto value_len = value_slice.length();
251 StringKey key(std::move(key_slice));
252 key.WritePrefix(0x40, output_.AddTiny(key.prefix_length()));
253 output_.Append(key.key());
254 NonBinaryStringValue emit(std::move(value_slice));
255 emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
256 // Allocate an index in the hpack table for this newly emitted entry.
257 // (we do so here because we know the length of the key and value)
258 uint32_t index = compressor_->table_.AllocateIndex(
259 key_len + value_len + hpack_constants::kEntryOverhead);
260 output_.Append(emit.data());
261 return index;
262 }
263
EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice,Slice value_slice)264 void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice,
265 Slice value_slice) {
266 StringKey key(std::move(key_slice));
267 key.WritePrefix(0x00, output_.AddTiny(key.prefix_length()));
268 output_.Append(key.key());
269 BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_);
270 emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
271 output_.Append(emit.data());
272 }
273
EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,Slice value_slice)274 uint32_t Encoder::EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
275 Slice value_slice) {
276 auto key_len = key_slice.length();
277 StringKey key(std::move(key_slice));
278 key.WritePrefix(0x40, output_.AddTiny(key.prefix_length()));
279 output_.Append(key.key());
280 BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_);
281 emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
282 // Allocate an index in the hpack table for this newly emitted entry.
283 // (we do so here because we know the length of the key and value)
284 uint32_t index = compressor_->table_.AllocateIndex(
285 key_len + emit.hpack_length() + hpack_constants::kEntryOverhead);
286 output_.Append(emit.data());
287 return index;
288 }
289
EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,Slice value_slice)290 void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
291 Slice value_slice) {
292 BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_);
293 VarintWriter<4> key(key_index);
294 uint8_t* data = output_.AddTiny(key.length() + emit.prefix_length());
295 key.Write(0x00, data);
296 emit.WritePrefix(data + key.length());
297 output_.Append(emit.data());
298 }
299
EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,Slice value_slice)300 void Encoder::EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice key_slice,
301 Slice value_slice) {
302 StringKey key(std::move(key_slice));
303 key.WritePrefix(0x00, output_.AddTiny(key.prefix_length()));
304 output_.Append(key.key());
305 NonBinaryStringValue emit(std::move(value_slice));
306 emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
307 output_.Append(emit.data());
308 }
309
AdvertiseTableSizeChange()310 void Encoder::AdvertiseTableSizeChange() {
311 VarintWriter<3> w(compressor_->table_.max_size());
312 w.Write(0x20, output_.AddTiny(w.length()));
313 }
314
EmitTo(absl::string_view key,const Slice & value,Encoder * encoder)315 void SliceIndex::EmitTo(absl::string_view key, const Slice& value,
316 Encoder* encoder) {
317 auto& table = encoder->hpack_table();
318 using It = std::vector<ValueIndex>::iterator;
319 It prev = values_.end();
320 size_t transport_length =
321 key.length() + value.length() + hpack_constants::kEntryOverhead;
322 if (transport_length > HPackEncoderTable::MaxEntrySize()) {
323 encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
324 Slice::FromStaticString(key), value.Ref());
325 return;
326 }
327 // Linear scan through previous values to see if we find the value.
328 for (It it = values_.begin(); it != values_.end(); ++it) {
329 if (value == it->value) {
330 // Got a hit... is it still in the decode table?
331 if (table.ConvertibleToDynamicIndex(it->index)) {
332 // Yes, emit the index and proceed to cleanup.
333 encoder->EmitIndexed(table.DynamicIndex(it->index));
334 } else {
335 // Not current, emit a new literal and update the index.
336 it->index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
337 Slice::FromStaticString(key), value.Ref());
338 }
339 // Bubble this entry up if we can - ensures that the most used values end
340 // up towards the start of the array.
341 if (prev != values_.end()) std::swap(*prev, *it);
342 // If there are entries at the end of the array, and those entries are no
343 // longer in the table, remove them.
344 while (!values_.empty() &&
345 !table.ConvertibleToDynamicIndex(values_.back().index)) {
346 values_.pop_back();
347 }
348 // All done, early out.
349 return;
350 }
351 prev = it;
352 }
353 // No hit, emit a new literal and add it to the index.
354 uint32_t index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
355 Slice::FromStaticString(key), value.Ref());
356 values_.emplace_back(value.Ref(), index);
357 }
358
Encode(const Slice & key,const Slice & value)359 void Encoder::Encode(const Slice& key, const Slice& value) {
360 if (absl::EndsWith(key.as_string_view(), "-bin")) {
361 EmitLitHdrWithBinaryStringKeyNotIdx(key.Ref(), value.Ref());
362 } else {
363 EmitLitHdrWithNonBinaryStringKeyNotIdx(key.Ref(), value.Ref());
364 }
365 }
366
EncodeWith(HttpSchemeMetadata,HttpSchemeMetadata::ValueType value,Encoder * encoder)367 void Compressor<HttpSchemeMetadata, HttpSchemeCompressor>::EncodeWith(
368 HttpSchemeMetadata, HttpSchemeMetadata::ValueType value, Encoder* encoder) {
369 switch (value) {
370 case HttpSchemeMetadata::ValueType::kHttp:
371 encoder->EmitIndexed(6); // :scheme: http
372 break;
373 case HttpSchemeMetadata::ValueType::kHttps:
374 encoder->EmitIndexed(7); // :scheme: https
375 break;
376 case HttpSchemeMetadata::ValueType::kInvalid:
377 LOG(ERROR) << "Not encoding bad http scheme";
378 encoder->NoteEncodingError();
379 break;
380 }
381 }
382
EncodeWith(HttpStatusMetadata,uint32_t status,Encoder * encoder)383 void Compressor<HttpStatusMetadata, HttpStatusCompressor>::EncodeWith(
384 HttpStatusMetadata, uint32_t status, Encoder* encoder) {
385 if (status == 200) {
386 encoder->EmitIndexed(8); // :status: 200
387 return;
388 }
389 uint8_t index = 0;
390 switch (status) {
391 case 204:
392 index = 9; // :status: 204
393 break;
394 case 206:
395 index = 10; // :status: 206
396 break;
397 case 304:
398 index = 11; // :status: 304
399 break;
400 case 400:
401 index = 12; // :status: 400
402 break;
403 case 404:
404 index = 13; // :status: 404
405 break;
406 case 500:
407 index = 14; // :status: 500
408 break;
409 }
410 if (GPR_LIKELY(index != 0)) {
411 encoder->EmitIndexed(index);
412 } else {
413 encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
414 Slice::FromStaticString(":status"), Slice::FromInt64(status));
415 }
416 }
417
EncodeWith(HttpMethodMetadata,HttpMethodMetadata::ValueType method,Encoder * encoder)418 void Compressor<HttpMethodMetadata, HttpMethodCompressor>::EncodeWith(
419 HttpMethodMetadata, HttpMethodMetadata::ValueType method,
420 Encoder* encoder) {
421 switch (method) {
422 case HttpMethodMetadata::ValueType::kPost:
423 encoder->EmitIndexed(3); // :method: POST
424 break;
425 case HttpMethodMetadata::ValueType::kGet:
426 encoder->EmitIndexed(2); // :method: GET
427 break;
428 case HttpMethodMetadata::ValueType::kPut:
429 // Right now, we only emit PUT as a method for testing purposes, so it's
430 // fine to not index it.
431 encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
432 Slice::FromStaticString(":method"), Slice::FromStaticString("PUT"));
433 break;
434 case HttpMethodMetadata::ValueType::kInvalid:
435 LOG(ERROR) << "Not encoding bad http method";
436 encoder->NoteEncodingError();
437 break;
438 }
439 }
440
EncodeAlwaysIndexed(uint32_t * index,absl::string_view key,Slice value,size_t)441 void Encoder::EncodeAlwaysIndexed(uint32_t* index, absl::string_view key,
442 Slice value, size_t) {
443 if (compressor_->table_.ConvertibleToDynamicIndex(*index)) {
444 EmitIndexed(compressor_->table_.DynamicIndex(*index));
445 } else {
446 *index = EmitLitHdrWithNonBinaryStringKeyIncIdx(
447 Slice::FromStaticString(key), std::move(value));
448 }
449 }
450
EncodeIndexedKeyWithBinaryValue(uint32_t * index,absl::string_view key,Slice value)451 void Encoder::EncodeIndexedKeyWithBinaryValue(uint32_t* index,
452 absl::string_view key,
453 Slice value) {
454 if (compressor_->table_.ConvertibleToDynamicIndex(*index)) {
455 EmitLitHdrWithBinaryStringKeyNotIdx(
456 compressor_->table_.DynamicIndex(*index), std::move(value));
457 } else {
458 *index = EmitLitHdrWithBinaryStringKeyIncIdx(Slice::FromStaticString(key),
459 std::move(value));
460 }
461 }
462
EncodeRepeatingSliceValue(const absl::string_view & key,const Slice & slice,uint32_t * index,size_t max_compression_size)463 void Encoder::EncodeRepeatingSliceValue(const absl::string_view& key,
464 const Slice& slice, uint32_t* index,
465 size_t max_compression_size) {
466 if (hpack_constants::SizeForEntry(key.size(), slice.size()) >
467 max_compression_size) {
468 EmitLitHdrWithBinaryStringKeyNotIdx(Slice::FromStaticString(key),
469 slice.Ref());
470 } else {
471 EncodeIndexedKeyWithBinaryValue(index, key, slice.Ref());
472 }
473 }
474
EncodeWith(absl::string_view key,Timestamp deadline,Encoder * encoder)475 void TimeoutCompressorImpl::EncodeWith(absl::string_view key,
476 Timestamp deadline, Encoder* encoder) {
477 const Timeout timeout = Timeout::FromDuration(deadline - Timestamp::Now());
478 auto& table = encoder->hpack_table();
479 for (size_t i = 0; i < kNumPreviousValues; i++) {
480 const auto& previous = previous_timeouts_[i];
481 if (!table.ConvertibleToDynamicIndex(previous.index)) continue;
482 const double ratio = timeout.RatioVersus(previous.timeout);
483 // If the timeout we're sending is shorter than a previous timeout, but
484 // within 3% of it, we'll consider sending it.
485 if (ratio > -3 && ratio <= 0) {
486 encoder->EmitIndexed(table.DynamicIndex(previous.index));
487 return;
488 }
489 }
490 Slice encoded = timeout.Encode();
491 uint32_t index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
492 Slice::FromStaticString(key), std::move(encoded));
493 uint32_t i = next_previous_value_;
494 ++next_previous_value_;
495 previous_timeouts_[i % kNumPreviousValues] = PreviousTimeout{timeout, index};
496 }
497
Encoder(HPackCompressor * compressor,bool use_true_binary_metadata,SliceBuffer & output)498 Encoder::Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
499 SliceBuffer& output)
500 : use_true_binary_metadata_(use_true_binary_metadata),
501 compressor_(compressor),
502 output_(output) {
503 if (std::exchange(compressor_->advertise_table_size_change_, false)) {
504 AdvertiseTableSizeChange();
505 }
506 }
507
508 } // namespace hpack_encoder_detail
509 } // namespace grpc_core
510