| // Copyright (c) 2012 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 "spdy/core/spdy_framer.h" |
| |
| #include <stdlib.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <limits> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/macros.h" |
| #include "common/platform/api/quiche_logging.h" |
| #include "common/platform/api/quiche_test.h" |
| #include "common/quiche_text_utils.h" |
| #include "spdy/core/array_output_buffer.h" |
| #include "spdy/core/mock_spdy_framer_visitor.h" |
| #include "spdy/core/recording_headers_handler.h" |
| #include "spdy/core/spdy_bitmasks.h" |
| #include "spdy/core/spdy_frame_builder.h" |
| #include "spdy/core/spdy_frame_reader.h" |
| #include "spdy/core/spdy_protocol.h" |
| #include "spdy/core/spdy_test_utils.h" |
| |
| using ::http2::Http2DecoderAdapter; |
| using ::testing::_; |
| |
| namespace spdy { |
| |
| namespace test { |
| |
| namespace { |
| |
| const int64_t kSize = 1024 * 1024; |
| char output_buffer[kSize] = ""; |
| |
| // frame_list_char is used to hold frames to be compared with output_buffer. |
| const int64_t buffer_size = 64 * 1024; |
| char frame_list_char[buffer_size] = ""; |
| } // namespace |
| |
| class MockDebugVisitor : public SpdyFramerDebugVisitorInterface { |
| public: |
| MOCK_METHOD4(OnSendCompressedFrame, |
| void(SpdyStreamId stream_id, |
| SpdyFrameType type, |
| size_t payload_len, |
| size_t frame_len)); |
| |
| MOCK_METHOD3(OnReceiveCompressedFrame, |
| void(SpdyStreamId stream_id, |
| SpdyFrameType type, |
| size_t frame_len)); |
| }; |
| |
| MATCHER_P(IsFrameUnionOf, frame_list, "") { |
| size_t size_verified = 0; |
| for (const auto& frame : *frame_list) { |
| if (arg.size() < size_verified + frame.size()) { |
| QUICHE_LOG(FATAL) |
| << "Incremental header serialization should not lead to a " |
| << "higher total frame length than non-incremental method."; |
| return false; |
| } |
| if (memcmp(arg.data() + size_verified, frame.data(), frame.size())) { |
| CompareCharArraysWithHexError( |
| "Header serialization methods should be equivalent: ", |
| reinterpret_cast<unsigned char*>(arg.data() + size_verified), |
| frame.size(), reinterpret_cast<unsigned char*>(frame.data()), |
| frame.size()); |
| return false; |
| } |
| size_verified += frame.size(); |
| } |
| return size_verified == arg.size(); |
| } |
| |
| class SpdyFramerPeer { |
| public: |
| // TODO(dahollings): Remove these methods when deprecating non-incremental |
| // header serialization path. |
| static std::unique_ptr<SpdyHeadersIR> CloneSpdyHeadersIR( |
| const SpdyHeadersIR& headers) { |
| auto new_headers = std::make_unique<SpdyHeadersIR>( |
| headers.stream_id(), headers.header_block().Clone()); |
| new_headers->set_fin(headers.fin()); |
| new_headers->set_has_priority(headers.has_priority()); |
| new_headers->set_weight(headers.weight()); |
| new_headers->set_parent_stream_id(headers.parent_stream_id()); |
| new_headers->set_exclusive(headers.exclusive()); |
| if (headers.padded()) { |
| new_headers->set_padding_len(headers.padding_payload_len() + 1); |
| } |
| return new_headers; |
| } |
| |
| static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer, |
| const SpdyHeadersIR& headers) { |
| SpdySerializedFrame serialized_headers_old_version( |
| framer->SerializeHeaders(headers)); |
| framer->hpack_encoder_.reset(nullptr); |
| auto* saved_debug_visitor = framer->debug_visitor_; |
| framer->debug_visitor_ = nullptr; |
| |
| std::vector<SpdySerializedFrame> frame_list; |
| ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); |
| SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers)); |
| while (it.HasNextFrame()) { |
| size_t size_before = frame_list_buffer.Size(); |
| EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); |
| frame_list.emplace_back( |
| SpdySerializedFrame(frame_list_buffer.Begin() + size_before, |
| frame_list_buffer.Size() - size_before, false)); |
| } |
| framer->debug_visitor_ = saved_debug_visitor; |
| |
| EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); |
| return serialized_headers_old_version; |
| } |
| |
| static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer, |
| const SpdyHeadersIR& headers, |
| ArrayOutputBuffer* output) { |
| if (output == nullptr) { |
| return SerializeHeaders(framer, headers); |
| } |
| output->Reset(); |
| EXPECT_TRUE(framer->SerializeHeaders(headers, output)); |
| SpdySerializedFrame serialized_headers_old_version(output->Begin(), |
| output->Size(), false); |
| framer->hpack_encoder_.reset(nullptr); |
| auto* saved_debug_visitor = framer->debug_visitor_; |
| framer->debug_visitor_ = nullptr; |
| |
| std::vector<SpdySerializedFrame> frame_list; |
| ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); |
| SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers)); |
| while (it.HasNextFrame()) { |
| size_t size_before = frame_list_buffer.Size(); |
| EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); |
| frame_list.emplace_back( |
| SpdySerializedFrame(frame_list_buffer.Begin() + size_before, |
| frame_list_buffer.Size() - size_before, false)); |
| } |
| framer->debug_visitor_ = saved_debug_visitor; |
| |
| EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); |
| return serialized_headers_old_version; |
| } |
| |
| static std::unique_ptr<SpdyPushPromiseIR> CloneSpdyPushPromiseIR( |
| const SpdyPushPromiseIR& push_promise) { |
| auto new_push_promise = std::make_unique<SpdyPushPromiseIR>( |
| push_promise.stream_id(), push_promise.promised_stream_id(), |
| push_promise.header_block().Clone()); |
| new_push_promise->set_fin(push_promise.fin()); |
| if (push_promise.padded()) { |
| new_push_promise->set_padding_len(push_promise.padding_payload_len() + 1); |
| } |
| return new_push_promise; |
| } |
| |
| static SpdySerializedFrame SerializePushPromise( |
| SpdyFramer* framer, |
| const SpdyPushPromiseIR& push_promise) { |
| SpdySerializedFrame serialized_headers_old_version = |
| framer->SerializePushPromise(push_promise); |
| framer->hpack_encoder_.reset(nullptr); |
| auto* saved_debug_visitor = framer->debug_visitor_; |
| framer->debug_visitor_ = nullptr; |
| |
| std::vector<SpdySerializedFrame> frame_list; |
| ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); |
| frame_list_buffer.Reset(); |
| SpdyFramer::SpdyPushPromiseFrameIterator it( |
| framer, CloneSpdyPushPromiseIR(push_promise)); |
| while (it.HasNextFrame()) { |
| size_t size_before = frame_list_buffer.Size(); |
| EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); |
| frame_list.emplace_back( |
| SpdySerializedFrame(frame_list_buffer.Begin() + size_before, |
| frame_list_buffer.Size() - size_before, false)); |
| } |
| framer->debug_visitor_ = saved_debug_visitor; |
| |
| EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); |
| return serialized_headers_old_version; |
| } |
| |
| static SpdySerializedFrame SerializePushPromise( |
| SpdyFramer* framer, |
| const SpdyPushPromiseIR& push_promise, |
| ArrayOutputBuffer* output) { |
| if (output == nullptr) { |
| return SerializePushPromise(framer, push_promise); |
| } |
| output->Reset(); |
| EXPECT_TRUE(framer->SerializePushPromise(push_promise, output)); |
| SpdySerializedFrame serialized_headers_old_version(output->Begin(), |
| output->Size(), false); |
| framer->hpack_encoder_.reset(nullptr); |
| auto* saved_debug_visitor = framer->debug_visitor_; |
| framer->debug_visitor_ = nullptr; |
| |
| std::vector<SpdySerializedFrame> frame_list; |
| ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); |
| frame_list_buffer.Reset(); |
| SpdyFramer::SpdyPushPromiseFrameIterator it( |
| framer, CloneSpdyPushPromiseIR(push_promise)); |
| while (it.HasNextFrame()) { |
| size_t size_before = frame_list_buffer.Size(); |
| EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); |
| frame_list.emplace_back( |
| SpdySerializedFrame(frame_list_buffer.Begin() + size_before, |
| frame_list_buffer.Size() - size_before, false)); |
| } |
| framer->debug_visitor_ = saved_debug_visitor; |
| |
| EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); |
| return serialized_headers_old_version; |
| } |
| }; |
| |
| class TestSpdyVisitor : public SpdyFramerVisitorInterface, |
| public SpdyFramerDebugVisitorInterface { |
| public: |
| // This is larger than our max frame size because header blocks that |
| // are too long can spill over into CONTINUATION frames. |
| static constexpr size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024; |
| |
| explicit TestSpdyVisitor(SpdyFramer::CompressionOption option) |
| : framer_(option), |
| error_count_(0), |
| headers_frame_count_(0), |
| push_promise_frame_count_(0), |
| goaway_count_(0), |
| setting_count_(0), |
| settings_ack_sent_(0), |
| settings_ack_received_(0), |
| continuation_count_(0), |
| altsvc_count_(0), |
| priority_count_(0), |
| on_unknown_frame_result_(false), |
| last_window_update_stream_(0), |
| last_window_update_delta_(0), |
| last_push_promise_stream_(0), |
| last_push_promise_promised_stream_(0), |
| data_bytes_(0), |
| fin_frame_count_(0), |
| fin_flag_count_(0), |
| end_of_stream_count_(0), |
| control_frame_header_data_count_(0), |
| zero_length_control_frame_header_data_count_(0), |
| data_frame_count_(0), |
| last_payload_len_(0), |
| last_frame_len_(0), |
| header_buffer_(new char[kDefaultHeaderBufferSize]), |
| header_buffer_length_(0), |
| header_buffer_size_(kDefaultHeaderBufferSize), |
| header_stream_id_(static_cast<SpdyStreamId>(-1)), |
| header_control_type_(SpdyFrameType::DATA), |
| header_buffer_valid_(false) {} |
| |
| void OnError(Http2DecoderAdapter::SpdyFramerError error, |
| std::string /*detailed_error*/) override { |
| QUICHE_VLOG(1) << "SpdyFramer Error: " |
| << Http2DecoderAdapter::SpdyFramerErrorToString(error); |
| ++error_count_; |
| } |
| |
| void OnDataFrameHeader(SpdyStreamId stream_id, |
| size_t length, |
| bool fin) override { |
| QUICHE_VLOG(1) << "OnDataFrameHeader(" << stream_id << ", " << length |
| << ", " << fin << ")"; |
| ++data_frame_count_; |
| header_stream_id_ = stream_id; |
| } |
| |
| void OnStreamFrameData(SpdyStreamId stream_id, |
| const char* data, |
| size_t len) override { |
| QUICHE_VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len |
| << ", " |
| << ") data:\n" |
| << quiche::QuicheTextUtils::HexDump( |
| absl::string_view(data, len)); |
| EXPECT_EQ(header_stream_id_, stream_id); |
| |
| data_bytes_ += len; |
| } |
| |
| void OnStreamEnd(SpdyStreamId stream_id) override { |
| QUICHE_VLOG(1) << "OnStreamEnd(" << stream_id << ")"; |
| EXPECT_EQ(header_stream_id_, stream_id); |
| ++end_of_stream_count_; |
| } |
| |
| void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override { |
| QUICHE_VLOG(1) << "OnStreamPadding(" << stream_id << ", " << value << ")\n"; |
| EXPECT_EQ(header_stream_id_, stream_id); |
| // Count the padding length field byte against total data bytes. |
| data_bytes_ += 1; |
| } |
| |
| void OnStreamPadding(SpdyStreamId stream_id, size_t len) override { |
| QUICHE_VLOG(1) << "OnStreamPadding(" << stream_id << ", " << len << ")\n"; |
| EXPECT_EQ(header_stream_id_, stream_id); |
| data_bytes_ += len; |
| } |
| |
| SpdyHeadersHandlerInterface* OnHeaderFrameStart( |
| SpdyStreamId /*stream_id*/) override { |
| if (headers_handler_ == nullptr) { |
| headers_handler_ = std::make_unique<RecordingHeadersHandler>(); |
| } |
| return headers_handler_.get(); |
| } |
| |
| void OnHeaderFrameEnd(SpdyStreamId /*stream_id*/) override { |
| QUICHE_CHECK(headers_handler_ != nullptr); |
| headers_ = headers_handler_->decoded_block().Clone(); |
| header_bytes_received_ = headers_handler_->uncompressed_header_bytes(); |
| headers_handler_.reset(); |
| } |
| |
| void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override { |
| QUICHE_VLOG(1) << "OnRstStream(" << stream_id << ", " << error_code << ")"; |
| ++fin_frame_count_; |
| } |
| |
| void OnSetting(SpdySettingsId id, uint32_t value) override { |
| QUICHE_VLOG(1) << "OnSetting(" << id << ", " << std::hex << value << ")"; |
| ++setting_count_; |
| } |
| |
| void OnSettingsAck() override { |
| QUICHE_VLOG(1) << "OnSettingsAck"; |
| ++settings_ack_received_; |
| } |
| |
| void OnSettingsEnd() override { |
| QUICHE_VLOG(1) << "OnSettingsEnd"; |
| ++settings_ack_sent_; |
| } |
| |
| void OnPing(SpdyPingId unique_id, bool is_ack) override { |
| QUICHE_LOG(DFATAL) << "OnPing(" << unique_id << ", " << (is_ack ? 1 : 0) |
| << ")"; |
| } |
| |
| void OnGoAway(SpdyStreamId last_accepted_stream_id, |
| SpdyErrorCode error_code) override { |
| QUICHE_VLOG(1) << "OnGoAway(" << last_accepted_stream_id << ", " |
| << error_code << ")"; |
| ++goaway_count_; |
| } |
| |
| void OnHeaders(SpdyStreamId stream_id, |
| bool has_priority, |
| int weight, |
| SpdyStreamId parent_stream_id, |
| bool exclusive, |
| bool fin, |
| bool end) override { |
| QUICHE_VLOG(1) << "OnHeaders(" << stream_id << ", " << has_priority << ", " |
| << weight << ", " << parent_stream_id << ", " << exclusive |
| << ", " << fin << ", " << end << ")"; |
| ++headers_frame_count_; |
| InitHeaderStreaming(SpdyFrameType::HEADERS, stream_id); |
| if (fin) { |
| ++fin_flag_count_; |
| } |
| header_has_priority_ = has_priority; |
| header_parent_stream_id_ = parent_stream_id; |
| header_exclusive_ = exclusive; |
| } |
| |
| void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override { |
| QUICHE_VLOG(1) << "OnWindowUpdate(" << stream_id << ", " |
| << delta_window_size << ")"; |
| last_window_update_stream_ = stream_id; |
| last_window_update_delta_ = delta_window_size; |
| } |
| |
| void OnPushPromise(SpdyStreamId stream_id, |
| SpdyStreamId promised_stream_id, |
| bool end) override { |
| QUICHE_VLOG(1) << "OnPushPromise(" << stream_id << ", " |
| << promised_stream_id << ", " << end << ")"; |
| ++push_promise_frame_count_; |
| InitHeaderStreaming(SpdyFrameType::PUSH_PROMISE, stream_id); |
| last_push_promise_stream_ = stream_id; |
| last_push_promise_promised_stream_ = promised_stream_id; |
| } |
| |
| void OnContinuation(SpdyStreamId stream_id, bool end) override { |
| QUICHE_VLOG(1) << "OnContinuation(" << stream_id << ", " << end << ")"; |
| ++continuation_count_; |
| } |
| |
| void OnAltSvc(SpdyStreamId stream_id, |
| absl::string_view origin, |
| const SpdyAltSvcWireFormat::AlternativeServiceVector& |
| altsvc_vector) override { |
| QUICHE_VLOG(1) << "OnAltSvc(" << stream_id << ", \"" << origin |
| << "\", altsvc_vector)"; |
| test_altsvc_ir_ = std::make_unique<SpdyAltSvcIR>(stream_id); |
| if (origin.length() > 0) { |
| test_altsvc_ir_->set_origin(std::string(origin)); |
| } |
| for (const auto& altsvc : altsvc_vector) { |
| test_altsvc_ir_->add_altsvc(altsvc); |
| } |
| ++altsvc_count_; |
| } |
| |
| void OnPriority(SpdyStreamId stream_id, |
| SpdyStreamId parent_stream_id, |
| int weight, |
| bool exclusive) override { |
| QUICHE_VLOG(1) << "OnPriority(" << stream_id << ", " << parent_stream_id |
| << ", " << weight << ", " << (exclusive ? 1 : 0) << ")"; |
| ++priority_count_; |
| } |
| |
| void OnPriorityUpdate(SpdyStreamId prioritized_stream_id, |
| absl::string_view priority_field_value) override { |
| QUICHE_VLOG(1) << "OnPriorityUpdate(" << prioritized_stream_id << ", " |
| << priority_field_value << ")"; |
| } |
| |
| bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override { |
| QUICHE_VLOG(1) << "OnUnknownFrame(" << stream_id << ", " << frame_type |
| << ")"; |
| return on_unknown_frame_result_; |
| } |
| |
| void OnSendCompressedFrame(SpdyStreamId stream_id, |
| SpdyFrameType type, |
| size_t payload_len, |
| size_t frame_len) override { |
| QUICHE_VLOG(1) << "OnSendCompressedFrame(" << stream_id << ", " << type |
| << ", " << payload_len << ", " << frame_len << ")"; |
| last_payload_len_ = payload_len; |
| last_frame_len_ = frame_len; |
| } |
| |
| void OnReceiveCompressedFrame(SpdyStreamId stream_id, |
| SpdyFrameType type, |
| size_t frame_len) override { |
| QUICHE_VLOG(1) << "OnReceiveCompressedFrame(" << stream_id << ", " << type |
| << ", " << frame_len << ")"; |
| last_frame_len_ = frame_len; |
| } |
| |
| // Convenience function which runs a framer simulation with particular input. |
| void SimulateInFramer(const unsigned char* input, size_t size) { |
| deframer_.set_visitor(this); |
| size_t input_remaining = size; |
| const char* input_ptr = reinterpret_cast<const char*>(input); |
| while (input_remaining > 0 && deframer_.spdy_framer_error() == |
| Http2DecoderAdapter::SPDY_NO_ERROR) { |
| // To make the tests more interesting, we feed random (and small) chunks |
| // into the framer. This simulates getting strange-sized reads from |
| // the socket. |
| const size_t kMaxReadSize = 32; |
| size_t bytes_read = |
| (rand() % std::min(input_remaining, kMaxReadSize)) + 1; // NOLINT |
| size_t bytes_processed = deframer_.ProcessInput(input_ptr, bytes_read); |
| input_remaining -= bytes_processed; |
| input_ptr += bytes_processed; |
| } |
| } |
| |
| void InitHeaderStreaming(SpdyFrameType header_control_type, |
| SpdyStreamId stream_id) { |
| if (!IsDefinedFrameType(SerializeFrameType(header_control_type))) { |
| QUICHE_DLOG(FATAL) << "Attempted to init header streaming with " |
| << "invalid control frame type: " |
| << header_control_type; |
| } |
| memset(header_buffer_.get(), 0, header_buffer_size_); |
| header_buffer_length_ = 0; |
| header_stream_id_ = stream_id; |
| header_control_type_ = header_control_type; |
| header_buffer_valid_ = true; |
| } |
| |
| void set_extension_visitor(ExtensionVisitorInterface* extension) { |
| deframer_.set_extension_visitor(extension); |
| } |
| |
| // Override the default buffer size (16K). Call before using the framer! |
| void set_header_buffer_size(size_t header_buffer_size) { |
| header_buffer_size_ = header_buffer_size; |
| header_buffer_.reset(new char[header_buffer_size]); |
| } |
| |
| SpdyFramer framer_; |
| Http2DecoderAdapter deframer_; |
| |
| // Counters from the visitor callbacks. |
| int error_count_; |
| int headers_frame_count_; |
| int push_promise_frame_count_; |
| int goaway_count_; |
| int setting_count_; |
| int settings_ack_sent_; |
| int settings_ack_received_; |
| int continuation_count_; |
| int altsvc_count_; |
| int priority_count_; |
| std::unique_ptr<SpdyAltSvcIR> test_altsvc_ir_; |
| bool on_unknown_frame_result_; |
| SpdyStreamId last_window_update_stream_; |
| int last_window_update_delta_; |
| SpdyStreamId last_push_promise_stream_; |
| SpdyStreamId last_push_promise_promised_stream_; |
| int data_bytes_; |
| int fin_frame_count_; // The count of RST_STREAM type frames received. |
| int fin_flag_count_; // The count of frames with the FIN flag set. |
| int end_of_stream_count_; // The count of zero-length data frames. |
| int control_frame_header_data_count_; // The count of chunks received. |
| // The count of zero-length control frame header data chunks received. |
| int zero_length_control_frame_header_data_count_; |
| int data_frame_count_; |
| size_t last_payload_len_; |
| size_t last_frame_len_; |
| |
| // Header block streaming state: |
| std::unique_ptr<char[]> header_buffer_; |
| size_t header_buffer_length_; |
| size_t header_buffer_size_; |
| size_t header_bytes_received_; |
| SpdyStreamId header_stream_id_; |
| SpdyFrameType header_control_type_; |
| bool header_buffer_valid_; |
| std::unique_ptr<RecordingHeadersHandler> headers_handler_; |
| Http2HeaderBlock headers_; |
| bool header_has_priority_; |
| SpdyStreamId header_parent_stream_id_; |
| bool header_exclusive_; |
| }; |
| |
| class TestExtension : public ExtensionVisitorInterface { |
| public: |
| void OnSetting(SpdySettingsId id, uint32_t value) override { |
| settings_received_.push_back({id, value}); |
| } |
| |
| // Called when non-standard frames are received. |
| bool OnFrameHeader(SpdyStreamId stream_id, |
| size_t length, |
| uint8_t type, |
| uint8_t flags) override { |
| stream_id_ = stream_id; |
| length_ = length; |
| type_ = type; |
| flags_ = flags; |
| return true; |
| } |
| |
| // The payload for a single frame may be delivered as multiple calls to |
| // OnFramePayload. |
| void OnFramePayload(const char* data, size_t len) override { |
| payload_.append(data, len); |
| } |
| |
| std::vector<std::pair<SpdySettingsId, uint32_t>> settings_received_; |
| SpdyStreamId stream_id_ = 0; |
| size_t length_ = 0; |
| uint8_t type_ = 0; |
| uint8_t flags_ = 0; |
| std::string payload_; |
| }; |
| |
| // Exposes SpdyUnknownIR::set_length() for testing purposes. |
| class TestSpdyUnknownIR : public SpdyUnknownIR { |
| public: |
| using SpdyUnknownIR::set_length; |
| using SpdyUnknownIR::SpdyUnknownIR; |
| }; |
| |
| enum Output { USE, NOT_USE }; |
| |
| class SpdyFramerTest : public QuicheTestWithParam<Output> { |
| public: |
| SpdyFramerTest() |
| : output_(output_buffer, kSize), |
| framer_(SpdyFramer::ENABLE_COMPRESSION) {} |
| |
| protected: |
| void SetUp() override { |
| switch (GetParam()) { |
| case USE: |
| use_output_ = true; |
| break; |
| case NOT_USE: |
| // TODO(yasong): remove this case after |
| // gfe2_reloadable_flag_write_queue_zero_copy_buffer deprecates. |
| use_output_ = false; |
| break; |
| } |
| } |
| |
| void CompareFrame(const std::string& description, |
| const SpdySerializedFrame& actual_frame, |
| const unsigned char* expected, |
| const int expected_len) { |
| const unsigned char* actual = |
| reinterpret_cast<const unsigned char*>(actual_frame.data()); |
| CompareCharArraysWithHexError(description, actual, actual_frame.size(), |
| expected, expected_len); |
| } |
| |
| bool use_output_ = false; |
| ArrayOutputBuffer output_; |
| SpdyFramer framer_; |
| Http2DecoderAdapter deframer_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(SpdyFramerTests, |
| SpdyFramerTest, |
| ::testing::Values(USE, NOT_USE)); |
| |
| // Test that we can encode and decode a SpdyHeaderBlock in serialized form. |
| TEST_P(SpdyFramerTest, HeaderBlockInBuffer) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| |
| // Encode the header block into a Headers frame. |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.SetHeader("alpha", "beta"); |
| headers.SetHeader("gamma", "charlie"); |
| headers.SetHeader("cookie", "key1=value1; key2=value2"); |
| SpdySerializedFrame frame( |
| SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), |
| frame.size()); |
| |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_EQ(headers.header_block(), visitor.headers_); |
| } |
| |
| // Test that if there's not a full frame, we fail to parse it. |
| TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| |
| // Encode the header block into a Headers frame. |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.SetHeader("alpha", "beta"); |
| headers.SetHeader("gamma", "charlie"); |
| SpdySerializedFrame frame( |
| SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), |
| frame.size() - 2); |
| |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_THAT(visitor.headers_, testing::IsEmpty()); |
| } |
| |
| // Test that we can encode and decode stream dependency values in a header |
| // frame. |
| TEST_P(SpdyFramerTest, HeaderStreamDependencyValues) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| |
| const SpdyStreamId parent_stream_id_test_array[] = {0, 3}; |
| for (SpdyStreamId parent_stream_id : parent_stream_id_test_array) { |
| const bool exclusive_test_array[] = {true, false}; |
| for (bool exclusive : exclusive_test_array) { |
| SpdyHeadersIR headers(1); |
| headers.set_has_priority(true); |
| headers.set_parent_stream_id(parent_stream_id); |
| headers.set_exclusive(exclusive); |
| SpdySerializedFrame frame( |
| SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), |
| frame.size()); |
| |
| EXPECT_TRUE(visitor.header_has_priority_); |
| EXPECT_EQ(parent_stream_id, visitor.header_parent_stream_id_); |
| EXPECT_EQ(exclusive, visitor.header_exclusive_); |
| } |
| } |
| } |
| |
| // Test that if we receive a frame with payload length field at the |
| // advertised max size, we do not set an error in ProcessInput. |
| TEST_P(SpdyFramerTest, AcceptMaxFrameSizeSetting) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| // DATA frame with maximum allowed payload length. |
| unsigned char kH2FrameData[] = { |
| 0x00, 0x40, 0x00, // Length: 2^14 |
| 0x00, // Type: HEADERS |
| 0x00, // Flags: None |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Junk payload |
| }; |
| |
| SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), |
| sizeof(kH2FrameData), false); |
| |
| EXPECT_CALL(visitor, OnDataFrameHeader(1, 1 << 14, false)); |
| EXPECT_CALL(visitor, OnStreamFrameData(1, _, 4)); |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_FALSE(deframer_.HasError()); |
| } |
| |
| // Test that if we receive a frame with payload length larger than the |
| // advertised max size, we set an error of SPDY_INVALID_CONTROL_FRAME_SIZE. |
| TEST_P(SpdyFramerTest, ExceedMaxFrameSizeSetting) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| // DATA frame with too large payload length. |
| unsigned char kH2FrameData[] = { |
| 0x00, 0x40, 0x01, // Length: 2^14 + 1 |
| 0x00, // Type: HEADERS |
| 0x00, // Flags: None |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Junk payload |
| }; |
| |
| SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), |
| sizeof(kH2FrameData), false); |
| |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD, _)); |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a DATA frame with padding length larger than the |
| // payload length, we set an error of SPDY_INVALID_PADDING |
| TEST_P(SpdyFramerTest, OversizedDataPaddingError) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| // DATA frame with invalid padding length. |
| // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses |
| // MSVC, where |char| is signed by default, which would not compile because of |
| // the element exceeding 127. |
| unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x00, // Type: DATA |
| 0x09, // Flags: END_STREAM|PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xff, // PadLen: 255 trailing bytes (Too Long) |
| 0x00, 0x00, 0x00, 0x00, // Padding |
| }; |
| |
| SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), |
| sizeof(kH2FrameData), false); |
| |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, 1)); |
| EXPECT_CALL(visitor, OnStreamPadding(1, 1)); |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING, _)); |
| } |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a DATA frame with padding length not larger than the |
| // payload length, we do not set an error of SPDY_INVALID_PADDING |
| TEST_P(SpdyFramerTest, CorrectlySizedDataPaddingNoError) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| // DATA frame with valid Padding length |
| char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x00, // Type: DATA |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x04, // PadLen: 4 trailing bytes |
| 0x00, 0x00, 0x00, 0x00, // Padding |
| }; |
| |
| SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); |
| |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, false)); |
| EXPECT_CALL(visitor, OnStreamPadLength(1, 4)); |
| EXPECT_CALL(visitor, OnError(_, _)).Times(0); |
| // Note that OnStreamFrameData(1, _, 1)) is never called |
| // since there is no data, only padding |
| EXPECT_CALL(visitor, OnStreamPadding(1, 4)); |
| } |
| |
| EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_FALSE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a HEADERS frame with padding length larger than the |
| // payload length, we set an error of SPDY_INVALID_PADDING |
| TEST_P(SpdyFramerTest, OversizedHeadersPaddingError) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| // HEADERS frame with invalid padding length. |
| // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses |
| // MSVC, where |char| is signed by default, which would not compile because of |
| // the element exceeding 127. |
| unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xff, // PadLen: 255 trailing bytes (Too Long) |
| 0x00, 0x00, 0x00, 0x00, // Padding |
| }; |
| |
| SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), |
| sizeof(kH2FrameData), false); |
| |
| EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1); |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING, _)); |
| EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a HEADERS frame with padding length not larger |
| // than the payload length, we do not set an error of SPDY_INVALID_PADDING |
| TEST_P(SpdyFramerTest, CorrectlySizedHeadersPaddingNoError) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| // HEADERS frame with invalid Padding length |
| char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x04, // PadLen: 4 trailing bytes |
| 0x00, 0x00, 0x00, 0x00, // Padding |
| }; |
| |
| SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); |
| |
| EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1); |
| |
| EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_FALSE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a DATA with stream ID zero, we signal an error |
| // (but don't crash). |
| TEST_P(SpdyFramerTest, DataWithStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| const char bytes[] = "hello"; |
| SpdyDataIR data_ir(/* stream_id = */ 0, bytes); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a HEADERS with stream ID zero, we signal an error |
| // (but don't crash). |
| TEST_P(SpdyFramerTest, HeadersWithStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyHeadersIR headers(/* stream_id = */ 0); |
| headers.SetHeader("alpha", "beta"); |
| SpdySerializedFrame frame( |
| SpdyFramerPeer::SerializeHeaders(&framer_, headers, &output_)); |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a PRIORITY with stream ID zero, we signal an error |
| // (but don't crash). |
| TEST_P(SpdyFramerTest, PriorityWithStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyPriorityIR priority_ir(/* stream_id = */ 0, |
| /* parent_stream_id = */ 1, |
| /* weight = */ 16, |
| /* exclusive = */ true); |
| SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a RST_STREAM with stream ID zero, we signal an error |
| // (but don't crash). |
| TEST_P(SpdyFramerTest, RstStreamWithStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyRstStreamIR rst_stream_ir(/* stream_id = */ 0, ERROR_CODE_PROTOCOL_ERROR); |
| SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream_ir)); |
| if (use_output_) { |
| EXPECT_TRUE(framer_.SerializeRstStream(rst_stream_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a SETTINGS with stream ID other than zero, |
| // we signal an error (but don't crash). |
| TEST_P(SpdyFramerTest, SettingsWithStreamIdNotZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| // Settings frame with invalid StreamID of 0x01 |
| char kH2FrameData[] = { |
| 0x00, 0x00, 0x06, // Length: 6 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE |
| 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141 |
| }; |
| |
| SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a GOAWAY with stream ID other than zero, |
| // we signal an error (but don't crash). |
| TEST_P(SpdyFramerTest, GoawayWithStreamIdNotZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| // GOAWAY frame with invalid StreamID of 0x01 |
| char kH2FrameData[] = { |
| 0x00, 0x00, 0x0a, // Length: 10 |
| 0x07, // Type: GOAWAY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Last: 0 |
| 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR |
| 0x47, 0x41, // Description |
| }; |
| |
| SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a CONTINUATION with stream ID zero, we signal |
| // SPDY_INVALID_STREAM_ID. |
| TEST_P(SpdyFramerTest, ContinuationWithStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyContinuationIR continuation(/* stream_id = */ 0); |
| std::string some_nonsense_encoding = "some nonsense encoding"; |
| continuation.take_encoding(std::move(some_nonsense_encoding)); |
| continuation.set_end_headers(true); |
| SpdySerializedFrame frame(framer_.SerializeContinuation(continuation)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeContinuation(continuation, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a PUSH_PROMISE with stream ID zero, we signal |
| // SPDY_INVALID_STREAM_ID. |
| TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 0, |
| /* promised_stream_id = */ 4); |
| push_promise.SetHeader("alpha", "beta"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer_, push_promise, use_output_ ? &output_ : nullptr)); |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test that if we receive a PUSH_PROMISE with promised stream ID zero, we |
| // signal SPDY_INVALID_CONTROL_FRAME. |
| TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 3, |
| /* promised_stream_id = */ 0); |
| push_promise.SetHeader("alpha", "beta"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer_, push_promise, use_output_ ? &output_ : nullptr)); |
| |
| EXPECT_CALL(visitor, |
| OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, _)); |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, MultiValueHeader) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| std::string value("value1\0value2", 13); |
| // TODO(jgraettinger): If this pattern appears again, move to test class. |
| Http2HeaderBlock header_set; |
| header_set["name"] = value; |
| std::string buffer; |
| HpackEncoder encoder; |
| encoder.DisableCompression(); |
| encoder.EncodeHeaderSet(header_set, &buffer); |
| // Frame builder with plentiful buffer size. |
| SpdyFrameBuilder frame(1024); |
| frame.BeginNewFrame(SpdyFrameType::HEADERS, |
| HEADERS_FLAG_PRIORITY | HEADERS_FLAG_END_HEADERS, 3, |
| buffer.size() + 5 /* priority */); |
| frame.WriteUInt32(0); // Priority exclusivity and dependent stream. |
| frame.WriteUInt8(255); // Priority weight. |
| frame.WriteBytes(&buffer[0], buffer.size()); |
| |
| SpdySerializedFrame control_frame(frame.take()); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| |
| EXPECT_THAT(visitor.headers_, testing::ElementsAre(testing::Pair( |
| "name", absl::string_view(value)))); |
| } |
| |
| TEST_P(SpdyFramerTest, CompressEmptyHeaders) { |
| // See https://crbug.com/172383/ |
| SpdyHeadersIR headers(1); |
| headers.SetHeader("server", "SpdyServer 1.0"); |
| headers.SetHeader("date", "Mon 12 Jan 2009 12:12:12 PST"); |
| headers.SetHeader("status", "200"); |
| headers.SetHeader("version", "HTTP/1.1"); |
| headers.SetHeader("content-type", "text/html"); |
| headers.SetHeader("content-length", "12"); |
| headers.SetHeader("x-empty-header", ""); |
| |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| SpdySerializedFrame frame1( |
| SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); |
| } |
| |
| TEST_P(SpdyFramerTest, Basic) { |
| // Send HEADERS frames with PRIORITY and END_HEADERS set. |
| // frame-format off |
| const unsigned char kH2Input[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x24, // Flags: END_HEADERS|PRIORITY |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Parent: 0 |
| 0x82, // Weight: 131 |
| |
| 0x00, 0x00, 0x01, // Length: 1 |
| 0x01, // Type: HEADERS |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x8c, // :status: 200 |
| |
| 0x00, 0x00, 0x0c, // Length: 12 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| 0xde, 0xad, 0xbe, 0xef, // |
| 0xde, 0xad, 0xbe, 0xef, // |
| |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x24, // Flags: END_HEADERS|PRIORITY |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x00, 0x00, // Parent: 0 |
| 0x82, // Weight: 131 |
| |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| 0xde, 0xad, 0xbe, 0xef, // |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x08, // Error: CANCEL |
| |
| 0x00, 0x00, 0x00, // Length: 0 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x00, 0x08, // Error: CANCEL |
| }; |
| // frame-format on |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kH2Input, sizeof(kH2Input)); |
| |
| EXPECT_EQ(24, visitor.data_bytes_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(2, visitor.fin_frame_count_); |
| |
| EXPECT_EQ(3, visitor.headers_frame_count_); |
| |
| EXPECT_EQ(0, visitor.fin_flag_count_); |
| EXPECT_EQ(0, visitor.end_of_stream_count_); |
| EXPECT_EQ(4, visitor.data_frame_count_); |
| } |
| |
| // Verifies that the decoder stops delivering events after a user error. |
| TEST_P(SpdyFramerTest, BasicWithError) { |
| // Send HEADERS frames with PRIORITY and END_HEADERS set. |
| // frame-format off |
| const unsigned char kH2Input[] = { |
| 0x00, 0x00, 0x01, // Length: 1 |
| 0x01, // Type: HEADERS |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x8c, // :status: 200 |
| |
| 0x00, 0x00, 0x0c, // Length: 12 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| 0xde, 0xad, 0xbe, 0xef, // |
| 0xde, 0xad, 0xbe, 0xef, // |
| |
| 0x00, 0x00, 0x06, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x24, // Flags: END_HEADERS|PRIORITY |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x00, 0x00, // Parent: 0 |
| 0x82, // Weight: 131 |
| 0x8c, // :status: 200 |
| |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| 0xde, 0xad, 0xbe, 0xef, // |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x08, // Error: CANCEL |
| |
| 0x00, 0x00, 0x00, // Length: 0 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x00, 0x08, // Error: CANCEL |
| }; |
| // frame-format on |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| testing::InSequence s; |
| EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, true)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(1)); |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(1)); |
| EXPECT_CALL(visitor, OnDataFrameHeader(1, 12, false)); |
| EXPECT_CALL(visitor, OnStreamFrameData(1, _, 12)); |
| EXPECT_CALL(visitor, OnHeaders(3, true, 131, 0, false, false, true)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(3)); |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(3)); |
| EXPECT_CALL(visitor, OnDataFrameHeader(3, 8, false)) |
| .WillOnce( |
| testing::InvokeWithoutArgs([this]() { deframer_.StopProcessing(); })); |
| // Remaining frames are not processed due to the error. |
| EXPECT_CALL( |
| visitor, |
| OnError(http2::Http2DecoderAdapter::SpdyFramerError::SPDY_STOP_PROCESSING, |
| "Ignoring further events on this connection.")); |
| |
| size_t processed = deframer_.ProcessInput( |
| reinterpret_cast<const char*>(kH2Input), sizeof(kH2Input)); |
| EXPECT_LT(processed, sizeof(kH2Input)); |
| } |
| |
| // Test that the FIN flag on a data frame signifies EOF. |
| TEST_P(SpdyFramerTest, FinOnDataFrame) { |
| // Send HEADERS frames with END_HEADERS set. |
| // frame-format off |
| const unsigned char kH2Input[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x24, // Flags: END_HEADERS|PRIORITY |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Parent: 0 |
| 0x82, // Weight: 131 |
| |
| 0x00, 0x00, 0x01, // Length: 1 |
| 0x01, // Type: HEADERS |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x8c, // :status: 200 |
| |
| 0x00, 0x00, 0x0c, // Length: 12 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| 0xde, 0xad, 0xbe, 0xef, // |
| 0xde, 0xad, 0xbe, 0xef, // |
| |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x00, // Type: DATA |
| 0x01, // Flags: END_STREAM |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xde, 0xad, 0xbe, 0xef, // Payload |
| }; |
| // frame-format on |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kH2Input, sizeof(kH2Input)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(2, visitor.headers_frame_count_); |
| EXPECT_EQ(16, visitor.data_bytes_); |
| EXPECT_EQ(0, visitor.fin_frame_count_); |
| EXPECT_EQ(0, visitor.fin_flag_count_); |
| EXPECT_EQ(1, visitor.end_of_stream_count_); |
| EXPECT_EQ(2, visitor.data_frame_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, FinOnHeadersFrame) { |
| // Send HEADERS frames with END_HEADERS set. |
| // frame-format off |
| const unsigned char kH2Input[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x01, // Type: HEADERS |
| 0x24, // Flags: END_HEADERS|PRIORITY |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Parent: 0 |
| 0x82, // Weight: 131 |
| |
| 0x00, 0x00, 0x01, // Length: 1 |
| 0x01, // Type: HEADERS |
| 0x05, // Flags: END_STREAM|END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x8c, // :status: 200 |
| }; |
| // frame-format on |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kH2Input, sizeof(kH2Input)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(2, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.data_bytes_); |
| EXPECT_EQ(0, visitor.fin_frame_count_); |
| EXPECT_EQ(1, visitor.fin_flag_count_); |
| EXPECT_EQ(1, visitor.end_of_stream_count_); |
| EXPECT_EQ(0, visitor.data_frame_count_); |
| } |
| |
| // Verify we can decompress the stream even if handed over to the |
| // framer 1 byte at a time. |
| TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) { |
| const char kHeader1[] = "header1"; |
| const char kHeader2[] = "header2"; |
| const char kValue1[] = "value1"; |
| const char kValue2[] = "value2"; |
| |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.SetHeader(kHeader1, kValue1); |
| headers.SetHeader(kHeader2, kValue2); |
| SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders( |
| &framer_, headers, use_output_ ? &output_ : nullptr)); |
| |
| const char bytes[] = "this is a test test test test test!"; |
| SpdyDataIR data_ir(/* stream_id = */ 1, |
| absl::string_view(bytes, ABSL_ARRAYSIZE(bytes))); |
| data_ir.set_fin(true); |
| SpdySerializedFrame send_frame(framer_.SerializeData(data_ir)); |
| |
| // Run the inputs through the framer. |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| const unsigned char* data; |
| data = reinterpret_cast<const unsigned char*>(headers_frame.data()); |
| for (size_t idx = 0; idx < headers_frame.size(); ++idx) { |
| visitor.SimulateInFramer(data + idx, 1); |
| ASSERT_EQ(0, visitor.error_count_); |
| } |
| data = reinterpret_cast<const unsigned char*>(send_frame.data()); |
| for (size_t idx = 0; idx < send_frame.size(); ++idx) { |
| visitor.SimulateInFramer(data + idx, 1); |
| ASSERT_EQ(0, visitor.error_count_); |
| } |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(ABSL_ARRAYSIZE(bytes), static_cast<unsigned>(visitor.data_bytes_)); |
| EXPECT_EQ(0, visitor.fin_frame_count_); |
| EXPECT_EQ(0, visitor.fin_flag_count_); |
| EXPECT_EQ(1, visitor.end_of_stream_count_); |
| EXPECT_EQ(1, visitor.data_frame_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, WindowUpdateFrame) { |
| SpdyWindowUpdateIR window_update(/* stream_id = */ 1, |
| /* delta = */ 0x12345678); |
| SpdySerializedFrame frame(framer_.SerializeWindowUpdate(window_update)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeWindowUpdate(window_update, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x08, // Type: WINDOW_UPDATE |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x12, 0x34, 0x56, 0x78, // Increment: 305419896 |
| }; |
| |
| CompareFrame(kDescription, frame, kH2FrameData, ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| TEST_P(SpdyFramerTest, CreateDataFrame) { |
| { |
| const char kDescription[] = "'hello' data frame, no FIN"; |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 'h', 'e', 'l', 'l', // Payload |
| 'o', // |
| }; |
| // frame-format on |
| const char bytes[] = "hello"; |
| |
| SpdyDataIR data_ir(/* stream_id = */ 1, bytes); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| |
| SpdyDataIR data_header_ir(/* stream_id = */ 1); |
| data_header_ir.SetDataShallow(bytes); |
| frame = |
| framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_header_ir); |
| CompareCharArraysWithHexError( |
| kDescription, reinterpret_cast<const unsigned char*>(frame.data()), |
| kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); |
| } |
| |
| { |
| const char kDescription[] = "'hello' data frame with more padding, no FIN"; |
| // clang-format off |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0xfd, // Length: 253 |
| 0x00, // Type: DATA |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xf7, // PadLen: 247 trailing bytes |
| 'h', 'e', 'l', 'l', // Payload |
| 'o', // |
| // Padding of 247 0x00(s). |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| // frame-format on |
| // clang-format on |
| const char bytes[] = "hello"; |
| |
| SpdyDataIR data_ir(/* stream_id = */ 1, bytes); |
| // 247 zeros and the pad length field make the overall padding to be 248 |
| // bytes. |
| data_ir.set_padding_len(248); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| |
| frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); |
| CompareCharArraysWithHexError( |
| kDescription, reinterpret_cast<const unsigned char*>(frame.data()), |
| kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); |
| } |
| |
| { |
| const char kDescription[] = "'hello' data frame with few padding, no FIN"; |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x0d, // Length: 13 |
| 0x00, // Type: DATA |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x07, // PadLen: 7 trailing bytes |
| 'h', 'e', 'l', 'l', // Payload |
| 'o', // |
| 0x00, 0x00, 0x00, 0x00, // Padding |
| 0x00, 0x00, 0x00, // Padding |
| }; |
| // frame-format on |
| const char bytes[] = "hello"; |
| |
| SpdyDataIR data_ir(/* stream_id = */ 1, bytes); |
| // 7 zeros and the pad length field make the overall padding to be 8 bytes. |
| data_ir.set_padding_len(8); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| |
| frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); |
| CompareCharArraysWithHexError( |
| kDescription, reinterpret_cast<const unsigned char*>(frame.data()), |
| kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); |
| } |
| |
| { |
| const char kDescription[] = |
| "'hello' data frame with 1 byte padding, no FIN"; |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x06, // Length: 6 |
| 0x00, // Type: DATA |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, // PadLen: 0 trailing bytes |
| 'h', 'e', 'l', 'l', // Payload |
| 'o', // |
| }; |
| // frame-format on |
| const char bytes[] = "hello"; |
| |
| SpdyDataIR data_ir(/* stream_id = */ 1, bytes); |
| // The pad length field itself is used for the 1-byte padding and no padding |
| // payload is needed. |
| data_ir.set_padding_len(1); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| |
| frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); |
| CompareCharArraysWithHexError( |
| kDescription, reinterpret_cast<const unsigned char*>(frame.data()), |
| kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); |
| } |
| |
| { |
| const char kDescription[] = "Data frame with negative data byte, no FIN"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x01, // Length: 1 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0xff, // Payload |
| }; |
| SpdyDataIR data_ir(/* stream_id = */ 1, "\xff"); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "'hello' data frame, with FIN"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x00, // Type: DATA |
| 0x01, // Flags: END_STREAM |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x68, 0x65, 0x6c, 0x6c, // Payload |
| 0x6f, // |
| }; |
| SpdyDataIR data_ir(/* stream_id = */ 1, "hello"); |
| data_ir.set_fin(true); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Empty data frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x00, // Length: 0 |
| 0x00, // Type: DATA |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| }; |
| SpdyDataIR data_ir(/* stream_id = */ 1, ""); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| |
| frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); |
| CompareCharArraysWithHexError( |
| kDescription, reinterpret_cast<const unsigned char*>(frame.data()), |
| kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); |
| } |
| |
| { |
| const char kDescription[] = "Data frame with max stream ID"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x00, // Type: DATA |
| 0x01, // Flags: END_STREAM |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff |
| 0x68, 0x65, 0x6c, 0x6c, // Payload |
| 0x6f, // |
| }; |
| SpdyDataIR data_ir(/* stream_id = */ 0x7fffffff, "hello"); |
| data_ir.set_fin(true); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreateRstStream) { |
| { |
| const char kDescription[] = "RST_STREAM frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR |
| }; |
| SpdyRstStreamIR rst_stream(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR); |
| SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "RST_STREAM frame with max stream ID"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff |
| 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR |
| }; |
| SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF, |
| ERROR_CODE_PROTOCOL_ERROR); |
| SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "RST_STREAM frame with max status code"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff |
| 0x00, 0x00, 0x00, 0x02, // Error: INTERNAL_ERROR |
| }; |
| SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF, |
| ERROR_CODE_INTERNAL_ERROR); |
| SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreateSettings) { |
| { |
| const char kDescription[] = "Network byte order SETTINGS frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x06, // Length: 6 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE |
| 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141 |
| }; |
| |
| uint32_t kValue = 0x0a0b0c0d; |
| SpdySettingsIR settings_ir; |
| |
| SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE; |
| settings_ir.AddSetting(kId, kValue); |
| |
| SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Basic SETTINGS frame"; |
| // These end up seemingly out of order because of the way that our internal |
| // ordering for settings_ir works. HTTP2 has no requirement on ordering on |
| // the wire. |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x18, // Length: 24 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x01, // Param: HEADER_TABLE_SIZE |
| 0x00, 0x00, 0x00, 0x05, // Value: 5 |
| 0x00, 0x02, // Param: ENABLE_PUSH |
| 0x00, 0x00, 0x00, 0x06, // Value: 6 |
| 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS |
| 0x00, 0x00, 0x00, 0x07, // Value: 7 |
| 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE |
| 0x00, 0x00, 0x00, 0x08, // Value: 8 |
| }; |
| |
| SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5); |
| settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6); |
| settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7); |
| settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 8); |
| SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Empty SETTINGS frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x00, // Length: 0 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| }; |
| SpdySettingsIR settings_ir; |
| SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreatePingFrame) { |
| { |
| const char kDescription[] = "PING frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0x06, // Type: PING |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x12, 0x34, 0x56, 0x78, // Opaque |
| 0x9a, 0xbc, 0xde, 0xff, // Data |
| }; |
| const unsigned char kH2FrameDataWithAck[] = { |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0x06, // Type: PING |
| 0x01, // Flags: ACK |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x12, 0x34, 0x56, 0x78, // Opaque |
| 0x9a, 0xbc, 0xde, 0xff, // Data |
| }; |
| const SpdyPingId kPingId = 0x123456789abcdeffULL; |
| SpdyPingIR ping_ir(kPingId); |
| // Tests SpdyPingIR when the ping is not an ack. |
| ASSERT_FALSE(ping_ir.is_ack()); |
| SpdySerializedFrame frame(framer_.SerializePing(ping_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| |
| // Tests SpdyPingIR when the ping is an ack. |
| ping_ir.set_is_ack(true); |
| frame = framer_.SerializePing(ping_ir); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameDataWithAck, |
| ABSL_ARRAYSIZE(kH2FrameDataWithAck)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreateGoAway) { |
| { |
| const char kDescription[] = "GOAWAY frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x0a, // Length: 10 |
| 0x07, // Type: GOAWAY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x00, 0x00, 0x00, // Last: 0 |
| 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR |
| 0x47, 0x41, // Description |
| }; |
| SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0, ERROR_CODE_NO_ERROR, |
| "GA"); |
| SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "GOAWAY frame with max stream ID, status"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x0a, // Length: 10 |
| 0x07, // Type: GOAWAY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x7f, 0xff, 0xff, 0xff, // Last: 0x7fffffff |
| 0x00, 0x00, 0x00, 0x02, // Error: INTERNAL_ERROR |
| 0x47, 0x41, // Description |
| }; |
| SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0x7FFFFFFF, |
| ERROR_CODE_INTERNAL_ERROR, "GA"); |
| SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreateHeadersUncompressed) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| |
| { |
| const char kDescription[] = "HEADERS frame, no FIN"; |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x01, // Type: HEADERS |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| }; |
| // frame-format on |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.SetHeader("bar", "foo"); |
| headers.SetHeader("foo", "bar"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header name, FIN, max stream ID"; |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x0f, // Length: 15 |
| 0x01, // Type: HEADERS |
| 0x05, // Flags: END_STREAM|END_HEADERS |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 |
| |
| 0x00, // Unindexed Entry |
| 0x00, // Name Len: 0 |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| }; |
| // frame-format on |
| SpdyHeadersIR headers(/* stream_id = */ 0x7fffffff); |
| headers.set_fin(true); |
| headers.SetHeader("", "foo"); |
| headers.SetHeader("foo", "bar"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header val, FIN, max stream ID"; |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x0f, // Length: 15 |
| 0x01, // Type: HEADERS |
| 0x05, // Flags: END_STREAM|END_HEADERS |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x00, // Value Len: 0 |
| }; |
| // frame-format on |
| SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); |
| headers_ir.set_fin(true); |
| headers_ir.SetHeader("bar", "foo"); |
| headers_ir.SetHeader("foo", ""); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers_ir, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header val, FIN, max stream ID, pri"; |
| |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x01, // Type: HEADERS |
| 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 |
| 0x00, 0x00, 0x00, 0x00, // Parent: 0 |
| 0xdb, // Weight: 220 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x00, // Value Len: 0 |
| }; |
| // frame-format on |
| SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); |
| headers_ir.set_fin(true); |
| headers_ir.set_has_priority(true); |
| headers_ir.set_weight(220); |
| headers_ir.SetHeader("bar", "foo"); |
| headers_ir.SetHeader("foo", ""); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers_ir, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, " |
| "exclusive=true, parent_stream=0"; |
| |
| // frame-format off |
| const unsigned char kV4FrameData[] = { |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x01, // Type: HEADERS |
| 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 |
| 0x80, 0x00, 0x00, 0x00, // Parent: 0 (Exclusive) |
| 0xdb, // Weight: 220 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x00, // Value Len: 0 |
| }; |
| // frame-format on |
| SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); |
| headers_ir.set_fin(true); |
| headers_ir.set_has_priority(true); |
| headers_ir.set_weight(220); |
| headers_ir.set_exclusive(true); |
| headers_ir.set_parent_stream_id(0); |
| headers_ir.SetHeader("bar", "foo"); |
| headers_ir.SetHeader("foo", ""); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers_ir, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kV4FrameData, |
| ABSL_ARRAYSIZE(kV4FrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, " |
| "exclusive=false, parent_stream=max stream ID"; |
| |
| // frame-format off |
| const unsigned char kV4FrameData[] = { |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x01, // Type: HEADERS |
| 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 |
| 0x7f, 0xff, 0xff, 0xff, // Parent: 2147483647 |
| 0xdb, // Weight: 220 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x00, // Value Len: 0 |
| }; |
| // frame-format on |
| SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); |
| headers_ir.set_fin(true); |
| headers_ir.set_has_priority(true); |
| headers_ir.set_weight(220); |
| headers_ir.set_exclusive(false); |
| headers_ir.set_parent_stream_id(0x7fffffff); |
| headers_ir.SetHeader("bar", "foo"); |
| headers_ir.SetHeader("foo", ""); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers_ir, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kV4FrameData, |
| ABSL_ARRAYSIZE(kV4FrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header name, FIN, max stream ID, padded"; |
| |
| // frame-format off |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x15, // Length: 21 |
| 0x01, // Type: HEADERS |
| 0x0d, // Flags: END_STREAM|END_HEADERS|PADDED |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 |
| 0x05, // PadLen: 5 trailing bytes |
| |
| 0x00, // Unindexed Entry |
| 0x00, // Name Len: 0 |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| |
| 0x00, 0x00, 0x00, 0x00, // Padding |
| 0x00, // Padding |
| }; |
| // frame-format on |
| SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); |
| headers_ir.set_fin(true); |
| headers_ir.SetHeader("", "foo"); |
| headers_ir.SetHeader("foo", "bar"); |
| headers_ir.set_padding_len(6); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers_ir, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreateWindowUpdate) { |
| { |
| const char kDescription[] = "WINDOW_UPDATE frame"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x08, // Type: WINDOW_UPDATE |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x01, // Increment: 1 |
| }; |
| SpdySerializedFrame frame(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1))); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1), &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "WINDOW_UPDATE frame with max stream ID"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x08, // Type: WINDOW_UPDATE |
| 0x00, // Flags: none |
| 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff |
| 0x00, 0x00, 0x00, 0x01, // Increment: 1 |
| }; |
| SpdySerializedFrame frame(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1))); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1), |
| &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| |
| { |
| const char kDescription[] = "WINDOW_UPDATE frame with max window delta"; |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x08, // Type: WINDOW_UPDATE |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x7f, 0xff, 0xff, 0xff, // Increment: 0x7fffffff |
| }; |
| SpdySerializedFrame frame(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF))); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF), |
| &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kH2FrameData, |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreatePushPromiseUncompressed) { |
| { |
| // Test framing PUSH_PROMISE without padding. |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| const char kDescription[] = "PUSH_PROMISE frame without padding"; |
| |
| // frame-format off |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x16, // Length: 22 |
| 0x05, // Type: PUSH_PROMISE |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x29, // Stream: 41 |
| 0x00, 0x00, 0x00, 0x3a, // Promise: 58 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| }; |
| // frame-format on |
| |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 41, |
| /* promised_stream_id = */ 58); |
| push_promise.SetHeader("bar", "foo"); |
| push_promise.SetHeader("foo", "bar"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer, push_promise, use_output_ ? &output_ : nullptr)); |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| { |
| // Test framing PUSH_PROMISE with one byte of padding. |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| const char kDescription[] = "PUSH_PROMISE frame with one byte of padding"; |
| |
| // frame-format off |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x17, // Length: 23 |
| 0x05, // Type: PUSH_PROMISE |
| 0x0c, // Flags: END_HEADERS|PADDED |
| 0x00, 0x00, 0x00, 0x29, // Stream: 41 |
| 0x00, // PadLen: 0 trailing bytes |
| 0x00, 0x00, 0x00, 0x3a, // Promise: 58 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| }; |
| // frame-format on |
| |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 41, |
| /* promised_stream_id = */ 58); |
| push_promise.set_padding_len(1); |
| push_promise.SetHeader("bar", "foo"); |
| push_promise.SetHeader("foo", "bar"); |
| output_.Reset(); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer, push_promise, use_output_ ? &output_ : nullptr)); |
| |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| { |
| // Test framing PUSH_PROMISE with 177 bytes of padding. |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| const char kDescription[] = "PUSH_PROMISE frame with 177 bytes of padding"; |
| |
| // frame-format off |
| // clang-format off |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0xc7, // Length: 199 |
| 0x05, // Type: PUSH_PROMISE |
| 0x0c, // Flags: END_HEADERS|PADDED |
| 0x00, 0x00, 0x00, 0x2a, // Stream: 42 |
| 0xb0, // PadLen: 176 trailing bytes |
| 0x00, 0x00, 0x00, 0x39, // Promise: 57 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| |
| // Padding of 176 0x00(s). |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| // clang-format on |
| // frame-format on |
| |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 42, |
| /* promised_stream_id = */ 57); |
| push_promise.set_padding_len(177); |
| push_promise.SetHeader("bar", "foo"); |
| push_promise.SetHeader("foo", "bar"); |
| output_.Reset(); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer, push_promise, use_output_ ? &output_ : nullptr)); |
| |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| } |
| |
| // Regression test for https://crbug.com/464748. |
| TEST_P(SpdyFramerTest, GetNumberRequiredContinuationFrames) { |
| EXPECT_EQ(1u, GetNumberRequiredContinuationFrames(16383 + 16374)); |
| EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 16374 + 1)); |
| EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374)); |
| EXPECT_EQ(3u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374 + 1)); |
| } |
| |
| TEST_P(SpdyFramerTest, CreateContinuationUncompressed) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| const char kDescription[] = "CONTINUATION frame"; |
| |
| // frame-format off |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x09, // Type: CONTINUATION |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x2a, // Stream: 42 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| }; |
| // frame-format on |
| |
| Http2HeaderBlock header_block; |
| header_block["bar"] = "foo"; |
| header_block["foo"] = "bar"; |
| std::string buffer; |
| HpackEncoder encoder; |
| encoder.DisableCompression(); |
| encoder.EncodeHeaderSet(header_block, &buffer); |
| |
| SpdyContinuationIR continuation(/* stream_id = */ 42); |
| continuation.take_encoding(std::move(buffer)); |
| continuation.set_end_headers(true); |
| |
| SpdySerializedFrame frame(framer.SerializeContinuation(continuation)); |
| if (use_output_) { |
| ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| // Test that if we send an unexpected CONTINUATION |
| // we signal an error (but don't crash). |
| TEST_P(SpdyFramerTest, SendUnexpectedContinuation) { |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| // frame-format off |
| char kH2FrameData[] = { |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x09, // Type: CONTINUATION |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x2a, // Stream: 42 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| 0x03, // Value Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x66, 0x6f, 0x6f, // foo |
| 0x03, // Value Len: 3 |
| 0x62, 0x61, 0x72, // bar |
| }; |
| // frame-format on |
| |
| SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); |
| |
| // We shouldn't have to read the whole frame before we signal an error. |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, _)); |
| EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); |
| EXPECT_TRUE(deframer_.HasError()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, CreatePushPromiseThenContinuationUncompressed) { |
| { |
| // Test framing in a case such that a PUSH_PROMISE frame, with one byte of |
| // padding, cannot hold all the data payload, which is overflowed to the |
| // consecutive CONTINUATION frame. |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| const char kDescription[] = |
| "PUSH_PROMISE and CONTINUATION frames with one byte of padding"; |
| |
| // frame-format off |
| const unsigned char kPartialPushPromiseFrameData[] = { |
| 0x00, 0x3f, 0xf6, // Length: 16374 |
| 0x05, // Type: PUSH_PROMISE |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x2a, // Stream: 42 |
| 0x00, // PadLen: 0 trailing bytes |
| 0x00, 0x00, 0x00, 0x39, // Promise: 57 |
| |
| 0x00, // Unindexed Entry |
| 0x03, // Name Len: 3 |
| 0x78, 0x78, 0x78, // xxx |
| 0x7f, 0x80, 0x7f, // Value Len: 16361 |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| }; |
| const unsigned char kContinuationFrameData[] = { |
| 0x00, 0x00, 0x16, // Length: 22 |
| 0x09, // Type: CONTINUATION |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x2a, // Stream: 42 |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, 0x78, 0x78, 0x78, // xxxx |
| 0x78, // x |
| }; |
| // frame-format on |
| |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 42, |
| /* promised_stream_id = */ 57); |
| push_promise.set_padding_len(1); |
| std::string big_value(kHttp2MaxControlFrameSendSize, 'x'); |
| push_promise.SetHeader("xxx", big_value); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer, push_promise, use_output_ ? &output_ : nullptr)); |
| |
| // The entire frame should look like below: |
| // Name Length in Byte |
| // ------------------------------------------- Begin of PUSH_PROMISE frame |
| // PUSH_PROMISE header 9 |
| // Pad length field 1 |
| // Promised stream 4 |
| // Length field of key 2 |
| // Content of key 3 |
| // Length field of value 3 |
| // Part of big_value 16361 |
| // ------------------------------------------- Begin of CONTINUATION frame |
| // CONTINUATION header 9 |
| // Remaining of big_value 22 |
| // ------------------------------------------- End |
| |
| // Length of everything listed above except big_value. |
| int len_non_data_payload = 31; |
| EXPECT_EQ(kHttp2MaxControlFrameSendSize + len_non_data_payload, |
| frame.size()); |
| |
| // Partially compare the PUSH_PROMISE frame against the template. |
| const unsigned char* frame_data = |
| reinterpret_cast<const unsigned char*>(frame.data()); |
| CompareCharArraysWithHexError(kDescription, frame_data, |
| ABSL_ARRAYSIZE(kPartialPushPromiseFrameData), |
| kPartialPushPromiseFrameData, |
| ABSL_ARRAYSIZE(kPartialPushPromiseFrameData)); |
| |
| // Compare the CONTINUATION frame against the template. |
| frame_data += kHttp2MaxControlFrameSendSize; |
| CompareCharArraysWithHexError( |
| kDescription, frame_data, ABSL_ARRAYSIZE(kContinuationFrameData), |
| kContinuationFrameData, ABSL_ARRAYSIZE(kContinuationFrameData)); |
| } |
| } |
| |
| TEST_P(SpdyFramerTest, CreateAltSvc) { |
| const char kDescription[] = "ALTSVC frame"; |
| const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC); |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x49, kType, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 'o', |
| 'r', 'i', 'g', 'i', 'n', 'p', 'i', 'd', '1', '=', '"', 'h', |
| 'o', 's', 't', ':', '4', '4', '3', '"', ';', ' ', 'm', 'a', |
| '=', '5', ',', 'p', '%', '2', '2', '%', '3', 'D', 'i', '%', |
| '3', 'A', 'd', '=', '"', 'h', '_', '\\', '\\', 'o', '\\', '"', |
| 's', 't', ':', '1', '2', '3', '"', ';', ' ', 'm', 'a', '=', |
| '4', '2', ';', ' ', 'v', '=', '"', '2', '4', '"'}; |
| SpdyAltSvcIR altsvc_ir(/* stream_id = */ 3); |
| altsvc_ir.set_origin("origin"); |
| altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector())); |
| altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( |
| "p\"=i:d", "h_\\o\"st", 123, 42, |
| SpdyAltSvcWireFormat::VersionVector{24})); |
| SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| TEST_P(SpdyFramerTest, CreatePriority) { |
| const char kDescription[] = "PRIORITY frame"; |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x05, // Length: 5 |
| 0x02, // Type: PRIORITY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x02, // Stream: 2 |
| 0x80, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) |
| 0x10, // Weight: 17 |
| }; |
| SpdyPriorityIR priority_ir(/* stream_id = */ 2, |
| /* parent_stream_id = */ 1, |
| /* weight = */ 17, |
| /* exclusive = */ true); |
| SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| TEST_P(SpdyFramerTest, CreatePriorityUpdate) { |
| const char kDescription[] = "PRIORITY_UPDATE frame"; |
| const unsigned char kType = |
| SerializeFrameType(SpdyFrameType::PRIORITY_UPDATE); |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x07, // frame length |
| kType, // frame type |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 for PRIORITY_UPDATE |
| 0x00, 0x00, 0x00, 0x03, // prioritized stream ID |
| 'u', '=', '0'}; // priority field value |
| SpdyPriorityUpdateIR priority_update_ir(/* stream_id = */ 0, |
| /* prioritized_stream_id = */ 3, |
| /* priority_field_value = */ "u=0"); |
| SpdySerializedFrame frame(framer_.SerializeFrame(priority_update_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(priority_update_ir, &output_), |
| frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| TEST_P(SpdyFramerTest, CreateAcceptCh) { |
| const char kDescription[] = "ACCEPT_CH frame"; |
| const unsigned char kType = SerializeFrameType(SpdyFrameType::ACCEPT_CH); |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x2d, // frame length |
| kType, // frame type |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 for ACCEPT_CH |
| 0x00, 0x0f, // origin length |
| 'w', 'w', 'w', '.', 'e', 'x', // origin |
| 'a', 'm', 'p', 'l', 'e', '.', // |
| 'c', 'o', 'm', // |
| 0x00, 0x03, // value length |
| 'f', 'o', 'o', // value |
| 0x00, 0x10, // origin length |
| 'm', 'a', 'i', 'l', '.', 'e', // |
| 'x', 'a', 'm', 'p', 'l', 'e', // |
| '.', 'c', 'o', 'm', // |
| 0x00, 0x03, // value length |
| 'b', 'a', 'r'}; // value |
| SpdyAcceptChIR accept_ch_ir( |
| {{"www.example.com", "foo"}, {"mail.example.com", "bar"}}); |
| SpdySerializedFrame frame(framer_.SerializeFrame(accept_ch_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(accept_ch_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| TEST_P(SpdyFramerTest, CreateUnknown) { |
| const char kDescription[] = "Unknown frame"; |
| const uint8_t kType = 0xaf; |
| const uint8_t kFlags = 0x11; |
| const uint8_t kLength = strlen(kDescription); |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, kLength, // Length: 13 |
| kType, // Type: undefined |
| kFlags, // Flags: arbitrary, undefined |
| 0x00, 0x00, 0x00, 0x02, // Stream: 2 |
| 0x55, 0x6e, 0x6b, 0x6e, // "Unkn" |
| 0x6f, 0x77, 0x6e, 0x20, // "own " |
| 0x66, 0x72, 0x61, 0x6d, // "fram" |
| 0x65, // "e" |
| }; |
| SpdyUnknownIR unknown_ir(/* stream_id = */ 2, |
| /* type = */ kType, |
| /* flags = */ kFlags, |
| /* payload = */ kDescription); |
| SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| // Test serialization of a SpdyUnknownIR with a defined type, a length field |
| // that does not match the payload size and in fact exceeds framer limits, and a |
| // stream ID that effectively flips the reserved bit. |
| TEST_P(SpdyFramerTest, CreateUnknownUnchecked) { |
| const char kDescription[] = "Unknown frame"; |
| const uint8_t kType = 0x00; |
| const uint8_t kFlags = 0x11; |
| const uint8_t kLength = std::numeric_limits<uint8_t>::max(); |
| const unsigned int kStreamId = kStreamIdMask + 42; |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, kLength, // Length: 16426 |
| kType, // Type: DATA, defined |
| kFlags, // Flags: arbitrary, undefined |
| 0x80, 0x00, 0x00, 0x29, // Stream: 2147483689 |
| 0x55, 0x6e, 0x6b, 0x6e, // "Unkn" |
| 0x6f, 0x77, 0x6e, 0x20, // "own " |
| 0x66, 0x72, 0x61, 0x6d, // "fram" |
| 0x65, // "e" |
| }; |
| TestSpdyUnknownIR unknown_ir(/* stream_id = */ kStreamId, |
| /* type = */ kType, |
| /* flags = */ kFlags, |
| /* payload = */ kDescription); |
| unknown_ir.set_length(kLength); |
| SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir)); |
| if (use_output_) { |
| EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) { |
| SpdyHeadersIR headers_ir(/* stream_id = */ 1); |
| headers_ir.SetHeader("alpha", "beta"); |
| headers_ir.SetHeader("gamma", "delta"); |
| SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( |
| &framer_, headers_ir, use_output_ ? &output_ : nullptr)); |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.control_frame_header_data_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_EQ(0, visitor.end_of_stream_count_); |
| EXPECT_EQ(headers_ir.header_block(), visitor.headers_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) { |
| SpdyHeadersIR headers_ir(/* stream_id = */ 1); |
| headers_ir.set_fin(true); |
| headers_ir.SetHeader("alpha", "beta"); |
| headers_ir.SetHeader("gamma", "delta"); |
| SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( |
| &framer_, headers_ir, use_output_ ? &output_ : nullptr)); |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.control_frame_header_data_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_EQ(1, visitor.end_of_stream_count_); |
| EXPECT_EQ(headers_ir.header_block(), visitor.headers_); |
| } |
| |
| TEST_P(SpdyFramerTest, TooLargeHeadersFrameUsesContinuation) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.set_padding_len(256); |
| |
| // Exact payload length will change with HPACK, but this should be long |
| // enough to cause an overflow. |
| const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; |
| std::string big_value(kBigValueSize, 'x'); |
| headers.SetHeader("aa", big_value); |
| SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers, use_output_ ? &output_ : nullptr)); |
| EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(1, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, MultipleContinuationFramesWithIterator) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| auto headers = std::make_unique<SpdyHeadersIR>(/* stream_id = */ 1); |
| headers->set_padding_len(256); |
| |
| // Exact payload length will change with HPACK, but this should be long |
| // enough to cause an overflow. |
| const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; |
| std::string big_valuex(kBigValueSize, 'x'); |
| headers->SetHeader("aa", big_valuex); |
| std::string big_valuez(kBigValueSize, 'z'); |
| headers->SetHeader("bb", big_valuez); |
| |
| SpdyFramer::SpdyHeaderFrameIterator frame_it(&framer, std::move(headers)); |
| |
| EXPECT_TRUE(frame_it.HasNextFrame()); |
| EXPECT_GT(frame_it.NextFrame(&output_), 0u); |
| SpdySerializedFrame headers_frame(output_.Begin(), output_.Size(), false); |
| EXPECT_EQ(headers_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(headers_frame.data()), |
| headers_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| |
| output_.Reset(); |
| EXPECT_TRUE(frame_it.HasNextFrame()); |
| EXPECT_GT(frame_it.NextFrame(&output_), 0u); |
| SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false); |
| EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(first_cont_frame.data()), |
| first_cont_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(1, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| |
| output_.Reset(); |
| EXPECT_TRUE(frame_it.HasNextFrame()); |
| EXPECT_GT(frame_it.NextFrame(&output_), 0u); |
| SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false); |
| EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(second_cont_frame.data()), |
| second_cont_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(2, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| |
| EXPECT_FALSE(frame_it.HasNextFrame()); |
| } |
| |
| TEST_P(SpdyFramerTest, PushPromiseFramesWithIterator) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| auto push_promise = |
| std::make_unique<SpdyPushPromiseIR>(/* stream_id = */ 1, |
| /* promised_stream_id = */ 2); |
| push_promise->set_padding_len(256); |
| |
| // Exact payload length will change with HPACK, but this should be long |
| // enough to cause an overflow. |
| const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; |
| std::string big_valuex(kBigValueSize, 'x'); |
| push_promise->SetHeader("aa", big_valuex); |
| std::string big_valuez(kBigValueSize, 'z'); |
| push_promise->SetHeader("bb", big_valuez); |
| |
| SpdyFramer::SpdyPushPromiseFrameIterator frame_it(&framer, |
| std::move(push_promise)); |
| |
| EXPECT_TRUE(frame_it.HasNextFrame()); |
| EXPECT_GT(frame_it.NextFrame(&output_), 0u); |
| SpdySerializedFrame push_promise_frame(output_.Begin(), output_.Size(), |
| false); |
| EXPECT_EQ(push_promise_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(push_promise_frame.data()), |
| push_promise_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.push_promise_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| |
| EXPECT_TRUE(frame_it.HasNextFrame()); |
| output_.Reset(); |
| EXPECT_GT(frame_it.NextFrame(&output_), 0u); |
| SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false); |
| |
| EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(first_cont_frame.data()), |
| first_cont_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.push_promise_frame_count_); |
| EXPECT_EQ(1, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| |
| EXPECT_TRUE(frame_it.HasNextFrame()); |
| output_.Reset(); |
| EXPECT_GT(frame_it.NextFrame(&output_), 0u); |
| SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false); |
| EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(second_cont_frame.data()), |
| second_cont_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.push_promise_frame_count_); |
| EXPECT_EQ(2, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| |
| EXPECT_FALSE(frame_it.HasNextFrame()); |
| } |
| |
| class SpdyControlFrameIteratorTest : public quiche::test::QuicheTest { |
| public: |
| SpdyControlFrameIteratorTest() : output_(output_buffer, kSize) {} |
| |
| void RunTest(std::unique_ptr<SpdyFrameIR> ir) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| SpdySerializedFrame frame(framer.SerializeFrame(*ir)); |
| std::unique_ptr<SpdyFrameSequence> it = |
| SpdyFramer::CreateIterator(&framer, std::move(ir)); |
| EXPECT_TRUE(it->HasNextFrame()); |
| EXPECT_EQ(it->NextFrame(&output_), frame.size()); |
| EXPECT_FALSE(it->HasNextFrame()); |
| } |
| |
| private: |
| ArrayOutputBuffer output_; |
| }; |
| |
| TEST_F(SpdyControlFrameIteratorTest, RstStreamFrameWithIterator) { |
| auto ir = std::make_unique<SpdyRstStreamIR>(0, ERROR_CODE_PROTOCOL_ERROR); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_F(SpdyControlFrameIteratorTest, SettingsFrameWithIterator) { |
| auto ir = std::make_unique<SpdySettingsIR>(); |
| uint32_t kValue = 0x0a0b0c0d; |
| SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE; |
| ir->AddSetting(kId, kValue); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_F(SpdyControlFrameIteratorTest, PingFrameWithIterator) { |
| const SpdyPingId kPingId = 0x123456789abcdeffULL; |
| auto ir = std::make_unique<SpdyPingIR>(kPingId); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_F(SpdyControlFrameIteratorTest, GoAwayFrameWithIterator) { |
| auto ir = std::make_unique<SpdyGoAwayIR>(0, ERROR_CODE_NO_ERROR, "GA"); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_F(SpdyControlFrameIteratorTest, WindowUpdateFrameWithIterator) { |
| auto ir = std::make_unique<SpdyWindowUpdateIR>(1, 1); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_F(SpdyControlFrameIteratorTest, AtlSvcFrameWithIterator) { |
| auto ir = std::make_unique<SpdyAltSvcIR>(3); |
| ir->set_origin("origin"); |
| ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector())); |
| ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService( |
| "p\"=i:d", "h_\\o\"st", 123, 42, |
| SpdyAltSvcWireFormat::VersionVector{24})); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_F(SpdyControlFrameIteratorTest, PriorityFrameWithIterator) { |
| auto ir = std::make_unique<SpdyPriorityIR>(2, 1, 17, true); |
| RunTest(std::move(ir)); |
| } |
| |
| TEST_P(SpdyFramerTest, TooLargePushPromiseFrameUsesContinuation) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 1, |
| /* promised_stream_id = */ 2); |
| push_promise.set_padding_len(256); |
| |
| // Exact payload length will change with HPACK, but this should be long |
| // enough to cause an overflow. |
| const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; |
| std::string big_value(kBigValueSize, 'x'); |
| push_promise.SetHeader("aa", big_value); |
| SpdySerializedFrame control_frame(SpdyFramerPeer::SerializePushPromise( |
| &framer, push_promise, use_output_ ? &output_ : nullptr)); |
| EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize); |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_TRUE(visitor.header_buffer_valid_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.push_promise_frame_count_); |
| EXPECT_EQ(1, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| } |
| |
| // Check that the framer stops delivering header data chunks once the visitor |
| // declares it doesn't want any more. This is important to guard against |
| // "zip bomb" types of attacks. |
| TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) { |
| const size_t kHeaderBufferChunks = 4; |
| const size_t kHeaderBufferSize = |
| kHttp2DefaultFramePayloadLimit / kHeaderBufferChunks; |
| const size_t kBigValueSize = kHeaderBufferSize * 2; |
| std::string big_value(kBigValueSize, 'x'); |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.set_fin(true); |
| headers.SetHeader("aa", big_value); |
| SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( |
| &framer_, headers, use_output_ ? &output_ : nullptr)); |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| visitor.set_header_buffer_size(kHeaderBufferSize); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| // It's up to the visitor to ignore extraneous header data; the framer |
| // won't throw an error. |
| EXPECT_GT(visitor.header_bytes_received_, visitor.header_buffer_size_); |
| EXPECT_EQ(1, visitor.end_of_stream_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) { |
| // Create a GoAway frame that has a few extra bytes at the end. |
| const size_t length = 20; |
| |
| // HTTP/2 GOAWAY frames are only bound by a minimal length, since they may |
| // carry opaque data. Verify that minimal length is tested. |
| ASSERT_GT(kGoawayFrameMinimumSize, kFrameHeaderSize); |
| const size_t less_than_min_length = |
| kGoawayFrameMinimumSize - kFrameHeaderSize - 1; |
| ASSERT_LE(less_than_min_length, std::numeric_limits<unsigned char>::max()); |
| const unsigned char kH2Len = static_cast<unsigned char>(less_than_min_length); |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, kH2Len, // Length: min length - 1 |
| 0x07, // Type: GOAWAY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x00, 0x00, 0x00, // Last: 0 |
| 0x00, 0x00, 0x00, // Truncated Status Field |
| }; |
| const size_t pad_length = length + kFrameHeaderSize - sizeof(kH2FrameData); |
| std::string pad(pad_length, 'A'); |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| |
| visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); |
| visitor.SimulateInFramer(reinterpret_cast<const unsigned char*>(pad.c_str()), |
| pad.length()); |
| |
| EXPECT_EQ(1, visitor.error_count_); // This generated an error. |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(0, visitor.goaway_count_); // Frame not parsed. |
| } |
| |
| TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) { |
| SpdySettingsIR settings_ir; |
| SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| SetFrameLength(&control_frame, 0); |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), kFrameHeaderSize); |
| // Zero-len settings frames are permitted as of HTTP/2. |
| EXPECT_EQ(0, visitor.error_count_); |
| } |
| |
| // Tests handling of SETTINGS frames with invalid length. |
| TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) { |
| SpdySettingsIR settings_ir; |
| |
| // Add settings to more than fill the frame so that we don't get a buffer |
| // overflow when calling SimulateInFramer() below. These settings must be |
| // distinct parameters because SpdySettingsIR has a map for settings, and |
| // will collapse multiple copies of the same parameter. |
| settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 0x00000002); |
| settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 0x00000002); |
| SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| const size_t kNewLength = 8; |
| SetFrameLength(&control_frame, kNewLength); |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| kFrameHeaderSize + kNewLength); |
| // Should generate an error, since its not possible to have a |
| // settings frame of length kNewLength. |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of larger SETTINGS frames. |
| TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) { |
| SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5); |
| settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6); |
| settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7); |
| |
| SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| |
| // Read all at once. |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(3, visitor.setting_count_); |
| EXPECT_EQ(1, visitor.settings_ack_sent_); |
| |
| // Read data in small chunks. |
| size_t framed_data = 0; |
| size_t unframed_data = control_frame.size(); |
| size_t kReadChunkSize = 5; // Read five bytes at a time. |
| while (unframed_data > 0) { |
| size_t to_read = std::min(kReadChunkSize, unframed_data); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data() + framed_data), |
| to_read); |
| unframed_data -= to_read; |
| framed_data += to_read; |
| } |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(3 * 2, visitor.setting_count_); |
| EXPECT_EQ(2, visitor.settings_ack_sent_); |
| } |
| |
| // Tests handling of SETTINGS frame with duplicate entries. |
| TEST_P(SpdyFramerTest, ReadDuplicateSettings) { |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x01, // Param: HEADER_TABLE_SIZE |
| 0x00, 0x00, 0x00, 0x02, // Value: 2 |
| 0x00, 0x01, // Param: HEADER_TABLE_SIZE |
| 0x00, 0x00, 0x00, 0x03, // Value: 3 |
| 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS |
| 0x00, 0x00, 0x00, 0x03, // Value: 3 |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); |
| |
| // In HTTP/2, duplicate settings are allowed; |
| // each setting replaces the previous value for that setting. |
| EXPECT_EQ(3, visitor.setting_count_); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.settings_ack_sent_); |
| } |
| |
| // Tests handling of SETTINGS frame with a setting we don't recognize. |
| TEST_P(SpdyFramerTest, ReadUnknownSettingsId) { |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x06, // Length: 6 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x10, // Param: 16 |
| 0x00, 0x00, 0x00, 0x02, // Value: 2 |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); |
| |
| // In HTTP/2, we ignore unknown settings because of extensions. However, we |
| // pass the SETTINGS to the visitor, which can decide how to handle them. |
| EXPECT_EQ(1, visitor.setting_count_); |
| EXPECT_EQ(0, visitor.error_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadKnownAndUnknownSettingsWithExtension) { |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x10, // Param: 16 |
| 0x00, 0x00, 0x00, 0x02, // Value: 2 |
| 0x00, 0x5f, // Param: 95 |
| 0x00, 0x01, 0x00, 0x02, // Value: 65538 |
| 0x00, 0x02, // Param: ENABLE_PUSH |
| 0x00, 0x00, 0x00, 0x01, // Value: 1 |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| TestExtension extension; |
| visitor.set_extension_visitor(&extension); |
| visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); |
| |
| // In HTTP/2, we ignore unknown settings because of extensions. However, we |
| // pass the SETTINGS to the visitor, which can decide how to handle them. |
| EXPECT_EQ(3, visitor.setting_count_); |
| EXPECT_EQ(0, visitor.error_count_); |
| |
| // The extension receives all SETTINGS, including the non-standard SETTINGS. |
| EXPECT_THAT( |
| extension.settings_received_, |
| testing::ElementsAre(testing::Pair(16, 2), testing::Pair(95, 65538), |
| testing::Pair(2, 1))); |
| } |
| |
| // Tests handling of SETTINGS frame with entries out of order. |
| TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) { |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x02, // Param: ENABLE_PUSH |
| 0x00, 0x00, 0x00, 0x02, // Value: 2 |
| 0x00, 0x01, // Param: HEADER_TABLE_SIZE |
| 0x00, 0x00, 0x00, 0x03, // Value: 3 |
| 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS |
| 0x00, 0x00, 0x00, 0x03, // Value: 3 |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); |
| |
| // In HTTP/2, settings are allowed in any order. |
| EXPECT_EQ(3, visitor.setting_count_); |
| EXPECT_EQ(0, visitor.error_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ProcessSettingsAckFrame) { |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x00, // Length: 0 |
| 0x04, // Type: SETTINGS |
| 0x01, // Flags: ACK |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(0, visitor.setting_count_); |
| EXPECT_EQ(1, visitor.settings_ack_received_); |
| } |
| |
| TEST_P(SpdyFramerTest, ProcessDataFrameWithPadding) { |
| const int kPaddingLen = 119; |
| const char data_payload[] = "hello"; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| SpdyDataIR data_ir(/* stream_id = */ 1, data_payload); |
| data_ir.set_padding_len(kPaddingLen); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| |
| int bytes_consumed = 0; |
| |
| // Send the frame header. |
| EXPECT_CALL(visitor, |
| OnDataFrameHeader(1, kPaddingLen + strlen(data_payload), false)); |
| QUICHE_CHECK_EQ(kDataFrameMinimumSize, |
| deframer_.ProcessInput(frame.data(), kDataFrameMinimumSize)); |
| QUICHE_CHECK_EQ(deframer_.state(), |
| Http2DecoderAdapter::SPDY_READ_DATA_FRAME_PADDING_LENGTH); |
| QUICHE_CHECK_EQ(deframer_.spdy_framer_error(), |
| Http2DecoderAdapter::SPDY_NO_ERROR); |
| bytes_consumed += kDataFrameMinimumSize; |
| |
| // Send the padding length field. |
| EXPECT_CALL(visitor, OnStreamPadLength(1, kPaddingLen - 1)); |
| QUICHE_CHECK_EQ(1u, deframer_.ProcessInput(frame.data() + bytes_consumed, 1)); |
| QUICHE_CHECK_EQ(deframer_.state(), |
| Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME); |
| QUICHE_CHECK_EQ(deframer_.spdy_framer_error(), |
| Http2DecoderAdapter::SPDY_NO_ERROR); |
| bytes_consumed += 1; |
| |
| // Send the first two bytes of the data payload, i.e., "he". |
| EXPECT_CALL(visitor, OnStreamFrameData(1, _, 2)); |
| QUICHE_CHECK_EQ(2u, deframer_.ProcessInput(frame.data() + bytes_consumed, 2)); |
| QUICHE_CHECK_EQ(deframer_.state(), |
| Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME); |
| QUICHE_CHECK_EQ(deframer_.spdy_framer_error(), |
| Http2DecoderAdapter::SPDY_NO_ERROR); |
| bytes_consumed += 2; |
| |
| // Send the rest three bytes of the data payload, i.e., "llo". |
| EXPECT_CALL(visitor, OnStreamFrameData(1, _, 3)); |
| QUICHE_CHECK_EQ(3u, deframer_.ProcessInput(frame.data() + bytes_consumed, 3)); |
| QUICHE_CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING); |
| QUICHE_CHECK_EQ(deframer_.spdy_framer_error(), |
| Http2DecoderAdapter::SPDY_NO_ERROR); |
| bytes_consumed += 3; |
| |
| // Send the first 100 bytes of the padding payload. |
| EXPECT_CALL(visitor, OnStreamPadding(1, 100)); |
| QUICHE_CHECK_EQ(100u, |
| deframer_.ProcessInput(frame.data() + bytes_consumed, 100)); |
| QUICHE_CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING); |
| QUICHE_CHECK_EQ(deframer_.spdy_framer_error(), |
| Http2DecoderAdapter::SPDY_NO_ERROR); |
| bytes_consumed += 100; |
| |
| // Send rest of the padding payload. |
| EXPECT_CALL(visitor, OnStreamPadding(1, 18)); |
| QUICHE_CHECK_EQ(18u, |
| deframer_.ProcessInput(frame.data() + bytes_consumed, 18)); |
| QUICHE_CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_READY_FOR_FRAME); |
| QUICHE_CHECK_EQ(deframer_.spdy_framer_error(), |
| Http2DecoderAdapter::SPDY_NO_ERROR); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadWindowUpdate) { |
| SpdySerializedFrame control_frame(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2))); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2), &output_)); |
| control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_EQ(1u, visitor.last_window_update_stream_); |
| EXPECT_EQ(2, visitor.last_window_update_delta_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadCompressedPushPromise) { |
| SpdyPushPromiseIR push_promise(/* stream_id = */ 42, |
| /* promised_stream_id = */ 57); |
| push_promise.SetHeader("foo", "bar"); |
| push_promise.SetHeader("bar", "foofoo"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer_, push_promise, use_output_ ? &output_ : nullptr)); |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), |
| frame.size()); |
| EXPECT_EQ(42u, visitor.last_push_promise_stream_); |
| EXPECT_EQ(57u, visitor.last_push_promise_promised_stream_); |
| EXPECT_EQ(push_promise.header_block(), visitor.headers_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadHeadersWithContinuation) { |
| // frame-format off |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x01, // Type: HEADERS |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x03, // PadLen: 3 trailing bytes |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', 'o', 'o', 'k', 'i', 'e', // Name |
| 0x07, // Value Len: 7 |
| 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value |
| 0x00, 0x00, 0x00, // Padding |
| |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x09, // Type: CONTINUATION |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', 'o', 'o', 'k', 'i', 'e', // Name |
| 0x08, // Value Len: 7 |
| 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', // Name (split) |
| |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x09, // Type: CONTINUATION |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 'o', 'o', 'k', 'i', 'e', // Name (continued) |
| 0x00, // Value Len: 0 |
| 0x00, // Unindexed Entry |
| 0x04, // Name Len: 4 |
| 'n', 'a', 'm', 'e', // Name |
| 0x05, // Value Len: 5 |
| 'v', 'a', 'l', 'u', 'e', // Value |
| }; |
| // frame-format on |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(2, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_EQ(0, visitor.end_of_stream_count_); |
| |
| EXPECT_THAT( |
| visitor.headers_, |
| testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "), |
| testing::Pair("name", "value"))); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadHeadersWithContinuationAndFin) { |
| // frame-format off |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x10, // Length: 20 |
| 0x01, // Type: HEADERS |
| 0x01, // Flags: END_STREAM |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', 'o', 'o', 'k', 'i', 'e', // Name |
| 0x07, // Value Len: 7 |
| 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value |
| |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x09, // Type: CONTINUATION |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', 'o', 'o', 'k', 'i', 'e', // Name |
| 0x08, // Value Len: 7 |
| 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', // Name (split) |
| |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x09, // Type: CONTINUATION |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 'o', 'o', 'k', 'i', 'e', // Name (continued) |
| 0x00, // Value Len: 0 |
| 0x00, // Unindexed Entry |
| 0x04, // Name Len: 4 |
| 'n', 'a', 'm', 'e', // Name |
| 0x05, // Value Len: 5 |
| 'v', 'a', 'l', 'u', 'e', // Value |
| }; |
| // frame-format on |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(2, visitor.continuation_count_); |
| EXPECT_EQ(1, visitor.fin_flag_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_EQ(1, visitor.end_of_stream_count_); |
| |
| EXPECT_THAT( |
| visitor.headers_, |
| testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "), |
| testing::Pair("name", "value"))); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadPushPromiseWithContinuation) { |
| // frame-format off |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x17, // Length: 23 |
| 0x05, // Type: PUSH_PROMISE |
| 0x08, // Flags: PADDED |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x02, // PadLen: 2 trailing bytes |
| 0x00, 0x00, 0x00, 0x2a, // Promise: 42 |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', 'o', 'o', 'k', 'i', 'e', // Name |
| 0x07, // Value Len: 7 |
| 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value |
| 0x00, 0x00, // Padding |
| |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x09, // Type: CONTINUATION |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', 'o', 'o', 'k', 'i', 'e', // Name |
| 0x08, // Value Len: 7 |
| 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value |
| 0x00, // Unindexed Entry |
| 0x06, // Name Len: 6 |
| 'c', // Name (split) |
| |
| 0x00, 0x00, 0x12, // Length: 18 |
| 0x09, // Type: CONTINUATION |
| 0x04, // Flags: END_HEADERS |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 'o', 'o', 'k', 'i', 'e', // Name (continued) |
| 0x00, // Value Len: 0 |
| 0x00, // Unindexed Entry |
| 0x04, // Name Len: 4 |
| 'n', 'a', 'm', 'e', // Name |
| 0x05, // Value Len: 5 |
| 'v', 'a', 'l', 'u', 'e', // Value |
| }; |
| // frame-format on |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1u, visitor.last_push_promise_stream_); |
| EXPECT_EQ(42u, visitor.last_push_promise_promised_stream_); |
| EXPECT_EQ(2, visitor.continuation_count_); |
| EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
| EXPECT_EQ(0, visitor.end_of_stream_count_); |
| |
| EXPECT_THAT( |
| visitor.headers_, |
| testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "), |
| testing::Pair("name", "value"))); |
| } |
| |
| // Receiving an unknown frame when a continuation is expected should |
| // result in a SPDY_UNEXPECTED_FRAME error |
| TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuation) { |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x10, // Length: 16 |
| 0x01, // Type: HEADERS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0xa9, // Type: UnknownFrameType(169) |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // Payload |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x08, 0x62, 0x61, 0x7a, // |
| 0x3d, 0x62, 0x69, 0x6e, // |
| 0x67, 0x00, 0x06, 0x63, // |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| // Assume the unknown frame is allowed |
| visitor.on_unknown_frame_result_ = true; |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0u, visitor.header_buffer_length_); |
| } |
| |
| // Receiving an unknown frame when a continuation is expected should |
| // result in a SPDY_UNEXPECTED_FRAME error |
| TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuationWithExtension) { |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x10, // Length: 16 |
| 0x01, // Type: HEADERS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0xa9, // Type: UnknownFrameType(169) |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // Payload |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x08, 0x62, 0x61, 0x7a, // |
| 0x3d, 0x62, 0x69, 0x6e, // |
| 0x67, 0x00, 0x06, 0x63, // |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| TestExtension extension; |
| visitor.set_extension_visitor(&extension); |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0u, visitor.header_buffer_length_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReceiveContinuationOnWrongStream) { |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x10, // Length: 16 |
| 0x01, // Type: HEADERS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0x09, // Type: CONTINUATION |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x02, // Stream: 2 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x08, 0x62, 0x61, 0x7a, // |
| 0x3d, 0x62, 0x69, 0x6e, // |
| 0x67, 0x00, 0x06, 0x63, // |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0u, visitor.header_buffer_length_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadContinuationOutOfOrder) { |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x18, // Length: 24 |
| 0x09, // Type: CONTINUATION |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0u, visitor.header_buffer_length_); |
| } |
| |
| TEST_P(SpdyFramerTest, ExpectContinuationReceiveData) { |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x10, // Length: 16 |
| 0x01, // Type: HEADERS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| |
| 0x00, 0x00, 0x00, // Length: 0 |
| 0x00, // Type: DATA |
| 0x01, // Flags: END_STREAM |
| 0x00, 0x00, 0x00, 0x04, // Stream: 4 |
| |
| 0xde, 0xad, 0xbe, 0xef, // Truncated Frame Header |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0u, visitor.header_buffer_length_); |
| EXPECT_EQ(0, visitor.data_frame_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ExpectContinuationReceiveControlFrame) { |
| const unsigned char kInput[] = { |
| 0x00, 0x00, 0x10, // Length: 16 |
| 0x01, // Type: HEADERS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| |
| 0x00, 0x00, 0x10, // Length: 16 |
| 0x01, // Type: HEADERS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x06, 0x63, 0x6f, // HPACK |
| 0x6f, 0x6b, 0x69, 0x65, // |
| 0x07, 0x66, 0x6f, 0x6f, // |
| 0x3d, 0x62, 0x61, 0x72, // |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kInput, sizeof(kInput)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.continuation_count_); |
| EXPECT_EQ(0u, visitor.header_buffer_length_); |
| EXPECT_EQ(0, visitor.data_frame_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadGarbage) { |
| unsigned char garbage_frame[256]; |
| memset(garbage_frame, ~0, sizeof(garbage_frame)); |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame)); |
| EXPECT_EQ(1, visitor.error_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadUnknownExtensionFrame) { |
| // The unrecognized frame type should still have a valid length. |
| const unsigned char unknown_frame[] = { |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0xff, // Type: UnknownFrameType(255) |
| 0xff, // Flags: 0xff |
| 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set) |
| 0xff, 0xff, 0xff, 0xff, // Payload |
| 0xff, 0xff, 0xff, 0xff, // |
| }; |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| |
| // Simulate the case where the stream id validation checks out. |
| visitor.on_unknown_frame_result_ = true; |
| visitor.SimulateInFramer(unknown_frame, ABSL_ARRAYSIZE(unknown_frame)); |
| EXPECT_EQ(0, visitor.error_count_); |
| |
| // Follow it up with a valid control frame to make sure we handle |
| // subsequent frames correctly. |
| SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10); |
| SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.setting_count_); |
| EXPECT_EQ(1, visitor.settings_ack_sent_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadUnknownExtensionFrameWithExtension) { |
| // The unrecognized frame type should still have a valid length. |
| const unsigned char unknown_frame[] = { |
| 0x00, 0x00, 0x14, // Length: 20 |
| 0xff, // Type: UnknownFrameType(255) |
| 0xff, // Flags: 0xff |
| 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set) |
| 0xff, 0xff, 0xff, 0xff, // Payload |
| 0xff, 0xff, 0xff, 0xff, // |
| 0xff, 0xff, 0xff, 0xff, // |
| 0xff, 0xff, 0xff, 0xff, // |
| 0xff, 0xff, 0xff, 0xff, // |
| }; |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| TestExtension extension; |
| visitor.set_extension_visitor(&extension); |
| visitor.SimulateInFramer(unknown_frame, ABSL_ARRAYSIZE(unknown_frame)); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(0x7fffffffu, extension.stream_id_); |
| EXPECT_EQ(20u, extension.length_); |
| EXPECT_EQ(255, extension.type_); |
| EXPECT_EQ(0xff, extension.flags_); |
| EXPECT_EQ(std::string(20, '\xff'), extension.payload_); |
| |
| // Follow it up with a valid control frame to make sure we handle |
| // subsequent frames correctly. |
| SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10); |
| SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data()), |
| control_frame.size()); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.setting_count_); |
| EXPECT_EQ(1, visitor.settings_ack_sent_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadGarbageWithValidLength) { |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0xff, // Type: UnknownFrameType(255) |
| 0xff, // Flags: 0xff |
| 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set) |
| 0xff, 0xff, 0xff, 0xff, // Payload |
| 0xff, 0xff, 0xff, 0xff, // |
| }; |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, ABSL_ARRAYSIZE(kFrameData)); |
| EXPECT_EQ(1, visitor.error_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadGarbageHPACKEncoding) { |
| const unsigned char kInput[] = { |
| 0x00, 0x12, 0x01, // Length: 4609 |
| 0x04, // Type: SETTINGS |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x01, 0xef, // Stream: 495 |
| 0xef, 0xff, // Param: 61439 |
| 0xff, 0xff, 0xff, 0xff, // Value: 4294967295 |
| 0xff, 0xff, // Param: 0xffff |
| 0xff, 0xff, 0xff, 0xff, // Value: 4294967295 |
| 0xff, 0xff, 0xff, 0xff, // Settings (Truncated) |
| 0xff, // |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kInput, ABSL_ARRAYSIZE(kInput)); |
| EXPECT_EQ(1, visitor.error_count_); |
| } |
| |
| TEST_P(SpdyFramerTest, SizesTest) { |
| EXPECT_EQ(9u, kFrameHeaderSize); |
| EXPECT_EQ(9u, kDataFrameMinimumSize); |
| EXPECT_EQ(9u, kHeadersFrameMinimumSize); |
| EXPECT_EQ(14u, kPriorityFrameSize); |
| EXPECT_EQ(13u, kRstStreamFrameSize); |
| EXPECT_EQ(9u, kSettingsFrameMinimumSize); |
| EXPECT_EQ(13u, kPushPromiseFrameMinimumSize); |
| EXPECT_EQ(17u, kPingFrameSize); |
| EXPECT_EQ(17u, kGoawayFrameMinimumSize); |
| EXPECT_EQ(13u, kWindowUpdateFrameSize); |
| EXPECT_EQ(9u, kContinuationFrameMinimumSize); |
| EXPECT_EQ(11u, kGetAltSvcFrameMinimumSize); |
| EXPECT_EQ(9u, kFrameMinimumSize); |
| |
| EXPECT_EQ(16384u, kHttp2DefaultFramePayloadLimit); |
| EXPECT_EQ(16393u, kHttp2DefaultFrameSizeLimit); |
| } |
| |
| TEST_P(SpdyFramerTest, StateToStringTest) { |
| EXPECT_STREQ("ERROR", Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_ERROR)); |
| EXPECT_STREQ("FRAME_COMPLETE", Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_FRAME_COMPLETE)); |
| EXPECT_STREQ("READY_FOR_FRAME", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_READY_FOR_FRAME)); |
| EXPECT_STREQ("READING_COMMON_HEADER", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_READING_COMMON_HEADER)); |
| EXPECT_STREQ("CONTROL_FRAME_PAYLOAD", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_CONTROL_FRAME_PAYLOAD)); |
| EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_IGNORE_REMAINING_PAYLOAD)); |
| EXPECT_STREQ("FORWARD_STREAM_FRAME", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME)); |
| EXPECT_STREQ( |
| "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK)); |
| EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_CONTROL_FRAME_HEADER_BLOCK)); |
| EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_SETTINGS_FRAME_PAYLOAD)); |
| EXPECT_STREQ("SPDY_ALTSVC_FRAME_PAYLOAD", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD)); |
| EXPECT_STREQ("UNKNOWN_STATE", |
| Http2DecoderAdapter::StateToString( |
| Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD + 1)); |
| } |
| |
| TEST_P(SpdyFramerTest, SpdyFramerErrorToStringTest) { |
| EXPECT_STREQ("NO_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_NO_ERROR)); |
| EXPECT_STREQ("INVALID_STREAM_ID", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); |
| EXPECT_STREQ("INVALID_CONTROL_FRAME", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME)); |
| EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_CONTROL_PAYLOAD_TOO_LARGE)); |
| EXPECT_STREQ("ZLIB_INIT_FAILURE", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_ZLIB_INIT_FAILURE)); |
| EXPECT_STREQ("UNSUPPORTED_VERSION", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_UNSUPPORTED_VERSION)); |
| EXPECT_STREQ("DECOMPRESS_FAILURE", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_DECOMPRESS_FAILURE)); |
| EXPECT_STREQ("COMPRESS_FAILURE", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_COMPRESS_FAILURE)); |
| EXPECT_STREQ("GOAWAY_FRAME_CORRUPT", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_GOAWAY_FRAME_CORRUPT)); |
| EXPECT_STREQ("RST_STREAM_FRAME_CORRUPT", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_RST_STREAM_FRAME_CORRUPT)); |
| EXPECT_STREQ("INVALID_PADDING", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INVALID_PADDING)); |
| EXPECT_STREQ("INVALID_DATA_FRAME_FLAGS", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS)); |
| EXPECT_STREQ("INVALID_CONTROL_FRAME_FLAGS", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_FLAGS)); |
| EXPECT_STREQ("UNEXPECTED_FRAME", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME)); |
| EXPECT_STREQ("INTERNAL_FRAMER_ERROR", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INTERNAL_FRAMER_ERROR)); |
| EXPECT_STREQ("INVALID_CONTROL_FRAME_SIZE", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE)); |
| EXPECT_STREQ("OVERSIZED_PAYLOAD", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD)); |
| EXPECT_STREQ("UNKNOWN_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString( |
| Http2DecoderAdapter::LAST_ERROR)); |
| EXPECT_STREQ("UNKNOWN_ERROR", |
| Http2DecoderAdapter::SpdyFramerErrorToString( |
| static_cast<Http2DecoderAdapter::SpdyFramerError>( |
| Http2DecoderAdapter::LAST_ERROR + 1))); |
| } |
| |
| TEST_P(SpdyFramerTest, DataFrameFlagsV4) { |
| uint8_t valid_data_flags = DATA_FLAG_FIN | DATA_FLAG_PADDED; |
| |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyDataIR data_ir(/* stream_id = */ 1, "hello"); |
| SpdySerializedFrame frame(framer_.SerializeData(data_ir)); |
| SetFrameFlags(&frame, flags); |
| |
| if (flags & ~valid_data_flags) { |
| EXPECT_CALL(visitor, OnError(_, _)); |
| } else { |
| EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, flags & DATA_FLAG_FIN)); |
| if (flags & DATA_FLAG_PADDED) { |
| // The first byte of payload is parsed as padding length, but 'h' |
| // (0x68) is too large a padding length for a 5 byte payload. |
| EXPECT_CALL(visitor, OnStreamPadding(_, 1)); |
| // Expect Error since the frame ends prematurely. |
| EXPECT_CALL(visitor, OnError(_, _)); |
| } else { |
| EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5)); |
| if (flags & DATA_FLAG_FIN) { |
| EXPECT_CALL(visitor, OnStreamEnd(_)); |
| } |
| } |
| } |
| |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| if (flags & ~valid_data_flags) { |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } else if (flags & DATA_FLAG_PADDED) { |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } else { |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| deframer_.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, RstStreamFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| SpdyRstStreamIR rst_stream(/* stream_id = */ 13, ERROR_CODE_CANCEL); |
| SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| SetFrameFlags(&frame, flags); |
| |
| EXPECT_CALL(visitor, OnRstStream(13, ERROR_CODE_CANCEL)); |
| |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| deframer_.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, SettingsFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16); |
| SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| SetFrameFlags(&frame, flags); |
| |
| if (flags & SETTINGS_FLAG_ACK) { |
| EXPECT_CALL(visitor, OnError(_, _)); |
| } else { |
| EXPECT_CALL(visitor, OnSettings()); |
| EXPECT_CALL(visitor, OnSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16)); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| } |
| |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| if (flags & SETTINGS_FLAG_ACK) { |
| // The frame is invalid because ACK frames should have no payload. |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } else { |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| deframer_.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, GoawayFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 97, ERROR_CODE_NO_ERROR, |
| "test"); |
| SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| SetFrameFlags(&frame, flags); |
| |
| EXPECT_CALL(visitor, OnGoAway(97, ERROR_CODE_NO_ERROR)); |
| |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| deframer_.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, HeadersFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| Http2DecoderAdapter deframer; |
| deframer.set_visitor(&visitor); |
| |
| SpdyHeadersIR headers_ir(/* stream_id = */ 57); |
| if (flags & HEADERS_FLAG_PRIORITY) { |
| headers_ir.set_weight(3); |
| headers_ir.set_has_priority(true); |
| headers_ir.set_parent_stream_id(5); |
| headers_ir.set_exclusive(true); |
| } |
| headers_ir.SetHeader("foo", "bar"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( |
| &framer, headers_ir, use_output_ ? &output_ : nullptr)); |
| uint8_t set_flags = flags & ~HEADERS_FLAG_PADDED; |
| SetFrameFlags(&frame, set_flags); |
| |
| // Expected callback values |
| SpdyStreamId stream_id = 57; |
| bool has_priority = false; |
| int weight = 0; |
| SpdyStreamId parent_stream_id = 0; |
| bool exclusive = false; |
| bool fin = flags & CONTROL_FLAG_FIN; |
| bool end = flags & HEADERS_FLAG_END_HEADERS; |
| if (flags & HEADERS_FLAG_PRIORITY) { |
| has_priority = true; |
| weight = 3; |
| parent_stream_id = 5; |
| exclusive = true; |
| } |
| EXPECT_CALL(visitor, OnHeaders(stream_id, has_priority, weight, |
| parent_stream_id, exclusive, fin, end)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(57)).Times(1); |
| if (end) { |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(57)).Times(1); |
| } |
| if (flags & DATA_FLAG_FIN && end) { |
| EXPECT_CALL(visitor, OnStreamEnd(_)); |
| } else { |
| // Do not close the stream if we are expecting a CONTINUATION frame. |
| EXPECT_CALL(visitor, OnStreamEnd(_)).Times(0); |
| } |
| |
| deframer.ProcessInput(frame.data(), frame.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer.spdy_framer_error()); |
| deframer.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, PingFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| SpdySerializedFrame frame(framer_.SerializePing(SpdyPingIR(42))); |
| SetFrameFlags(&frame, flags); |
| |
| EXPECT_CALL(visitor, OnPing(42, flags & PING_FLAG_ACK)); |
| |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| deframer_.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, WindowUpdateFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdySerializedFrame frame(framer_.SerializeWindowUpdate( |
| SpdyWindowUpdateIR(/* stream_id = */ 4, /* delta = */ 1024))); |
| SetFrameFlags(&frame, flags); |
| |
| EXPECT_CALL(visitor, OnWindowUpdate(4, 1024)); |
| |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| deframer_.Reset(); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, PushPromiseFrameFlags) { |
| const SpdyStreamId client_id = 123; // Must be odd. |
| const SpdyStreamId promised_id = 22; // Must be even. |
| uint8_t flags = 0; |
| do { |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| testing::StrictMock<test::MockDebugVisitor> debug_visitor; |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| Http2DecoderAdapter deframer; |
| deframer.set_visitor(&visitor); |
| deframer.set_debug_visitor(&debug_visitor); |
| framer.set_debug_visitor(&debug_visitor); |
| |
| EXPECT_CALL( |
| debug_visitor, |
| OnSendCompressedFrame(client_id, SpdyFrameType::PUSH_PROMISE, _, _)); |
| |
| SpdyPushPromiseIR push_promise(client_id, promised_id); |
| push_promise.SetHeader("foo", "bar"); |
| SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( |
| &framer, push_promise, use_output_ ? &output_ : nullptr)); |
| // TODO(jgraettinger): Add padding to SpdyPushPromiseIR, |
| // and implement framing. |
| SetFrameFlags(&frame, flags & ~HEADERS_FLAG_PADDED); |
| |
| bool end = flags & PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame( |
| client_id, SpdyFrameType::PUSH_PROMISE, _)); |
| EXPECT_CALL(visitor, OnPushPromise(client_id, promised_id, end)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(client_id)).Times(1); |
| if (end) { |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(client_id)).Times(1); |
| } |
| |
| deframer.ProcessInput(frame.data(), frame.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer.spdy_framer_error()); |
| } while (++flags != 0); |
| } |
| |
| TEST_P(SpdyFramerTest, ContinuationFrameFlags) { |
| uint8_t flags = 0; |
| do { |
| if (use_output_) { |
| output_.Reset(); |
| } |
| SCOPED_TRACE(testing::Message() |
| << "Flags " << std::hex << static_cast<int>(flags)); |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| testing::StrictMock<test::MockDebugVisitor> debug_visitor; |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| Http2DecoderAdapter deframer; |
| deframer.set_visitor(&visitor); |
| deframer.set_debug_visitor(&debug_visitor); |
| framer.set_debug_visitor(&debug_visitor); |
| |
| EXPECT_CALL(debug_visitor, |
| OnSendCompressedFrame(42, SpdyFrameType::HEADERS, _, _)); |
| EXPECT_CALL(debug_visitor, |
| OnReceiveCompressedFrame(42, SpdyFrameType::HEADERS, _)); |
| EXPECT_CALL(visitor, OnHeaders(42, false, 0, 0, false, false, false)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(42)).Times(1); |
| |
| SpdyHeadersIR headers_ir(/* stream_id = */ 42); |
| headers_ir.SetHeader("foo", "bar"); |
| SpdySerializedFrame frame0; |
| if (use_output_) { |
| EXPECT_TRUE(framer.SerializeHeaders(headers_ir, &output_)); |
| frame0 = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } else { |
| frame0 = framer.SerializeHeaders(headers_ir); |
| } |
| SetFrameFlags(&frame0, 0); |
| |
| SpdyContinuationIR continuation(/* stream_id = */ 42); |
| SpdySerializedFrame frame1; |
| if (use_output_) { |
| char* begin = output_.Begin() + output_.Size(); |
| ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_)); |
| frame1 = |
| SpdySerializedFrame(begin, output_.Size() - frame0.size(), false); |
| } else { |
| frame1 = framer.SerializeContinuation(continuation); |
| } |
| SetFrameFlags(&frame1, flags); |
| |
| EXPECT_CALL(debug_visitor, |
| OnReceiveCompressedFrame(42, SpdyFrameType::CONTINUATION, _)); |
| EXPECT_CALL(visitor, OnContinuation(42, flags & HEADERS_FLAG_END_HEADERS)); |
| bool end = flags & HEADERS_FLAG_END_HEADERS; |
| if (end) { |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(42)).Times(1); |
| } |
| |
| deframer.ProcessInput(frame0.data(), frame0.size()); |
| deframer.ProcessInput(frame1.data(), frame1.size()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer.spdy_framer_error()); |
| } while (++flags != 0); |
| } |
| |
| // TODO(mlavan): Add TEST_P(SpdyFramerTest, AltSvcFrameFlags) |
| |
| // Test handling of a RST_STREAM with out-of-bounds status codes. |
| TEST_P(SpdyFramerTest, RstStreamStatusBounds) { |
| const unsigned char kH2RstStreamInvalid[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR |
| }; |
| const unsigned char kH2RstStreamNumStatusCodes[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0xff, // Error: 255 |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_NO_ERROR)); |
| deframer_.ProcessInput(reinterpret_cast<const char*>(kH2RstStreamInvalid), |
| ABSL_ARRAYSIZE(kH2RstStreamInvalid)); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| deframer_.Reset(); |
| |
| EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_INTERNAL_ERROR)); |
| deframer_.ProcessInput( |
| reinterpret_cast<const char*>(kH2RstStreamNumStatusCodes), |
| ABSL_ARRAYSIZE(kH2RstStreamNumStatusCodes)); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Test handling of GOAWAY frames with out-of-bounds status code. |
| TEST_P(SpdyFramerTest, GoAwayStatusBounds) { |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x0a, // Length: 10 |
| 0x07, // Type: GOAWAY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x00, 0x00, 0x01, // Last: 1 |
| 0xff, 0xff, 0xff, 0xff, // Error: 0xffffffff |
| 0x47, 0x41, // Description |
| }; |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnGoAway(1, ERROR_CODE_INTERNAL_ERROR)); |
| deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData), |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of a GOAWAY frame with out-of-bounds stream ID. |
| TEST_P(SpdyFramerTest, GoAwayStreamIdBounds) { |
| const unsigned char kH2FrameData[] = { |
| 0x00, 0x00, 0x08, // Length: 8 |
| 0x07, // Type: GOAWAY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0xff, 0xff, 0xff, 0xff, // Last: 0x7fffffff (R-bit set) |
| 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnGoAway(0x7fffffff, ERROR_CODE_NO_ERROR)); |
| deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData), |
| ABSL_ARRAYSIZE(kH2FrameData)); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, OnAltSvcWithOrigin) { |
| const SpdyStreamId kStreamId = 0; // Stream id must be zero if origin given. |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyAltSvcWireFormat::AlternativeService altsvc1( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); |
| SpdyAltSvcWireFormat::AlternativeService altsvc2( |
| "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); |
| SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; |
| altsvc_vector.push_back(altsvc1); |
| altsvc_vector.push_back(altsvc2); |
| EXPECT_CALL(visitor, |
| OnAltSvc(kStreamId, absl::string_view("o_r|g!n"), altsvc_vector)); |
| |
| SpdyAltSvcIR altsvc_ir(kStreamId); |
| altsvc_ir.set_origin("o_r|g!n"); |
| altsvc_ir.add_altsvc(altsvc1); |
| altsvc_ir.add_altsvc(altsvc2); |
| SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, OnAltSvcNoOrigin) { |
| const SpdyStreamId kStreamId = 1; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| SpdyAltSvcWireFormat::AlternativeService altsvc1( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); |
| SpdyAltSvcWireFormat::AlternativeService altsvc2( |
| "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); |
| SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; |
| altsvc_vector.push_back(altsvc1); |
| altsvc_vector.push_back(altsvc2); |
| EXPECT_CALL(visitor, |
| OnAltSvc(kStreamId, absl::string_view(""), altsvc_vector)); |
| |
| SpdyAltSvcIR altsvc_ir(kStreamId); |
| altsvc_ir.add_altsvc(altsvc1); |
| altsvc_ir.add_altsvc(altsvc2); |
| SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, OnAltSvcEmptyProtocolId) { |
| const SpdyStreamId kStreamId = 0; // Stream id must be zero if origin given. |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, |
| OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, _)); |
| |
| SpdyAltSvcIR altsvc_ir(kStreamId); |
| altsvc_ir.set_origin("o1"); |
| altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector())); |
| altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( |
| "", "h1", 443, 10, SpdyAltSvcWireFormat::VersionVector())); |
| SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); |
| if (use_output_) { |
| output_.Reset(); |
| EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size()); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, OnAltSvcBadLengths) { |
| const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC); |
| const unsigned char kFrameDataOriginLenLargerThanFrame[] = { |
| 0x00, 0x00, 0x05, kType, 0x00, 0x00, 0x00, |
| 0x00, 0x03, 0x42, 0x42, 'f', 'o', 'o', |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| |
| deframer_.set_visitor(&visitor); |
| visitor.SimulateInFramer(kFrameDataOriginLenLargerThanFrame, |
| sizeof(kFrameDataOriginLenLargerThanFrame)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of ALTSVC frames delivered in small chunks. |
| TEST_P(SpdyFramerTest, ReadChunkedAltSvcFrame) { |
| SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1); |
| SpdyAltSvcWireFormat::AlternativeService altsvc1( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); |
| SpdyAltSvcWireFormat::AlternativeService altsvc2( |
| "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); |
| altsvc_ir.add_altsvc(altsvc1); |
| altsvc_ir.add_altsvc(altsvc2); |
| |
| SpdySerializedFrame control_frame(framer_.SerializeAltSvc(altsvc_ir)); |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| |
| // Read data in small chunks. |
| size_t framed_data = 0; |
| size_t unframed_data = control_frame.size(); |
| size_t kReadChunkSize = 5; // Read five bytes at a time. |
| while (unframed_data > 0) { |
| size_t to_read = std::min(kReadChunkSize, unframed_data); |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(control_frame.data() + framed_data), |
| to_read); |
| unframed_data -= to_read; |
| framed_data += to_read; |
| } |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.altsvc_count_); |
| ASSERT_NE(nullptr, visitor.test_altsvc_ir_); |
| ASSERT_EQ(2u, visitor.test_altsvc_ir_->altsvc_vector().size()); |
| EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[0] == altsvc1); |
| EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[1] == altsvc2); |
| } |
| |
| // While RFC7838 Section 4 says that an ALTSVC frame on stream 0 with empty |
| // origin MUST be ignored, it is not implemented at the framer level: instead, |
| // such frames are passed on to the consumer. |
| TEST_P(SpdyFramerTest, ReadAltSvcFrame) { |
| constexpr struct { |
| uint32_t stream_id; |
| const char* origin; |
| } test_cases[] = {{0, ""}, |
| {1, ""}, |
| {0, "https://www.example.com"}, |
| {1, "https://www.example.com"}}; |
| for (const auto& test_case : test_cases) { |
| SpdyAltSvcIR altsvc_ir(test_case.stream_id); |
| SpdyAltSvcWireFormat::AlternativeService altsvc( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); |
| altsvc_ir.add_altsvc(altsvc); |
| altsvc_ir.set_origin(test_case.origin); |
| SpdySerializedFrame frame(framer_.SerializeAltSvc(altsvc_ir)); |
| |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| deframer_.set_visitor(&visitor); |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.altsvc_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| } |
| |
| // An ALTSVC frame with invalid Alt-Svc-Field-Value results in an error. |
| TEST_P(SpdyFramerTest, ErrorOnAltSvcFrameWithInvalidValue) { |
| // Alt-Svc-Field-Value must be "clear" or must contain an "=" character |
| // per RFC7838 Section 3. |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x16, // Length: 22 |
| 0x0a, // Type: ALTSVC |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, // Origin-Len: 0 |
| 0x74, 0x68, 0x69, 0x73, // thisisnotavalidvalue |
| 0x69, 0x73, 0x6e, 0x6f, 0x74, 0x61, 0x76, 0x61, |
| 0x6c, 0x69, 0x64, 0x76, 0x61, 0x6c, 0x75, 0x65, |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); |
| deframer_.set_visitor(&visitor); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(0, visitor.altsvc_count_); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, |
| deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadPriorityUpdateFrame) { |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x07, // payload length |
| 0x10, // frame type PRIORITY_UPDATE |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 |
| 0x00, 0x00, 0x00, 0x03, // prioritized stream ID, must not be zero |
| 'f', 'o', 'o' // priority field value |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnPriorityUpdate(3, "foo")); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| EXPECT_FALSE(deframer_.HasError()); |
| } |
| |
| TEST_P(SpdyFramerTest, ReadPriorityUpdateFrameWithEmptyPriorityFieldValue) { |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x04, // payload length |
| 0x10, // frame type PRIORITY_UPDATE |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 |
| 0x00, 0x00, 0x00, 0x03 // prioritized stream ID, must not be zero |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnPriorityUpdate(3, "")); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| EXPECT_FALSE(deframer_.HasError()); |
| } |
| |
| TEST_P(SpdyFramerTest, PriorityUpdateFrameWithEmptyPayload) { |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x00, // payload length |
| 0x10, // frame type PRIORITY_UPDATE |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, |
| OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, _)); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| EXPECT_TRUE(deframer_.HasError()); |
| } |
| |
| TEST_P(SpdyFramerTest, PriorityUpdateFrameWithShortPayload) { |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x02, // payload length |
| 0x10, // frame type PRIORITY_UPDATE |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 |
| 0x00, 0x01 // payload not long enough to hold 32 bits of prioritized |
| // stream ID |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, |
| OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, _)); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| EXPECT_TRUE(deframer_.HasError()); |
| } |
| |
| TEST_P(SpdyFramerTest, PriorityUpdateFrameOnIncorrectStream) { |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x04, // payload length |
| 0x10, // frame type PRIORITY_UPDATE |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x01, // invalid stream ID, must be 0 |
| 0x00, 0x00, 0x00, 0x01, // prioritized stream ID, must not be zero |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| EXPECT_TRUE(deframer_.HasError()); |
| } |
| |
| TEST_P(SpdyFramerTest, PriorityUpdateFramePrioritizingIncorrectStream) { |
| const char kFrameData[] = { |
| 0x00, 0x00, 0x04, // payload length |
| 0x10, // frame type PRIORITY_UPDATE |
| 0x00, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 |
| 0x00, 0x00, 0x00, 0x00, // prioritized stream ID, must not be zero |
| }; |
| |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); |
| deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); |
| EXPECT_TRUE(deframer_.HasError()); |
| } |
| |
| // Tests handling of PRIORITY frames. |
| TEST_P(SpdyFramerTest, ReadPriority) { |
| SpdyPriorityIR priority(/* stream_id = */ 3, |
| /* parent_stream_id = */ 1, |
| /* weight = */ 256, |
| /* exclusive = */ false); |
| SpdySerializedFrame frame(framer_.SerializePriority(priority)); |
| if (use_output_) { |
| output_.Reset(); |
| ASSERT_TRUE(framer_.SerializePriority(priority, &output_)); |
| frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); |
| } |
| testing::StrictMock<test::MockSpdyFramerVisitor> visitor; |
| deframer_.set_visitor(&visitor); |
| EXPECT_CALL(visitor, OnPriority(3, 1, 256, false)); |
| deframer_.ProcessInput(frame.data(), frame.size()); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of PRIORITY frame with incorrect size. |
| TEST_P(SpdyFramerTest, ReadIncorrectlySizedPriority) { |
| // PRIORITY frame of size 4, which isn't correct. |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x02, // Type: PRIORITY |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x00, 0x01, // Priority (Truncated) |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of PING frame with incorrect size. |
| TEST_P(SpdyFramerTest, ReadIncorrectlySizedPing) { |
| // PING frame of size 4, which isn't correct. |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x04, // Length: 4 |
| 0x06, // Type: PING |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x00, // Stream: 0 |
| 0x00, 0x00, 0x00, 0x01, // Ping (Truncated) |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of WINDOW_UPDATE frame with incorrect size. |
| TEST_P(SpdyFramerTest, ReadIncorrectlySizedWindowUpdate) { |
| // WINDOW_UPDATE frame of size 3, which isn't correct. |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x03, // Length: 3 |
| 0x08, // Type: WINDOW_UPDATE |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x01, // WindowUpdate (Truncated) |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Tests handling of RST_STREAM frame with incorrect size. |
| TEST_P(SpdyFramerTest, ReadIncorrectlySizedRstStream) { |
| // RST_STREAM frame of size 3, which isn't correct. |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x03, // Length: 3 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x03, // Stream: 3 |
| 0x00, 0x00, 0x01, // RstStream (Truncated) |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Regression test for https://crbug.com/548674: |
| // RST_STREAM with payload must not be accepted. |
| TEST_P(SpdyFramerTest, ReadInvalidRstStreamWithPayload) { |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x07, // Length: 7 |
| 0x03, // Type: RST_STREAM |
| 0x00, // Flags: none |
| 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR |
| 'f', 'o', 'o' // Payload: "foo" |
| }; |
| |
| TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); |
| visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); |
| |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, |
| visitor.deframer_.spdy_framer_error()) |
| << Http2DecoderAdapter::SpdyFramerErrorToString( |
| visitor.deframer_.spdy_framer_error()); |
| } |
| |
| // Test that SpdyFramer processes all passed input in one call to ProcessInput. |
| TEST_P(SpdyFramerTest, ProcessAllInput) { |
| auto visitor = |
| std::make_unique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION); |
| deframer_.set_visitor(visitor.get()); |
| |
| // Create two input frames. |
| SpdyHeadersIR headers(/* stream_id = */ 1); |
| headers.SetHeader("alpha", "beta"); |
| headers.SetHeader("gamma", "charlie"); |
| headers.SetHeader("cookie", "key1=value1; key2=value2"); |
| SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders( |
| &framer_, headers, use_output_ ? &output_ : nullptr)); |
| |
| const char four_score[] = "Four score and seven years ago"; |
| SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score); |
| SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir)); |
| |
| // Put them in a single buffer (new variables here to make it easy to |
| // change the order and type of frames). |
| SpdySerializedFrame frame1 = std::move(headers_frame); |
| SpdySerializedFrame frame2 = std::move(four_score_frame); |
| |
| const size_t frame1_size = frame1.size(); |
| const size_t frame2_size = frame2.size(); |
| |
| QUICHE_VLOG(1) << "frame1_size = " << frame1_size; |
| QUICHE_VLOG(1) << "frame2_size = " << frame2_size; |
| |
| std::string input_buffer; |
| input_buffer.append(frame1.data(), frame1_size); |
| input_buffer.append(frame2.data(), frame2_size); |
| |
| const char* buf = input_buffer.data(); |
| const size_t buf_size = input_buffer.size(); |
| |
| QUICHE_VLOG(1) << "buf_size = " << buf_size; |
| |
| size_t processed = deframer_.ProcessInput(buf, buf_size); |
| EXPECT_EQ(buf_size, processed); |
| EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); |
| EXPECT_EQ(1, visitor->headers_frame_count_); |
| EXPECT_EQ(1, visitor->data_frame_count_); |
| EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_)); |
| } |
| |
| namespace { |
| void CheckFrameAndIRSize(SpdyFrameIR* ir, |
| SpdyFramer* framer, |
| ArrayOutputBuffer* output_buffer) { |
| output_buffer->Reset(); |
| SpdyFrameType type = ir->frame_type(); |
| size_t ir_size = ir->size(); |
| framer->SerializeFrame(*ir, output_buffer); |
| if (type == SpdyFrameType::HEADERS || type == SpdyFrameType::PUSH_PROMISE) { |
| // For HEADERS and PUSH_PROMISE, the size is an estimate. |
| EXPECT_GE(ir_size, output_buffer->Size() * 9 / 10); |
| EXPECT_LT(ir_size, output_buffer->Size() * 11 / 10); |
| } else { |
| EXPECT_EQ(ir_size, output_buffer->Size()); |
| } |
| } |
| } // namespace |
| |
| TEST_P(SpdyFramerTest, SpdyFrameIRSize) { |
| SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); |
| |
| const char bytes[] = "this is a very short data frame"; |
| SpdyDataIR data_ir(1, absl::string_view(bytes, ABSL_ARRAYSIZE(bytes))); |
| CheckFrameAndIRSize(&data_ir, &framer, &output_); |
| |
| SpdyRstStreamIR rst_ir(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR); |
| CheckFrameAndIRSize(&rst_ir, &framer, &output_); |
| |
| SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5); |
| settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6); |
| settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7); |
| CheckFrameAndIRSize(&settings_ir, &framer, &output_); |
| |
| SpdyPingIR ping_ir(42); |
| CheckFrameAndIRSize(&ping_ir, &framer, &output_); |
| |
| SpdyGoAwayIR goaway_ir(97, ERROR_CODE_NO_ERROR, "Goaway description"); |
| CheckFrameAndIRSize(&goaway_ir, &framer, &output_); |
| |
| SpdyHeadersIR headers_ir(1); |
| headers_ir.SetHeader("alpha", "beta"); |
| headers_ir.SetHeader("gamma", "charlie"); |
| headers_ir.SetHeader("cookie", "key1=value1; key2=value2"); |
| CheckFrameAndIRSize(&headers_ir, &framer, &output_); |
| |
| SpdyHeadersIR headers_ir_with_continuation(1); |
| headers_ir_with_continuation.SetHeader("alpha", std::string(100000, 'x')); |
| headers_ir_with_continuation.SetHeader("beta", std::string(100000, 'x')); |
| headers_ir_with_continuation.SetHeader("cookie", "key1=value1; key2=value2"); |
| CheckFrameAndIRSize(&headers_ir_with_continuation, &framer, &output_); |
| |
| SpdyWindowUpdateIR window_update_ir(4, 1024); |
| CheckFrameAndIRSize(&window_update_ir, &framer, &output_); |
| |
| SpdyPushPromiseIR push_promise_ir(3, 8); |
| push_promise_ir.SetHeader("alpha", std::string(100000, 'x')); |
| push_promise_ir.SetHeader("beta", std::string(100000, 'x')); |
| push_promise_ir.SetHeader("cookie", "key1=value1; key2=value2"); |
| CheckFrameAndIRSize(&push_promise_ir, &framer, &output_); |
| |
| SpdyAltSvcWireFormat::AlternativeService altsvc1( |
| "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); |
| SpdyAltSvcWireFormat::AlternativeService altsvc2( |
| "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); |
| SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; |
| altsvc_vector.push_back(altsvc1); |
| altsvc_vector.push_back(altsvc2); |
| SpdyAltSvcIR altsvc_ir(0); |
| altsvc_ir.set_origin("o_r|g!n"); |
| altsvc_ir.add_altsvc(altsvc1); |
| altsvc_ir.add_altsvc(altsvc2); |
| CheckFrameAndIRSize(&altsvc_ir, &framer, &output_); |
| |
| SpdyPriorityIR priority_ir(3, 1, 256, false); |
| CheckFrameAndIRSize(&priority_ir, &framer, &output_); |
| |
| const char kDescription[] = "Unknown frame"; |
| const uint8_t kType = 0xaf; |
| const uint8_t kFlags = 0x11; |
| SpdyUnknownIR unknown_ir(2, kType, kFlags, kDescription); |
| CheckFrameAndIRSize(&unknown_ir, &framer, &output_); |
| } |
| |
| } // namespace test |
| |
| } // namespace spdy |