LCOV - code coverage report
Current view: top level - source/common/http/http2 - metadata_decoder.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 56 106 52.8 %
Date: 2024-01-05 06:35:25 Functions: 10 13 76.9 %

          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

Generated by: LCOV version 1.15