| #include "spdy/core/metadata_extension.h" | 
 |  | 
 | #include <list> | 
 | #include <string> | 
 |  | 
 | #include "absl/memory/memory.h" | 
 | #include "absl/strings/str_cat.h" | 
 | #include "http2/decoder/decode_buffer.h" | 
 | #include "http2/hpack/decoder/hpack_decoder.h" | 
 | #include "common/platform/api/quiche_bug_tracker.h" | 
 | #include "common/platform/api/quiche_logging.h" | 
 | #include "spdy/core/hpack/hpack_encoder.h" | 
 | #include "spdy/core/http2_header_block_hpack_listener.h" | 
 |  | 
 | namespace spdy { | 
 |  | 
 | // Non-standard constants related to METADATA frames. | 
 | const SpdySettingsId MetadataVisitor::kMetadataExtensionId = 0x4d44; | 
 | const uint8_t MetadataVisitor::kMetadataFrameType = 0x4d; | 
 | const uint8_t MetadataVisitor::kEndMetadataFlag = 0x4; | 
 |  | 
 | namespace { | 
 |  | 
 | const size_t kMaxMetadataBlockSize = 1 << 20;  // 1 MB | 
 |  | 
 | // This class uses an HpackEncoder to serialize a METADATA block as a series of | 
 | // METADATA frames. | 
 | class MetadataFrameSequence : public MetadataSerializer::FrameSequence { | 
 |  public: | 
 |   MetadataFrameSequence(SpdyStreamId stream_id, spdy::SpdyHeaderBlock payload) | 
 |       : stream_id_(stream_id), payload_(std::move(payload)) { | 
 |     // Metadata should not use HPACK compression. | 
 |     encoder_.DisableCompression(); | 
 |     HpackEncoder::Representations r; | 
 |     for (const auto& kv_pair : payload_) { | 
 |       r.push_back(kv_pair); | 
 |     } | 
 |     progressive_encoder_ = encoder_.EncodeRepresentations(r); | 
 |   } | 
 |  | 
 |   // Copies are not allowed. | 
 |   MetadataFrameSequence(const MetadataFrameSequence& other) = delete; | 
 |   MetadataFrameSequence& operator=(const MetadataFrameSequence& other) = delete; | 
 |  | 
 |   std::unique_ptr<spdy::SpdyFrameIR> Next() override; | 
 |  | 
 |  private: | 
 |   SpdyStreamId stream_id_; | 
 |   SpdyHeaderBlock payload_; | 
 |   HpackEncoder encoder_; | 
 |   std::unique_ptr<HpackEncoder::ProgressiveEncoder> progressive_encoder_; | 
 | }; | 
 |  | 
 | std::unique_ptr<spdy::SpdyFrameIR> MetadataFrameSequence::Next() { | 
 |   if (!progressive_encoder_->HasNext()) { | 
 |     return nullptr; | 
 |   } | 
 |   // METADATA frames obey the HTTP/2 maximum frame size. | 
 |   std::string payload = | 
 |       progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit); | 
 |   const bool end_metadata = (!progressive_encoder_->HasNext()); | 
 |   const uint8_t flags = end_metadata ? MetadataVisitor::kEndMetadataFlag : 0; | 
 |   return absl::make_unique<spdy::SpdyUnknownIR>( | 
 |       stream_id_, MetadataVisitor::kMetadataFrameType, flags, | 
 |       std::move(payload)); | 
 | } | 
 |  | 
 | }  // anonymous namespace | 
 |  | 
 | struct MetadataVisitor::MetadataPayloadState { | 
 |   MetadataPayloadState(size_t remaining, bool end) | 
 |       : bytes_remaining(remaining), end_metadata(end) {} | 
 |   std::list<std::string> buffer; | 
 |   size_t bytes_remaining; | 
 |   bool end_metadata; | 
 | }; | 
 |  | 
 | MetadataVisitor::MetadataVisitor(OnCompletePayload on_payload, | 
 |                                  OnMetadataSupport on_support) | 
 |     : on_payload_(std::move(on_payload)), | 
 |       on_support_(std::move(on_support)), | 
 |       peer_supports_metadata_(MetadataSupportState::UNSPECIFIED) {} | 
 |  | 
 | MetadataVisitor::~MetadataVisitor() {} | 
 |  | 
 | void MetadataVisitor::OnSetting(SpdySettingsId id, uint32_t value) { | 
 |   QUICHE_VLOG(1) << "MetadataVisitor::OnSetting(" << id << ", " << value << ")"; | 
 |   if (id == kMetadataExtensionId) { | 
 |     if (value == 0) { | 
 |       const MetadataSupportState previous_state = peer_supports_metadata_; | 
 |       peer_supports_metadata_ = MetadataSupportState::NOT_SUPPORTED; | 
 |       if (previous_state == MetadataSupportState::UNSPECIFIED || | 
 |           previous_state == MetadataSupportState::SUPPORTED) { | 
 |         on_support_(false); | 
 |       } | 
 |     } else if (value == 1) { | 
 |       const MetadataSupportState previous_state = peer_supports_metadata_; | 
 |       peer_supports_metadata_ = MetadataSupportState::SUPPORTED; | 
 |       if (previous_state == MetadataSupportState::UNSPECIFIED || | 
 |           previous_state == MetadataSupportState::NOT_SUPPORTED) { | 
 |         on_support_(true); | 
 |       } | 
 |     } else { | 
 |       LOG_EVERY_N_SEC(WARNING, 1) | 
 |           << "Unrecognized value for setting " << id << ": " << value; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool MetadataVisitor::OnFrameHeader(SpdyStreamId stream_id, size_t length, | 
 |                                     uint8_t type, uint8_t flags) { | 
 |   QUICHE_VLOG(1) << "OnFrameHeader(stream_id=" << stream_id | 
 |                  << ", length=" << length << ", type=" << static_cast<int>(type) | 
 |                  << ", flags=" << static_cast<int>(flags); | 
 |   // TODO(birenroy): Consider disabling METADATA handling until our setting | 
 |   // advertising METADATA support has been acked. | 
 |   if (type != kMetadataFrameType) { | 
 |     return false; | 
 |   } | 
 |   auto it = metadata_map_.find(stream_id); | 
 |   if (it == metadata_map_.end()) { | 
 |     auto state = absl::make_unique<MetadataPayloadState>( | 
 |         length, flags & kEndMetadataFlag); | 
 |     auto result = metadata_map_.insert(std::make_pair(stream_id, | 
 |                                                       std::move(state))); | 
 |     QUICHE_BUG_IF(bug_if_2781_1, !result.second) << "Map insertion failed."; | 
 |     it = result.first; | 
 |   } else { | 
 |     QUICHE_BUG_IF(bug_22051_1, it->second->end_metadata) | 
 |         << "Inconsistent metadata payload state!"; | 
 |     QUICHE_BUG_IF(bug_if_2781_2, it->second->bytes_remaining > 0) | 
 |         << "Incomplete metadata block!"; | 
 |   } | 
 |  | 
 |   if (it->second == nullptr) { | 
 |     QUICHE_BUG(bug_2781_3) << "Null metadata payload state!"; | 
 |     return false; | 
 |   } | 
 |   current_stream_ = stream_id; | 
 |   it->second->bytes_remaining = length; | 
 |   it->second->end_metadata = (flags & kEndMetadataFlag); | 
 |   return true; | 
 | } | 
 |  | 
 | void MetadataVisitor::OnFramePayload(const char* data, size_t len) { | 
 |   QUICHE_VLOG(1) << "OnFramePayload(stream_id=" << current_stream_ | 
 |                  << ", len=" << len << ")"; | 
 |   auto it = metadata_map_.find(current_stream_); | 
 |   if (it == metadata_map_.end() || it->second == nullptr) { | 
 |     QUICHE_BUG(bug_2781_4) << "Invalid order of operations on MetadataVisitor."; | 
 |   } else { | 
 |     MetadataPayloadState* state = it->second.get();  // For readability. | 
 |     state->buffer.push_back(std::string(data, len)); | 
 |     if (len < state->bytes_remaining) { | 
 |       state->bytes_remaining -= len; | 
 |     } else { | 
 |       QUICHE_BUG_IF(bug_22051_2, len > state->bytes_remaining) | 
 |           << "Metadata payload overflow! len: " << len | 
 |           << " bytes_remaining: " << state->bytes_remaining; | 
 |       state->bytes_remaining = 0; | 
 |       if (state->end_metadata) { | 
 |         // The whole process of decoding the HPACK-encoded metadata block, | 
 |         // below, is more cumbersome than it ought to be. | 
 |         spdy::Http2HeaderBlockHpackListener listener; | 
 |         http2::HpackDecoder decoder(&listener, kMaxMetadataBlockSize); | 
 |  | 
 |         // If any operations fail, the decode process should be aborted. | 
 |         bool success = decoder.StartDecodingBlock(); | 
 |         for (const std::string& slice : state->buffer) { | 
 |           if (!success) { | 
 |             break; | 
 |           } | 
 |           http2::DecodeBuffer buffer(slice.data(), slice.size()); | 
 |           success = success && decoder.DecodeFragment(&buffer); | 
 |         } | 
 |         success = | 
 |             success && decoder.EndDecodingBlock() && !listener.hpack_error(); | 
 |         if (success) { | 
 |           on_payload_(current_stream_, listener.release_header_block()); | 
 |         } | 
 |         // TODO(birenroy): add varz counting metadata decode successes/failures. | 
 |         metadata_map_.erase(it); | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | std::unique_ptr<MetadataSerializer::FrameSequence> | 
 | MetadataSerializer::FrameSequenceForPayload(SpdyStreamId stream_id, | 
 |                                             MetadataPayload payload) { | 
 |   return absl::make_unique<MetadataFrameSequence>(stream_id, | 
 |                                                   std::move(payload)); | 
 | } | 
 |  | 
 | }  // namespace spdy |