| #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; |
| } |
| std::string payload; |
| // METADATA frames obey the HTTP/2 maximum frame size. |
| progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit, &payload); |
| 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 |