| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/quic/quic_headers_stream.h" |
| |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "net/quic/quic_bug_tracker.h" |
| #include "net/quic/quic_flags.h" |
| #include "net/quic/quic_header_list.h" |
| #include "net/quic/quic_spdy_session.h" |
| #include "net/quic/quic_time.h" |
| #include "net/spdy/spdy_protocol.h" |
| |
| using base::StringPiece; |
| using net::HTTP2; |
| using net::SpdyFrameType; |
| using std::string; |
| |
| namespace net { |
| |
| namespace { |
| |
| class HeaderTableDebugVisitor : public HpackHeaderTable::DebugVisitorInterface { |
| public: |
| HeaderTableDebugVisitor( |
| const QuicClock* clock, |
| std::unique_ptr<QuicHeadersStream::HpackDebugVisitor> visitor) |
| : clock_(clock), headers_stream_hpack_visitor_(std::move(visitor)) {} |
| |
| int64_t OnNewEntry(const HpackEntry& entry) override { |
| DVLOG(1) << entry.GetDebugString(); |
| return clock_->ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds(); |
| } |
| |
| void OnUseEntry(const HpackEntry& entry) override { |
| const QuicTime::Delta elapsed( |
| clock_->ApproximateNow() |
| .Subtract(QuicTime::Delta::FromMicroseconds(entry.time_added())) |
| .Subtract(QuicTime::Zero())); |
| DVLOG(1) << entry.GetDebugString() << " " << elapsed.ToMilliseconds() |
| << " ms"; |
| headers_stream_hpack_visitor_->OnUseEntry(elapsed); |
| } |
| |
| private: |
| const QuicClock* clock_; |
| std::unique_ptr<QuicHeadersStream::HpackDebugVisitor> |
| headers_stream_hpack_visitor_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HeaderTableDebugVisitor); |
| }; |
| |
| } // namespace |
| |
| QuicHeadersStream::HpackDebugVisitor::HpackDebugVisitor() {} |
| |
| QuicHeadersStream::HpackDebugVisitor::~HpackDebugVisitor() {} |
| |
| // A SpdyFramer visitor which passed SYN_STREAM and SYN_REPLY frames to |
| // the QuicSpdyStream, and closes the connection if any unexpected frames |
| // are received. |
| class QuicHeadersStream::SpdyFramerVisitor |
| : public SpdyFramerVisitorInterface, |
| public SpdyFramerDebugVisitorInterface { |
| public: |
| explicit SpdyFramerVisitor(QuicHeadersStream* stream) : stream_(stream) {} |
| |
| // SpdyFramerVisitorInterface implementation |
| void OnSynStream(SpdyStreamId stream_id, |
| SpdyStreamId associated_stream_id, |
| SpdyPriority priority, |
| bool fin, |
| bool unidirectional) override { |
| CloseConnection("SPDY SYN_STREAM frame received."); |
| } |
| |
| void OnSynReply(SpdyStreamId stream_id, bool fin) override { |
| CloseConnection("SPDY SYN_REPLY frame received."); |
| } |
| |
| bool OnControlFrameHeaderData(SpdyStreamId stream_id, |
| const char* header_data, |
| size_t len) override { |
| if (!stream_->IsConnected()) { |
| return false; |
| } |
| stream_->OnControlFrameHeaderData(stream_id, header_data, len); |
| return true; |
| } |
| |
| void OnStreamFrameData(SpdyStreamId stream_id, |
| const char* data, |
| size_t len) override { |
| CloseConnection("SPDY DATA frame received."); |
| } |
| |
| void OnStreamEnd(SpdyStreamId stream_id) override { |
| // The framer invokes OnStreamEnd after processing a SYN_STREAM |
| // or SYN_REPLY frame that had the fin bit set. |
| } |
| |
| void OnStreamPadding(SpdyStreamId stream_id, size_t len) override { |
| CloseConnection("SPDY frame padding received."); |
| } |
| |
| SpdyHeadersHandlerInterface* OnHeaderFrameStart( |
| SpdyStreamId /* stream_id */) override { |
| return &header_list_; |
| } |
| |
| void OnHeaderFrameEnd(SpdyStreamId /* stream_id */, |
| bool end_headers) override { |
| if (end_headers) { |
| if (stream_->IsConnected()) { |
| stream_->OnHeaderList(header_list_); |
| } |
| header_list_.Clear(); |
| } |
| } |
| |
| void OnError(SpdyFramer* framer) override { |
| CloseConnection(base::StringPrintf( |
| "SPDY framing error: %s", |
| SpdyFramer::ErrorCodeToString(framer->error_code()))); |
| } |
| |
| void OnDataFrameHeader(SpdyStreamId stream_id, |
| size_t length, |
| bool fin) override { |
| CloseConnection("SPDY DATA frame received."); |
| } |
| |
| void OnRstStream(SpdyStreamId stream_id, |
| SpdyRstStreamStatus status) override { |
| CloseConnection("SPDY RST_STREAM frame received."); |
| } |
| |
| void OnSetting(SpdySettingsIds id, uint8_t flags, uint32_t value) override { |
| if (!FLAGS_quic_respect_http2_settings_frame) { |
| CloseConnection("SPDY SETTINGS frame received."); |
| return; |
| } |
| switch (id) { |
| case SETTINGS_HEADER_TABLE_SIZE: |
| stream_->UpdateHeaderEncoderTableSize(value); |
| break; |
| // TODO(fayang): Need to support SETTINGS_MAX_HEADER_LIST_SIZE when |
| // clients are actually sending it. |
| default: |
| CloseConnection("Unsupported field of HTTP/2 SETTINGS frame: " + |
| base::IntToString(id)); |
| } |
| } |
| |
| void OnSettingsAck() override { |
| if (!FLAGS_quic_respect_http2_settings_frame) { |
| CloseConnection("SPDY SETTINGS frame received."); |
| } |
| } |
| |
| void OnSettingsEnd() override { |
| if (!FLAGS_quic_respect_http2_settings_frame) { |
| CloseConnection("SPDY SETTINGS frame received."); |
| } |
| } |
| |
| void OnPing(SpdyPingId unique_id, bool is_ack) override { |
| CloseConnection("SPDY PING frame received."); |
| } |
| |
| void OnGoAway(SpdyStreamId last_accepted_stream_id, |
| SpdyGoAwayStatus status) override { |
| CloseConnection("SPDY GOAWAY frame received."); |
| } |
| |
| void OnHeaders(SpdyStreamId stream_id, |
| bool has_priority, |
| int weight, |
| SpdyStreamId parent_stream_id, |
| bool exclusive, |
| bool fin, |
| bool end) override { |
| if (!stream_->IsConnected()) { |
| return; |
| } |
| |
| // TODO(mpw): avoid down-conversion and plumb SpdyStreamPrecedence through |
| // QuicHeadersStream. |
| SpdyPriority priority = |
| has_priority ? Http2WeightToSpdy3Priority(weight) : 0; |
| stream_->OnHeaders(stream_id, has_priority, priority, fin); |
| } |
| |
| void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override { |
| CloseConnection("SPDY WINDOW_UPDATE frame received."); |
| } |
| |
| void OnPushPromise(SpdyStreamId stream_id, |
| SpdyStreamId promised_stream_id, |
| bool end) override { |
| if (!stream_->supports_push_promise()) { |
| CloseConnection("PUSH_PROMISE not supported."); |
| return; |
| } |
| if (!stream_->IsConnected()) { |
| return; |
| } |
| stream_->OnPushPromise(stream_id, promised_stream_id, end); |
| } |
| |
| void OnContinuation(SpdyStreamId stream_id, bool end) override {} |
| |
| void OnPriority(SpdyStreamId stream_id, |
| SpdyStreamId parent_id, |
| int weight, |
| bool exclusive) override { |
| CloseConnection("SPDY PRIORITY frame received."); |
| } |
| |
| bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override { |
| CloseConnection("Unknown frame type received."); |
| return false; |
| } |
| |
| // SpdyFramerDebugVisitorInterface implementation |
| void OnSendCompressedFrame(SpdyStreamId stream_id, |
| SpdyFrameType type, |
| size_t payload_len, |
| size_t frame_len) override { |
| if (payload_len == 0) { |
| QUIC_BUG << "Zero payload length."; |
| return; |
| } |
| int compression_pct = 100 - (100 * frame_len) / payload_len; |
| DVLOG(1) << "Net.QuicHpackCompressionPercentage: " << compression_pct; |
| UMA_HISTOGRAM_PERCENTAGE("Net.QuicHpackCompressionPercentage", |
| compression_pct); |
| } |
| |
| void OnReceiveCompressedFrame(SpdyStreamId stream_id, |
| SpdyFrameType type, |
| size_t frame_len) override { |
| if (stream_->IsConnected()) { |
| stream_->OnCompressedFrameSize(frame_len); |
| } |
| } |
| |
| private: |
| void CloseConnection(const string& details) { |
| if (stream_->IsConnected()) { |
| stream_->CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, |
| details); |
| } |
| } |
| |
| private: |
| QuicHeadersStream* stream_; |
| QuicHeaderList header_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SpdyFramerVisitor); |
| }; |
| |
| QuicHeadersStream::QuicHeadersStream(QuicSpdySession* session) |
| : ReliableQuicStream(kHeadersStreamId, session), |
| spdy_session_(session), |
| stream_id_(kInvalidStreamId), |
| promised_stream_id_(kInvalidStreamId), |
| fin_(false), |
| frame_len_(0), |
| uncompressed_frame_len_(0), |
| measure_headers_hol_blocking_time_( |
| FLAGS_quic_measure_headers_hol_blocking_time), |
| supports_push_promise_(session->perspective() == Perspective::IS_CLIENT && |
| FLAGS_quic_supports_push_promise), |
| cur_max_timestamp_(QuicTime::Zero()), |
| prev_max_timestamp_(QuicTime::Zero()), |
| spdy_framer_(HTTP2), |
| spdy_framer_visitor_(new SpdyFramerVisitor(this)) { |
| spdy_framer_.set_visitor(spdy_framer_visitor_.get()); |
| spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get()); |
| // The headers stream is exempt from connection level flow control. |
| DisableConnectionFlowControlForThisStream(); |
| } |
| |
| QuicHeadersStream::~QuicHeadersStream() {} |
| |
| size_t QuicHeadersStream::WriteHeaders(QuicStreamId stream_id, |
| SpdyHeaderBlock headers, |
| bool fin, |
| SpdyPriority priority, |
| QuicAckListenerInterface* ack_listener) { |
| SpdyHeadersIR headers_frame(stream_id, std::move(headers)); |
| headers_frame.set_fin(fin); |
| if (session()->perspective() == Perspective::IS_CLIENT) { |
| headers_frame.set_has_priority(true); |
| headers_frame.set_weight(Spdy3PriorityToHttp2Weight(priority)); |
| } |
| SpdySerializedFrame frame(spdy_framer_.SerializeFrame(headers_frame)); |
| WriteOrBufferData(StringPiece(frame.data(), frame.size()), false, |
| ack_listener); |
| return frame.size(); |
| } |
| |
| size_t QuicHeadersStream::WritePushPromise( |
| QuicStreamId original_stream_id, |
| QuicStreamId promised_stream_id, |
| SpdyHeaderBlock headers, |
| QuicAckListenerInterface* ack_listener) { |
| if (session()->perspective() == Perspective::IS_CLIENT) { |
| QUIC_BUG << "Client shouldn't send PUSH_PROMISE"; |
| return 0; |
| } |
| |
| SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id, |
| std::move(headers)); |
| |
| // PUSH_PROMISE must not be the last frame sent out, at least followed by |
| // response headers. |
| push_promise.set_fin(false); |
| |
| SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise)); |
| WriteOrBufferData(StringPiece(frame.data(), frame.size()), false, |
| ack_listener); |
| return frame.size(); |
| } |
| |
| void QuicHeadersStream::OnDataAvailable() { |
| char buffer[1024]; |
| struct iovec iov; |
| QuicTime timestamp(QuicTime::Zero()); |
| while (true) { |
| iov.iov_base = buffer; |
| iov.iov_len = arraysize(buffer); |
| if (measure_headers_hol_blocking_time_) { |
| if (!sequencer()->GetReadableRegion(&iov, ×tamp)) { |
| // No more data to read. |
| break; |
| } |
| DCHECK(timestamp.IsInitialized()); |
| cur_max_timestamp_ = QuicTime::Max(timestamp, cur_max_timestamp_); |
| } else { |
| if (sequencer()->GetReadableRegions(&iov, 1) != 1) { |
| // No more data to read. |
| break; |
| } |
| } |
| if (spdy_framer_.ProcessInput(static_cast<char*>(iov.iov_base), |
| iov.iov_len) != iov.iov_len) { |
| // Error processing data. |
| return; |
| } |
| sequencer()->MarkConsumed(iov.iov_len); |
| } |
| } |
| |
| void QuicHeadersStream::OnHeaders(SpdyStreamId stream_id, |
| bool has_priority, |
| SpdyPriority priority, |
| bool fin) { |
| if (has_priority) { |
| if (session()->perspective() == Perspective::IS_CLIENT) { |
| CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "Server must not send priorities."); |
| return; |
| } |
| spdy_session_->OnStreamHeadersPriority(stream_id, priority); |
| } else { |
| if (session()->perspective() == Perspective::IS_SERVER) { |
| CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "Client must send priorities."); |
| return; |
| } |
| } |
| DCHECK_EQ(kInvalidStreamId, stream_id_); |
| DCHECK_EQ(kInvalidStreamId, promised_stream_id_); |
| stream_id_ = stream_id; |
| fin_ = fin; |
| } |
| |
| void QuicHeadersStream::OnPushPromise(SpdyStreamId stream_id, |
| SpdyStreamId promised_stream_id, |
| bool end) { |
| DCHECK_EQ(kInvalidStreamId, stream_id_); |
| DCHECK_EQ(kInvalidStreamId, promised_stream_id_); |
| stream_id_ = stream_id; |
| promised_stream_id_ = promised_stream_id; |
| } |
| |
| void QuicHeadersStream::OnControlFrameHeaderData(SpdyStreamId stream_id, |
| const char* header_data, |
| size_t len) { |
| DCHECK_EQ(stream_id_, stream_id); |
| if (len == 0) { |
| DCHECK_NE(0u, stream_id_); |
| DCHECK_NE(0u, frame_len_); |
| if (measure_headers_hol_blocking_time_) { |
| if (prev_max_timestamp_ > cur_max_timestamp_) { |
| // prev_max_timestamp_ > cur_max_timestamp_ implies that |
| // headers from lower numbered streams actually came off the |
| // wire after headers for the current stream, hence there was |
| // HOL blocking. |
| QuicTime::Delta delta(prev_max_timestamp_.Subtract(cur_max_timestamp_)); |
| DVLOG(1) << "stream " << stream_id |
| << ": Net.QuicSession.HeadersHOLBlockedTime " |
| << delta.ToMilliseconds(); |
| spdy_session_->OnHeadersHeadOfLineBlocking(delta); |
| } |
| prev_max_timestamp_ = std::max(prev_max_timestamp_, cur_max_timestamp_); |
| cur_max_timestamp_ = QuicTime::Zero(); |
| } |
| if (promised_stream_id_ == kInvalidStreamId) { |
| spdy_session_->OnStreamHeadersComplete(stream_id_, fin_, frame_len_); |
| } else { |
| spdy_session_->OnPromiseHeadersComplete(stream_id_, promised_stream_id_, |
| frame_len_); |
| } |
| if (uncompressed_frame_len_ != 0) { |
| int compression_pct = 100 - (100 * frame_len_) / uncompressed_frame_len_; |
| DVLOG(1) << "Net.QuicHpackDecompressionPercentage: " << compression_pct; |
| UMA_HISTOGRAM_PERCENTAGE("Net.QuicHpackDecompressionPercentage", |
| compression_pct); |
| } |
| // Reset state for the next frame. |
| promised_stream_id_ = kInvalidStreamId; |
| stream_id_ = kInvalidStreamId; |
| fin_ = false; |
| frame_len_ = 0; |
| uncompressed_frame_len_ = 0; |
| } else { |
| uncompressed_frame_len_ += len; |
| if (promised_stream_id_ == kInvalidStreamId) { |
| spdy_session_->OnStreamHeaders(stream_id_, StringPiece(header_data, len)); |
| } else { |
| spdy_session_->OnPromiseHeaders(stream_id_, |
| StringPiece(header_data, len)); |
| } |
| } |
| } |
| |
| void QuicHeadersStream::OnHeaderList(const QuicHeaderList& header_list) { |
| DVLOG(1) << "Received header list for stream " << stream_id_ << ": " |
| << header_list.DebugString(); |
| if (measure_headers_hol_blocking_time_) { |
| if (prev_max_timestamp_ > cur_max_timestamp_) { |
| // prev_max_timestamp_ > cur_max_timestamp_ implies that |
| // headers from lower numbered streams actually came off the |
| // wire after headers for the current stream, hence there was |
| // HOL blocking. |
| QuicTime::Delta delta = prev_max_timestamp_.Subtract(cur_max_timestamp_); |
| DVLOG(1) << "stream " << stream_id_ |
| << ": Net.QuicSession.HeadersHOLBlockedTime " |
| << delta.ToMilliseconds(); |
| spdy_session_->OnHeadersHeadOfLineBlocking(delta); |
| } |
| prev_max_timestamp_ = std::max(prev_max_timestamp_, cur_max_timestamp_); |
| cur_max_timestamp_ = QuicTime::Zero(); |
| } |
| if (promised_stream_id_ == kInvalidStreamId) { |
| spdy_session_->OnStreamHeaderList(stream_id_, fin_, frame_len_, |
| header_list); |
| } else { |
| spdy_session_->OnPromiseHeaderList(stream_id_, promised_stream_id_, |
| frame_len_, header_list); |
| } |
| // Reset state for the next frame. |
| promised_stream_id_ = kInvalidStreamId; |
| stream_id_ = kInvalidStreamId; |
| fin_ = false; |
| frame_len_ = 0; |
| uncompressed_frame_len_ = 0; |
| } |
| |
| void QuicHeadersStream::OnCompressedFrameSize(size_t frame_len) { |
| frame_len_ += frame_len; |
| } |
| |
| bool QuicHeadersStream::IsConnected() { |
| return session()->connection()->connected(); |
| } |
| |
| void QuicHeadersStream::DisableHpackDynamicTable() { |
| spdy_framer_.UpdateHeaderEncoderTableSize(0); |
| } |
| |
| void QuicHeadersStream::SetHpackEncoderDebugVisitor( |
| std::unique_ptr<HpackDebugVisitor> visitor) { |
| spdy_framer_.SetEncoderHeaderTableDebugVisitor( |
| std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor( |
| session()->connection()->helper()->GetClock(), std::move(visitor)))); |
| } |
| |
| void QuicHeadersStream::SetHpackDecoderDebugVisitor( |
| std::unique_ptr<HpackDebugVisitor> visitor) { |
| spdy_framer_.SetDecoderHeaderTableDebugVisitor( |
| std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor( |
| session()->connection()->helper()->GetClock(), std::move(visitor)))); |
| } |
| |
| void QuicHeadersStream::UpdateHeaderEncoderTableSize(uint32_t value) { |
| spdy_framer_.UpdateHeaderEncoderTableSize(value); |
| } |
| |
| } // namespace net |