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