| #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 |