| /* |
| * |
| * Copyright 2015 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #include <grpc/support/port_platform.h> |
| |
| #include "src/core/ext/transport/chttp2/transport/hpack_encoder.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <memory> |
| |
| #include <grpc/slice.h> |
| #include <grpc/slice_buffer.h> |
| #include <grpc/support/log.h> |
| |
| #include "src/core/ext/transport/chttp2/transport/bin_encoder.h" |
| #include "src/core/ext/transport/chttp2/transport/frame.h" |
| #include "src/core/ext/transport/chttp2/transport/hpack_constants.h" |
| #include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h" |
| #include "src/core/ext/transport/chttp2/transport/varint.h" |
| #include "src/core/lib/iomgr/exec_ctx.h" |
| #include "src/core/lib/surface/validate_metadata.h" |
| #include "src/core/lib/transport/timeout_encoding.h" |
| |
| namespace grpc_core { |
| |
| namespace { |
| |
| constexpr size_t kDataFrameHeaderSize = 9; |
| |
| } /* namespace */ |
| |
| /* fills p (which is expected to be kDataFrameHeaderSize bytes long) |
| * with a data frame header */ |
| static void FillHeader(uint8_t* p, uint8_t type, uint32_t id, size_t len, |
| uint8_t flags) { |
| /* len is the current frame size (i.e. for the frame we're finishing). |
| We finish a frame if: |
| 1) We called ensure_space(), (i.e. add_tiny_header_data()) and adding |
| 'need_bytes' to the frame would cause us to exceed max_frame_size. |
| 2) We called add_header_data, and adding the slice would cause us to exceed |
| max_frame_size. |
| 3) We're done encoding the header. |
| |
| Thus, len is always <= max_frame_size. |
| max_frame_size is derived from GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE, |
| which has a max allowable value of 16777215 (see chttp_transport.cc). |
| Thus, the following assert can be a debug assert. */ |
| GPR_DEBUG_ASSERT(len < 16777316); |
| *p++ = static_cast<uint8_t>(len >> 16); |
| *p++ = static_cast<uint8_t>(len >> 8); |
| *p++ = static_cast<uint8_t>(len); |
| *p++ = type; |
| *p++ = flags; |
| *p++ = static_cast<uint8_t>(id >> 24); |
| *p++ = static_cast<uint8_t>(id >> 16); |
| *p++ = static_cast<uint8_t>(id >> 8); |
| *p++ = static_cast<uint8_t>(id); |
| } |
| |
| size_t HPackCompressor::Framer::CurrentFrameSize() const { |
| const size_t frame_size = |
| output_->length - prefix_.output_length_at_start_of_frame; |
| GPR_DEBUG_ASSERT(frame_size <= max_frame_size_); |
| return frame_size; |
| } |
| |
| // finish a frame - fill in the previously reserved header |
| void HPackCompressor::Framer::FinishFrame(bool is_header_boundary) { |
| const uint8_t type = is_first_frame_ ? GRPC_CHTTP2_FRAME_HEADER |
| : GRPC_CHTTP2_FRAME_CONTINUATION; |
| uint8_t flags = 0; |
| // per the HTTP/2 spec: |
| // A HEADERS frame carries the END_STREAM flag that signals the end of a |
| // stream. However, a HEADERS frame with the END_STREAM flag set can be |
| // followed by CONTINUATION frames on the same stream. Logically, the |
| // CONTINUATION frames are part of the HEADERS frame. |
| // Thus, we add the END_STREAM flag to the HEADER frame (the first frame). |
| if (is_first_frame_ && is_end_of_stream_) { |
| flags |= GRPC_CHTTP2_DATA_FLAG_END_STREAM; |
| } |
| // per the HTTP/2 spec: |
| // A HEADERS frame without the END_HEADERS flag set MUST be followed by |
| // a CONTINUATION frame for the same stream. |
| // Thus, we add the END_HEADER flag to the last frame. |
| if (is_header_boundary) { |
| flags |= GRPC_CHTTP2_DATA_FLAG_END_HEADERS; |
| } |
| FillHeader(GRPC_SLICE_START_PTR(output_->slices[prefix_.header_idx]), type, |
| stream_id_, CurrentFrameSize(), flags); |
| stats_->framing_bytes += kDataFrameHeaderSize; |
| is_first_frame_ = false; |
| } |
| |
| // begin a new frame: reserve off header space, remember how many bytes we'd |
| // output before beginning |
| HPackCompressor::Framer::FramePrefix HPackCompressor::Framer::BeginFrame() { |
| grpc_slice reserved; |
| reserved.refcount = nullptr; |
| reserved.data.inlined.length = kDataFrameHeaderSize; |
| return FramePrefix{grpc_slice_buffer_add_indexed(output_, reserved), |
| output_->length}; |
| } |
| |
| // make sure that the current frame is of the type desired, and has sufficient |
| // space to add at least about_to_add bytes -- finishes the current frame if |
| // needed |
| void HPackCompressor::Framer::EnsureSpace(size_t need_bytes) { |
| if (GPR_LIKELY(CurrentFrameSize() + need_bytes <= max_frame_size_)) { |
| return; |
| } |
| FinishFrame(false); |
| prefix_ = BeginFrame(); |
| } |
| |
| void HPackCompressor::Framer::Add(Slice slice) { |
| while (true) { |
| const size_t len = slice.length(); |
| if (len == 0) return; |
| const size_t remaining = max_frame_size_ - CurrentFrameSize(); |
| if (len <= remaining) { |
| stats_->header_bytes += len; |
| grpc_slice_buffer_add(output_, slice.TakeCSlice()); |
| return; |
| } else { |
| stats_->header_bytes += remaining; |
| Slice tail = slice.Split(remaining); |
| grpc_slice_buffer_add(output_, slice.TakeCSlice()); |
| slice = std::move(tail); |
| FinishFrame(false); |
| prefix_ = BeginFrame(); |
| } |
| } |
| } |
| |
| uint8_t* HPackCompressor::Framer::AddTiny(size_t len) { |
| EnsureSpace(len); |
| stats_->header_bytes += len; |
| return grpc_slice_buffer_tiny_add(output_, len); |
| } |
| |
| void HPackCompressor::Framer::EmitIndexed(uint32_t elem_index) { |
| VarintWriter<1> w(elem_index); |
| w.Write(0x80, AddTiny(w.length())); |
| } |
| |
| struct WireValue { |
| WireValue(uint8_t huffman_prefix, bool insert_null_before_wire_value, |
| Slice slice) |
| : data(std::move(slice)), |
| huffman_prefix(huffman_prefix), |
| insert_null_before_wire_value(insert_null_before_wire_value), |
| length(data.length() + (insert_null_before_wire_value ? 1 : 0)) {} |
| Slice data; |
| const uint8_t huffman_prefix; |
| const bool insert_null_before_wire_value; |
| const size_t length; |
| }; |
| |
| static WireValue GetWireValue(Slice value, bool true_binary_enabled, |
| bool is_bin_hdr) { |
| if (is_bin_hdr) { |
| if (true_binary_enabled) { |
| return WireValue(0x00, true, std::move(value)); |
| } else { |
| return WireValue(0x80, false, |
| Slice(grpc_chttp2_base64_encode_and_huffman_compress( |
| value.c_slice()))); |
| } |
| } else { |
| /* TODO(ctiller): opportunistically compress non-binary headers */ |
| return WireValue(0x00, false, std::move(value)); |
| } |
| } |
| |
| struct DefinitelyInterned { |
| static bool IsBinary(grpc_slice key) { |
| return grpc_is_refcounted_slice_binary_header(key); |
| } |
| }; |
| struct UnsureIfInterned { |
| static bool IsBinary(grpc_slice key) { |
| return grpc_is_binary_header_internal(key); |
| } |
| }; |
| |
| class BinaryStringValue { |
| public: |
| explicit BinaryStringValue(Slice value, bool use_true_binary_metadata) |
| : wire_value_( |
| GetWireValue(std::move(value), use_true_binary_metadata, true)), |
| len_val_(wire_value_.length) {} |
| |
| size_t prefix_length() const { |
| return len_val_.length() + |
| (wire_value_.insert_null_before_wire_value ? 1 : 0); |
| } |
| |
| void WritePrefix(uint8_t* prefix_data) { |
| len_val_.Write(wire_value_.huffman_prefix, prefix_data); |
| if (wire_value_.insert_null_before_wire_value) { |
| prefix_data[len_val_.length()] = 0; |
| } |
| } |
| |
| Slice data() { return std::move(wire_value_.data); } |
| |
| private: |
| WireValue wire_value_; |
| VarintWriter<1> len_val_; |
| }; |
| |
| class NonBinaryStringValue { |
| public: |
| explicit NonBinaryStringValue(Slice value) |
| : value_(std::move(value)), len_val_(value_.length()) {} |
| |
| size_t prefix_length() const { return len_val_.length(); } |
| |
| void WritePrefix(uint8_t* prefix_data) { len_val_.Write(0x00, prefix_data); } |
| |
| Slice data() { return std::move(value_); } |
| |
| private: |
| Slice value_; |
| VarintWriter<1> len_val_; |
| }; |
| |
| class StringKey { |
| public: |
| explicit StringKey(Slice key) |
| : key_(std::move(key)), len_key_(key_.length()) {} |
| |
| size_t prefix_length() const { return 1 + len_key_.length(); } |
| |
| void WritePrefix(uint8_t type, uint8_t* data) { |
| data[0] = type; |
| len_key_.Write(0x00, data + 1); |
| } |
| |
| Slice key() { return std::move(key_); } |
| |
| private: |
| Slice key_; |
| VarintWriter<1> len_key_; |
| }; |
| |
| void HPackCompressor::Framer::EmitLitHdrWithNonBinaryStringKeyIncIdx( |
| Slice key_slice, Slice value_slice) { |
| StringKey key(std::move(key_slice)); |
| key.WritePrefix(0x40, AddTiny(key.prefix_length())); |
| Add(key.key()); |
| NonBinaryStringValue emit(std::move(value_slice)); |
| emit.WritePrefix(AddTiny(emit.prefix_length())); |
| Add(emit.data()); |
| } |
| |
| void HPackCompressor::Framer::EmitLitHdrWithBinaryStringKeyNotIdx( |
| Slice key_slice, Slice value_slice) { |
| StringKey key(std::move(key_slice)); |
| key.WritePrefix(0x00, AddTiny(key.prefix_length())); |
| Add(key.key()); |
| BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_); |
| emit.WritePrefix(AddTiny(emit.prefix_length())); |
| Add(emit.data()); |
| } |
| |
| void HPackCompressor::Framer::EmitLitHdrWithBinaryStringKeyIncIdx( |
| Slice key_slice, Slice value_slice) { |
| StringKey key(std::move(key_slice)); |
| key.WritePrefix(0x40, AddTiny(key.prefix_length())); |
| Add(key.key()); |
| BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_); |
| emit.WritePrefix(AddTiny(emit.prefix_length())); |
| Add(emit.data()); |
| } |
| |
| void HPackCompressor::Framer::EmitLitHdrWithBinaryStringKeyNotIdx( |
| uint32_t key_index, Slice value_slice) { |
| BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_); |
| VarintWriter<4> key(key_index); |
| uint8_t* data = AddTiny(key.length() + emit.prefix_length()); |
| key.Write(0x00, data); |
| emit.WritePrefix(data + key.length()); |
| Add(emit.data()); |
| } |
| |
| void HPackCompressor::Framer::EmitLitHdrWithNonBinaryStringKeyNotIdx( |
| Slice key_slice, Slice value_slice) { |
| StringKey key(std::move(key_slice)); |
| key.WritePrefix(0x00, AddTiny(key.prefix_length())); |
| Add(key.key()); |
| NonBinaryStringValue emit(std::move(value_slice)); |
| emit.WritePrefix(AddTiny(emit.prefix_length())); |
| Add(emit.data()); |
| } |
| |
| void HPackCompressor::Framer::AdvertiseTableSizeChange() { |
| VarintWriter<3> w(compressor_->table_.max_size()); |
| w.Write(0x20, AddTiny(w.length())); |
| } |
| |
| void HPackCompressor::SliceIndex::EmitTo(absl::string_view key, |
| const Slice& value, Framer* framer) { |
| auto& table = framer->compressor_->table_; |
| using It = std::vector<ValueIndex>::iterator; |
| It prev = values_.end(); |
| uint32_t transport_length = |
| key.length() + value.length() + hpack_constants::kEntryOverhead; |
| if (transport_length > HPackEncoderTable::MaxEntrySize()) { |
| framer->EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice::FromStaticString(key), |
| value.Ref()); |
| return; |
| } |
| // Linear scan through previous values to see if we find the value. |
| for (It it = values_.begin(); it != values_.end(); ++it) { |
| if (value == it->value) { |
| // Got a hit... is it still in the decode table? |
| if (table.ConvertableToDynamicIndex(it->index)) { |
| // Yes, emit the index and proceed to cleanup. |
| framer->EmitIndexed(table.DynamicIndex(it->index)); |
| } else { |
| // Not current, emit a new literal and update the index. |
| it->index = table.AllocateIndex(transport_length); |
| framer->EmitLitHdrWithNonBinaryStringKeyIncIdx( |
| Slice::FromStaticString(key), value.Ref()); |
| } |
| // Bubble this entry up if we can - ensures that the most used values end |
| // up towards the start of the array. |
| if (prev != values_.end()) std::swap(*prev, *it); |
| // If there are entries at the end of the array, and those entries are no |
| // longer in the table, remove them. |
| while (!values_.empty() && |
| !table.ConvertableToDynamicIndex(values_.back().index)) { |
| values_.pop_back(); |
| } |
| // All done, early out. |
| return; |
| } |
| prev = it; |
| } |
| // No hit, emit a new literal and add it to the index. |
| uint32_t index = table.AllocateIndex(transport_length); |
| framer->EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key), |
| value.Ref()); |
| values_.emplace_back(value.Ref(), index); |
| } |
| |
| void HPackCompressor::Framer::Encode(const Slice& key, const Slice& value) { |
| if (absl::EndsWith(key.as_string_view(), "-bin")) { |
| EmitLitHdrWithBinaryStringKeyNotIdx(key.Ref(), value.Ref()); |
| } else { |
| EmitLitHdrWithNonBinaryStringKeyNotIdx(key.Ref(), value.Ref()); |
| } |
| } |
| |
| void HPackCompressor::Framer::Encode(HttpPathMetadata, const Slice& value) { |
| compressor_->path_index_.EmitTo(HttpPathMetadata::key(), value, this); |
| } |
| |
| void HPackCompressor::Framer::Encode(HttpAuthorityMetadata, |
| const Slice& value) { |
| compressor_->authority_index_.EmitTo(HttpAuthorityMetadata::key(), value, |
| this); |
| } |
| |
| void HPackCompressor::Framer::Encode(TeMetadata, TeMetadata::ValueType value) { |
| GPR_ASSERT(value == TeMetadata::ValueType::kTrailers); |
| EncodeAlwaysIndexed( |
| &compressor_->te_index_, "te", Slice::FromStaticString("trailers"), |
| 2 /* te */ + 8 /* trailers */ + hpack_constants::kEntryOverhead); |
| } |
| |
| void HPackCompressor::Framer::Encode(ContentTypeMetadata, |
| ContentTypeMetadata::ValueType value) { |
| if (value != ContentTypeMetadata::ValueType::kApplicationGrpc) { |
| gpr_log(GPR_ERROR, "Not encoding bad content-type header"); |
| return; |
| } |
| EncodeAlwaysIndexed(&compressor_->content_type_index_, "content-type", |
| Slice::FromStaticString("application/grpc"), |
| 12 /* content-type */ + 16 /* application/grpc */ + |
| hpack_constants::kEntryOverhead); |
| } |
| |
| void HPackCompressor::Framer::Encode(HttpSchemeMetadata, |
| HttpSchemeMetadata::ValueType value) { |
| switch (value) { |
| case HttpSchemeMetadata::ValueType::kHttp: |
| EmitIndexed(6); // :scheme: http |
| break; |
| case HttpSchemeMetadata::ValueType::kHttps: |
| EmitIndexed(7); // :scheme: https |
| break; |
| case HttpSchemeMetadata::ValueType::kInvalid: |
| GPR_ASSERT(false); |
| break; |
| } |
| } |
| |
| void HPackCompressor::Framer::Encode(GrpcTraceBinMetadata, const Slice& slice) { |
| EncodeRepeatingSliceValue(GrpcTraceBinMetadata::key(), slice, |
| &compressor_->grpc_trace_bin_index_, |
| HPackEncoderTable::MaxEntrySize()); |
| } |
| |
| void HPackCompressor::Framer::Encode(GrpcTagsBinMetadata, const Slice& slice) { |
| EncodeRepeatingSliceValue(GrpcTagsBinMetadata::key(), slice, |
| &compressor_->grpc_tags_bin_index_, |
| HPackEncoderTable::MaxEntrySize()); |
| } |
| |
| void HPackCompressor::Framer::Encode(HttpStatusMetadata, uint32_t status) { |
| if (status == 200) { |
| EmitIndexed(8); // :status: 200 |
| return; |
| } |
| uint8_t index = 0; |
| switch (status) { |
| case 204: |
| index = 9; // :status: 204 |
| break; |
| case 206: |
| index = 10; // :status: 206 |
| break; |
| case 304: |
| index = 11; // :status: 304 |
| break; |
| case 400: |
| index = 12; // :status: 400 |
| break; |
| case 404: |
| index = 13; // :status: 404 |
| break; |
| case 500: |
| index = 14; // :status: 500 |
| break; |
| } |
| if (GPR_LIKELY(index != 0)) { |
| EmitIndexed(index); |
| } else { |
| EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(":status"), |
| Slice::FromInt64(status)); |
| } |
| } |
| |
| void HPackCompressor::Framer::Encode(HttpMethodMetadata, |
| HttpMethodMetadata::ValueType method) { |
| switch (method) { |
| case HttpMethodMetadata::ValueType::kPost: |
| EmitIndexed(3); // :method: POST |
| break; |
| case HttpMethodMetadata::ValueType::kGet: |
| EmitIndexed(2); // :method: GET |
| break; |
| case HttpMethodMetadata::ValueType::kPut: |
| // Right now, we only emit PUT as a method for testing purposes, so it's |
| // fine to not index it. |
| EmitLitHdrWithNonBinaryStringKeyNotIdx(Slice::FromStaticString(":method"), |
| Slice::FromStaticString("PUT")); |
| break; |
| case HttpMethodMetadata::ValueType::kInvalid: |
| GPR_ASSERT(false); |
| break; |
| } |
| } |
| |
| void HPackCompressor::Framer::EncodeAlwaysIndexed(uint32_t* index, |
| absl::string_view key, |
| Slice value, |
| uint32_t transport_length) { |
| if (compressor_->table_.ConvertableToDynamicIndex(*index)) { |
| EmitIndexed(compressor_->table_.DynamicIndex(*index)); |
| } else { |
| *index = compressor_->table_.AllocateIndex(transport_length); |
| EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key), |
| std::move(value)); |
| } |
| } |
| |
| void HPackCompressor::Framer::EncodeIndexedKeyWithBinaryValue( |
| uint32_t* index, absl::string_view key, Slice value) { |
| if (compressor_->table_.ConvertableToDynamicIndex(*index)) { |
| EmitLitHdrWithBinaryStringKeyNotIdx( |
| compressor_->table_.DynamicIndex(*index), std::move(value)); |
| } else { |
| *index = compressor_->table_.AllocateIndex(key.length() + value.length() + |
| hpack_constants::kEntryOverhead); |
| EmitLitHdrWithBinaryStringKeyIncIdx(Slice::FromStaticString(key), |
| std::move(value)); |
| } |
| } |
| |
| void HPackCompressor::Framer::EncodeRepeatingSliceValue( |
| const absl::string_view& key, const Slice& slice, uint32_t* index, |
| size_t max_compression_size) { |
| if (hpack_constants::SizeForEntry(key.size(), slice.size()) > |
| max_compression_size) { |
| EmitLitHdrWithBinaryStringKeyNotIdx(Slice::FromStaticString(key), |
| slice.Ref()); |
| } else { |
| EncodeIndexedKeyWithBinaryValue(index, key, slice.Ref()); |
| } |
| } |
| |
| void HPackCompressor::Framer::Encode(GrpcTimeoutMetadata, Timestamp deadline) { |
| Timeout timeout = Timeout::FromDuration(deadline - ExecCtx::Get()->Now()); |
| for (auto it = compressor_->previous_timeouts_.begin(); |
| it != compressor_->previous_timeouts_.end(); ++it) { |
| double ratio = timeout.RatioVersus(it->timeout); |
| // If the timeout we're sending is shorter than a previous timeout, but |
| // within 3% of it, we'll consider sending it. |
| if (ratio > -3 && ratio <= 0 && |
| compressor_->table_.ConvertableToDynamicIndex(it->index)) { |
| EmitIndexed(compressor_->table_.DynamicIndex(it->index)); |
| // Put this timeout to the front of the queue - forces common timeouts to |
| // be considered earlier. |
| std::swap(*it, *compressor_->previous_timeouts_.begin()); |
| return; |
| } |
| } |
| // Clean out some expired timeouts. |
| while (!compressor_->previous_timeouts_.empty() && |
| !compressor_->table_.ConvertableToDynamicIndex( |
| compressor_->previous_timeouts_.back().index)) { |
| compressor_->previous_timeouts_.pop_back(); |
| } |
| Slice encoded = timeout.Encode(); |
| uint32_t index = compressor_->table_.AllocateIndex( |
| GrpcTimeoutMetadata::key().length() + encoded.length() + |
| hpack_constants::kEntryOverhead); |
| compressor_->previous_timeouts_.push_back(PreviousTimeout{timeout, index}); |
| EmitLitHdrWithNonBinaryStringKeyIncIdx( |
| Slice::FromStaticString(GrpcTimeoutMetadata::key()), std::move(encoded)); |
| } |
| |
| void HPackCompressor::Framer::Encode(UserAgentMetadata, const Slice& slice) { |
| if (hpack_constants::SizeForEntry(UserAgentMetadata::key().size(), |
| slice.size()) > |
| HPackEncoderTable::MaxEntrySize()) { |
| EmitLitHdrWithNonBinaryStringKeyNotIdx( |
| Slice::FromStaticString(UserAgentMetadata::key()), slice.Ref()); |
| return; |
| } |
| if (!slice.is_equivalent(compressor_->user_agent_)) { |
| compressor_->user_agent_ = slice.Ref(); |
| compressor_->user_agent_index_ = 0; |
| } |
| EncodeAlwaysIndexed(&compressor_->user_agent_index_, UserAgentMetadata::key(), |
| slice.Ref(), |
| hpack_constants::SizeForEntry( |
| UserAgentMetadata::key().size(), slice.size())); |
| } |
| |
| void HPackCompressor::Framer::Encode(GrpcStatusMetadata, |
| grpc_status_code status) { |
| const uint32_t code = static_cast<uint32_t>(status); |
| uint32_t* index = nullptr; |
| if (code < kNumCachedGrpcStatusValues) { |
| index = &compressor_->cached_grpc_status_[code]; |
| if (compressor_->table_.ConvertableToDynamicIndex(*index)) { |
| EmitIndexed(compressor_->table_.DynamicIndex(*index)); |
| return; |
| } |
| } |
| Slice key = Slice::FromStaticString(GrpcStatusMetadata::key()); |
| Slice value = Slice::FromInt64(code); |
| const uint32_t transport_length = |
| key.length() + value.length() + hpack_constants::kEntryOverhead; |
| if (index != nullptr) { |
| *index = compressor_->table_.AllocateIndex(transport_length); |
| EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key), std::move(value)); |
| } else { |
| EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key), std::move(value)); |
| } |
| } |
| |
| void HPackCompressor::Framer::Encode(GrpcEncodingMetadata, |
| grpc_compression_algorithm value) { |
| uint32_t* index = nullptr; |
| if (value < GRPC_COMPRESS_ALGORITHMS_COUNT) { |
| index = &compressor_->cached_grpc_encoding_[static_cast<uint32_t>(value)]; |
| if (compressor_->table_.ConvertableToDynamicIndex(*index)) { |
| EmitIndexed(compressor_->table_.DynamicIndex(*index)); |
| return; |
| } |
| } |
| auto key = Slice::FromStaticString(GrpcEncodingMetadata::key()); |
| auto encoded_value = GrpcEncodingMetadata::Encode(value); |
| uint32_t transport_length = |
| key.length() + encoded_value.length() + hpack_constants::kEntryOverhead; |
| if (index != nullptr) { |
| *index = compressor_->table_.AllocateIndex(transport_length); |
| EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key), |
| std::move(encoded_value)); |
| } else { |
| EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key), |
| std::move(encoded_value)); |
| } |
| } |
| |
| void HPackCompressor::Framer::Encode(GrpcAcceptEncodingMetadata, |
| CompressionAlgorithmSet value) { |
| if (compressor_->grpc_accept_encoding_index_ != 0 && |
| value == compressor_->grpc_accept_encoding_ && |
| compressor_->table_.ConvertableToDynamicIndex( |
| compressor_->grpc_accept_encoding_index_)) { |
| EmitIndexed(compressor_->table_.DynamicIndex( |
| compressor_->grpc_accept_encoding_index_)); |
| return; |
| } |
| auto key = Slice::FromStaticString(GrpcAcceptEncodingMetadata::key()); |
| auto encoded_value = GrpcAcceptEncodingMetadata::Encode(value); |
| uint32_t transport_length = |
| key.length() + encoded_value.length() + hpack_constants::kEntryOverhead; |
| compressor_->grpc_accept_encoding_index_ = |
| compressor_->table_.AllocateIndex(transport_length); |
| compressor_->grpc_accept_encoding_ = value; |
| EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key), |
| std::move(encoded_value)); |
| } |
| |
| void HPackCompressor::SetMaxUsableSize(uint32_t max_table_size) { |
| max_usable_size_ = max_table_size; |
| SetMaxTableSize(std::min(table_.max_size(), max_table_size)); |
| } |
| |
| void HPackCompressor::SetMaxTableSize(uint32_t max_table_size) { |
| if (table_.SetMaxSize(std::min(max_usable_size_, max_table_size))) { |
| advertise_table_size_change_ = true; |
| if (GRPC_TRACE_FLAG_ENABLED(grpc_http_trace)) { |
| gpr_log(GPR_INFO, "set max table size from encoder to %d", |
| max_table_size); |
| } |
| } |
| } |
| |
| HPackCompressor::Framer::Framer(const EncodeHeaderOptions& options, |
| HPackCompressor* compressor, |
| grpc_slice_buffer* output) |
| : max_frame_size_(options.max_frame_size), |
| use_true_binary_metadata_(options.use_true_binary_metadata), |
| is_end_of_stream_(options.is_end_of_stream), |
| stream_id_(options.stream_id), |
| output_(output), |
| stats_(options.stats), |
| compressor_(compressor), |
| prefix_(BeginFrame()) { |
| if (std::exchange(compressor_->advertise_table_size_change_, false)) { |
| AdvertiseTableSizeChange(); |
| } |
| } |
| |
| } // namespace grpc_core |