Line data Source code
1 : #include "source/common/http/http2/metadata_decoder.h" 2 : 3 : #include "source/common/common/assert.h" 4 : #include "source/common/runtime/runtime_features.h" 5 : 6 : #include "absl/container/fixed_array.h" 7 : #include "quiche/http2/decoder/decode_buffer.h" 8 : #include "quiche/http2/hpack/decoder/hpack_decoder.h" 9 : #include "quiche/http2/hpack/decoder/hpack_decoder_listener.h" 10 : 11 : namespace Envoy { 12 : namespace Http { 13 : namespace Http2 { 14 : namespace { 15 : 16 : class QuicheDecoderListener : public http2::HpackDecoderListener { 17 : public: 18 10 : explicit QuicheDecoderListener(MetadataMap& map) : map_(map) {} 19 : 20 : // HpackDecoderListener 21 0 : void OnHeaderListStart() override {} 22 52 : void OnHeader(const std::string& name, const std::string& value) override { 23 52 : map_.emplace(name, value); 24 52 : } 25 6 : void OnHeaderListEnd() override {} 26 0 : void OnHeaderErrorDetected(absl::string_view error_message) override { 27 0 : ENVOY_LOG_MISC(error, "Failed to decode payload: {}", error_message); 28 0 : map_.clear(); 29 0 : } 30 : 31 : private: 32 : MetadataMap& map_; 33 : }; 34 : 35 : } // anonymous namespace 36 : 37 : // Since QuicheDecoderListener and http2::HpackDecoder are implementation details, this struct is 38 : // defined here rather than the header file. 39 : struct MetadataDecoder::HpackDecoderContext { 40 : HpackDecoderContext(MetadataMap& map, size_t max_payload_size_bound) 41 10 : : listener(map), decoder(&listener, max_payload_size_bound) {} 42 : QuicheDecoderListener listener; 43 : http2::HpackDecoder decoder; 44 : }; 45 : 46 4 : MetadataDecoder::MetadataDecoder(MetadataCallback cb) { 47 4 : nghttp2_hd_inflater* inflater; 48 4 : int rv = nghttp2_hd_inflate_new(&inflater); 49 4 : ASSERT(rv == 0); 50 4 : inflater_ = Inflater(inflater); 51 : 52 4 : ASSERT(cb != nullptr); 53 4 : callback_ = std::move(cb); 54 : 55 4 : resetDecoderContext(); 56 4 : } 57 : 58 4 : MetadataDecoder::~MetadataDecoder() = default; 59 : 60 44 : bool MetadataDecoder::receiveMetadata(const uint8_t* data, size_t len) { 61 44 : ASSERT(data != nullptr && len != 0); 62 44 : payload_.add(data, len); 63 : 64 44 : total_payload_size_ += len; 65 44 : return total_payload_size_ <= max_payload_size_bound_; 66 44 : } 67 : 68 6 : bool MetadataDecoder::onMetadataFrameComplete(bool end_metadata) { 69 6 : bool success; 70 6 : if (Runtime::runtimeFeatureEnabled( 71 6 : "envoy.reloadable_features.http2_decode_metadata_with_quiche")) { 72 6 : success = decodeMetadataPayload(end_metadata); 73 6 : } else { 74 0 : success = decodeMetadataPayloadUsingNghttp2(end_metadata); 75 0 : } 76 6 : if (!success) { 77 0 : return false; 78 0 : } 79 : 80 6 : if (end_metadata) { 81 6 : callback_(std::move(metadata_map_)); 82 6 : resetDecoderContext(); 83 6 : } 84 6 : return true; 85 6 : } 86 : 87 0 : bool MetadataDecoder::decodeMetadataPayloadUsingNghttp2(bool end_metadata) { 88 0 : Buffer::RawSliceVector slices = payload_.getRawSlices(); 89 0 : const int num_slices = slices.size(); 90 : 91 : // Data consumed by nghttp2 so far. 92 0 : ssize_t payload_size_consumed = 0; 93 : // Decodes header block using nghttp2. 94 0 : for (int i = 0; i < num_slices; i++) { 95 0 : nghttp2_nv nv; 96 0 : int inflate_flags = 0; 97 0 : auto slice = slices[i]; 98 : // is_end indicates if the data in slice is the last data in the current 99 : // header block. 100 0 : bool is_end = i == (num_slices - 1) && end_metadata; 101 : 102 : // Feeds data to nghttp2 to decode. 103 0 : while (slice.len_ > 0) { 104 0 : ssize_t result = 105 0 : nghttp2_hd_inflate_hd2(inflater_.get(), &nv, &inflate_flags, 106 0 : reinterpret_cast<uint8_t*>(slice.mem_), slice.len_, is_end); 107 0 : if (result < 0 || (result == 0 && slice.len_ > 0)) { 108 : // If decoding fails, or there is data left in slice, but no data can be consumed by 109 : // nghttp2, return false. 110 0 : ENVOY_LOG(error, "Failed to decode payload."); 111 0 : return false; 112 0 : } 113 : 114 0 : slice.mem_ = reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(slice.mem_) + result); 115 0 : slice.len_ -= result; 116 0 : payload_size_consumed += result; 117 : 118 0 : if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { 119 : // One header key value pair has been successfully decoded. 120 0 : metadata_map_->emplace(std::string(reinterpret_cast<char*>(nv.name), nv.namelen), 121 0 : std::string(reinterpret_cast<char*>(nv.value), nv.valuelen)); 122 0 : } 123 0 : } 124 : 125 0 : if (slice.len_ == 0 && is_end) { 126 : // After one header block is decoded, reset inflater. 127 0 : ASSERT(end_metadata); 128 0 : nghttp2_hd_inflate_end_headers(inflater_.get()); 129 0 : } 130 0 : } 131 : 132 0 : payload_.drain(payload_size_consumed); 133 0 : return true; 134 0 : } 135 : 136 6 : bool MetadataDecoder::decodeMetadataPayload(bool end_metadata) { 137 6 : Buffer::RawSliceVector slices = payload_.getRawSlices(); 138 : 139 : // Data consumed by the decoder so far. 140 6 : ssize_t payload_size_consumed = 0; 141 14 : for (const Buffer::RawSlice& slice : slices) { 142 14 : http2::DecodeBuffer db(static_cast<char*>(slice.mem_), slice.len_); 143 28 : while (db.HasData()) { 144 14 : if (!decoder_context_->decoder.DecodeFragment(&db)) { 145 0 : ENVOY_LOG_MISC(error, "Failed to decode payload: {}", 146 0 : decoder_context_->decoder.detailed_error()); 147 0 : return false; 148 0 : } 149 14 : } 150 14 : payload_size_consumed += slice.len_; 151 14 : } 152 6 : if (end_metadata) { 153 6 : const bool result = decoder_context_->decoder.EndDecodingBlock(); 154 6 : if (!result) { 155 0 : ENVOY_LOG_MISC(error, "Failed to decode payload: {}", 156 0 : decoder_context_->decoder.detailed_error()); 157 0 : return false; 158 0 : } 159 6 : } 160 6 : payload_.drain(payload_size_consumed); 161 6 : return true; 162 6 : } 163 : 164 10 : void MetadataDecoder::resetDecoderContext() { 165 10 : metadata_map_ = std::make_unique<MetadataMap>(); 166 10 : decoder_context_ = std::make_unique<HpackDecoderContext>(*metadata_map_, max_payload_size_bound_); 167 10 : } 168 : 169 : } // namespace Http2 170 : } // namespace Http 171 : } // namespace Envoy