| // Copyright 2016 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/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h" |
| |
| #include <stdlib.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| |
| #include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" |
| #include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" |
| #include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h" |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h" |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" |
| |
| using ::testing::AssertionFailure; |
| using ::testing::AssertionResult; |
| using ::testing::AssertionSuccess; |
| |
| namespace spdy { |
| namespace test { |
| |
| // Specify whether to process headers as request or response in visitor-related |
| // params. |
| enum class HeaderDirection { REQUEST, RESPONSE }; |
| |
| // Types of HTTP/2 frames, per RFC 7540. |
| // TODO(jamessynge): Switch to using http2/http2_constants.h when ready. |
| enum Http2FrameType { |
| DATA = 0, |
| HEADERS = 1, |
| PRIORITY = 2, |
| RST_STREAM = 3, |
| SETTINGS = 4, |
| PUSH_PROMISE = 5, |
| PING = 6, |
| GOAWAY = 7, |
| WINDOW_UPDATE = 8, |
| CONTINUATION = 9, |
| ALTSVC = 10, |
| |
| // Not a frame type. |
| UNSET = -1, |
| UNKNOWN = -2, |
| }; |
| |
| // TODO(jamessynge): Switch to using http2/http2_constants.h when ready. |
| const char* Http2FrameTypeToString(Http2FrameType v) { |
| switch (v) { |
| case DATA: |
| return "DATA"; |
| case HEADERS: |
| return "HEADERS"; |
| case PRIORITY: |
| return "PRIORITY"; |
| case RST_STREAM: |
| return "RST_STREAM"; |
| case SETTINGS: |
| return "SETTINGS"; |
| case PUSH_PROMISE: |
| return "PUSH_PROMISE"; |
| case PING: |
| return "PING"; |
| case GOAWAY: |
| return "GOAWAY"; |
| case WINDOW_UPDATE: |
| return "WINDOW_UPDATE"; |
| case CONTINUATION: |
| return "CONTINUATION"; |
| case ALTSVC: |
| return "ALTSVC"; |
| case UNSET: |
| return "UNSET"; |
| case UNKNOWN: |
| return "UNKNOWN"; |
| default: |
| return "Invalid Http2FrameType"; |
| } |
| } |
| |
| // TODO(jamessynge): Switch to using http2/http2_constants.h when ready. |
| inline std::ostream& operator<<(std::ostream& out, Http2FrameType v) { |
| return out << Http2FrameTypeToString(v); |
| } |
| |
| // Flag bits in the flag field of the common header of HTTP/2 frames |
| // (see https://httpwg.github.io/specs/rfc7540.html#FrameHeader for details on |
| // the fixed 9-octet header structure shared by all frames). |
| // Flag bits are only valid for specified frame types. |
| // TODO(jamessynge): Switch to using http2/http2_constants.h when ready. |
| enum Http2HeaderFlag { |
| NO_FLAGS = 0, |
| |
| END_STREAM_FLAG = 0x1, |
| ACK_FLAG = 0x1, |
| END_HEADERS_FLAG = 0x4, |
| PADDED_FLAG = 0x8, |
| PRIORITY_FLAG = 0x20, |
| }; |
| |
| // Returns name of frame type. |
| // TODO(jamessynge): Switch to using http2/http2_constants.h when ready. |
| const char* Http2FrameTypeToString(Http2FrameType v); |
| |
| void SpdyDeframerVisitorInterface::OnPingAck( |
| std::unique_ptr<SpdyPingIR> frame) { |
| OnPing(std::move(frame)); |
| } |
| |
| void SpdyDeframerVisitorInterface::OnSettingsAck( |
| std::unique_ptr<SpdySettingsIR> frame) { |
| OnSettings(std::move(frame), nullptr); |
| } |
| |
| class SpdyTestDeframerImpl : public SpdyTestDeframer, |
| public SpdyHeadersHandlerInterface { |
| public: |
| explicit SpdyTestDeframerImpl( |
| std::unique_ptr<SpdyDeframerVisitorInterface> listener) |
| : listener_(std::move(listener)) { |
| CHECK(listener_ != nullptr); |
| } |
| SpdyTestDeframerImpl(const SpdyTestDeframerImpl&) = delete; |
| SpdyTestDeframerImpl& operator=(const SpdyTestDeframerImpl&) = delete; |
| ~SpdyTestDeframerImpl() override = default; |
| |
| bool AtFrameEnd() override; |
| |
| // Callbacks defined in SpdyFramerVisitorInterface. These are in the |
| // alphabetical order for ease of navigation, and are not in same order |
| // as in SpdyFramerVisitorInterface. |
| void OnAltSvc(SpdyStreamId stream_id, |
| SpdyStringPiece origin, |
| const SpdyAltSvcWireFormat::AlternativeServiceVector& |
| altsvc_vector) override; |
| void OnContinuation(SpdyStreamId stream_id, bool end) override; |
| SpdyHeadersHandlerInterface* OnHeaderFrameStart( |
| SpdyStreamId stream_id) override; |
| void OnHeaderFrameEnd(SpdyStreamId stream_id) override; |
| void OnDataFrameHeader(SpdyStreamId stream_id, |
| size_t length, |
| bool fin) override; |
| void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override; |
| void OnGoAway(SpdyStreamId last_accepted_stream_id, |
| SpdyErrorCode error_code) override; |
| bool OnGoAwayFrameData(const char* goaway_data, size_t len) override; |
| void OnHeaders(SpdyStreamId stream_id, |
| bool has_priority, |
| int weight, |
| SpdyStreamId parent_stream_id, |
| bool exclusive, |
| bool fin, |
| bool end) override; |
| void OnPing(SpdyPingId unique_id, bool is_ack) override; |
| void OnPriority(SpdyStreamId stream_id, |
| SpdyStreamId parent_stream_id, |
| int weight, |
| bool exclusive) override; |
| void OnPushPromise(SpdyStreamId stream_id, |
| SpdyStreamId promised_stream_id, |
| bool end) override; |
| void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override; |
| void OnSetting(SpdySettingsId id, uint32_t value) override; |
| void OnSettings() override; |
| void OnSettingsAck() override; |
| void OnSettingsEnd() override; |
| void OnStreamFrameData(SpdyStreamId stream_id, |
| const char* data, |
| size_t len) override; |
| void OnStreamEnd(SpdyStreamId stream_id) override; |
| void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override; |
| void OnStreamPadding(SpdyStreamId stream_id, size_t len) override; |
| bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override; |
| void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override; |
| |
| // Callbacks defined in SpdyHeadersHandlerInterface. |
| |
| void OnHeaderBlockStart() override; |
| void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override; |
| void OnHeaderBlockEnd(size_t header_bytes_parsed, |
| size_t compressed_header_bytes_parsed) override; |
| |
| protected: |
| void AtDataEnd(); |
| void AtGoAwayEnd(); |
| void AtHeadersEnd(); |
| void AtPushPromiseEnd(); |
| |
| // Per-physical frame state. |
| // Frame type of the frame currently being processed. |
| Http2FrameType frame_type_ = UNSET; |
| // Stream id of the frame currently being processed. |
| SpdyStreamId stream_id_; |
| // Did the most recent frame header include the END_HEADERS flag? |
| bool end_ = false; |
| // Did the most recent frame header include the ack flag? |
| bool ack_ = false; |
| |
| // Per-HPACK block state. Only valid while processing a HEADERS or |
| // PUSH_PROMISE frame, and its CONTINUATION frames. |
| // Did the most recent HEADERS or PUSH_PROMISE include the END_STREAM flag? |
| // Note that this does not necessarily indicate that the current frame is |
| // the last frame for the stream (may be followed by CONTINUATION frames, |
| // may only half close). |
| bool fin_ = false; |
| bool got_hpack_end_ = false; |
| |
| std::unique_ptr<SpdyString> data_; |
| |
| // Total length of the data frame. |
| size_t data_len_ = 0; |
| |
| // Amount of skipped padding (i.e. total length of padding, including Pad |
| // Length field). |
| size_t padding_len_ = 0; |
| |
| std::unique_ptr<SpdyString> goaway_description_; |
| std::unique_ptr<StringPairVector> headers_; |
| std::unique_ptr<SettingVector> settings_; |
| std::unique_ptr<TestHeadersHandler> headers_handler_; |
| |
| std::unique_ptr<SpdyGoAwayIR> goaway_ir_; |
| std::unique_ptr<SpdyHeadersIR> headers_ir_; |
| std::unique_ptr<SpdyPushPromiseIR> push_promise_ir_; |
| std::unique_ptr<SpdySettingsIR> settings_ir_; |
| |
| private: |
| std::unique_ptr<SpdyDeframerVisitorInterface> listener_; |
| }; |
| |
| // static |
| std::unique_ptr<SpdyTestDeframer> SpdyTestDeframer::CreateConverter( |
| std::unique_ptr<SpdyDeframerVisitorInterface> listener) { |
| return SpdyMakeUnique<SpdyTestDeframerImpl>(std::move(listener)); |
| } |
| |
| void SpdyTestDeframerImpl::AtDataEnd() { |
| SPDY_DVLOG(1) << "AtDataEnd"; |
| CHECK_EQ(data_len_, padding_len_ + data_->size()); |
| auto ptr = SpdyMakeUnique<SpdyDataIR>(stream_id_, std::move(*data_)); |
| CHECK_EQ(0u, data_->size()); |
| data_.reset(); |
| |
| CHECK_LE(0u, padding_len_); |
| CHECK_LE(padding_len_, 256u); |
| if (padding_len_ > 0) { |
| ptr->set_padding_len(padding_len_); |
| } |
| padding_len_ = 0; |
| |
| ptr->set_fin(fin_); |
| listener_->OnData(std::move(ptr)); |
| frame_type_ = UNSET; |
| fin_ = false; |
| data_len_ = 0; |
| } |
| |
| void SpdyTestDeframerImpl::AtGoAwayEnd() { |
| SPDY_DVLOG(1) << "AtDataEnd"; |
| CHECK_EQ(frame_type_, GOAWAY); |
| if (HTTP2_DIE_IF_NULL(goaway_description_)->empty()) { |
| listener_->OnGoAway(std::move(goaway_ir_)); |
| } else { |
| listener_->OnGoAway(SpdyMakeUnique<SpdyGoAwayIR>( |
| goaway_ir_->last_good_stream_id(), goaway_ir_->error_code(), |
| std::move(*goaway_description_))); |
| CHECK_EQ(0u, goaway_description_->size()); |
| } |
| goaway_description_.reset(); |
| goaway_ir_.reset(); |
| frame_type_ = UNSET; |
| } |
| |
| void SpdyTestDeframerImpl::AtHeadersEnd() { |
| SPDY_DVLOG(1) << "AtDataEnd"; |
| CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(end_) << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(got_hpack_end_); |
| |
| CHECK(headers_ir_ != nullptr); |
| CHECK(headers_ != nullptr); |
| CHECK(headers_handler_ != nullptr); |
| |
| CHECK_LE(0u, padding_len_); |
| CHECK_LE(padding_len_, 256u); |
| if (padding_len_ > 0) { |
| headers_ir_->set_padding_len(padding_len_); |
| } |
| padding_len_ = 0; |
| |
| headers_ir_->set_header_block(headers_handler_->decoded_block().Clone()); |
| headers_handler_.reset(); |
| listener_->OnHeaders(std::move(headers_ir_), std::move(headers_)); |
| |
| frame_type_ = UNSET; |
| fin_ = false; |
| end_ = false; |
| got_hpack_end_ = false; |
| } |
| |
| void SpdyTestDeframerImpl::AtPushPromiseEnd() { |
| SPDY_DVLOG(1) << "AtDataEnd"; |
| CHECK(frame_type_ == PUSH_PROMISE || frame_type_ == CONTINUATION) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(end_) << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| |
| CHECK(push_promise_ir_ != nullptr); |
| CHECK(headers_ != nullptr); |
| CHECK(headers_handler_ != nullptr); |
| |
| CHECK_EQ(headers_ir_.get(), nullptr); |
| |
| CHECK_LE(0u, padding_len_); |
| CHECK_LE(padding_len_, 256u); |
| if (padding_len_ > 0) { |
| push_promise_ir_->set_padding_len(padding_len_); |
| } |
| padding_len_ = 0; |
| |
| push_promise_ir_->set_header_block(headers_handler_->decoded_block().Clone()); |
| headers_handler_.reset(); |
| listener_->OnPushPromise(std::move(push_promise_ir_), std::move(headers_)); |
| |
| frame_type_ = UNSET; |
| end_ = false; |
| } |
| |
| bool SpdyTestDeframerImpl::AtFrameEnd() { |
| bool incomplete_logical_header = false; |
| // The caller says that the SpdyFrame has reached the end of the frame, |
| // so if we have any accumulated data, flush it. |
| switch (frame_type_) { |
| case DATA: |
| AtDataEnd(); |
| break; |
| |
| case GOAWAY: |
| AtGoAwayEnd(); |
| break; |
| |
| case HEADERS: |
| if (end_) { |
| AtHeadersEnd(); |
| } else { |
| incomplete_logical_header = true; |
| } |
| break; |
| |
| case PUSH_PROMISE: |
| if (end_) { |
| AtPushPromiseEnd(); |
| } else { |
| incomplete_logical_header = true; |
| } |
| break; |
| |
| case CONTINUATION: |
| if (end_) { |
| if (headers_ir_) { |
| AtHeadersEnd(); |
| } else if (push_promise_ir_) { |
| AtPushPromiseEnd(); |
| } else { |
| SPDY_LOG(FATAL) << "Where is the SpdyFrameIR for the headers!"; |
| } |
| } else { |
| incomplete_logical_header = true; |
| } |
| break; |
| |
| case UNSET: |
| // Except for the frame types above, the others don't leave any record |
| // in the state of this object. Make sure nothing got left by accident. |
| CHECK_EQ(data_.get(), nullptr); |
| CHECK_EQ(goaway_description_.get(), nullptr); |
| CHECK_EQ(goaway_ir_.get(), nullptr); |
| CHECK_EQ(headers_.get(), nullptr); |
| CHECK_EQ(headers_handler_.get(), nullptr); |
| CHECK_EQ(headers_ir_.get(), nullptr); |
| CHECK_EQ(push_promise_ir_.get(), nullptr); |
| CHECK_EQ(settings_.get(), nullptr); |
| CHECK_EQ(settings_ir_.get(), nullptr); |
| break; |
| |
| default: |
| SPDY_BUG << "Expected UNSET, instead frame_type_==" << frame_type_; |
| return false; |
| } |
| frame_type_ = UNSET; |
| stream_id_ = 0; |
| end_ = false; |
| ack_ = false; |
| if (!incomplete_logical_header) { |
| fin_ = false; |
| } |
| return true; |
| } |
| |
| // Overridden methods from SpdyFramerVisitorInterface in alpha order... |
| |
| void SpdyTestDeframerImpl::OnAltSvc( |
| SpdyStreamId stream_id, |
| SpdyStringPiece origin, |
| const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector) { |
| SPDY_DVLOG(1) << "OnAltSvc stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| auto ptr = SpdyMakeUnique<SpdyAltSvcIR>(stream_id); |
| ptr->set_origin(SpdyString(origin)); |
| for (auto& altsvc : altsvc_vector) { |
| ptr->add_altsvc(altsvc); |
| } |
| listener_->OnAltSvc(std::move(ptr)); |
| } |
| |
| // A CONTINUATION frame contains a Header Block Fragment, and immediately |
| // follows another frame that contains a Header Block Fragment (HEADERS, |
| // PUSH_PROMISE or CONTINUATION). The last such frame has the END flag set. |
| // SpdyFramer ensures that the behavior is correct before calling the visitor. |
| void SpdyTestDeframerImpl::OnContinuation(SpdyStreamId stream_id, bool end) { |
| SPDY_DVLOG(1) << "OnContinuation stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| CHECK_NE(nullptr, headers_.get()); |
| frame_type_ = CONTINUATION; |
| |
| stream_id_ = stream_id; |
| end_ = end; |
| } |
| |
| // Note that length includes the padding length (0 to 256, when the optional |
| // padding length field is counted). Padding comes after the payload, both |
| // for DATA frames and for control frames. |
| void SpdyTestDeframerImpl::OnDataFrameHeader(SpdyStreamId stream_id, |
| size_t length, |
| bool fin) { |
| SPDY_DVLOG(1) << "OnDataFrameHeader stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| CHECK_EQ(data_.get(), nullptr); |
| frame_type_ = DATA; |
| |
| stream_id_ = stream_id; |
| fin_ = fin; |
| data_len_ = length; |
| data_ = SpdyMakeUnique<SpdyString>(); |
| } |
| |
| // The SpdyFramer will not process any more data at this point. |
| void SpdyTestDeframerImpl::OnError( |
| http2::Http2DecoderAdapter::SpdyFramerError error) { |
| SPDY_DVLOG(1) << "SpdyFramer detected an error in the stream: " |
| << http2::Http2DecoderAdapter::SpdyFramerErrorToString(error) |
| << " frame_type_: " << Http2FrameTypeToString(frame_type_); |
| listener_->OnError(error, this); |
| } |
| |
| // Received a GOAWAY frame from the peer. The last stream id it accepted from us |
| // is |last_accepted_stream_id|. |status| is a protocol defined error code. |
| // The frame may also contain data. After this OnGoAwayFrameData will be called |
| // for any non-zero amount of data, and after that it will be called with len==0 |
| // to indicate the end of the GOAWAY frame. |
| void SpdyTestDeframerImpl::OnGoAway(SpdyStreamId last_good_stream_id, |
| SpdyErrorCode error_code) { |
| SPDY_DVLOG(1) << "OnGoAway last_good_stream_id: " << last_good_stream_id |
| << " error code: " << error_code; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| frame_type_ = GOAWAY; |
| goaway_ir_ = |
| SpdyMakeUnique<SpdyGoAwayIR>(last_good_stream_id, error_code, ""); |
| goaway_description_ = SpdyMakeUnique<SpdyString>(); |
| } |
| |
| // If len==0 then we've reached the end of the GOAWAY frame. |
| bool SpdyTestDeframerImpl::OnGoAwayFrameData(const char* goaway_data, |
| size_t len) { |
| SPDY_DVLOG(1) << "OnGoAwayFrameData"; |
| CHECK_EQ(frame_type_, GOAWAY) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(goaway_description_ != nullptr); |
| goaway_description_->append(goaway_data, len); |
| return true; |
| } |
| |
| SpdyHeadersHandlerInterface* SpdyTestDeframerImpl::OnHeaderFrameStart( |
| SpdyStreamId /*stream_id*/) { |
| return this; |
| } |
| |
| void SpdyTestDeframerImpl::OnHeaderFrameEnd(SpdyStreamId stream_id) { |
| SPDY_DVLOG(1) << "OnHeaderFrameEnd stream_id: " << stream_id; |
| } |
| |
| // Received the fixed portion of a HEADERS frame. Called before the variable |
| // length (including zero length) Header Block Fragment is processed. If fin |
| // is true then there will be no DATA or trailing HEADERS after this HEADERS |
| // frame. |
| // If end is true, then there will be no CONTINUATION frame(s) following this |
| // frame; else if true then there will be CONTINATION frames(s) immediately |
| // following this frame, terminated by a CONTINUATION frame with end==true. |
| void SpdyTestDeframerImpl::OnHeaders(SpdyStreamId stream_id, |
| bool has_priority, |
| int weight, |
| SpdyStreamId parent_stream_id, |
| bool exclusive, |
| bool fin, |
| bool end) { |
| SPDY_DVLOG(1) << "OnHeaders stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| frame_type_ = HEADERS; |
| |
| stream_id_ = stream_id; |
| fin_ = fin; |
| end_ = end; |
| |
| headers_ = SpdyMakeUnique<StringPairVector>(); |
| headers_handler_ = SpdyMakeUnique<TestHeadersHandler>(); |
| headers_ir_ = SpdyMakeUnique<SpdyHeadersIR>(stream_id); |
| headers_ir_->set_fin(fin); |
| if (has_priority) { |
| headers_ir_->set_has_priority(true); |
| headers_ir_->set_weight(weight); |
| headers_ir_->set_parent_stream_id(parent_stream_id); |
| headers_ir_->set_exclusive(exclusive); |
| } |
| } |
| |
| // The HTTP/2 protocol refers to the payload, |unique_id| here, as 8 octets of |
| // opaque data that is to be echoed back to the sender, with the ACK bit added. |
| // It isn't defined as a counter, |
| // or frame id, as the SpdyPingId naming might imply. |
| // Responding to a PING is supposed to be at the highest priority. |
| void SpdyTestDeframerImpl::OnPing(uint64_t unique_id, bool is_ack) { |
| SPDY_DVLOG(1) << "OnPing unique_id: " << unique_id |
| << " is_ack: " << (is_ack ? "true" : "false"); |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| auto ptr = SpdyMakeUnique<SpdyPingIR>(unique_id); |
| if (is_ack) { |
| ptr->set_is_ack(is_ack); |
| listener_->OnPingAck(std::move(ptr)); |
| } else { |
| listener_->OnPing(std::move(ptr)); |
| } |
| } |
| |
| void SpdyTestDeframerImpl::OnPriority(SpdyStreamId stream_id, |
| SpdyStreamId parent_stream_id, |
| int weight, |
| bool exclusive) { |
| SPDY_DVLOG(1) << "OnPriority stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| |
| listener_->OnPriority(SpdyMakeUnique<SpdyPriorityIR>( |
| stream_id, parent_stream_id, weight, exclusive)); |
| } |
| |
| void SpdyTestDeframerImpl::OnPushPromise(SpdyStreamId stream_id, |
| SpdyStreamId promised_stream_id, |
| bool end) { |
| SPDY_DVLOG(1) << "OnPushPromise stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| |
| frame_type_ = PUSH_PROMISE; |
| stream_id_ = stream_id; |
| end_ = end; |
| |
| headers_ = SpdyMakeUnique<StringPairVector>(); |
| headers_handler_ = SpdyMakeUnique<TestHeadersHandler>(); |
| push_promise_ir_ = |
| SpdyMakeUnique<SpdyPushPromiseIR>(stream_id, promised_stream_id); |
| } |
| |
| // Closes the specified stream. After this the sender may still send PRIORITY |
| // frames for this stream, which we can ignore. |
| void SpdyTestDeframerImpl::OnRstStream(SpdyStreamId stream_id, |
| SpdyErrorCode error_code) { |
| SPDY_DVLOG(1) << "OnRstStream stream_id: " << stream_id |
| << " error code: " << error_code; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_GT(stream_id, 0u); |
| |
| listener_->OnRstStream( |
| SpdyMakeUnique<SpdyRstStreamIR>(stream_id, error_code)); |
| } |
| |
| // Called for an individual setting. There is no negotiation; the sender is |
| // stating the value that the sender is using. |
| void SpdyTestDeframerImpl::OnSetting(SpdySettingsId id, uint32_t value) { |
| SPDY_DVLOG(1) << "OnSetting id: " << id << std::hex << " value: " << value; |
| CHECK_EQ(frame_type_, SETTINGS) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(settings_ != nullptr); |
| SpdyKnownSettingsId known_id; |
| if (ParseSettingsId(id, &known_id)) { |
| settings_->push_back(std::make_pair(known_id, value)); |
| settings_ir_->AddSetting(known_id, value); |
| } |
| } |
| |
| // Called at the start of a SETTINGS frame with setting entries, but not the |
| // (required) ACK of a SETTINGS frame. There is no stream_id because |
| // the settings apply to the entire connection, not to an individual stream. |
| void SpdyTestDeframerImpl::OnSettings() { |
| SPDY_DVLOG(1) << "OnSettings"; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_EQ(nullptr, settings_ir_.get()); |
| CHECK_EQ(nullptr, settings_.get()); |
| frame_type_ = SETTINGS; |
| ack_ = false; |
| |
| settings_ = SpdyMakeUnique<SettingVector>(); |
| settings_ir_ = SpdyMakeUnique<SpdySettingsIR>(); |
| } |
| |
| void SpdyTestDeframerImpl::OnSettingsAck() { |
| SPDY_DVLOG(1) << "OnSettingsAck"; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| auto ptr = SpdyMakeUnique<SpdySettingsIR>(); |
| ptr->set_is_ack(true); |
| listener_->OnSettingsAck(std::move(ptr)); |
| } |
| |
| void SpdyTestDeframerImpl::OnSettingsEnd() { |
| SPDY_DVLOG(1) << "OnSettingsEnd"; |
| CHECK_EQ(frame_type_, SETTINGS) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(!ack_); |
| CHECK_NE(nullptr, settings_ir_.get()); |
| CHECK_NE(nullptr, settings_.get()); |
| listener_->OnSettings(std::move(settings_ir_), std::move(settings_)); |
| frame_type_ = UNSET; |
| } |
| |
| // Called for a zero length DATA frame with the END_STREAM flag set, or at the |
| // end a complete HPACK block (and its padding) that started with a HEADERS |
| // frame with the END_STREAM flag set. Doesn't apply to PUSH_PROMISE frames |
| // because they don't have END_STREAM flags. |
| void SpdyTestDeframerImpl::OnStreamEnd(SpdyStreamId stream_id) { |
| SPDY_DVLOG(1) << "OnStreamEnd stream_id: " << stream_id; |
| CHECK_EQ(stream_id_, stream_id); |
| CHECK(frame_type_ == DATA || frame_type_ == HEADERS || |
| frame_type_ == CONTINUATION) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(fin_); |
| } |
| |
| // The data arg points into the non-padding payload of a DATA frame. |
| // This must be a DATA frame (i.e. this method will not be |
| // called for HEADERS or CONTINUATION frames). |
| // This method may be called multiple times for a single DATA frame, depending |
| // upon buffer boundaries. |
| void SpdyTestDeframerImpl::OnStreamFrameData(SpdyStreamId stream_id, |
| const char* data, |
| size_t len) { |
| SPDY_DVLOG(1) << "OnStreamFrameData stream_id: " << stream_id |
| << " len: " << len; |
| CHECK_EQ(stream_id_, stream_id); |
| CHECK_EQ(frame_type_, DATA); |
| data_->append(data, len); |
| } |
| |
| // Called when receiving the padding length field at the start of the DATA frame |
| // payload. value will be in the range 0 to 255. |
| void SpdyTestDeframerImpl::OnStreamPadLength(SpdyStreamId stream_id, |
| size_t value) { |
| SPDY_DVLOG(1) << "OnStreamPadding stream_id: " << stream_id |
| << " value: " << value; |
| CHECK(frame_type_ == DATA || frame_type_ == HEADERS || |
| frame_type_ == PUSH_PROMISE) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_EQ(stream_id_, stream_id); |
| CHECK_GE(255u, value); |
| // Count the padding length byte against total padding. |
| padding_len_ += 1; |
| CHECK_EQ(1u, padding_len_); |
| } |
| |
| // Called when padding is skipped over at the end of the DATA frame. len will |
| // be in the range 1 to 255. |
| void SpdyTestDeframerImpl::OnStreamPadding(SpdyStreamId stream_id, size_t len) { |
| SPDY_DVLOG(1) << "OnStreamPadding stream_id: " << stream_id |
| << " len: " << len; |
| CHECK(frame_type_ == DATA || frame_type_ == HEADERS || |
| frame_type_ == PUSH_PROMISE) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_EQ(stream_id_, stream_id); |
| CHECK_LE(1u, len); |
| CHECK_GE(255u, len); |
| padding_len_ += len; |
| CHECK_LE(padding_len_, 256u) << "len=" << len; |
| } |
| |
| // WINDOW_UPDATE is supposed to be hop-by-hop, according to the spec. |
| // stream_id is 0 if the update applies to the connection, else stream_id |
| // will be the id of a stream previously seen, which maybe half or fully |
| // closed. |
| void SpdyTestDeframerImpl::OnWindowUpdate(SpdyStreamId stream_id, |
| int delta_window_size) { |
| SPDY_DVLOG(1) << "OnWindowUpdate stream_id: " << stream_id |
| << " delta_window_size: " << delta_window_size; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK_NE(0, delta_window_size); |
| |
| listener_->OnWindowUpdate( |
| SpdyMakeUnique<SpdyWindowUpdateIR>(stream_id, delta_window_size)); |
| } |
| |
| // Return true to indicate that the stream_id is valid; if not valid then |
| // SpdyFramer considers the connection corrupted. Requires keeping track |
| // of the set of currently open streams. For now we'll assume that unknown |
| // frame types are unsupported. |
| bool SpdyTestDeframerImpl::OnUnknownFrame(SpdyStreamId stream_id, |
| uint8_t /*frame_type*/) { |
| SPDY_DVLOG(1) << "OnAltSvc stream_id: " << stream_id; |
| CHECK_EQ(frame_type_, UNSET) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| frame_type_ = UNKNOWN; |
| |
| stream_id_ = stream_id; |
| return false; |
| } |
| |
| // Callbacks defined in SpdyHeadersHandlerInterface. |
| |
| void SpdyTestDeframerImpl::OnHeaderBlockStart() { |
| CHECK(frame_type_ == HEADERS || frame_type_ == PUSH_PROMISE) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(headers_ != nullptr); |
| CHECK_EQ(0u, headers_->size()); |
| got_hpack_end_ = false; |
| } |
| |
| void SpdyTestDeframerImpl::OnHeader(SpdyStringPiece key, |
| SpdyStringPiece value) { |
| CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION || |
| frame_type_ == PUSH_PROMISE) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(!got_hpack_end_); |
| HTTP2_DIE_IF_NULL(headers_)->emplace_back(SpdyString(key), SpdyString(value)); |
| HTTP2_DIE_IF_NULL(headers_handler_)->OnHeader(key, value); |
| } |
| |
| void SpdyTestDeframerImpl::OnHeaderBlockEnd( |
| size_t /* header_bytes_parsed */, |
| size_t /* compressed_header_bytes_parsed */) { |
| CHECK(headers_ != nullptr); |
| CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION || |
| frame_type_ == PUSH_PROMISE) |
| << " frame_type_=" << Http2FrameTypeToString(frame_type_); |
| CHECK(end_); |
| CHECK(!got_hpack_end_); |
| got_hpack_end_ = true; |
| } |
| |
| class LoggingSpdyDeframerDelegate : public SpdyDeframerVisitorInterface { |
| public: |
| explicit LoggingSpdyDeframerDelegate( |
| std::unique_ptr<SpdyDeframerVisitorInterface> wrapped) |
| : wrapped_(std::move(wrapped)) { |
| if (!wrapped_) { |
| wrapped_ = SpdyMakeUnique<SpdyDeframerVisitorInterface>(); |
| } |
| } |
| ~LoggingSpdyDeframerDelegate() override = default; |
| |
| void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnAltSvc"; |
| wrapped_->OnAltSvc(std::move(frame)); |
| } |
| void OnData(std::unique_ptr<SpdyDataIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnData"; |
| wrapped_->OnData(std::move(frame)); |
| } |
| void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnGoAway"; |
| wrapped_->OnGoAway(std::move(frame)); |
| } |
| |
| // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which |
| // significantly modifies the headers, so the actual header entries (name |
| // and value strings) are provided in a vector. |
| void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame, |
| std::unique_ptr<StringPairVector> headers) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnHeaders"; |
| wrapped_->OnHeaders(std::move(frame), std::move(headers)); |
| } |
| |
| void OnPing(std::unique_ptr<SpdyPingIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPing"; |
| wrapped_->OnPing(std::move(frame)); |
| } |
| void OnPingAck(std::unique_ptr<SpdyPingIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPingAck"; |
| wrapped_->OnPingAck(std::move(frame)); |
| } |
| |
| void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPriority"; |
| wrapped_->OnPriority(std::move(frame)); |
| } |
| |
| // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which |
| // significantly modifies the headers, so the actual header entries (name |
| // and value strings) are provided in a vector. |
| void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame, |
| std::unique_ptr<StringPairVector> headers) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPushPromise"; |
| wrapped_->OnPushPromise(std::move(frame), std::move(headers)); |
| } |
| |
| void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnRstStream"; |
| wrapped_->OnRstStream(std::move(frame)); |
| } |
| |
| // SpdySettingsIR has a map for settings, so loses info about the order of |
| // settings, and whether the same setting appeared more than once, so the |
| // the actual settings (parameter and value) are provided in a vector. |
| void OnSettings(std::unique_ptr<SpdySettingsIR> frame, |
| std::unique_ptr<SettingVector> settings) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettings"; |
| wrapped_->OnSettings(std::move(frame), std::move(settings)); |
| } |
| |
| // A settings frame with an ACK has no content, but for uniformity passing |
| // a frame with the ACK flag set. |
| void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettingsAck"; |
| wrapped_->OnSettingsAck(std::move(frame)); |
| } |
| |
| void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnWindowUpdate"; |
| wrapped_->OnWindowUpdate(std::move(frame)); |
| } |
| |
| // The SpdyFramer will not process any more data at this point. |
| void OnError(http2::Http2DecoderAdapter::SpdyFramerError error, |
| SpdyTestDeframer* deframer) override { |
| SPDY_DVLOG(1) << "LoggingSpdyDeframerDelegate::OnError"; |
| wrapped_->OnError(error, deframer); |
| } |
| |
| private: |
| std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_; |
| }; |
| |
| // static |
| std::unique_ptr<SpdyDeframerVisitorInterface> |
| SpdyDeframerVisitorInterface::LogBeforeVisiting( |
| std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_listener) { |
| return SpdyMakeUnique<LoggingSpdyDeframerDelegate>( |
| std::move(wrapped_listener)); |
| } |
| |
| CollectedFrame::CollectedFrame() = default; |
| |
| CollectedFrame::CollectedFrame(CollectedFrame&& other) |
| : frame_ir(std::move(other.frame_ir)), |
| headers(std::move(other.headers)), |
| settings(std::move(other.settings)), |
| error_reported(other.error_reported) {} |
| |
| CollectedFrame::~CollectedFrame() = default; |
| |
| CollectedFrame& CollectedFrame::operator=(CollectedFrame&& other) { |
| frame_ir = std::move(other.frame_ir); |
| headers = std::move(other.headers); |
| settings = std::move(other.settings); |
| error_reported = other.error_reported; |
| return *this; |
| } |
| |
| AssertionResult CollectedFrame::VerifyHasHeaders( |
| const StringPairVector& expected_headers) const { |
| VERIFY_NE(headers.get(), nullptr); |
| VERIFY_THAT(*headers, ::testing::ContainerEq(expected_headers)); |
| return AssertionSuccess(); |
| } |
| |
| AssertionResult CollectedFrame::VerifyHasSettings( |
| const SettingVector& expected_settings) const { |
| VERIFY_NE(settings.get(), nullptr); |
| VERIFY_THAT(*settings, testing::ContainerEq(expected_settings)); |
| return AssertionSuccess(); |
| } |
| |
| DeframerCallbackCollector::DeframerCallbackCollector( |
| std::vector<CollectedFrame>* collected_frames) |
| : collected_frames_(HTTP2_DIE_IF_NULL(collected_frames)) {} |
| |
| void DeframerCallbackCollector::OnAltSvc( |
| std::unique_ptr<SpdyAltSvcIR> frame_ir) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| void DeframerCallbackCollector::OnData(std::unique_ptr<SpdyDataIR> frame_ir) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| void DeframerCallbackCollector::OnGoAway( |
| std::unique_ptr<SpdyGoAwayIR> frame_ir) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which |
| // significantly modifies the headers, so the actual header entries (name |
| // and value strings) are provided in a vector. |
| void DeframerCallbackCollector::OnHeaders( |
| std::unique_ptr<SpdyHeadersIR> frame_ir, |
| std::unique_ptr<StringPairVector> headers) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| cf.headers = std::move(headers); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| void DeframerCallbackCollector::OnPing(std::unique_ptr<SpdyPingIR> frame_ir) { |
| EXPECT_TRUE(frame_ir && !frame_ir->is_ack()); |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| void DeframerCallbackCollector::OnPingAck( |
| std::unique_ptr<SpdyPingIR> frame_ir) { |
| EXPECT_TRUE(frame_ir && frame_ir->is_ack()); |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| void DeframerCallbackCollector::OnPriority( |
| std::unique_ptr<SpdyPriorityIR> frame_ir) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which |
| // significantly modifies the headers, so the actual header entries (name |
| // and value strings) are provided in a vector. |
| void DeframerCallbackCollector::OnPushPromise( |
| std::unique_ptr<SpdyPushPromiseIR> frame_ir, |
| std::unique_ptr<StringPairVector> headers) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| cf.headers = std::move(headers); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| void DeframerCallbackCollector::OnRstStream( |
| std::unique_ptr<SpdyRstStreamIR> frame_ir) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| // SpdySettingsIR has a map for settings, so loses info about the order of |
| // settings, and whether the same setting appeared more than once, so the |
| // the actual settings (parameter and value) are provided in a vector. |
| void DeframerCallbackCollector::OnSettings( |
| std::unique_ptr<SpdySettingsIR> frame_ir, |
| std::unique_ptr<SettingVector> settings) { |
| EXPECT_TRUE(frame_ir && !frame_ir->is_ack()); |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| cf.settings = std::move(settings); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| // A settings frame_ir with an ACK has no content, but for uniformity passing |
| // a frame_ir with the ACK flag set. |
| void DeframerCallbackCollector::OnSettingsAck( |
| std::unique_ptr<SpdySettingsIR> frame_ir) { |
| EXPECT_TRUE(frame_ir && frame_ir->is_ack()); |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| void DeframerCallbackCollector::OnWindowUpdate( |
| std::unique_ptr<SpdyWindowUpdateIR> frame_ir) { |
| CollectedFrame cf; |
| cf.frame_ir = std::move(frame_ir); |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| // The SpdyFramer will not process any more data at this point. |
| void DeframerCallbackCollector::OnError( |
| http2::Http2DecoderAdapter::SpdyFramerError /*error*/, |
| SpdyTestDeframer* /*deframer*/) { |
| CollectedFrame cf; |
| cf.error_reported = true; |
| collected_frames_->push_back(std::move(cf)); |
| } |
| |
| } // namespace test |
| } // namespace spdy |