| // 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: DATA |
| 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, OnCommonHeader(1, 16384, 0x0, 0x0)); |
| 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: DATA |
| 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, OnCommonHeader(1, 16385, 0x0, 0x0)); |
| 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, OnCommonHeader(1, 5, 0x0, 0x9)); |
| 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, OnCommonHeader(1, 5, 0x0, 0x8)); |
| 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, OnCommonHeader(1, 5, 0x1, 0x8)); |
| 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, OnCommonHeader(1, 5, 0x1, 0x8)); |
| 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, OnCommonHeader(0, _, 0x0, _)); |
| 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, OnCommonHeader(0, _, 0x1, _)); |
| 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, OnCommonHeader(0, _, 0x2, _)); |
| 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, OnCommonHeader(0, _, 0x3, _)); |
| 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, OnCommonHeader(1, 6, 0x4, 0x0)); |
| 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, OnCommonHeader(1, 10, 0x7, 0x0)); |
| 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, OnCommonHeader(0, _, 0x9, _)); |
| 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, OnCommonHeader(0, _, 0x5, _)); |
| 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, OnCommonHeader(3, _, 0x5, _)); |
| 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; |
| HpackEncoder encoder; |
| encoder.DisableCompression(); |
| std::string buffer = encoder.EncodeHeaderBlock(header_set); |
| // 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: 6 |
| 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, OnCommonHeader(1, 1, 0x1, 0x4)); |
| EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, true)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(1)); |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(1)); |
| EXPECT_CALL(visitor, OnCommonHeader(1, 12, 0x0, 0x0)); |
| EXPECT_CALL(visitor, OnDataFrameHeader(1, 12, false)); |
| EXPECT_CALL(visitor, OnStreamFrameData(1, _, 12)); |
| EXPECT_CALL(visitor, OnCommonHeader(3, 6, 0x1, 0x24)); |
| EXPECT_CALL(visitor, OnHeaders(3, true, 131, 0, false, false, true)); |
| EXPECT_CALL(visitor, OnHeaderFrameStart(3)); |
| EXPECT_CALL(visitor, OnHeaderFrameEnd(3)); |
| EXPECT_CALL(visitor, OnCommonHeader(3, 8, 0x0, 0x0)); |
| 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"; |
| HpackEncoder encoder; |
| encoder.DisableCompression(); |
| std::string buffer = encoder.EncodeHeaderBlock(header_block); |
| |
| 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, OnCommonHeader(42, 18, 0x9, 0x4)); |
| 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", &
|