diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/net/spdy | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/net/spdy')
72 files changed, 38867 insertions, 0 deletions
diff --git a/chromium/net/spdy/buffered_spdy_framer.cc b/chromium/net/spdy/buffered_spdy_framer.cc new file mode 100644 index 00000000000..46d028404fe --- /dev/null +++ b/chromium/net/spdy/buffered_spdy_framer.cc @@ -0,0 +1,331 @@ +// 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 "net/spdy/buffered_spdy_framer.h" + +#include "base/logging.h" + +namespace net { + +SpdyMajorVersion NextProtoToSpdyMajorVersion(NextProto next_proto) { + switch (next_proto) { + case kProtoSPDY2: + case kProtoSPDY21: + return SPDY2; + case kProtoSPDY3: + case kProtoSPDY31: + return SPDY3; + // SPDY/4 and HTTP/2 share the same framing for now. + case kProtoSPDY4a2: + case kProtoHTTP2Draft04: + return SPDY4; + case kProtoUnknown: + case kProtoHTTP11: + case kProtoSPDY1: + case kProtoQUIC1SPDY3: + break; + } + NOTREACHED(); + return SPDY2; +} + +BufferedSpdyFramer::BufferedSpdyFramer(SpdyMajorVersion version, + bool enable_compression) + : spdy_framer_(version), + visitor_(NULL), + header_buffer_used_(0), + header_buffer_valid_(false), + header_stream_id_(SpdyFramer::kInvalidStream), + frames_received_(0) { + spdy_framer_.set_enable_compression(enable_compression); + memset(header_buffer_, 0, sizeof(header_buffer_)); +} + +BufferedSpdyFramer::~BufferedSpdyFramer() { +} + +void BufferedSpdyFramer::set_visitor( + BufferedSpdyFramerVisitorInterface* visitor) { + visitor_ = visitor; + spdy_framer_.set_visitor(this); +} + +void BufferedSpdyFramer::set_debug_visitor( + SpdyFramerDebugVisitorInterface* debug_visitor) { + spdy_framer_.set_debug_visitor(debug_visitor); +} + +void BufferedSpdyFramer::OnError(SpdyFramer* spdy_framer) { + DCHECK(spdy_framer); + visitor_->OnError(spdy_framer->error_code()); +} + +void BufferedSpdyFramer::OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional) { + frames_received_++; + DCHECK(!control_frame_fields_.get()); + control_frame_fields_.reset(new ControlFrameFields()); + control_frame_fields_->type = SYN_STREAM; + control_frame_fields_->stream_id = stream_id; + control_frame_fields_->associated_stream_id = associated_stream_id; + control_frame_fields_->priority = priority; + control_frame_fields_->credential_slot = credential_slot; + control_frame_fields_->fin = fin; + control_frame_fields_->unidirectional = unidirectional; + + InitHeaderStreaming(stream_id); +} + +void BufferedSpdyFramer::OnHeaders(SpdyStreamId stream_id, + bool fin) { + frames_received_++; + DCHECK(!control_frame_fields_.get()); + control_frame_fields_.reset(new ControlFrameFields()); + control_frame_fields_->type = HEADERS; + control_frame_fields_->stream_id = stream_id; + control_frame_fields_->fin = fin; + + InitHeaderStreaming(stream_id); +} + +void BufferedSpdyFramer::OnSynReply(SpdyStreamId stream_id, + bool fin) { + frames_received_++; + DCHECK(!control_frame_fields_.get()); + control_frame_fields_.reset(new ControlFrameFields()); + control_frame_fields_->type = SYN_REPLY; + control_frame_fields_->stream_id = stream_id; + control_frame_fields_->fin = fin; + + InitHeaderStreaming(stream_id); +} + +bool BufferedSpdyFramer::OnCredentialFrameData(const char* frame_data, + size_t len) { + DCHECK(false); + return false; +} + +bool BufferedSpdyFramer::OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) { + CHECK_EQ(header_stream_id_, stream_id); + + if (len == 0) { + // Indicates end-of-header-block. + CHECK(header_buffer_valid_); + + SpdyHeaderBlock headers; + size_t parsed_len = spdy_framer_.ParseHeaderBlockInBuffer( + header_buffer_, header_buffer_used_, &headers); + // TODO(rch): this really should be checking parsed_len != len, + // but a bunch of tests fail. Need to figure out why. + if (parsed_len == 0) { + visitor_->OnStreamError( + stream_id, "Could not parse Spdy Control Frame Header."); + return false; + } + DCHECK(control_frame_fields_.get()); + switch (control_frame_fields_->type) { + case SYN_STREAM: + visitor_->OnSynStream(control_frame_fields_->stream_id, + control_frame_fields_->associated_stream_id, + control_frame_fields_->priority, + control_frame_fields_->credential_slot, + control_frame_fields_->fin, + control_frame_fields_->unidirectional, + headers); + break; + case SYN_REPLY: + visitor_->OnSynReply(control_frame_fields_->stream_id, + control_frame_fields_->fin, + headers); + break; + case HEADERS: + visitor_->OnHeaders(control_frame_fields_->stream_id, + control_frame_fields_->fin, + headers); + break; + default: + DCHECK(false) << "Unexpect control frame type: " + << control_frame_fields_->type; + break; + } + control_frame_fields_.reset(NULL); + return true; + } + + const size_t available = kHeaderBufferSize - header_buffer_used_; + if (len > available) { + header_buffer_valid_ = false; + visitor_->OnStreamError( + stream_id, "Received more data than the allocated size."); + return false; + } + memcpy(header_buffer_ + header_buffer_used_, header_data, len); + header_buffer_used_ += len; + return true; +} + +void BufferedSpdyFramer::OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) { + frames_received_++; + header_stream_id_ = stream_id; +} + +void BufferedSpdyFramer::OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) { + visitor_->OnStreamFrameData(stream_id, data, len, fin); +} + +void BufferedSpdyFramer::OnSettings(bool clear_persisted) { + visitor_->OnSettings(clear_persisted); +} + +void BufferedSpdyFramer::OnSetting(SpdySettingsIds id, + uint8 flags, + uint32 value) { + visitor_->OnSetting(id, flags, value); +} + +void BufferedSpdyFramer::OnPing(uint32 unique_id) { + visitor_->OnPing(unique_id); +} + +void BufferedSpdyFramer::OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) { + visitor_->OnRstStream(stream_id, status); +} +void BufferedSpdyFramer::OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) { + visitor_->OnGoAway(last_accepted_stream_id, status); +} + +void BufferedSpdyFramer::OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) { + visitor_->OnWindowUpdate(stream_id, delta_window_size); +} + +void BufferedSpdyFramer::OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) { + visitor_->OnPushPromise(stream_id, promised_stream_id); +} + +int BufferedSpdyFramer::protocol_version() { + return spdy_framer_.protocol_version(); +} + +size_t BufferedSpdyFramer::ProcessInput(const char* data, size_t len) { + return spdy_framer_.ProcessInput(data, len); +} + +void BufferedSpdyFramer::Reset() { + spdy_framer_.Reset(); +} + +SpdyFramer::SpdyError BufferedSpdyFramer::error_code() const { + return spdy_framer_.error_code(); +} + +SpdyFramer::SpdyState BufferedSpdyFramer::state() const { + return spdy_framer_.state(); +} + +bool BufferedSpdyFramer::MessageFullyRead() { + return state() == SpdyFramer::SPDY_AUTO_RESET; +} + +bool BufferedSpdyFramer::HasError() { + return spdy_framer_.HasError(); +} + +SpdyFrame* BufferedSpdyFramer::CreateSynStream( + SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + return spdy_framer_.CreateSynStream(stream_id, associated_stream_id, priority, + credential_slot, flags, compressed, + headers); +} + +SpdyFrame* BufferedSpdyFramer::CreateSynReply( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + return spdy_framer_.CreateSynReply(stream_id, flags, compressed, headers); +} + +SpdyFrame* BufferedSpdyFramer::CreateRstStream( + SpdyStreamId stream_id, + SpdyRstStreamStatus status) const { + return spdy_framer_.CreateRstStream(stream_id, status); +} + +SpdyFrame* BufferedSpdyFramer::CreateSettings( + const SettingsMap& values) const { + return spdy_framer_.CreateSettings(values); +} + +SpdyFrame* BufferedSpdyFramer::CreatePingFrame( + uint32 unique_id) const { + return spdy_framer_.CreatePingFrame(unique_id); +} + +SpdyFrame* BufferedSpdyFramer::CreateGoAway( + SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) const { + return spdy_framer_.CreateGoAway(last_accepted_stream_id, status); +} + +SpdyFrame* BufferedSpdyFramer::CreateHeaders( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + return spdy_framer_.CreateHeaders(stream_id, flags, compressed, headers); +} + +SpdyFrame* BufferedSpdyFramer::CreateWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) const { + return spdy_framer_.CreateWindowUpdate(stream_id, delta_window_size); +} + +SpdyFrame* BufferedSpdyFramer::CreateCredentialFrame( + const SpdyCredential& credential) const { + return spdy_framer_.CreateCredentialFrame(credential); +} + +SpdyFrame* BufferedSpdyFramer::CreateDataFrame(SpdyStreamId stream_id, + const char* data, + uint32 len, + SpdyDataFlags flags) { + return spdy_framer_.CreateDataFrame(stream_id, data, len, flags); +} + +SpdyPriority BufferedSpdyFramer::GetHighestPriority() const { + return spdy_framer_.GetHighestPriority(); +} + +void BufferedSpdyFramer::InitHeaderStreaming(SpdyStreamId stream_id) { + memset(header_buffer_, 0, kHeaderBufferSize); + header_buffer_used_ = 0; + header_buffer_valid_ = true; + header_stream_id_ = stream_id; + DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream); +} + +} // namespace net diff --git a/chromium/net/spdy/buffered_spdy_framer.h b/chromium/net/spdy/buffered_spdy_framer.h new file mode 100644 index 00000000000..1786067d6a3 --- /dev/null +++ b/chromium/net/spdy/buffered_spdy_framer.h @@ -0,0 +1,262 @@ +// 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. + +#ifndef NET_SPDY_BUFFERED_SPDY_FRAMER_H_ +#define NET_SPDY_BUFFERED_SPDY_FRAMER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" +#include "net/socket/next_proto.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +// Returns the SPDY major version corresponding to the given NextProto +// value, which must represent a SPDY-like protocol. +NET_EXPORT_PRIVATE SpdyMajorVersion NextProtoToSpdyMajorVersion( + NextProto next_proto); + +class NET_EXPORT_PRIVATE BufferedSpdyFramerVisitorInterface { + public: + BufferedSpdyFramerVisitorInterface() {} + + // Called if an error is detected in the SpdyFrame protocol. + virtual void OnError(SpdyFramer::SpdyError error_code) = 0; + + // Called if an error is detected in a SPDY stream. + virtual void OnStreamError(SpdyStreamId stream_id, + const std::string& description) = 0; + + // Called after all the header data for SYN_STREAM control frame is received. + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional, + const SpdyHeaderBlock& headers) = 0; + + // Called after all the header data for SYN_REPLY control frame is received. + virtual void OnSynReply(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) = 0; + + // Called after all the header data for HEADERS control frame is received. + virtual void OnHeaders(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) = 0; + + // Called when data is received. + // |stream_id| The stream receiving data. + // |data| A buffer containing the data received. + // |len| The length of the data buffer (at most 2^24 - 1 for SPDY/3, + // but 2^16 - 1 - 8 for SPDY/4). + // When the other side has finished sending data on this stream, + // this method will be called with a zero-length buffer. + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) = 0; + + // Called when a SETTINGS frame is received. + // |clear_persisted| True if the respective flag is set on the SETTINGS frame. + virtual void OnSettings(bool clear_persisted) = 0; + + // Called when an individual setting within a SETTINGS frame has been parsed + // and validated. + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0; + + // Called when a PING frame has been parsed. + virtual void OnPing(uint32 unique_id) = 0; + + // Called when a RST_STREAM frame has been parsed. + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) = 0; + + // Called when a GOAWAY frame has been parsed. + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) = 0; + + // Called when a WINDOW_UPDATE frame has been parsed. + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) = 0; + + // Called when a PUSH_PROMISE frame has been parsed. + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) = 0; + + protected: + virtual ~BufferedSpdyFramerVisitorInterface() {} + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramerVisitorInterface); +}; + +class NET_EXPORT_PRIVATE BufferedSpdyFramer + : public SpdyFramerVisitorInterface { + public: + BufferedSpdyFramer(SpdyMajorVersion version, + bool enable_compression); + virtual ~BufferedSpdyFramer(); + + // Sets callbacks to be called from the buffered spdy framer. A visitor must + // be set, or else the framer will likely crash. It is acceptable for the + // visitor to do nothing. If this is called multiple times, only the last + // visitor will be used. + void set_visitor(BufferedSpdyFramerVisitorInterface* visitor); + + // Set debug callbacks to be called from the framer. The debug visitor is + // completely optional and need not be set in order for normal operation. + // If this is called multiple times, only the last visitor will be used. + void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor); + + // SpdyFramerVisitorInterface + virtual void OnError(SpdyFramer* spdy_framer) OVERRIDE; + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional) OVERRIDE; + virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE; + virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE; + virtual bool OnCredentialFrameData(const char* frame_data, + size_t len) OVERRIDE; + virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) OVERRIDE; + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) OVERRIDE; + virtual void OnSettings(bool clear_persisted) OVERRIDE; + virtual void OnSetting( + SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE; + virtual void OnPing(uint32 unique_id) OVERRIDE; + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) OVERRIDE; + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) OVERRIDE; + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) OVERRIDE; + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) OVERRIDE; + virtual void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) OVERRIDE; + + // SpdyFramer methods. + size_t ProcessInput(const char* data, size_t len); + int protocol_version(); + void Reset(); + SpdyFramer::SpdyError error_code() const; + SpdyFramer::SpdyState state() const; + bool MessageFullyRead(); + bool HasError(); + SpdyFrame* CreateSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers); + SpdyFrame* CreateSynReply(SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers); + SpdyFrame* CreateRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) const; + SpdyFrame* CreateSettings(const SettingsMap& values) const; + SpdyFrame* CreatePingFrame(uint32 unique_id) const; + SpdyFrame* CreateGoAway( + SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) const; + SpdyFrame* CreateHeaders(SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers); + SpdyFrame* CreateWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) const; + SpdyFrame* CreateCredentialFrame( + const SpdyCredential& credential) const; + SpdyFrame* CreateDataFrame(SpdyStreamId stream_id, + const char* data, + uint32 len, + SpdyDataFlags flags); + + // Serialize a frame of unknown type. + SpdySerializedFrame* SerializeFrame(const SpdyFrameIR& frame) { + return spdy_framer_.SerializeFrame(frame); + } + + SpdyPriority GetHighestPriority() const; + + size_t GetDataFrameMinimumSize() const { + return spdy_framer_.GetDataFrameMinimumSize(); + } + + size_t GetControlFrameHeaderSize() const { + return spdy_framer_.GetControlFrameHeaderSize(); + } + + size_t GetSynStreamMinimumSize() const { + return spdy_framer_.GetSynStreamMinimumSize(); + } + + size_t GetFrameMinimumSize() const { + return spdy_framer_.GetFrameMinimumSize(); + } + + size_t GetFrameMaximumSize() const { + return spdy_framer_.GetFrameMaximumSize(); + } + + size_t GetDataFrameMaximumPayload() const { + return spdy_framer_.GetDataFrameMaximumPayload(); + } + + int frames_received() const { return frames_received_; } + + private: + // The size of the header_buffer_. + enum { kHeaderBufferSize = 32 * 1024 }; + + void InitHeaderStreaming(SpdyStreamId stream_id); + + SpdyFramer spdy_framer_; + BufferedSpdyFramerVisitorInterface* visitor_; + + // Header block streaming state: + char header_buffer_[kHeaderBufferSize]; + size_t header_buffer_used_; + bool header_buffer_valid_; + SpdyStreamId header_stream_id_; + int frames_received_; + + // Collection of fields from control frames that we need to + // buffer up from the spdy framer. + struct ControlFrameFields { + SpdyFrameType type; + SpdyStreamId stream_id; + SpdyStreamId associated_stream_id; + SpdyPriority priority; + uint8 credential_slot; + bool fin; + bool unidirectional; + }; + scoped_ptr<ControlFrameFields> control_frame_fields_; + + DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramer); +}; + +} // namespace net + +#endif // NET_SPDY_BUFFERED_SPDY_FRAMER_H_ diff --git a/chromium/net/spdy/buffered_spdy_framer_unittest.cc b/chromium/net/spdy/buffered_spdy_framer_unittest.cc new file mode 100644 index 00000000000..849138f98eb --- /dev/null +++ b/chromium/net/spdy/buffered_spdy_framer_unittest.cc @@ -0,0 +1,283 @@ +// 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 "net/spdy/buffered_spdy_framer.h" + +#include "net/spdy/spdy_test_util_common.h" +#include "testing/platform_test.h" + +namespace net { + +namespace { + +class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { + public: + explicit TestBufferedSpdyVisitor(SpdyMajorVersion spdy_version) + : buffered_spdy_framer_(spdy_version, true), + error_count_(0), + setting_count_(0), + syn_frame_count_(0), + syn_reply_frame_count_(0), + headers_frame_count_(0), + header_stream_id_(-1) { + } + + virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE { + LOG(INFO) << "SpdyFramer Error: " << error_code; + error_count_++; + } + + virtual void OnStreamError( + SpdyStreamId stream_id, + const std::string& description) OVERRIDE { + LOG(INFO) << "SpdyFramer Error on stream: " << stream_id << " " + << description; + error_count_++; + } + + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional, + const SpdyHeaderBlock& headers) OVERRIDE { + header_stream_id_ = stream_id; + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + syn_frame_count_++; + headers_ = headers; + } + + virtual void OnSynReply(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) OVERRIDE { + header_stream_id_ = stream_id; + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + syn_reply_frame_count_++; + headers_ = headers; + } + + virtual void OnHeaders(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) OVERRIDE { + header_stream_id_ = stream_id; + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + headers_frame_count_++; + headers_ = headers; + } + + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) OVERRIDE { + LOG(FATAL) << "Unexpected OnStreamFrameData call."; + } + + virtual void OnSettings(bool clear_persisted) OVERRIDE {} + + virtual void OnSetting(SpdySettingsIds id, + uint8 flags, + uint32 value) OVERRIDE { + setting_count_++; + } + + virtual void OnPing(uint32 unique_id) OVERRIDE {} + + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) OVERRIDE { + } + + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) OVERRIDE { + } + + bool OnCredentialFrameData(const char*, size_t) { + LOG(FATAL) << "Unexpected OnCredentialFrameData call."; + return false; + } + + void OnDataFrameHeader(const SpdyFrame* frame) { + LOG(FATAL) << "Unexpected OnDataFrameHeader call."; + } + + void OnRstStream(const SpdyFrame& frame) {} + void OnGoAway(const SpdyFrame& frame) {} + void OnPing(const SpdyFrame& frame) {} + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) OVERRIDE {} + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) OVERRIDE {} + void OnCredential(const SpdyFrame& frame) {} + + // Convenience function which runs a framer simulation with particular input. + void SimulateInFramer(const unsigned char* input, size_t size) { + buffered_spdy_framer_.set_visitor(this); + size_t input_remaining = size; + const char* input_ptr = reinterpret_cast<const char*>(input); + while (input_remaining > 0 && + buffered_spdy_framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) { + // To make the tests more interesting, we feed random (amd 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; + size_t bytes_processed = + buffered_spdy_framer_.ProcessInput(input_ptr, bytes_read); + input_remaining -= bytes_processed; + input_ptr += bytes_processed; + } + } + + BufferedSpdyFramer buffered_spdy_framer_; + + // Counters from the visitor callbacks. + int error_count_; + int setting_count_; + int syn_frame_count_; + int syn_reply_frame_count_; + int headers_frame_count_; + + // Header block streaming state: + SpdyStreamId header_stream_id_; + + // Headers from OnSyn, OnSynReply and OnHeaders for verification. + SpdyHeaderBlock headers_; +}; + +} // namespace + +class BufferedSpdyFramerTest + : public PlatformTest, + public ::testing::WithParamInterface<NextProto> { + protected: + // Returns true if the two header blocks have equivalent content. + bool CompareHeaderBlocks(const SpdyHeaderBlock* expected, + const SpdyHeaderBlock* actual) { + if (expected->size() != actual->size()) { + LOG(ERROR) << "Expected " << expected->size() << " headers; actually got " + << actual->size() << "."; + return false; + } + for (SpdyHeaderBlock::const_iterator it = expected->begin(); + it != expected->end(); + ++it) { + SpdyHeaderBlock::const_iterator it2 = actual->find(it->first); + if (it2 == actual->end()) { + LOG(ERROR) << "Expected header name '" << it->first << "'."; + return false; + } + if (it->second.compare(it2->second) != 0) { + LOG(ERROR) << "Expected header named '" << it->first + << "' to have a value of '" << it->second + << "'. The actual value received was '" << it2->second + << "'."; + return false; + } + } + return true; + } + + SpdyMajorVersion spdy_version() { + return NextProtoToSpdyMajorVersion(GetParam()); + } +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + BufferedSpdyFramerTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +TEST_P(BufferedSpdyFramerTest, OnSetting) { + SpdyFramer framer(spdy_version()); + SettingsMap settings; + settings[SETTINGS_UPLOAD_BANDWIDTH] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000002); + settings[SETTINGS_DOWNLOAD_BANDWIDTH] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000003); + + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + TestBufferedSpdyVisitor visitor(spdy_version()); + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(2, visitor.setting_count_); +} + +TEST_P(BufferedSpdyFramerTest, ReadSynStreamHeaderBlock) { + SpdyHeaderBlock headers; + headers["aa"] = "vv"; + headers["bb"] = "ww"; + BufferedSpdyFramer framer(spdy_version(), true); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + + TestBufferedSpdyVisitor visitor(spdy_version()); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(BufferedSpdyFramerTest, ReadSynReplyHeaderBlock) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + BufferedSpdyFramer framer(spdy_version(), true); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynReply(1, // stream_id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + + TestBufferedSpdyVisitor visitor(spdy_version()); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(0, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(BufferedSpdyFramerTest, ReadHeadersHeaderBlock) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + BufferedSpdyFramer framer(spdy_version(), true); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateHeaders(1, // stream_id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + + TestBufferedSpdyVisitor visitor(spdy_version()); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(0, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_bitmasks.h b/chromium/net/spdy/spdy_bitmasks.h new file mode 100644 index 00000000000..72fb9484103 --- /dev/null +++ b/chromium/net/spdy/spdy_bitmasks.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef NET_SPDY_SPDY_BITMASKS_H_ +#define NET_SPDY_SPDY_BITMASKS_H_ + +namespace net { + +// StreamId mask from the SpdyHeader +const unsigned int kStreamIdMask = 0x7fffffff; + +// Control flag mask from the SpdyHeader +const unsigned int kControlFlagMask = 0x8000; + +// Priority mask from the SYN_FRAME +const unsigned int kSpdy3PriorityMask = 0xe0; +const unsigned int kSpdy2PriorityMask = 0xc0; + +// Mask the lower 24 bits. +const unsigned int kLengthMask = 0xffffff; + +// Legal flags on data packets. +const int kDataFlagsMask = 0x01; + +// Legal flags on control packets. +const int kControlFlagsMask = 0x03; + +} // namespace net + +#endif // NET_SPDY_SPDY_BITMASKS_H_ diff --git a/chromium/net/spdy/spdy_buffer.cc b/chromium/net/spdy/spdy_buffer.cc new file mode 100644 index 00000000000..b93ae0a6428 --- /dev/null +++ b/chromium/net/spdy/spdy_buffer.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_buffer.h" + +#include <cstring> + +#include "base/callback.h" +#include "base/logging.h" +#include "net/base/io_buffer.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +namespace { + +// Makes a SpdyFrame with |size| bytes of data copied from +// |data|. |data| must be non-NULL and |size| must be positive. +scoped_ptr<SpdyFrame> MakeSpdyFrame(const char* data, size_t size) { + DCHECK(data); + DCHECK_GT(size, 0u); + scoped_ptr<char[]> frame_data(new char[size]); + std::memcpy(frame_data.get(), data, size); + scoped_ptr<SpdyFrame> frame( + new SpdyFrame(frame_data.release(), size, true /* owns_buffer */)); + return frame.Pass(); +} + +} // namespace + +// This class is an IOBuffer implementation that simply holds a +// reference to a SharedFrame object and a fixed offset. Used by +// SpdyBuffer::GetIOBufferForRemainingData(). +class SpdyBuffer::SharedFrameIOBuffer : public IOBuffer { + public: + SharedFrameIOBuffer(const scoped_refptr<SharedFrame>& shared_frame, + size_t offset) + : IOBuffer(shared_frame->data->data() + offset), + shared_frame_(shared_frame), + offset_(offset) {} + + private: + virtual ~SharedFrameIOBuffer() { + // Prevent ~IOBuffer() from trying to delete |data_|. + data_ = NULL; + } + + const scoped_refptr<SharedFrame> shared_frame_; + const size_t offset_; + + DISALLOW_COPY_AND_ASSIGN(SharedFrameIOBuffer); +}; + +SpdyBuffer::SpdyBuffer(scoped_ptr<SpdyFrame> frame) + : shared_frame_(new SharedFrame()), + offset_(0) { + shared_frame_->data = frame.Pass(); +} + +// The given data may not be strictly a SPDY frame; we (ab)use +// |frame_| just as a container. +SpdyBuffer::SpdyBuffer(const char* data, size_t size) : + shared_frame_(new SharedFrame()), + offset_(0) { + shared_frame_->data = MakeSpdyFrame(data, size); +} + +SpdyBuffer::~SpdyBuffer() { + if (GetRemainingSize() > 0) + ConsumeHelper(GetRemainingSize(), DISCARD); +} + +const char* SpdyBuffer::GetRemainingData() const { + return shared_frame_->data->data() + offset_; +} + +size_t SpdyBuffer::GetRemainingSize() const { + return shared_frame_->data->size() - offset_; +} + +void SpdyBuffer::AddConsumeCallback(const ConsumeCallback& consume_callback) { + consume_callbacks_.push_back(consume_callback); +} + +void SpdyBuffer::Consume(size_t consume_size) { + ConsumeHelper(consume_size, CONSUME); +}; + +IOBuffer* SpdyBuffer::GetIOBufferForRemainingData() { + return new SharedFrameIOBuffer(shared_frame_, offset_); +} + +void SpdyBuffer::ConsumeHelper(size_t consume_size, + ConsumeSource consume_source) { + DCHECK_GE(consume_size, 1u); + DCHECK_LE(consume_size, GetRemainingSize()); + offset_ += consume_size; + for (std::vector<ConsumeCallback>::const_iterator it = + consume_callbacks_.begin(); it != consume_callbacks_.end(); ++it) { + it->Run(consume_size, consume_source); + } +}; + +} // namespace net diff --git a/chromium/net/spdy/spdy_buffer.h b/chromium/net/spdy/spdy_buffer.h new file mode 100644 index 00000000000..60bdab7465d --- /dev/null +++ b/chromium/net/spdy/spdy_buffer.h @@ -0,0 +1,104 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_BUFFER_H_ +#define NET_SPDY_SPDY_BUFFER_H_ + +#include <cstddef> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" + +namespace net { + +class IOBuffer; +class SpdyFrame; + +// SpdyBuffer is a class to hold data read from or to be written to a +// SPDY connection. It is similar to a DrainableIOBuffer but is not +// ref-counted and will include a way to get notified when Consume() +// is called. +// +// NOTE(akalin): This explicitly does not inherit from IOBuffer to +// avoid the needless ref-counting and to avoid working around the +// fact that IOBuffer member functions are not virtual. +class NET_EXPORT_PRIVATE SpdyBuffer { + public: + // The source of a call to a ConsumeCallback. + enum ConsumeSource { + // Called via a call to Consume(). + CONSUME, + // Called via the SpdyBuffer being destroyed. + DISCARD + }; + + // A Callback that gets called when bytes are consumed with the + // (non-zero) number of bytes consumed and the source of the + // consume. May be called any number of times with CONSUME as the + // source followed by at most one call with DISCARD as the + // source. The sum of the number of bytes consumed equals the total + // size of the buffer. + typedef base::Callback<void(size_t, ConsumeSource)> ConsumeCallback; + + // Construct with the data in the given frame. Assumes that data is + // owned by |frame| or outlives it. + explicit SpdyBuffer(scoped_ptr<SpdyFrame> frame); + + // Construct with a copy of the given raw data. |data| must be + // non-NULL and |size| must be non-zero. + SpdyBuffer(const char* data, size_t size); + + // If there are bytes remaining in the buffer, triggers a call to + // any consume callbacks with a DISCARD source. + ~SpdyBuffer(); + + // Returns the remaining (unconsumed) data. + const char* GetRemainingData() const; + + // Returns the number of remaining (unconsumed) bytes. + size_t GetRemainingSize() const; + + // Add a callback to be called when bytes are consumed. The + // ConsumeCallback should not do anything complicated; ideally it + // should only update a counter. In particular, it must *not* cause + // the SpdyBuffer itself to be destroyed. + void AddConsumeCallback(const ConsumeCallback& consume_callback); + + // Consume the given number of bytes, which must be positive but not + // greater than GetRemainingSize(). + void Consume(size_t consume_size); + + // Returns an IOBuffer pointing to the data starting at + // GetRemainingData(). Use with care; the returned IOBuffer is not + // updated when Consume() is called. However, it may still be used + // past the lifetime of this object. + // + // This is used with Socket::Write(), which takes an IOBuffer* that + // may be written to even after the socket itself is destroyed. (See + // http://crbug.com/249725 .) + IOBuffer* GetIOBufferForRemainingData(); + + private: + void ConsumeHelper(size_t consume_size, ConsumeSource consume_source); + + // Ref-count the passed-in SpdyFrame to support the semantics of + // |GetIOBufferForRemainingData()|. + typedef base::RefCountedData<scoped_ptr<SpdyFrame> > SharedFrame; + + class SharedFrameIOBuffer; + + const scoped_refptr<SharedFrame> shared_frame_; + std::vector<ConsumeCallback> consume_callbacks_; + size_t offset_; + + DISALLOW_COPY_AND_ASSIGN(SpdyBuffer); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_BUFFER_H_ diff --git a/chromium/net/spdy/spdy_buffer_producer.cc b/chromium/net/spdy/spdy_buffer_producer.cc new file mode 100644 index 00000000000..3a9598c859f --- /dev/null +++ b/chromium/net/spdy/spdy_buffer_producer.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_buffer_producer.h" + +#include "base/logging.h" +#include "net/spdy/spdy_buffer.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +SpdyBufferProducer::SpdyBufferProducer() {} + +SpdyBufferProducer::~SpdyBufferProducer() {} + +SimpleBufferProducer::SimpleBufferProducer(scoped_ptr<SpdyBuffer> buffer) + : buffer_(buffer.Pass()) {} + +SimpleBufferProducer::~SimpleBufferProducer() {} + +scoped_ptr<SpdyBuffer> SimpleBufferProducer::ProduceBuffer() { + DCHECK(buffer_); + return buffer_.Pass(); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_buffer_producer.h b/chromium/net/spdy/spdy_buffer_producer.h new file mode 100644 index 00000000000..fe82b1ac31d --- /dev/null +++ b/chromium/net/spdy/spdy_buffer_producer.h @@ -0,0 +1,50 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_BUFFER_PRODUCER_H_ +#define NET_SPDY_SPDY_BUFFER_PRODUCER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" + +namespace net { + +class SpdyBuffer; + +// An object which provides a SpdyBuffer for writing. We pass these +// around instead of SpdyBuffers since some buffers have to be +// generated "just in time". +class NET_EXPORT_PRIVATE SpdyBufferProducer { + public: + SpdyBufferProducer(); + + // Produces the buffer to be written. Will be called at most once. + virtual scoped_ptr<SpdyBuffer> ProduceBuffer() = 0; + + virtual ~SpdyBufferProducer(); + + private: + DISALLOW_COPY_AND_ASSIGN(SpdyBufferProducer); +}; + +// A simple wrapper around a single SpdyBuffer. +class NET_EXPORT_PRIVATE SimpleBufferProducer : public SpdyBufferProducer { + public: + explicit SimpleBufferProducer(scoped_ptr<SpdyBuffer> buffer); + + virtual ~SimpleBufferProducer(); + + virtual scoped_ptr<SpdyBuffer> ProduceBuffer() OVERRIDE; + + private: + scoped_ptr<SpdyBuffer> buffer_; + + DISALLOW_COPY_AND_ASSIGN(SimpleBufferProducer); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_BUFFER_PRODUCER_H_ diff --git a/chromium/net/spdy/spdy_buffer_unittest.cc b/chromium/net/spdy/spdy_buffer_unittest.cc new file mode 100644 index 00000000000..7c14ee9d8f1 --- /dev/null +++ b/chromium/net/spdy/spdy_buffer_unittest.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_buffer.h" + +#include <cstddef> +#include <cstring> +#include <string> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/io_buffer.h" +#include "net/spdy/spdy_protocol.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +const char kData[] = "hello!\0hi."; +const size_t kDataSize = arraysize(kData); + +class SpdyBufferTest : public ::testing::Test {}; + +// Make a string from the data remaining in |buffer|. +std::string BufferToString(const SpdyBuffer& buffer) { + return std::string(buffer.GetRemainingData(), buffer.GetRemainingSize()); +} + +// Construct a SpdyBuffer from a SpdyFrame and make sure its data +// points to the frame's underlying data. +TEST_F(SpdyBufferTest, FrameConstructor) { + SpdyBuffer buffer( + scoped_ptr<SpdyFrame>( + new SpdyFrame(const_cast<char*>(kData), kDataSize, + false /* owns_buffer */))); + + EXPECT_EQ(kData, buffer.GetRemainingData()); + EXPECT_EQ(kDataSize, buffer.GetRemainingSize()); +} + +// Construct a SpdyBuffer from a const char*/size_t pair and make sure +// it makes a copy of the data. +TEST_F(SpdyBufferTest, DataConstructor) { + std::string data(kData, kDataSize); + SpdyBuffer buffer(data.data(), data.size()); + // This mutation shouldn't affect |buffer|'s data. + data[0] = 'H'; + + EXPECT_NE(kData, buffer.GetRemainingData()); + EXPECT_EQ(kDataSize, buffer.GetRemainingSize()); + EXPECT_EQ(std::string(kData, kDataSize), BufferToString(buffer)); +} + +void IncrementBy(size_t* x, + SpdyBuffer::ConsumeSource expected_consume_source, + size_t delta, + SpdyBuffer::ConsumeSource consume_source) { + EXPECT_EQ(expected_consume_source, consume_source); + *x += delta; +} + +// Construct a SpdyBuffer and call Consume() on it, which should +// update the remaining data pointer and size appropriately, as well +// as calling the consume callbacks. +TEST_F(SpdyBufferTest, Consume) { + SpdyBuffer buffer(kData, kDataSize); + + size_t x1 = 0; + size_t x2 = 0; + buffer.AddConsumeCallback( + base::Bind(&IncrementBy, &x1, SpdyBuffer::CONSUME)); + buffer.AddConsumeCallback( + base::Bind(&IncrementBy, &x2, SpdyBuffer::CONSUME)); + + EXPECT_EQ(std::string(kData, kDataSize), BufferToString(buffer)); + + buffer.Consume(5); + EXPECT_EQ(std::string(kData + 5, kDataSize - 5), BufferToString(buffer)); + EXPECT_EQ(5u, x1); + EXPECT_EQ(5u, x2); + + buffer.Consume(kDataSize - 5); + EXPECT_EQ(0u, buffer.GetRemainingSize()); + EXPECT_EQ(kDataSize, x1); + EXPECT_EQ(kDataSize, x2); +} + +// Construct a SpdyBuffer and attach a ConsumeCallback to it. The +// callback should be called when the SpdyBuffer is destroyed. +TEST_F(SpdyBufferTest, ConsumeOnDestruction) { + size_t x = 0; + + { + SpdyBuffer buffer(kData, kDataSize); + buffer.AddConsumeCallback( + base::Bind(&IncrementBy, &x, SpdyBuffer::DISCARD)); + } + + EXPECT_EQ(kDataSize, x); +} + +// Make sure the IOBuffer returned by GetIOBufferForRemainingData() +// points to the buffer's remaining data and isn't updated by +// Consume(). +TEST_F(SpdyBufferTest, GetIOBufferForRemainingData) { + SpdyBuffer buffer(kData, kDataSize); + + buffer.Consume(5); + scoped_refptr<IOBuffer> io_buffer = buffer.GetIOBufferForRemainingData(); + size_t io_buffer_size = buffer.GetRemainingSize(); + const std::string expectedData(kData + 5, kDataSize - 5); + EXPECT_EQ(expectedData, std::string(io_buffer->data(), io_buffer_size)); + + buffer.Consume(kDataSize - 5); + EXPECT_EQ(expectedData, std::string(io_buffer->data(), io_buffer_size)); +} + +// Make sure the IOBuffer returned by GetIOBufferForRemainingData() +// outlives the buffer itself. +TEST_F(SpdyBufferTest, IOBufferForRemainingDataOutlivesBuffer) { + scoped_ptr<SpdyBuffer> buffer(new SpdyBuffer(kData, kDataSize)); + + scoped_refptr<IOBuffer> io_buffer = buffer->GetIOBufferForRemainingData(); + buffer.reset(); + + // This will cause a use-after-free error if |io_buffer| doesn't + // outlive |buffer|. + std::memcpy(io_buffer->data(), kData, kDataSize); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/spdy/spdy_credential_builder.cc b/chromium/net/spdy/spdy_credential_builder.cc new file mode 100644 index 00000000000..79567b668af --- /dev/null +++ b/chromium/net/spdy/spdy_credential_builder.cc @@ -0,0 +1,86 @@ +// 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 "net/spdy/spdy_credential_builder.h" + +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "net/base/net_errors.h" +#include "net/cert/asn1_util.h" +#include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_framer.h" +#include "net/ssl/server_bound_cert_service.h" + +namespace net { + +namespace { + +std::vector<uint8> ToVector(base::StringPiece piece) { + return std::vector<uint8>(piece.data(), piece.data() + piece.length()); +} + +} // namespace + +// static +int SpdyCredentialBuilder::Build(const std::string& tls_unique, + const std::string& key, + const std::string& cert, + size_t slot, + SpdyCredential* credential) { + std::string secret = SpdyCredentialBuilder::GetCredentialSecret(tls_unique); + + // Extract the SubjectPublicKeyInfo from the certificate. + base::StringPiece public_key_info; + if(!asn1::ExtractSPKIFromDERCert(cert, &public_key_info)) + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + + // Next, extract the SubjectPublicKey data, which will actually + // be stored in the cert field of the credential frame. + base::StringPiece public_key; + if (!asn1::ExtractSubjectPublicKeyFromSPKI(public_key_info, &public_key)) + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + // Drop one byte of padding bits count from the BIT STRING + // (this will always be zero). Drop one byte of X9.62 format specification + // (this will always be 4 to indicated an uncompressed point). + DCHECK_GT(public_key.length(), 2u); + DCHECK_EQ(0, static_cast<int>(public_key[0])); + DCHECK_EQ(4, static_cast<int>(public_key[1])); + public_key = public_key.substr(2, public_key.length()); + + // Convert the strings into a vector<unit8> + std::vector<uint8> der_signature; + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( + ServerBoundCertService::kEPKIPassword, + ToVector(key), ToVector(public_key_info))); + scoped_ptr<crypto::ECSignatureCreator> creator( + crypto::ECSignatureCreator::Create(private_key.get())); + creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()), + secret.length(), &der_signature); + + std::vector<uint8> proof_vector; + if (!creator->DecodeSignature(der_signature, &proof_vector)) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + credential->slot = slot; + credential->certs.push_back(public_key.as_string()); + credential->proof.assign(proof_vector.begin(), proof_vector.end()); + return OK; +} + +// static +std::string SpdyCredentialBuilder::GetCredentialSecret( + const std::string& tls_unique) { + const char prefix[] = "SPDY CREDENTIAL ChannelID\0client -> server"; + std::string secret(prefix, arraysize(prefix)); + secret.append(tls_unique); + + return secret; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_credential_builder.h b/chromium/net/spdy/spdy_credential_builder.h new file mode 100644 index 00000000000..3bdc0a1171e --- /dev/null +++ b/chromium/net/spdy/spdy_credential_builder.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_ +#define NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_ + +#include <string> + +#include "net/base/net_export.h" + +namespace net { + +class SSLClientSocket; +struct SpdyCredential; + +// This class provides facilities for building the various fields of +// SPDY CREDENTIAL frames. +class NET_EXPORT_PRIVATE SpdyCredentialBuilder { + public: + static int Build(const std::string& tls_unique, + const std::string& key, + const std::string& cert, + size_t slot, + SpdyCredential* credential); + + private: + friend class SpdyCredentialBuilderTest; + + // Returns the secret data to be signed as part of a credential frame. + static std::string GetCredentialSecret(const std::string& tls_unique); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_ diff --git a/chromium/net/spdy/spdy_credential_builder_unittest.cc b/chromium/net/spdy/spdy_credential_builder_unittest.cc new file mode 100644 index 00000000000..f82f2f5860f --- /dev/null +++ b/chromium/net/spdy/spdy_credential_builder_unittest.cc @@ -0,0 +1,149 @@ +// 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 "net/spdy/spdy_credential_builder.h" + +#include "base/threading/sequenced_worker_pool.h" +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "net/cert/asn1_util.h" +#include "net/spdy/spdy_test_util_common.h" +#include "net/ssl/default_server_bound_cert_store.h" +#include "net/ssl/server_bound_cert_service.h" +#include "testing/platform_test.h" + +namespace net { + +namespace { + +const static size_t kSlot = 2; +const static char kSecretPrefix[] = + "SPDY CREDENTIAL ChannelID\0client -> server"; + +void CreateCertAndKey(std::string* cert, std::string* key) { + // TODO(rch): Share this code with ServerBoundCertServiceTest. + scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool = + new base::SequencedWorkerPool(1, "CreateCertAndKey"); + scoped_ptr<ServerBoundCertService> server_bound_cert_service( + new ServerBoundCertService(new DefaultServerBoundCertStore(NULL), + sequenced_worker_pool)); + + TestCompletionCallback callback; + ServerBoundCertService::RequestHandle request_handle; + int rv = server_bound_cert_service->GetOrCreateDomainBoundCert( + "www.google.com", key, cert, callback.callback(), &request_handle); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + sequenced_worker_pool->Shutdown(); +} + +} // namespace + +class SpdyCredentialBuilderTest : public testing::Test { + public: + SpdyCredentialBuilderTest() { + CreateCertAndKey(&cert_, &key_); + } + + protected: + int Build() { + return SpdyCredentialBuilder::Build( + MockClientSocket::kTlsUnique, key_, cert_, kSlot, &credential_); + } + + std::string GetCredentialSecret() { + return SpdyCredentialBuilder::GetCredentialSecret( + MockClientSocket::kTlsUnique); + } + + std::string cert_; + std::string key_; + SpdyCredential credential_; + MockECSignatureCreatorFactory ec_signature_creator_factory_; +}; + +// http://crbug.com/142833, http://crbug.com/140991. The following tests fail +// with OpenSSL due to the unimplemented ec_private_key_openssl.cc. +#if defined(USE_OPENSSL) +#define MAYBE_GetCredentialSecret DISABLED_GetCredentialSecret +#else +#define MAYBE_GetCredentialSecret GetCredentialSecret +#endif + +TEST_F(SpdyCredentialBuilderTest, MAYBE_GetCredentialSecret) { + std::string secret_str(kSecretPrefix, arraysize(kSecretPrefix)); + secret_str.append(MockClientSocket::kTlsUnique); + + EXPECT_EQ(secret_str, GetCredentialSecret()); +} + +#if defined(USE_OPENSSL) +#define MAYBE_Succeeds DISABLED_Succeeds +#else +#define MAYBE_Succeeds Succeeds +#endif + +TEST_F(SpdyCredentialBuilderTest, MAYBE_Succeeds) { + EXPECT_EQ(OK, Build()); +} + +#if defined(USE_OPENSSL) +#define MAYBE_SetsSlotCorrectly DISABLED_SetsSlotCorrectly +#else +#define MAYBE_SetsSlotCorrectly SetsSlotCorrectly +#endif + +TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsSlotCorrectly) { + ASSERT_EQ(OK, Build()); + EXPECT_EQ(kSlot, credential_.slot); +} + +#if defined(USE_OPENSSL) +#define MAYBE_SetsCertCorrectly DISABLED_SetsCertCorrectly +#else +#define MAYBE_SetsCertCorrectly SetsCertCorrectly +#endif + +TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsCertCorrectly) { + ASSERT_EQ(OK, Build()); + base::StringPiece spki; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki)); + base::StringPiece spk; + ASSERT_TRUE(asn1::ExtractSubjectPublicKeyFromSPKI(spki, &spk)); + EXPECT_EQ(1u, credential_.certs.size()); + EXPECT_EQ(0, (int)spk[0]); + EXPECT_EQ(4, (int)spk[1]); + EXPECT_EQ(spk.substr(2, spk.length()).as_string(), credential_.certs[0]); +} + +#if defined(USE_OPENSSL) +#define MAYBE_SetsProofCorrectly DISABLED_SetsProofCorrectly +#else +#define MAYBE_SetsProofCorrectly SetsProofCorrectly +#endif + +TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsProofCorrectly) { + ASSERT_EQ(OK, Build()); + base::StringPiece spki; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki)); + std::vector<uint8> spki_data(spki.data(), + spki.data() + spki.size()); + std::vector<uint8> key_data(key_.data(), + key_.data() + key_.length()); + std::vector<uint8> proof_data; + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( + ServerBoundCertService::kEPKIPassword, key_data, spki_data)); + scoped_ptr<crypto::ECSignatureCreator> creator( + crypto::ECSignatureCreator::Create(private_key.get())); + std::string secret = GetCredentialSecret(); + creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()), + secret.length(), &proof_data); + + std::string proof(proof_data.begin(), proof_data.end()); + EXPECT_EQ(proof, credential_.proof); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_credential_state.cc b/chromium/net/spdy/spdy_credential_state.cc new file mode 100644 index 00000000000..4549b3727f3 --- /dev/null +++ b/chromium/net/spdy/spdy_credential_state.cc @@ -0,0 +1,69 @@ +// 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 "net/spdy/spdy_credential_state.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "net/ssl/server_bound_cert_service.h" + +namespace net { + +namespace { + +GURL GetCanonicalOrigin(const GURL& url) { + std::string domain = + ServerBoundCertService::GetDomainForHost(url.host()); + DCHECK(!domain.empty()); + if (domain == url.host()) + return url.GetOrigin(); + return GURL(url.scheme() + "://" + domain + ":" + url.port()); +} + +} // namespace + +const size_t SpdyCredentialState::kDefaultNumSlots = 8; +const size_t SpdyCredentialState::kNoEntry = 0; + +SpdyCredentialState::SpdyCredentialState(size_t num_slots) + : slots_(num_slots), + last_added_(-1) {} + +SpdyCredentialState::~SpdyCredentialState() {} + +bool SpdyCredentialState::HasCredential(const GURL& origin) const { + return FindCredentialSlot(origin) != kNoEntry; +} + +size_t SpdyCredentialState::SetHasCredential(const GURL& origin) { + size_t i = FindCredentialSlot(origin); + if (i != kNoEntry) + return i; + // Add the new entry at the next index following the index of the last + // entry added, or at index 0 if the last added index is the last index. + if (last_added_ + 1 == slots_.size()) { + last_added_ = 0; + } else { + last_added_++; + } + slots_[last_added_] = GetCanonicalOrigin(origin); + return last_added_ + 1; +} + +size_t SpdyCredentialState::FindCredentialSlot(const GURL& origin) const { + GURL url = GetCanonicalOrigin(origin); + for (size_t i = 0; i < slots_.size(); i++) { + if (url == slots_[i]) + return i + 1; + } + return kNoEntry; +} + +void SpdyCredentialState::Resize(size_t size) { + slots_.resize(size); + if (last_added_ >= slots_.size()) + last_added_ = slots_.size() - 1; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_credential_state.h b/chromium/net/spdy/spdy_credential_state.h new file mode 100644 index 00000000000..505b012c07e --- /dev/null +++ b/chromium/net/spdy/spdy_credential_state.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef NET_SPDY_SPDY_CREDENTIAL_STATE_H_ +#define NET_SPDY_SPDY_CREDENTIAL_STATE_H_ + +#include <vector> + +#include "net/base/net_export.h" +#include "url/gurl.h" + +namespace net { + +// A class for tracking the credentials associated with a SPDY session. +class NET_EXPORT_PRIVATE SpdyCredentialState { + public: + explicit SpdyCredentialState(size_t num_slots); + ~SpdyCredentialState(); + + // Changes the number of credentials being tracked. If the new size is + // larger, then empty slots will be added to the end. If the new size is + // smaller than the current size, then the extra slots will be truncated + // from the end. + void Resize(size_t size); + + // Returns the one-based index in |slots_| for |url| or kNoEntry, if no entry + // for |url| exists. + size_t FindCredentialSlot(const GURL& url) const; + + // Returns true if there is a credential associated with |url|. + bool HasCredential(const GURL& url) const; + + // Adds the new credentials to be associated with all origins matching + // |url|. If there is space, then it will add in the first available + // position. Otherwise, an existing credential will be evicted. Returns + // the slot in which this domain was added. + size_t SetHasCredential(const GURL& url); + + // This value is defined as the default initial value in the SPDY spec unless + // otherwise negotiated via SETTINGS. + static const size_t kDefaultNumSlots; + + // Sentinel value to be returned by FindCredentialSlot when no entry exists. + static const size_t kNoEntry; + + private: + // Vector of origins that have credentials. + std::vector<GURL> slots_; + // Index of the last origin added to |slots_|. + size_t last_added_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_CREDENTIAL_STATE_H_ diff --git a/chromium/net/spdy/spdy_credential_state_unittest.cc b/chromium/net/spdy/spdy_credential_state_unittest.cc new file mode 100644 index 00000000000..b5129217560 --- /dev/null +++ b/chromium/net/spdy/spdy_credential_state_unittest.cc @@ -0,0 +1,108 @@ +// 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 "net/spdy/spdy_credential_state.h" + +#include "net/base/host_port_pair.h" +#include "testing/platform_test.h" + +namespace net { + +class SpdyCredentialStateTest : public PlatformTest { + public: + SpdyCredentialStateTest() + : state_(4), + origin1_("https://1.com"), + origin2_("https://2.com"), + origin3_("https://3.com"), + origin4_("https://4.com"), + origin5_("https://5.com"), + origin6_("https://6.com"), + origin11_("https://11.com"), + host1_("https://www.1.com:443") { + } + + protected: + SpdyCredentialState state_; + const GURL origin1_; + const GURL origin2_; + const GURL origin3_; + const GURL origin4_; + const GURL origin5_; + const GURL origin6_; + const GURL origin11_; + const GURL host1_; + + DISALLOW_COPY_AND_ASSIGN(SpdyCredentialStateTest); +}; + +TEST_F(SpdyCredentialStateTest, HasCredentialReturnsFalseWhenEmpty) { + EXPECT_FALSE(state_.HasCredential(origin1_)); + EXPECT_FALSE(state_.HasCredential(origin2_)); + EXPECT_FALSE(state_.HasCredential(origin3_)); +} + +TEST_F(SpdyCredentialStateTest, HasCredentialReturnsTrueWhenAdded) { + state_.SetHasCredential(origin1_); + EXPECT_TRUE(state_.HasCredential(origin1_)); + EXPECT_TRUE(state_.HasCredential(host1_)); + EXPECT_FALSE(state_.HasCredential(origin11_)); + EXPECT_FALSE(state_.HasCredential(origin2_)); + EXPECT_FALSE(state_.HasCredential(origin3_)); +} + +TEST_F(SpdyCredentialStateTest, SetCredentialAddsToEndOfList) { + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); + EXPECT_EQ(3u, (state_.SetHasCredential(origin3_))); +} + +TEST_F(SpdyCredentialStateTest, SetReturnsPositionIfAlreadyInList) { + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); +} + +TEST_F(SpdyCredentialStateTest, SetReplacesOldestElementWhenFull) { + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); + EXPECT_EQ(3u, (state_.SetHasCredential(origin3_))); + EXPECT_EQ(4u, (state_.SetHasCredential(origin4_))); + EXPECT_EQ(1u, (state_.SetHasCredential(origin5_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin6_))); + EXPECT_EQ(3u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(4u, (state_.SetHasCredential(origin2_))); +} + +TEST_F(SpdyCredentialStateTest, ResizeAddsEmptySpaceAtEnd) { + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); + EXPECT_EQ(3u, (state_.SetHasCredential(origin3_))); + EXPECT_EQ(4u, (state_.SetHasCredential(origin4_))); + state_.Resize(6); + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); + EXPECT_EQ(3u, (state_.SetHasCredential(origin3_))); + EXPECT_EQ(4u, (state_.SetHasCredential(origin4_))); + EXPECT_EQ(5u, (state_.SetHasCredential(origin5_))); + EXPECT_EQ(6u, (state_.SetHasCredential(origin6_))); +} + +TEST_F(SpdyCredentialStateTest, ResizeTrunatesFromEnd) { + EXPECT_EQ(1u, (state_.SetHasCredential(origin1_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin2_))); + EXPECT_EQ(3u, (state_.SetHasCredential(origin3_))); + EXPECT_EQ(4u, (state_.SetHasCredential(origin4_))); + state_.Resize(2); + EXPECT_TRUE(state_.HasCredential(origin1_)); + EXPECT_TRUE(state_.HasCredential(origin2_)); + EXPECT_FALSE(state_.HasCredential(origin3_)); + EXPECT_FALSE(state_.HasCredential(origin4_)); + EXPECT_EQ(1u, (state_.SetHasCredential(origin5_))); + EXPECT_EQ(2u, (state_.SetHasCredential(origin6_))); +} + + +} // namespace net diff --git a/chromium/net/spdy/spdy_frame_builder.cc b/chromium/net/spdy/spdy_frame_builder.cc new file mode 100644 index 00000000000..9e779ff4594 --- /dev/null +++ b/chromium/net/spdy/spdy_frame_builder.cc @@ -0,0 +1,191 @@ +// 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 "net/spdy/spdy_frame_builder.h" + +#include <limits> + +#include "base/logging.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +namespace { + +// A special structure for the 8 bit flags and 24 bit length fields. +union FlagsAndLength { + uint8 flags_[4]; // 8 bits + uint32 length_; // 24 bits +}; + +// Creates a FlagsAndLength. +FlagsAndLength CreateFlagsAndLength(uint8 flags, size_t length) { + DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask)); + FlagsAndLength flags_length; + flags_length.length_ = htonl(static_cast<uint32>(length)); + DCHECK_EQ(0, flags & ~kControlFlagsMask); + flags_length.flags_[0] = flags; + return flags_length; +} + +} // namespace + +SpdyFrameBuilder::SpdyFrameBuilder(size_t size) + : buffer_(new char[size]), + capacity_(size), + length_(0) { +} + +SpdyFrameBuilder::~SpdyFrameBuilder() { +} + +char* SpdyFrameBuilder::GetWritableBuffer(size_t length) { + if (!CanWrite(length)) { + return NULL; + } + return buffer_.get() + length_; +} + +bool SpdyFrameBuilder::Seek(size_t length) { + if (!CanWrite(length)) { + return false; + } + + length_ += length; + return true; +} + +bool SpdyFrameBuilder::WriteControlFrameHeader(const SpdyFramer& framer, + SpdyFrameType type, + uint8 flags) { + DCHECK_GE(type, FIRST_CONTROL_TYPE); + DCHECK_LE(type, LAST_CONTROL_TYPE); + DCHECK_GT(4, framer.protocol_version()); + bool success = true; + FlagsAndLength flags_length = CreateFlagsAndLength( + flags, capacity_ - framer.GetControlFrameHeaderSize()); + success &= WriteUInt16(kControlFlagMask | framer.protocol_version()); + success &= WriteUInt16(type); + success &= WriteBytes(&flags_length, sizeof(flags_length)); + DCHECK_EQ(framer.GetControlFrameHeaderSize(), length()); + return success; +} + +bool SpdyFrameBuilder::WriteDataFrameHeader(const SpdyFramer& framer, + SpdyStreamId stream_id, + SpdyDataFlags flags) { + if (framer.protocol_version() >= 4) { + return WriteFramePrefix(framer, DATA, flags, stream_id); + } + DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + bool success = true; + success &= WriteUInt32(stream_id); + size_t length_field = capacity_ - framer.GetDataFrameMinimumSize(); + DCHECK_EQ(0u, length_field & ~static_cast<size_t>(kLengthMask)); + FlagsAndLength flags_length; + flags_length.length_ = htonl(length_field); + DCHECK_EQ(0, flags & ~kDataFlagsMask); + flags_length.flags_[0] = flags; + success &= WriteBytes(&flags_length, sizeof(flags_length)); + DCHECK_EQ(framer.GetDataFrameMinimumSize(), length()); + return success; +} + +bool SpdyFrameBuilder::WriteFramePrefix(const SpdyFramer& framer, + SpdyFrameType type, + uint8 flags, + SpdyStreamId stream_id) { + DCHECK_LE(DATA, type); + DCHECK_GE(LAST_CONTROL_TYPE, type); + DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + DCHECK_LE(4, framer.protocol_version()); + bool success = true; + DCHECK_GT(1u<<16, capacity_); // Make sure length fits in 2B. + success &= WriteUInt16(capacity_); + success &= WriteUInt8(type); + success &= WriteUInt8(flags); + success &= WriteUInt32(stream_id); + DCHECK_EQ(framer.GetDataFrameMinimumSize(), length()); + return success; +} + +bool SpdyFrameBuilder::WriteString(const std::string& value) { + if (value.size() > 0xffff) { + DCHECK(false) << "Tried to write string with length > 16bit."; + return false; + } + + if (!WriteUInt16(static_cast<int>(value.size()))) + return false; + + return WriteBytes(value.data(), static_cast<uint16>(value.size())); +} + +bool SpdyFrameBuilder::WriteStringPiece32(const base::StringPiece& value) { + if (!WriteUInt32(value.size())) { + return false; + } + + return WriteBytes(value.data(), value.size()); +} + +bool SpdyFrameBuilder::WriteBytes(const void* data, uint32 data_len) { + if (!CanWrite(data_len)) { + return false; + } + + char* dest = GetWritableBuffer(data_len); + memcpy(dest, data, data_len); + Seek(data_len); + return true; +} + +bool SpdyFrameBuilder::RewriteLength(const SpdyFramer& framer) { + if (framer.protocol_version() < 4) { + return OverwriteLength(framer, + length_ - framer.GetControlFrameHeaderSize()); + } else { + return OverwriteLength(framer, length_); + } +} + +bool SpdyFrameBuilder::OverwriteLength(const SpdyFramer& framer, + size_t length) { + bool success = false; + const size_t old_length = length_; + + if (framer.protocol_version() < 4) { + FlagsAndLength flags_length = CreateFlagsAndLength( + 0, // We're not writing over the flags value anyway. + length); + + // Write into the correct location by temporarily faking the offset. + length_ = 5; // Offset at which the length field occurs. + success = WriteBytes(reinterpret_cast<char*>(&flags_length) + 1, + sizeof(flags_length) - 1); + } else { + length_ = 0; + success = WriteUInt16(length); + } + + length_ = old_length; + return success; +} + +bool SpdyFrameBuilder::CanWrite(size_t length) const { + if (length > kLengthMask) { + DCHECK(false); + return false; + } + + if (length_ + length > capacity_) { + DCHECK(false); + return false; + } + + return true; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_frame_builder.h b/chromium/net/spdy/spdy_frame_builder.h new file mode 100644 index 00000000000..825254de416 --- /dev/null +++ b/chromium/net/spdy/spdy_frame_builder.h @@ -0,0 +1,129 @@ +// 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. + +#ifndef NET_SPDY_SPDY_FRAME_BUILDER_H_ +#define NET_SPDY_SPDY_FRAME_BUILDER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/sys_byteorder.h" +#include "net/base/net_export.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +class SpdyFramer; + +// This class provides facilities for basic binary value packing +// into Spdy frames. +// +// The SpdyFrameBuilder supports appending primitive values (int, string, etc) +// to a frame instance. The SpdyFrameBuilder grows its internal memory buffer +// dynamically to hold the sequence of primitive values. The internal memory +// buffer is exposed as the "data" of the SpdyFrameBuilder. +class NET_EXPORT_PRIVATE SpdyFrameBuilder { + public: + // Initializes a SpdyFrameBuilder with a buffer of given size + explicit SpdyFrameBuilder(size_t size); + + ~SpdyFrameBuilder(); + + // Returns the size of the SpdyFrameBuilder's data. + size_t length() const { return length_; } + + // Returns a writeable buffer of given size in bytes, to be appended to the + // currently written frame. Does bounds checking on length but does not + // increment the underlying iterator. To do so, consumers should subsequently + // call Seek(). + // In general, consumers should use Write*() calls instead of this. + // Returns NULL on failure. + char* GetWritableBuffer(size_t length); + + // Seeks forward by the given number of bytes. Useful in conjunction with + // GetWriteableBuffer() above. + bool Seek(size_t length); + + // Populates this frame with a SPDY control frame header using + // version-specific information from the |framer| and length information from + // capacity_. The given type must be a control frame type. + // Used only for SPDY versions <4. + bool WriteControlFrameHeader(const SpdyFramer& framer, + SpdyFrameType type, + uint8 flags); + + // Populates this frame with a SPDY data frame header using version-specific + // information from the |framer| and length information from capacity_. + bool WriteDataFrameHeader(const SpdyFramer& framer, + SpdyStreamId stream_id, + SpdyDataFlags flags); + + // Populates this frame with a SPDY4/HTTP2 frame prefix using + // version-specific information from the |framer| and length information from + // capacity_. The given type must be a control frame type. + // Used only for SPDY versions >=4. + bool WriteFramePrefix(const SpdyFramer& framer, + SpdyFrameType type, + uint8 flags, + SpdyStreamId stream_id); + + // Takes the buffer from the SpdyFrameBuilder. + SpdyFrame* take() { + SpdyFrame* rv = new SpdyFrame(buffer_.release(), length_, true); + capacity_ = 0; + length_ = 0; + return rv; + } + + // Methods for adding to the payload. These values are appended to the end + // of the SpdyFrameBuilder payload. Note - binary integers are converted from + // host to network form. + bool WriteUInt8(uint8 value) { + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt16(uint16 value) { + value = htons(value); + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt32(uint32 value) { + value = htonl(value); + return WriteBytes(&value, sizeof(value)); + } + // TODO(hkhalil) Rename to WriteStringPiece16(). + bool WriteString(const std::string& value); + bool WriteStringPiece32(const base::StringPiece& value); + bool WriteBytes(const void* data, uint32 data_len); + + // Update (in-place) the length field in the frame being built to reflect the + // current actual length of bytes written to said frame through this builder. + // The framer parameter is used to determine version-specific location and + // size information of the length field to be written, and must be initialized + // with the correct version for the frame being written. + bool RewriteLength(const SpdyFramer& framer); + + // Update (in-place) the length field in the frame being built to reflect the + // given length. + // The framer parameter is used to determine version-specific location and + // size information of the length field to be written, and must be initialized + // with the correct version for the frame being written. + bool OverwriteLength(const SpdyFramer& framer, size_t length); + + protected: + const char* end_of_payload() const { return buffer_.get() + length_; } + + private: + // Checks to make sure that there is an appropriate amount of space for a + // write of given size, in bytes. + bool CanWrite(size_t length) const; + + scoped_ptr<char[]> buffer_; + size_t capacity_; // Allocation size of payload, set by constructor. + size_t length_; // Current length of the buffer. +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_FRAME_BUILDER_H_ diff --git a/chromium/net/spdy/spdy_frame_builder_test.cc b/chromium/net/spdy/spdy_frame_builder_test.cc new file mode 100644 index 00000000000..014449b2e57 --- /dev/null +++ b/chromium/net/spdy/spdy_frame_builder_test.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_frame_builder.h" + +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_protocol.h" +#include "testing/platform_test.h" + +namespace net { + +TEST(SpdyFrameBuilderTestVersionAgnostic, GetWritableBuffer) { + const size_t builder_size = 10; + SpdyFrameBuilder builder(builder_size); + char* writable_buffer = builder.GetWritableBuffer(builder_size); + memset(writable_buffer, ~1, builder_size); + EXPECT_TRUE(builder.Seek(builder_size)); + scoped_ptr<SpdyFrame> frame(builder.take()); + char expected[builder_size]; + memset(expected, ~1, builder_size); + EXPECT_EQ(base::StringPiece(expected, builder_size), + base::StringPiece(frame->data(), builder_size)); +} + +class SpdyFrameBuilderTest : public ::testing::TestWithParam<SpdyMajorVersion> { + protected: + virtual void SetUp() { + spdy_version_ = GetParam(); + } + + // Major version of SPDY protocol to be used. + SpdyMajorVersion spdy_version_; +}; + +// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3. +INSTANTIATE_TEST_CASE_P(SpdyFrameBuilderTests, + SpdyFrameBuilderTest, + ::testing::Values(SPDY2, SPDY3, SPDY4)); + +TEST_P(SpdyFrameBuilderTest, RewriteLength) { + // Create an empty SETTINGS frame both via framer and manually via builder. + // The one created via builder is initially given the incorrect length, but + // then is corrected via RewriteLength(). + SpdyFramer framer(spdy_version_); + SettingsMap settings; + scoped_ptr<SpdyFrame> expected(framer.CreateSettings(settings)); + SpdyFrameBuilder builder(expected->size() + 1); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(framer, SETTINGS, 0); + } else { + builder.WriteFramePrefix(framer, SETTINGS, 0, 0); + } + builder.WriteUInt32(0); // Write the number of settings. + EXPECT_TRUE(builder.GetWritableBuffer(1) != NULL); + builder.RewriteLength(framer); + scoped_ptr<SpdyFrame> built(builder.take()); + EXPECT_EQ(base::StringPiece(expected->data(), expected->size()), + base::StringPiece(built->data(), expected->size())); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_frame_reader.cc b/chromium/net/spdy/spdy_frame_reader.cc new file mode 100644 index 00000000000..0ab1f2f55e2 --- /dev/null +++ b/chromium/net/spdy/spdy_frame_reader.cc @@ -0,0 +1,184 @@ +// 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 <limits> + +#include "base/sys_byteorder.h" +#include "net/spdy/spdy_frame_reader.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len) + : data_(data), + len_(len), + ofs_(0) { +} + +bool SpdyFrameReader::ReadUInt8(uint8* result) { + // Make sure that we have the whole uint8. + if (!CanRead(1)) { + OnFailure(); + return false; + } + + // Read into result. + *result = *reinterpret_cast<const uint8*>(data_ + ofs_); + + // Iterate. + ofs_ += 1; + + return true; +} + +bool SpdyFrameReader::ReadUInt16(uint16* result) { + // Make sure that we have the whole uint16. + if (!CanRead(2)) { + OnFailure(); + return false; + } + + // Read into result. + *result = ntohs(*(reinterpret_cast<const uint16*>(data_ + ofs_))); + + // Iterate. + ofs_ += 2; + + return true; +} + +bool SpdyFrameReader::ReadUInt32(uint32* result) { + // Make sure that we have the whole uint32. + if (!CanRead(4)) { + OnFailure(); + return false; + } + + // Read into result. + *result = ntohl(*(reinterpret_cast<const uint32*>(data_ + ofs_))); + + // Iterate. + ofs_ += 4; + + return true; +} + +bool SpdyFrameReader::ReadUInt31(uint32* result) { + bool success = ReadUInt32(result); + + // Zero out highest-order bit. + if (success) { + *result &= 0x7fffffff; + } + + return success; +} + +bool SpdyFrameReader::ReadUInt24(uint32* result) { + // Make sure that we have the whole uint24. + if (!CanRead(3)) { + OnFailure(); + return false; + } + + // Read into result. + *result = 0; + memcpy(reinterpret_cast<char*>(result) + 1, data_ + ofs_, 3); + *result = ntohl(*result); + + // Iterate. + ofs_ += 3; + + return true; +} + +bool SpdyFrameReader::ReadStringPiece16(base::StringPiece* result) { + // Read resultant length. + uint16 result_len; + if (!ReadUInt16(&result_len)) { + // OnFailure() already called. + return false; + } + + // Make sure that we have the whole string. + if (!CanRead(result_len)) { + OnFailure(); + return false; + } + + // Set result. + result->set(data_ + ofs_, result_len); + + // Iterate. + ofs_ += result_len; + + return true; +} + +bool SpdyFrameReader::ReadStringPiece32(base::StringPiece* result) { + // Read resultant length. + uint32 result_len; + if (!ReadUInt32(&result_len)) { + // OnFailure() already called. + return false; + } + + // Make sure that we have the whole string. + if (!CanRead(result_len)) { + OnFailure(); + return false; + } + + // Set result. + result->set(data_ + ofs_, result_len); + + // Iterate. + ofs_ += result_len; + + return true; +} + +bool SpdyFrameReader::ReadBytes(void* result, size_t size) { + // Make sure that we have enough data to read. + if (!CanRead(size)) { + OnFailure(); + return false; + } + + // Read into result. + memcpy(result, data_ + ofs_, size); + + // Iterate. + ofs_ += size; + + return true; +} + +bool SpdyFrameReader::Seek(size_t size) { + if (!CanRead(size)) { + OnFailure(); + return false; + } + + // Iterate. + ofs_ += size; + + return true; +} + +bool SpdyFrameReader::IsDoneReading() const { + return len_ == ofs_; +} + +bool SpdyFrameReader::CanRead(size_t bytes) const { + return bytes <= (len_ - ofs_); +} + +void SpdyFrameReader::OnFailure() { + // Set our iterator to the end of the buffer so that further reads fail + // immediately. + ofs_ = len_; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_frame_reader.h b/chromium/net/spdy/spdy_frame_reader.h new file mode 100644 index 00000000000..886f5be7a47 --- /dev/null +++ b/chromium/net/spdy/spdy_frame_reader.h @@ -0,0 +1,123 @@ +// 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. + +#ifndef NET_SPDY_SPDY_FRAME_READER_H_ +#define NET_SPDY_SPDY_FRAME_READER_H_ + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" + +namespace net { + +// Used for reading SPDY frames. Though there isn't really anything terribly +// SPDY-specific here, it's a helper class that's useful when doing SPDY +// framing. +// +// To use, simply construct a SpdyFramerReader using the underlying buffer that +// you'd like to read fields from, then call one of the Read*() methods to +// actually do some reading. +// +// This class keeps an internal iterator to keep track of what's already been +// read and each successive Read*() call automatically increments said iterator +// on success. On failure, internal state of the SpdyFrameReader should not be +// trusted and it is up to the caller to throw away the failed instance and +// handle the error as appropriate. None of the Read*() methods should ever be +// called after failure, as they will also fail immediately. +class NET_EXPORT_PRIVATE SpdyFrameReader { + public: + // Caller must provide an underlying buffer to work on. + SpdyFrameReader(const char* data, const size_t len); + + // Empty destructor. + ~SpdyFrameReader() {} + + // Reads an 8-bit unsigned integer into the given output parameter. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadUInt8(uint8* result); + + // Reads a 16-bit unsigned integer into the given output parameter. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadUInt16(uint16* result); + + // Reads a 32-bit unsigned integer into the given output parameter. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadUInt32(uint32* result); + + // Reads a 31-bit unsigned integer into the given output parameter. This is + // equivalent to ReadUInt32() above except that the highest-order bit is + // discarded. + // Forwards the internal iterater (by 4B) on success. + // Returns true on success, false otherwise. + bool ReadUInt31(uint32* result); + + // Reads a 24-bit unsigned integer into the given output parameter. + // Forwards the internal iterater (by 3B) on success. + // Returns true on success, false otherwise. + bool ReadUInt24(uint32* result); + + // Reads a string prefixed with 16-bit length into the given output parameter. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadStringPiece16(base::StringPiece* result); + + // Reads a string prefixed with 32-bit length into the given output parameter. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadStringPiece32(base::StringPiece* result); + + // Reads a given number of bytes into the given buffer. The buffer + // must be of adequate size. + // Forwards the internal iterater on success. + // Returns true on success, false otherwise. + bool ReadBytes(void* result, size_t size); + + // Seeks a given number of bytes into the buffer from the current offset. + // Equivelant to an empty read. + // Forwards the internal iterator. + // Returns true on success, false otherwise. + bool Seek(size_t size); + + // Rewinds this reader to the beginning of the frame. + void Rewind() { ofs_ = 0; } + + // Returns true if the entirety of the underlying buffer has been read via + // Read*() calls. + bool IsDoneReading() const; + + // Returns the number of bytes that have been consumed by the reader so far. + size_t GetBytesConsumed() const { return ofs_; } + + private: + // Returns true if the underlying buffer has enough room to read the given + // amount of bytes. + bool CanRead(size_t bytes) const; + + // To be called when a read fails for any reason. + void OnFailure(); + + // The data buffer that we're reading from. + const char* data_; + + // The length of the data buffer that we're reading from. + const size_t len_; + + // The location of the next read from our data buffer. + size_t ofs_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_FRAME_READER_H_ diff --git a/chromium/net/spdy/spdy_frame_reader_test.cc b/chromium/net/spdy/spdy_frame_reader_test.cc new file mode 100644 index 00000000000..c72e5bebac0 --- /dev/null +++ b/chromium/net/spdy/spdy_frame_reader_test.cc @@ -0,0 +1,249 @@ +// 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 <algorithm> +#include <iostream> + +#include "base/memory/scoped_ptr.h" +#include "base/sys_byteorder.h" +#include "net/spdy/spdy_frame_reader.h" +#include "testing/platform_test.h" + +namespace net { + +TEST(SpdyFrameReaderTest, ReadUInt16) { + // Frame data in network byte order. + const uint16 kFrameData[] = { + htons(1), htons(1<<15), + }; + + SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData), + arraysize(kFrameData) * sizeof(uint16)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint16 uint16_val; + EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(1, uint16_val); + + EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(1<<15, uint16_val); +} + +TEST(SpdyFrameReaderTest, ReadUInt32) { + // Frame data in network byte order. + const uint32 kFrameData[] = { + htonl(1), htonl(1<<31), + }; + + SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData), + arraysize(kFrameData) * sizeof(uint32)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint32 uint32_val; + EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(1u, uint32_val); + + EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(1u<<31, uint32_val); +} + +TEST(SpdyFrameReaderTest, ReadStringPiece16) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x02, // uint16(2) + 0x48, 0x69, // "Hi" + 0x00, 0x10, // uint16(16) + 0x54, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x31, 0x2c, 0x20, + 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Hi")); + + EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3")); +} + +TEST(SpdyFrameReaderTest, ReadStringPiece32) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, 0x03, // uint32(3) + 0x66, 0x6f, 0x6f, // "foo" + 0x00, 0x00, 0x00, 0x10, // uint32(16) + 0x54, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x34, 0x2c, 0x20, + 0x35, 0x2c, 0x20, 0x36, // "Testing, 4, 5, 6" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("foo")); + + EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6")); +} + +TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, // part of a uint16 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, // part of a uint32 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint32 uint32_val; + EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small to fit the entire string. +TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x03, // uint16(3) + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small even to fit the length. +TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, // part of a uint16 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece32() with a buffer too small to fit the entire string. +TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, 0x03, // uint32(3) + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece32() with a buffer too small even to fit the length. +TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, // part of a uint32 + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + base::StringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); + + // Also make sure that trying to read a uint16, which technically could work, + // fails immediately due to previously encountered failed read. + uint16 uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +TEST(SpdyFrameReaderTest, ReadBytes) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x66, 0x6f, 0x6f, // "foo" + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + char dest1[3] = {}; + EXPECT_TRUE(frame_reader.ReadBytes(&dest1, arraysize(dest1))); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ("foo", base::StringPiece(dest1, arraysize(dest1))); + + char dest2[2] = {}; + EXPECT_TRUE(frame_reader.ReadBytes(&dest2, arraysize(dest2))); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ("Hi", base::StringPiece(dest2, arraysize(dest2))); +} + +TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x01, + }; + + SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + char dest[arraysize(kFrameData) + 2] = {}; + EXPECT_FALSE(frame_reader.ReadBytes(&dest, arraysize(kFrameData) + 1)); + EXPECT_STREQ("", dest); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_framer.cc b/chromium/net/spdy/spdy_framer.cc new file mode 100644 index 00000000000..89a8309ea44 --- /dev/null +++ b/chromium/net/spdy/spdy_framer.cc @@ -0,0 +1,2361 @@ +// 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. + +// TODO(rtenhove) clean up frame buffer size calculations so that we aren't +// constantly adding and subtracting header sizes; this is ugly and error- +// prone. + +#include "net/spdy/spdy_framer.h" + +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/stats_counters.h" +#include "base/third_party/valgrind/memcheck.h" +#include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_frame_reader.h" +#include "net/spdy/spdy_bitmasks.h" +#include "third_party/zlib/zlib.h" + +using std::vector; + +namespace net { + +namespace { + +// Compute the id of our dictionary so that we know we're using the +// right one when asked for it. +uLong CalculateDictionaryId(const char* dictionary, + const size_t dictionary_size) { + uLong initial_value = adler32(0L, Z_NULL, 0); + return adler32(initial_value, + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); +} + +struct DictionaryIds { + DictionaryIds() + : v2_dictionary_id(CalculateDictionaryId(kV2Dictionary, kV2DictionarySize)), + v3_dictionary_id(CalculateDictionaryId(kV3Dictionary, kV3DictionarySize)) + {} + const uLong v2_dictionary_id; + const uLong v3_dictionary_id; +}; + +// Adler ID for the SPDY header compressor dictionaries. Note that they are +// initialized lazily to avoid static initializers. +base::LazyInstance<DictionaryIds>::Leaky g_dictionary_ids; + +// Used to indicate no flags in a SPDY flags field. +const uint8 kNoFlags = 0; + +} // namespace + +const SpdyStreamId SpdyFramer::kInvalidStream = -1; +const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024; +// The size of the control frame buffer. Must be >= the minimum size of the +// largest control frame, which is SYN_STREAM. See GetSynStreamMinimumSize() for +// calculation details. +const size_t SpdyFramer::kControlFrameBufferSize = 18; + +#ifdef DEBUG_SPDY_STATE_CHANGES +#define CHANGE_STATE(newstate) \ + do { \ + LOG(INFO) << "Changing state from: " \ + << StateToString(state_) \ + << " to " << StateToString(newstate) << "\n"; \ + DCHECK(state_ != SPDY_ERROR); \ + DCHECK_EQ(previous_state_, state_); \ + previous_state_ = state_; \ + state_ = newstate; \ + } while (false) +#else +#define CHANGE_STATE(newstate) \ + do { \ + DCHECK(state_ != SPDY_ERROR); \ + DCHECK_EQ(previous_state_, state_); \ + previous_state_ = state_; \ + state_ = newstate; \ + } while (false) +#endif + +SettingsFlagsAndId SettingsFlagsAndId::FromWireFormat(int version, + uint32 wire) { + if (version < 3) { + ConvertFlagsAndIdForSpdy2(&wire); + } + return SettingsFlagsAndId(ntohl(wire) >> 24, ntohl(wire) & 0x00ffffff); +} + +SettingsFlagsAndId::SettingsFlagsAndId(uint8 flags, uint32 id) + : flags_(flags), id_(id & 0x00ffffff) { + DCHECK_GT(1u << 24, id) << "SPDY setting ID too large."; +} + +uint32 SettingsFlagsAndId::GetWireFormat(int version) const { + uint32 wire = htonl(id_ & 0x00ffffff) | htonl(flags_ << 24); + if (version < 3) { + ConvertFlagsAndIdForSpdy2(&wire); + } + return wire; +} + +// SPDY 2 had a bug in it with respect to byte ordering of id/flags field. +// This method is used to preserve buggy behavior and works on both +// little-endian and big-endian hosts. +// This method is also bidirectional (can be used to translate SPDY 2 to SPDY 3 +// as well as vice versa). +void SettingsFlagsAndId::ConvertFlagsAndIdForSpdy2(uint32* val) { + uint8* wire_array = reinterpret_cast<uint8*>(val); + std::swap(wire_array[0], wire_array[3]); + std::swap(wire_array[1], wire_array[2]); +} + +SpdyCredential::SpdyCredential() : slot(0) {} +SpdyCredential::~SpdyCredential() {} + +SpdyFramer::SpdyFramer(SpdyMajorVersion version) + : current_frame_buffer_(new char[kControlFrameBufferSize]), + enable_compression_(true), + visitor_(NULL), + debug_visitor_(NULL), + display_protocol_("SPDY"), + spdy_version_(version), + syn_frame_processed_(false), + probable_http_response_(false) { + DCHECK_GE(spdy_version_, SPDY_MIN_VERSION); + DCHECK_LE(spdy_version_, SPDY_MAX_VERSION); + Reset(); +} + +SpdyFramer::~SpdyFramer() { + if (header_compressor_.get()) { + deflateEnd(header_compressor_.get()); + } + if (header_decompressor_.get()) { + inflateEnd(header_decompressor_.get()); + } +} + +void SpdyFramer::Reset() { + state_ = SPDY_RESET; + previous_state_ = SPDY_RESET; + error_code_ = SPDY_NO_ERROR; + remaining_data_length_ = 0; + remaining_control_header_ = 0; + current_frame_buffer_length_ = 0; + current_frame_type_ = DATA; + current_frame_flags_ = 0; + current_frame_length_ = 0; + current_frame_stream_id_ = kInvalidStream; + settings_scratch_.Reset(); +} + +size_t SpdyFramer::GetDataFrameMinimumSize() const { + // Size, in bytes, of the data frame header. Future versions of SPDY + // will likely vary this, so we allow for the flexibility of a function call + // for this value as opposed to a constant. + return 8; +} + +// Size, in bytes, of the control frame header. +size_t SpdyFramer::GetControlFrameHeaderSize() const { + switch (protocol_version()) { + case SPDY2: + case SPDY3: + case SPDY4: + return 8; + } + LOG(DFATAL) << "Unhandled SPDY version."; + return 0; +} + +size_t SpdyFramer::GetSynStreamMinimumSize() const { + // Size, in bytes, of a SYN_STREAM frame not including the variable-length + // name-value block. + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 2 * 4 (stream IDs) + 1 (priority) + 1 (slot) + return GetControlFrameHeaderSize() + 10; + } else { + // Calculated as: + // frame prefix + 4 (associated stream ID) + 1 (priority) + 1 (slot) + return GetControlFrameHeaderSize() + 6; + } +} + +size_t SpdyFramer::GetSynReplyMinimumSize() const { + // Size, in bytes, of a SYN_REPLY frame not including the variable-length + // name-value block. + size_t size = GetControlFrameHeaderSize(); + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream IDs) + size += 4; + } + + // In SPDY 2, there were 2 unused bytes before payload. + if (protocol_version() < 3) { + size += 2; + } + + return size; +} + +size_t SpdyFramer::GetRstStreamSize() const { + // Size, in bytes, of a RST_STREAM frame. + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream id) + 4 (status code) + return GetControlFrameHeaderSize() + 8; + } else { + // Calculated as: + // frame prefix + 4 (status code) + return GetControlFrameHeaderSize() + 4; + } +} + +size_t SpdyFramer::GetSettingsMinimumSize() const { + // Size, in bytes, of a SETTINGS frame not including the IDs and values + // from the variable-length value block. Calculated as: + // control frame header + 4 (number of ID/value pairs) + return GetControlFrameHeaderSize() + 4; +} + +size_t SpdyFramer::GetPingSize() const { + // Size, in bytes, of this PING frame. Calculated as: + // control frame header + 4 (id) + return GetControlFrameHeaderSize() + 4; +} + +size_t SpdyFramer::GetGoAwaySize() const { + // Size, in bytes, of this GOAWAY frame. Calculated as: + // control frame header + 4 (last good stream id) + size_t size = GetControlFrameHeaderSize() + 4; + + // SPDY 3+ GOAWAY frames also contain a status. + if (protocol_version() >= 3) { + size += 4; + } + + return size; +} + +size_t SpdyFramer::GetHeadersMinimumSize() const { + // Size, in bytes, of a HEADERS frame not including the variable-length + // name-value block. + size_t size = GetControlFrameHeaderSize(); + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream IDs) + size += 4; + } + + // In SPDY 2, there were 2 unused bytes before payload. + if (protocol_version() < 3) { + size += 2; + } + + return size; +} + +size_t SpdyFramer::GetWindowUpdateSize() const { + // Size, in bytes, of a WINDOW_UPDATE frame. + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream id) + 4 (delta) + return GetControlFrameHeaderSize() + 8; + } else { + // Calculated as: + // frame prefix + 4 (delta) + return GetControlFrameHeaderSize() + 4; + } +} + +size_t SpdyFramer::GetCredentialMinimumSize() const { + // Size, in bytes, of a CREDENTIAL frame sans variable-length certificate list + // and proof. Calculated as: + // control frame header + 2 (slot) + return GetControlFrameHeaderSize() + 2; +} + +size_t SpdyFramer::GetBlockedSize() const { + DCHECK_LE(4, protocol_version()); + // Size, in bytes, of a BLOCKED frame. + // The BLOCKED frame has no payload beyond the control frame header. + return GetControlFrameHeaderSize(); +} + +size_t SpdyFramer::GetPushPromiseMinimumSize() const { + DCHECK_LE(4, protocol_version()); + // Size, in bytes, of a PUSH_PROMISE frame, sans the embedded header block. + // Calculated as frame prefix + 4 (promised stream id). + return GetControlFrameHeaderSize() + 4; +} + +size_t SpdyFramer::GetFrameMinimumSize() const { + return std::min(GetDataFrameMinimumSize(), GetControlFrameHeaderSize()); +} + +size_t SpdyFramer::GetFrameMaximumSize() const { + return (protocol_version() < 4) ? 0xffffff : 0xffff; +} + +size_t SpdyFramer::GetDataFrameMaximumPayload() const { + return GetFrameMaximumSize() - GetDataFrameMinimumSize(); +} + +const char* SpdyFramer::StateToString(int state) { + switch (state) { + case SPDY_ERROR: + return "ERROR"; + case SPDY_AUTO_RESET: + return "AUTO_RESET"; + case SPDY_RESET: + return "RESET"; + case SPDY_READING_COMMON_HEADER: + return "READING_COMMON_HEADER"; + case SPDY_CONTROL_FRAME_PAYLOAD: + return "CONTROL_FRAME_PAYLOAD"; + case SPDY_IGNORE_REMAINING_PAYLOAD: + return "IGNORE_REMAINING_PAYLOAD"; + case SPDY_FORWARD_STREAM_FRAME: + return "FORWARD_STREAM_FRAME"; + case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: + return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK"; + case SPDY_CONTROL_FRAME_HEADER_BLOCK: + return "SPDY_CONTROL_FRAME_HEADER_BLOCK"; + case SPDY_CREDENTIAL_FRAME_PAYLOAD: + return "SPDY_CREDENTIAL_FRAME_PAYLOAD"; + case SPDY_SETTINGS_FRAME_PAYLOAD: + return "SPDY_SETTINGS_FRAME_PAYLOAD"; + } + return "UNKNOWN_STATE"; +} + +void SpdyFramer::set_error(SpdyError error) { + DCHECK(visitor_); + error_code_ = error; + CHANGE_STATE(SPDY_ERROR); + visitor_->OnError(this); +} + +const char* SpdyFramer::ErrorCodeToString(int error_code) { + switch (error_code) { + case SPDY_NO_ERROR: + return "NO_ERROR"; + case SPDY_INVALID_CONTROL_FRAME: + return "INVALID_CONTROL_FRAME"; + case SPDY_CONTROL_PAYLOAD_TOO_LARGE: + return "CONTROL_PAYLOAD_TOO_LARGE"; + case SPDY_ZLIB_INIT_FAILURE: + return "ZLIB_INIT_FAILURE"; + case SPDY_UNSUPPORTED_VERSION: + return "UNSUPPORTED_VERSION"; + case SPDY_DECOMPRESS_FAILURE: + return "DECOMPRESS_FAILURE"; + case SPDY_COMPRESS_FAILURE: + return "COMPRESS_FAILURE"; + case SPDY_INVALID_DATA_FRAME_FLAGS: + return "SPDY_INVALID_DATA_FRAME_FLAGS"; + case SPDY_INVALID_CONTROL_FRAME_FLAGS: + return "SPDY_INVALID_CONTROL_FRAME_FLAGS"; + } + return "UNKNOWN_ERROR"; +} + +const char* SpdyFramer::StatusCodeToString(int status_code) { + switch (status_code) { + case RST_STREAM_INVALID: + return "INVALID"; + case RST_STREAM_PROTOCOL_ERROR: + return "PROTOCOL_ERROR"; + case RST_STREAM_INVALID_STREAM: + return "INVALID_STREAM"; + case RST_STREAM_REFUSED_STREAM: + return "REFUSED_STREAM"; + case RST_STREAM_UNSUPPORTED_VERSION: + return "UNSUPPORTED_VERSION"; + case RST_STREAM_CANCEL: + return "CANCEL"; + case RST_STREAM_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case RST_STREAM_FLOW_CONTROL_ERROR: + return "FLOW_CONTROL_ERROR"; + case RST_STREAM_STREAM_IN_USE: + return "STREAM_IN_USE"; + case RST_STREAM_STREAM_ALREADY_CLOSED: + return "STREAM_ALREADY_CLOSED"; + case RST_STREAM_INVALID_CREDENTIALS: + return "INVALID_CREDENTIALS"; + case RST_STREAM_FRAME_TOO_LARGE: + return "FRAME_TOO_LARGE"; + } + return "UNKNOWN_STATUS"; +} + +const char* SpdyFramer::FrameTypeToString(SpdyFrameType type) { + switch (type) { + case DATA: + return "DATA"; + case SYN_STREAM: + return "SYN_STREAM"; + case SYN_REPLY: + return "SYN_REPLY"; + case RST_STREAM: + return "RST_STREAM"; + case SETTINGS: + return "SETTINGS"; + case NOOP: + return "NOOP"; + case PING: + return "PING"; + case GOAWAY: + return "GOAWAY"; + case HEADERS: + return "HEADERS"; + case WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case CREDENTIAL: + return "CREDENTIAL"; + case BLOCKED: + return "BLOCKED"; + case PUSH_PROMISE: + return "PUSH_PROMISE"; + } + return "UNKNOWN_CONTROL_TYPE"; +} + +size_t SpdyFramer::ProcessInput(const char* data, size_t len) { + DCHECK(visitor_); + DCHECK(data); + + size_t original_len = len; + do { + previous_state_ = state_; + switch (state_) { + case SPDY_ERROR: + goto bottom; + + case SPDY_AUTO_RESET: + case SPDY_RESET: + Reset(); + if (len > 0) { + CHANGE_STATE(SPDY_READING_COMMON_HEADER); + } + break; + + case SPDY_READING_COMMON_HEADER: { + size_t bytes_read = ProcessCommonHeader(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: { + // Control frames that contain header blocks + // (SYN_STREAM, SYN_REPLY, HEADERS, PUSH_PROMISE) + // take a different path through the state machine - they + // will go: + // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK + // 2. SPDY_CONTROL_FRAME_HEADER_BLOCK + // + // SETTINGS frames take a slightly modified route: + // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK + // 2. SPDY_SETTINGS_FRAME_PAYLOAD + // + // All other control frames will use the alternate route directly to + // SPDY_CONTROL_FRAME_PAYLOAD + int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_SETTINGS_FRAME_PAYLOAD: { + int bytes_read = ProcessSettingsFramePayload(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_CONTROL_FRAME_HEADER_BLOCK: { + int bytes_read = ProcessControlFrameHeaderBlock(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_CREDENTIAL_FRAME_PAYLOAD: { + size_t bytes_read = ProcessCredentialFramePayload(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_CONTROL_FRAME_PAYLOAD: { + size_t bytes_read = ProcessControlFramePayload(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_IGNORE_REMAINING_PAYLOAD: + // control frame has too-large payload + // intentional fallthrough + case SPDY_FORWARD_STREAM_FRAME: { + size_t bytes_read = ProcessDataFramePayload(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + default: + LOG(DFATAL) << "Invalid value for " << display_protocol_ + << " framer state: " << state_; + // This ensures that we don't infinite-loop if state_ gets an + // invalid value somehow, such as due to a SpdyFramer getting deleted + // from a callback it calls. + goto bottom; + } + } while (state_ != previous_state_); + bottom: + DCHECK(len == 0 || state_ == SPDY_ERROR); + if (current_frame_buffer_length_ == 0 && + remaining_data_length_ == 0 && + remaining_control_header_ == 0) { + DCHECK(state_ == SPDY_RESET || state_ == SPDY_ERROR) + << "State: " << StateToString(state_); + } + + return original_len - len; +} + +size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) { + // This should only be called when we're in the SPDY_READING_COMMON_HEADER + // state. + DCHECK_EQ(state_, SPDY_READING_COMMON_HEADER); + + size_t original_len = len; + + // Update current frame buffer as needed. + if (current_frame_buffer_length_ < GetControlFrameHeaderSize()) { + size_t bytes_desired = + GetControlFrameHeaderSize() - current_frame_buffer_length_; + UpdateCurrentFrameBuffer(&data, &len, bytes_desired); + } + + if (current_frame_buffer_length_ < GetControlFrameHeaderSize()) { + // Not enough information to do anything meaningful. + return original_len - len; + } + + // Using a scoped_ptr here since we may need to create a new SpdyFrameReader + // when processing DATA frames below. + scoped_ptr<SpdyFrameReader> reader( + new SpdyFrameReader(current_frame_buffer_.get(), + current_frame_buffer_length_)); + + uint16 version = 0; + bool is_control_frame = false; + + uint16 control_frame_type_field = DATA; + // ProcessControlFrameHeader() will set current_frame_type_ to the + // correct value if this is a valid control frame. + current_frame_type_ = DATA; + if (protocol_version() < 4) { + bool successful_read = reader->ReadUInt16(&version); + DCHECK(successful_read); + is_control_frame = (version & kControlFlagMask) != 0; + version &= ~kControlFlagMask; // Only valid for control frames. + + if (is_control_frame) { + // We check control_frame_type_field's validity in + // ProcessControlFrameHeader(). + successful_read = reader->ReadUInt16(&control_frame_type_field); + } else { + reader->Rewind(); + successful_read = reader->ReadUInt31(¤t_frame_stream_id_); + } + DCHECK(successful_read); + + successful_read = reader->ReadUInt8(¤t_frame_flags_); + DCHECK(successful_read); + + uint32 length_field = 0; + successful_read = reader->ReadUInt24(&length_field); + DCHECK(successful_read); + remaining_data_length_ = length_field; + current_frame_length_ = remaining_data_length_ + reader->GetBytesConsumed(); + } else { + version = protocol_version(); + uint16 length_field = 0; + bool successful_read = reader->ReadUInt16(&length_field); + DCHECK(successful_read); + current_frame_length_ = length_field; + + uint8 control_frame_type_field_uint8 = DATA; + successful_read = reader->ReadUInt8(&control_frame_type_field_uint8); + DCHECK(successful_read); + // We check control_frame_type_field's validity in + // ProcessControlFrameHeader(). + control_frame_type_field = control_frame_type_field_uint8; + is_control_frame = (control_frame_type_field != DATA); + + successful_read = reader->ReadUInt8(¤t_frame_flags_); + DCHECK(successful_read); + + successful_read = reader->ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + + remaining_data_length_ = current_frame_length_ - reader->GetBytesConsumed(); + } + DCHECK_EQ(is_control_frame ? GetControlFrameHeaderSize() + : GetDataFrameMinimumSize(), + reader->GetBytesConsumed()); + DCHECK_EQ(current_frame_length_, + remaining_data_length_ + reader->GetBytesConsumed()); + + // This is just a sanity check for help debugging early frame errors. + if (remaining_data_length_ > 1000000u) { + // The strncmp for 5 is safe because we only hit this point if we + // have kMinCommonHeader (8) bytes + if (!syn_frame_processed_ && + strncmp(current_frame_buffer_.get(), "HTTP/", 5) == 0) { + LOG(WARNING) << "Unexpected HTTP response to " << display_protocol_ + << " request"; + probable_http_response_ = true; + } else { + LOG(WARNING) << "Unexpectedly large frame. " << display_protocol_ + << " session is likely corrupt."; + } + } + + // if we're here, then we have the common header all received. + if (!is_control_frame) { + if (current_frame_flags_ & ~DATA_FLAG_FIN) { + set_error(SPDY_INVALID_DATA_FRAME_FLAGS); + } else { + visitor_->OnDataFrameHeader(current_frame_stream_id_, + remaining_data_length_, + current_frame_flags_ & DATA_FLAG_FIN); + if (remaining_data_length_ > 0) { + CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME); + } else { + // Empty data frame. + if (current_frame_flags_ & DATA_FLAG_FIN) { + visitor_->OnStreamFrameData( + current_frame_stream_id_, NULL, 0, true); + } + CHANGE_STATE(SPDY_AUTO_RESET); + } + } + } else if (version != spdy_version_) { + // We check version before we check validity: version can never be + // 'invalid', it can only be unsupported. + DLOG(INFO) << "Unsupported SPDY version " << version + << " (expected " << spdy_version_ << ")"; + set_error(SPDY_UNSUPPORTED_VERSION); + } else { + ProcessControlFrameHeader(control_frame_type_field); + } + + return original_len - len; +} + +void SpdyFramer::ProcessControlFrameHeader(uint16 control_frame_type_field) { + DCHECK_EQ(SPDY_NO_ERROR, error_code_); + DCHECK_LE(GetControlFrameHeaderSize(), current_frame_buffer_length_); + + if (control_frame_type_field < FIRST_CONTROL_TYPE || + control_frame_type_field > LAST_CONTROL_TYPE) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return; + } + + current_frame_type_ = static_cast<SpdyFrameType>(control_frame_type_field); + + if (current_frame_type_ == NOOP) { + DLOG(INFO) << "NOOP control frame found. Ignoring."; + CHANGE_STATE(SPDY_AUTO_RESET); + return; + } + + // Do some sanity checking on the control frame sizes and flags. + switch (current_frame_type_) { + case SYN_STREAM: + if (current_frame_length_ < GetSynStreamMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ & + ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case SYN_REPLY: + if (current_frame_length_ < GetSynReplyMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ & ~CONTROL_FLAG_FIN) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case RST_STREAM: + if (current_frame_length_ != GetRstStreamSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case SETTINGS: + // Make sure that we have an integral number of 8-byte key/value pairs, + // plus a 4-byte length field. + if (current_frame_length_ < GetSettingsMinimumSize() || + (current_frame_length_ - GetControlFrameHeaderSize()) % 8 != 4) { + DLOG(WARNING) << "Invalid length for SETTINGS frame: " + << current_frame_length_; + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ & + ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case PING: + if (current_frame_length_ != GetPingSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case GOAWAY: + { + if (current_frame_length_ != GetGoAwaySize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + } + case HEADERS: + if (current_frame_length_ < GetHeadersMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ & ~CONTROL_FLAG_FIN) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case WINDOW_UPDATE: + if (current_frame_length_ != GetWindowUpdateSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case CREDENTIAL: + if (current_frame_length_ < GetCredentialMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case BLOCKED: + if (current_frame_length_ != GetBlockedSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case PUSH_PROMISE: + if (current_frame_length_ < GetPushPromiseMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + default: + LOG(WARNING) << "Valid " << display_protocol_ + << " control frame with unhandled type: " + << current_frame_type_; + // This branch should be unreachable because of the frame type bounds + // check above. However, we DLOG(FATAL) here in an effort to painfully + // club the head of the developer who failed to keep this file in sync + // with spdy_protocol.h. + DLOG(FATAL); + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + + if (state_ == SPDY_ERROR) { + return; + } + + if (current_frame_length_ > GetControlFrameBufferMaxSize()) { + DLOG(WARNING) << "Received control frame with way too big of a payload: " + << current_frame_length_; + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + return; + } + + if (current_frame_type_ == CREDENTIAL) { + CHANGE_STATE(SPDY_CREDENTIAL_FRAME_PAYLOAD); + return; + } + + // Determine the frame size without variable-length data. + int32 frame_size_without_variable_data; + switch (current_frame_type_) { + case SYN_STREAM: + syn_frame_processed_ = true; + frame_size_without_variable_data = GetSynStreamMinimumSize(); + break; + case SYN_REPLY: + syn_frame_processed_ = true; + frame_size_without_variable_data = GetSynReplyMinimumSize(); + break; + case SETTINGS: + frame_size_without_variable_data = GetSettingsMinimumSize(); + break; + case HEADERS: + frame_size_without_variable_data = GetHeadersMinimumSize(); + break; + case PUSH_PROMISE: + frame_size_without_variable_data = GetPushPromiseMinimumSize(); + break; + default: + frame_size_without_variable_data = -1; + break; + } + + if ((frame_size_without_variable_data == -1) && + (current_frame_length_ > kControlFrameBufferSize)) { + // We should already be in an error state. Double-check. + DCHECK_EQ(SPDY_ERROR, state_); + if (state_ != SPDY_ERROR) { + LOG(DFATAL) << display_protocol_ + << " control frame buffer too small for fixed-length frame."; + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + } + return; + } + + if (frame_size_without_variable_data > 0) { + // We have a control frame with a header block. We need to parse the + // remainder of the control frame's header before we can parse the header + // block. The start of the header block varies with the control type. + DCHECK_GE(frame_size_without_variable_data, + static_cast<int32>(current_frame_buffer_length_)); + remaining_control_header_ = frame_size_without_variable_data - + current_frame_buffer_length_; + + CHANGE_STATE(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK); + return; + } + + CHANGE_STATE(SPDY_CONTROL_FRAME_PAYLOAD); +} + +size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len, + size_t max_bytes) { + size_t bytes_to_read = std::min(*len, max_bytes); + if (bytes_to_read > 0) { + DCHECK_GE(kControlFrameBufferSize, + current_frame_buffer_length_ + bytes_to_read); + memcpy(current_frame_buffer_.get() + current_frame_buffer_length_, + *data, + bytes_to_read); + current_frame_buffer_length_ += bytes_to_read; + *data += bytes_to_read; + *len -= bytes_to_read; + } + return bytes_to_read; +} + +size_t SpdyFramer::GetSerializedLength(const int spdy_version, + const SpdyHeaderBlock* headers) { + const size_t num_name_value_pairs_size + = (spdy_version < 3) ? sizeof(uint16) : sizeof(uint32); + const size_t length_of_name_size = num_name_value_pairs_size; + const size_t length_of_value_size = num_name_value_pairs_size; + + size_t total_length = num_name_value_pairs_size; + for (SpdyHeaderBlock::const_iterator it = headers->begin(); + it != headers->end(); + ++it) { + // We add space for the length of the name and the length of the value as + // well as the length of the name and the length of the value. + total_length += length_of_name_size + it->first.size() + + length_of_value_size + it->second.size(); + } + return total_length; +} + +void SpdyFramer::WriteHeaderBlock(SpdyFrameBuilder* frame, + const int spdy_version, + const SpdyHeaderBlock* headers) { + if (spdy_version < 3) { + frame->WriteUInt16(headers->size()); // Number of headers. + } else { + frame->WriteUInt32(headers->size()); // Number of headers. + } + SpdyHeaderBlock::const_iterator it; + for (it = headers->begin(); it != headers->end(); ++it) { + if (spdy_version < 3) { + frame->WriteString(it->first); + frame->WriteString(it->second); + } else { + frame->WriteStringPiece32(it->first); + frame->WriteStringPiece32(it->second); + } + } +} + +// TODO(phajdan.jr): Clean up after we no longer need +// to workaround http://crbug.com/139744. +#if !defined(USE_SYSTEM_ZLIB) + +// These constants are used by zlib to differentiate between normal data and +// cookie data. Cookie data is handled specially by zlib when compressing. +enum ZDataClass { + // kZStandardData is compressed normally, save that it will never match + // against any other class of data in the window. + kZStandardData = Z_CLASS_STANDARD, + // kZCookieData is compressed in its own Huffman blocks and only matches in + // its entirety and only against other kZCookieData blocks. Any matches must + // be preceeded by a kZStandardData byte, or a semicolon to prevent matching + // a suffix. It's assumed that kZCookieData ends in a semicolon to prevent + // prefix matches. + kZCookieData = Z_CLASS_COOKIE, + // kZHuffmanOnlyData is only Huffman compressed - no matches are performed + // against the window. + kZHuffmanOnlyData = Z_CLASS_HUFFMAN_ONLY, +}; + +// WriteZ writes |data| to the deflate context |out|. WriteZ will flush as +// needed when switching between classes of data. +static void WriteZ(const base::StringPiece& data, + ZDataClass clas, + z_stream* out) { + int rv; + + // If we are switching from standard to non-standard data then we need to end + // the current Huffman context to avoid it leaking between them. + if (out->clas == kZStandardData && + clas != kZStandardData) { + out->avail_in = 0; + rv = deflate(out, Z_PARTIAL_FLUSH); + DCHECK_EQ(Z_OK, rv); + DCHECK_EQ(0u, out->avail_in); + DCHECK_LT(0u, out->avail_out); + } + + out->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data.data())); + out->avail_in = data.size(); + out->clas = clas; + if (clas == kZStandardData) { + rv = deflate(out, Z_NO_FLUSH); + } else { + rv = deflate(out, Z_PARTIAL_FLUSH); + } + if (!data.empty()) { + // If we didn't provide any data then zlib will return Z_BUF_ERROR. + DCHECK_EQ(Z_OK, rv); + } + DCHECK_EQ(0u, out->avail_in); + DCHECK_LT(0u, out->avail_out); +} + +// WriteLengthZ writes |n| as a |length|-byte, big-endian number to |out|. +static void WriteLengthZ(size_t n, + unsigned length, + ZDataClass clas, + z_stream* out) { + char buf[4]; + DCHECK_LE(length, sizeof(buf)); + for (unsigned i = 1; i <= length; i++) { + buf[length - i] = n; + n >>= 8; + } + WriteZ(base::StringPiece(buf, length), clas, out); +} + +// WriteHeaderBlockToZ serialises |headers| to the deflate context |z| in a +// manner that resists the length of the compressed data from compromising +// cookie data. +void SpdyFramer::WriteHeaderBlockToZ(const SpdyHeaderBlock* headers, + z_stream* z) const { + unsigned length_length = 4; + if (spdy_version_ < 3) + length_length = 2; + + WriteLengthZ(headers->size(), length_length, kZStandardData, z); + + std::map<std::string, std::string>::const_iterator it; + for (it = headers->begin(); it != headers->end(); ++it) { + WriteLengthZ(it->first.size(), length_length, kZStandardData, z); + WriteZ(it->first, kZStandardData, z); + + if (it->first == "cookie") { + // We require the cookie values (save for the last) to end with a + // semicolon and (save for the first) to start with a space. This is + // typically the format that we are given them in but we reserialize them + // to be sure. + + std::vector<base::StringPiece> cookie_values; + size_t cookie_length = 0; + base::StringPiece cookie_data(it->second); + + for (;;) { + while (!cookie_data.empty() && + (cookie_data[0] == ' ' || cookie_data[0] == '\t')) { + cookie_data.remove_prefix(1); + } + if (cookie_data.empty()) + break; + + size_t i; + for (i = 0; i < cookie_data.size(); i++) { + if (cookie_data[i] == ';') + break; + } + if (i < cookie_data.size()) { + cookie_values.push_back(cookie_data.substr(0, i)); + cookie_length += i + 2 /* semicolon and space */; + cookie_data.remove_prefix(i + 1); + } else { + cookie_values.push_back(cookie_data); + cookie_length += cookie_data.size(); + cookie_data.remove_prefix(i); + } + } + + WriteLengthZ(cookie_length, length_length, kZStandardData, z); + for (size_t i = 0; i < cookie_values.size(); i++) { + std::string cookie; + // Since zlib will only back-reference complete cookies, a cookie that + // is currently last (and so doesn't have a trailing semicolon) won't + // match if it's later in a non-final position. The same is true of + // the first cookie. + if (i == 0 && cookie_values.size() == 1) { + cookie = cookie_values[i].as_string(); + } else if (i == 0) { + cookie = cookie_values[i].as_string() + ";"; + } else if (i < cookie_values.size() - 1) { + cookie = " " + cookie_values[i].as_string() + ";"; + } else { + cookie = " " + cookie_values[i].as_string(); + } + WriteZ(cookie, kZCookieData, z); + } + } else if (it->first == "accept" || + it->first == "accept-charset" || + it->first == "accept-encoding" || + it->first == "accept-language" || + it->first == "host" || + it->first == "version" || + it->first == "method" || + it->first == "scheme" || + it->first == ":host" || + it->first == ":version" || + it->first == ":method" || + it->first == ":scheme" || + it->first == "user-agent") { + WriteLengthZ(it->second.size(), length_length, kZStandardData, z); + WriteZ(it->second, kZStandardData, z); + } else { + // Non-whitelisted headers are Huffman compressed in their own block, but + // don't match against the window. + WriteLengthZ(it->second.size(), length_length, kZStandardData, z); + WriteZ(it->second, kZHuffmanOnlyData, z); + } + } + + z->avail_in = 0; + int rv = deflate(z, Z_SYNC_FLUSH); + DCHECK_EQ(Z_OK, rv); + z->clas = kZStandardData; +} +#endif // !defined(USE_SYSTEM_ZLIB) + +size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data, + size_t len) { + DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_); + size_t original_len = len; + + if (remaining_control_header_ > 0) { + size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len, + remaining_control_header_); + remaining_control_header_ -= bytes_read; + remaining_data_length_ -= bytes_read; + } + + if (remaining_control_header_ == 0) { + SpdyFrameReader reader(current_frame_buffer_.get(), + current_frame_buffer_length_); + reader.Seek(GetControlFrameHeaderSize()); // Seek past frame header. + + switch (current_frame_type_) { + case SYN_STREAM: + { + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + if (current_frame_stream_id_ == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + + SpdyStreamId associated_to_stream_id = kInvalidStream; + successful_read = reader.ReadUInt31(&associated_to_stream_id); + DCHECK(successful_read); + + SpdyPriority priority = 0; + successful_read = reader.ReadUInt8(&priority); + DCHECK(successful_read); + if (protocol_version() < 3) { + priority = priority >> 6; + } else { + priority = priority >> 5; + } + + uint8 slot = 0; + if (protocol_version() < 3) { + // SPDY 2 had an unused byte here. Seek past it. + reader.Seek(1); + } else { + successful_read = reader.ReadUInt8(&slot); + DCHECK(successful_read); + } + + DCHECK(reader.IsDoneReading()); + if (debug_visitor_) { + debug_visitor_->OnReceiveCompressedFrame( + current_frame_stream_id_, + current_frame_type_, + current_frame_length_); + } + visitor_->OnSynStream( + current_frame_stream_id_, + associated_to_stream_id, + priority, + slot, + (current_frame_flags_ & CONTROL_FLAG_FIN) != 0, + (current_frame_flags_ & CONTROL_FLAG_UNIDIRECTIONAL) != 0); + } + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + break; + case SETTINGS: + visitor_->OnSettings(current_frame_flags_ & + SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS); + CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD); + break; + case SYN_REPLY: + case HEADERS: + // SYN_REPLY and HEADERS are the same, save for the visitor call. + { + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + if (current_frame_stream_id_ == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + if (protocol_version() < 3) { + // SPDY 2 had two unused bytes here. Seek past them. + reader.Seek(2); + } + DCHECK(reader.IsDoneReading()); + if (debug_visitor_) { + debug_visitor_->OnReceiveCompressedFrame( + current_frame_stream_id_, + current_frame_type_, + current_frame_length_); + } + if (current_frame_type_ == SYN_REPLY) { + visitor_->OnSynReply( + current_frame_stream_id_, + (current_frame_flags_ & CONTROL_FLAG_FIN) != 0); + } else { + visitor_->OnHeaders( + current_frame_stream_id_, + (current_frame_flags_ & CONTROL_FLAG_FIN) != 0); + } + } + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + break; + case PUSH_PROMISE: + { + DCHECK_LE(4, protocol_version()); + if (current_frame_stream_id_ == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + SpdyStreamId promised_stream_id = kInvalidStream; + bool successful_read = reader.ReadUInt31(&promised_stream_id); + DCHECK(successful_read); + DCHECK(reader.IsDoneReading()); + if (promised_stream_id == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + if (debug_visitor_) { + debug_visitor_->OnReceiveCompressedFrame( + current_frame_stream_id_, + current_frame_type_, + current_frame_length_); + } + visitor_->OnPushPromise(current_frame_stream_id_, promised_stream_id); + } + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + break; + default: + DCHECK(false); + } + } + return original_len - len; +} + +// Does not buffer the control payload. Instead, either passes directly to the +// visitor or decompresses and then passes directly to the visitor, via +// IncrementallyDeliverControlFrameHeaderData() or +// IncrementallyDecompressControlFrameHeaderData() respectively. +size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data, + size_t data_len) { + DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_); + + bool processed_successfully = true; + if (current_frame_type_ != SYN_STREAM && + current_frame_type_ != SYN_REPLY && + current_frame_type_ != HEADERS && + current_frame_type_ != PUSH_PROMISE) { + LOG(DFATAL) << "Unhandled frame type in ProcessControlFrameHeaderBlock."; + } + size_t process_bytes = std::min(data_len, remaining_data_length_); + if (process_bytes > 0) { + if (enable_compression_) { + processed_successfully = IncrementallyDecompressControlFrameHeaderData( + current_frame_stream_id_, data, process_bytes); + } else { + processed_successfully = IncrementallyDeliverControlFrameHeaderData( + current_frame_stream_id_, data, process_bytes); + } + + remaining_data_length_ -= process_bytes; + } + + // Handle the case that there is no futher data in this frame. + if (remaining_data_length_ == 0 && processed_successfully) { + // The complete header block has been delivered. We send a zero-length + // OnControlFrameHeaderData() to indicate this. + visitor_->OnControlFrameHeaderData(current_frame_stream_id_, NULL, 0); + + // If this is a FIN, tell the caller. + if (current_frame_flags_ & CONTROL_FLAG_FIN) { + visitor_->OnStreamFrameData(current_frame_stream_id_, NULL, 0, true); + } + + CHANGE_STATE(SPDY_AUTO_RESET); + } + + // Handle error. + if (!processed_successfully) { + return data_len; + } + + // Return amount processed. + return process_bytes; +} + +size_t SpdyFramer::ProcessSettingsFramePayload(const char* data, + size_t data_len) { + DCHECK_EQ(SPDY_SETTINGS_FRAME_PAYLOAD, state_); + DCHECK_EQ(SETTINGS, current_frame_type_); + size_t unprocessed_bytes = std::min(data_len, remaining_data_length_); + size_t processed_bytes = 0; + + // Loop over our incoming data. + while (unprocessed_bytes > 0) { + // Process up to one setting at a time. + size_t processing = std::min( + unprocessed_bytes, + static_cast<size_t>(8 - settings_scratch_.setting_buf_len)); + + // Check if we have a complete setting in our input. + if (processing == 8) { + // Parse the setting directly out of the input without buffering. + if (!ProcessSetting(data + processed_bytes)) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return processed_bytes; + } + } else { + // Continue updating settings_scratch_.setting_buf. + memcpy(settings_scratch_.setting_buf + settings_scratch_.setting_buf_len, + data + processed_bytes, + processing); + settings_scratch_.setting_buf_len += processing; + + // Check if we have a complete setting buffered. + if (settings_scratch_.setting_buf_len == 8) { + if (!ProcessSetting(settings_scratch_.setting_buf)) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return processed_bytes; + } + // Reset settings_scratch_.setting_buf for our next setting. + settings_scratch_.setting_buf_len = 0; + } + } + + // Iterate. + unprocessed_bytes -= processing; + processed_bytes += processing; + } + + // Check if we're done handling this SETTINGS frame. + remaining_data_length_ -= processed_bytes; + if (remaining_data_length_ == 0) { + CHANGE_STATE(SPDY_AUTO_RESET); + } + + return processed_bytes; +} + +bool SpdyFramer::ProcessSetting(const char* data) { + // Extract fields. + // Maintain behavior of old SPDY 2 bug with byte ordering of flags/id. + const uint32 id_and_flags_wire = *(reinterpret_cast<const uint32*>(data)); + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(spdy_version_, id_and_flags_wire); + uint8 flags = id_and_flags.flags(); + uint32 value = ntohl(*(reinterpret_cast<const uint32*>(data + 4))); + + // Validate id. + switch (id_and_flags.id()) { + case SETTINGS_UPLOAD_BANDWIDTH: + case SETTINGS_DOWNLOAD_BANDWIDTH: + case SETTINGS_ROUND_TRIP_TIME: + case SETTINGS_MAX_CONCURRENT_STREAMS: + case SETTINGS_CURRENT_CWND: + case SETTINGS_DOWNLOAD_RETRANS_RATE: + case SETTINGS_INITIAL_WINDOW_SIZE: + // Valid values. + break; + default: + DLOG(WARNING) << "Unknown SETTINGS ID: " << id_and_flags.id(); + return false; + } + SpdySettingsIds id = static_cast<SpdySettingsIds>(id_and_flags.id()); + + // Detect duplciates. + if (static_cast<uint32>(id) <= settings_scratch_.last_setting_id) { + DLOG(WARNING) << "Duplicate entry or invalid ordering for id " << id + << " in " << display_protocol_ << " SETTINGS frame " + << "(last settikng id was " + << settings_scratch_.last_setting_id << ")."; + return false; + } + settings_scratch_.last_setting_id = id; + + // Validate flags. + uint8 kFlagsMask = SETTINGS_FLAG_PLEASE_PERSIST | SETTINGS_FLAG_PERSISTED; + if ((flags & ~(kFlagsMask)) != 0) { + DLOG(WARNING) << "Unknown SETTINGS flags provided for id " << id << ": " + << flags; + return false; + } + + // Validation succeeded. Pass on to visitor. + visitor_->OnSetting(id, flags, value); + return true; +} + +size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) { + size_t original_len = len; + size_t bytes_read = + UpdateCurrentFrameBuffer(&data, &len, remaining_data_length_); + remaining_data_length_ -= bytes_read; + if (remaining_data_length_ == 0) { + SpdyFrameReader reader(current_frame_buffer_.get(), + current_frame_buffer_length_); + reader.Seek(GetControlFrameHeaderSize()); // Skip frame header. + + // Use frame-specific handlers. + switch (current_frame_type_) { + case RST_STREAM: { + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + SpdyRstStreamStatus status = RST_STREAM_INVALID; + uint32 status_raw = status; + successful_read = reader.ReadUInt32(&status_raw); + DCHECK(successful_read); + if (status_raw > RST_STREAM_INVALID && + status_raw < RST_STREAM_NUM_STATUS_CODES) { + status = static_cast<SpdyRstStreamStatus>(status_raw); + } else { + // TODO(hkhalil): Probably best to OnError here, depending on + // our interpretation of the spec. Keeping with existing liberal + // behavior for now. + } + DCHECK(reader.IsDoneReading()); + visitor_->OnRstStream(current_frame_stream_id_, status); + } + break; + case PING: { + SpdyPingId id = 0; + bool successful_read = reader.ReadUInt32(&id); + DCHECK(successful_read); + DCHECK(reader.IsDoneReading()); + visitor_->OnPing(id); + } + break; + case GOAWAY: { + bool successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + SpdyGoAwayStatus status = GOAWAY_OK; + if (spdy_version_ >= 3) { + uint32 status_raw = GOAWAY_OK; + successful_read = reader.ReadUInt32(&status_raw); + DCHECK(successful_read); + if (status_raw >= GOAWAY_OK && + status_raw < static_cast<uint32>(GOAWAY_NUM_STATUS_CODES)) { + status = static_cast<SpdyGoAwayStatus>(status_raw); + } else { + // TODO(hkhalil): Probably best to OnError here, depending on + // our interpretation of the spec. Keeping with existing liberal + // behavior for now. + } + } + DCHECK(reader.IsDoneReading()); + visitor_->OnGoAway(current_frame_stream_id_, status); + } + break; + case WINDOW_UPDATE: { + uint32 delta_window_size = 0; + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + successful_read = reader.ReadUInt32(&delta_window_size); + DCHECK(successful_read); + DCHECK(reader.IsDoneReading()); + visitor_->OnWindowUpdate(current_frame_stream_id_, + delta_window_size); + } + break; + case BLOCKED: { + DCHECK_LE(4, protocol_version()); + DCHECK(reader.IsDoneReading()); + visitor_->OnBlocked(current_frame_stream_id_); + } + break; + default: + // Unreachable. + LOG(FATAL) << "Unhandled control frame " << current_frame_type_; + } + + CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD); + } + return original_len - len; +} + +size_t SpdyFramer::ProcessCredentialFramePayload(const char* data, size_t len) { + if (len > 0) { + // Clamp to the actual remaining payload. + if (len > remaining_data_length_) { + len = remaining_data_length_; + } + bool processed_succesfully = visitor_->OnCredentialFrameData(data, len); + remaining_data_length_ -= len; + if (!processed_succesfully) { + set_error(SPDY_CREDENTIAL_FRAME_CORRUPT); + } else if (remaining_data_length_ == 0) { + visitor_->OnCredentialFrameData(NULL, 0); + CHANGE_STATE(SPDY_AUTO_RESET); + } + } + return len; +} + +size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) { + size_t original_len = len; + + if (remaining_data_length_ > 0) { + size_t amount_to_forward = std::min(remaining_data_length_, len); + if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) { + // Only inform the visitor if there is data. + if (amount_to_forward) { + visitor_->OnStreamFrameData( + current_frame_stream_id_, data, amount_to_forward, false); + } + } + data += amount_to_forward; + len -= amount_to_forward; + remaining_data_length_ -= amount_to_forward; + + // If the FIN flag is set, and there is no more data in this data + // frame, inform the visitor of EOF via a 0-length data frame. + if (!remaining_data_length_ && current_frame_flags_ & DATA_FLAG_FIN) { + visitor_->OnStreamFrameData(current_frame_stream_id_, NULL, 0, true); + } + } + + if (remaining_data_length_ == 0) { + CHANGE_STATE(SPDY_AUTO_RESET); + } + return original_len - len; +} + +size_t SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data, + size_t header_length, + SpdyHeaderBlock* block) const { + SpdyFrameReader reader(header_data, header_length); + + // Read number of headers. + uint32 num_headers; + if (spdy_version_ < 3) { + uint16 temp; + if (!reader.ReadUInt16(&temp)) { + DLOG(INFO) << "Unable to read number of headers."; + return 0; + } + num_headers = temp; + } else { + if (!reader.ReadUInt32(&num_headers)) { + DLOG(INFO) << "Unable to read number of headers."; + return 0; + } + } + + // Read each header. + for (uint32 index = 0; index < num_headers; ++index) { + base::StringPiece temp; + + // Read header name. + if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp) + : !reader.ReadStringPiece32(&temp)) { + DLOG(INFO) << "Unable to read header name (" << index + 1 << " of " + << num_headers << ")."; + return 0; + } + std::string name = temp.as_string(); + + // Read header value. + if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp) + : !reader.ReadStringPiece32(&temp)) { + DLOG(INFO) << "Unable to read header value (" << index + 1 << " of " + << num_headers << ")."; + return 0; + } + std::string value = temp.as_string(); + + // Ensure no duplicates. + if (block->find(name) != block->end()) { + DLOG(INFO) << "Duplicate header '" << name << "' (" << index + 1 << " of " + << num_headers << ")."; + return 0; + } + + // Store header. + (*block)[name] = value; + } + return reader.GetBytesConsumed(); +} + +/* static */ +bool SpdyFramer::ParseCredentialData(const char* data, size_t len, + SpdyCredential* credential) { + DCHECK(credential); + + SpdyFrameReader parser(data, len); + base::StringPiece temp; + if (!parser.ReadUInt16(&credential->slot)) { + return false; + } + + if (!parser.ReadStringPiece32(&temp)) { + return false; + } + credential->proof = temp.as_string(); + + while (!parser.IsDoneReading()) { + if (!parser.ReadStringPiece32(&temp)) { + return false; + } + credential->certs.push_back(temp.as_string()); + } + return true; +} + +SpdyFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id, + const char* data, + uint32 len, SpdyDataFlags flags) const { + DCHECK_EQ(0, flags & (!DATA_FLAG_FIN)); + + SpdyDataIR data_ir(stream_id, base::StringPiece(data, len)); + data_ir.set_fin(flags & DATA_FLAG_FIN); + return SerializeData(data_ir); +} + +SpdySerializedFrame* SpdyFramer::SerializeData(const SpdyDataIR& data) const { + const size_t kSize = GetDataFrameMinimumSize() + data.data().length(); + + SpdyDataFlags flags = DATA_FLAG_NONE; + if (data.fin()) { + flags = DATA_FLAG_FIN; + } + + SpdyFrameBuilder builder(kSize); + builder.WriteDataFrameHeader(*this, data.stream_id(), flags); + builder.WriteBytes(data.data().data(), data.data().length()); + DCHECK_EQ(kSize, builder.length()); + return builder.take(); +} + +SpdySerializedFrame* SpdyFramer::SerializeDataFrameHeader( + const SpdyDataIR& data) const { + const size_t kSize = GetDataFrameMinimumSize(); + + SpdyDataFlags flags = DATA_FLAG_NONE; + if (data.fin()) { + flags = DATA_FLAG_FIN; + } + + SpdyFrameBuilder builder(kSize); + builder.WriteDataFrameHeader(*this, data.stream_id(), flags); + if (protocol_version() < 4) { + builder.OverwriteLength(*this, data.data().length()); + } else { + builder.OverwriteLength(*this, data.data().length() + kSize); + } + DCHECK_EQ(kSize, builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateSynStream( + SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + DCHECK_EQ(0, flags & ~CONTROL_FLAG_FIN & ~CONTROL_FLAG_UNIDIRECTIONAL); + DCHECK_EQ(enable_compression_, compressed); + + SpdySynStreamIR syn_stream(stream_id); + syn_stream.set_associated_to_stream_id(associated_stream_id); + syn_stream.set_priority(priority); + syn_stream.set_slot(credential_slot); + syn_stream.set_fin((flags & CONTROL_FLAG_FIN) != 0); + syn_stream.set_unidirectional((flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0); + // TODO(hkhalil): Avoid copy here. + *(syn_stream.GetMutableNameValueBlock()) = *headers; + + return SerializeSynStream(syn_stream); +} + +SpdySerializedFrame* SpdyFramer::SerializeSynStream( + const SpdySynStreamIR& syn_stream) { + uint8 flags = 0; + if (syn_stream.fin()) { + flags |= CONTROL_FLAG_FIN; + } + if (syn_stream.unidirectional()) { + flags |= CONTROL_FLAG_UNIDIRECTIONAL; + } + + // The size of this frame, including variable-length name-value block. + const size_t size = GetSynStreamMinimumSize() + + GetSerializedLength(syn_stream.name_value_block()); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, SYN_STREAM, flags); + builder.WriteUInt32(syn_stream.stream_id()); + } else { + builder.WriteFramePrefix(*this, + SYN_STREAM, + flags, + syn_stream.stream_id()); + } + builder.WriteUInt32(syn_stream.associated_to_stream_id()); + uint8 priority = syn_stream.priority(); + if (priority > GetLowestPriority()) { + DLOG(DFATAL) << "Priority out-of-bounds."; + priority = GetLowestPriority(); + } + builder.WriteUInt8(priority << ((spdy_version_ < 3) ? 6 : 5)); + builder.WriteUInt8(syn_stream.slot()); + DCHECK_EQ(GetSynStreamMinimumSize(), builder.length()); + SerializeNameValueBlock(&builder, syn_stream); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(syn_stream.name_value_block())); + debug_visitor_->OnSendCompressedFrame(syn_stream.stream_id(), + SYN_STREAM, + payload_len, + builder.length()); + } + + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateSynReply( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + DCHECK_EQ(0, flags & ~CONTROL_FLAG_FIN); + DCHECK_EQ(enable_compression_, compressed); + + SpdySynReplyIR syn_reply(stream_id); + syn_reply.set_fin(flags & CONTROL_FLAG_FIN); + // TODO(hkhalil): Avoid copy here. + *(syn_reply.GetMutableNameValueBlock()) = *headers; + + return SerializeSynReply(syn_reply); +} + +SpdySerializedFrame* SpdyFramer::SerializeSynReply( + const SpdySynReplyIR& syn_reply) { + uint8 flags = 0; + if (syn_reply.fin()) { + flags |= CONTROL_FLAG_FIN; + } + + // The size of this frame, including variable-length name-value block. + size_t size = GetSynReplyMinimumSize() + + GetSerializedLength(syn_reply.name_value_block()); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, SYN_REPLY, flags); + builder.WriteUInt32(syn_reply.stream_id()); + } else { + builder.WriteFramePrefix(*this, + SYN_REPLY, + flags, + syn_reply.stream_id()); + } + if (protocol_version() < 3) { + builder.WriteUInt16(0); // Unused. + } + DCHECK_EQ(GetSynReplyMinimumSize(), builder.length()); + SerializeNameValueBlock(&builder, syn_reply); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(syn_reply.name_value_block())); + debug_visitor_->OnSendCompressedFrame(syn_reply.stream_id(), + SYN_REPLY, + payload_len, + builder.length()); + } + + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateRstStream( + SpdyStreamId stream_id, + SpdyRstStreamStatus status) const { + SpdyRstStreamIR rst_stream(stream_id, status); + return SerializeRstStream(rst_stream); +} + +SpdySerializedFrame* SpdyFramer::SerializeRstStream( + const SpdyRstStreamIR& rst_stream) const { + SpdyFrameBuilder builder(GetRstStreamSize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, RST_STREAM, 0); + builder.WriteUInt32(rst_stream.stream_id()); + } else { + builder.WriteFramePrefix(*this, + RST_STREAM, + 0, + rst_stream.stream_id()); + } + builder.WriteUInt32(rst_stream.status()); + DCHECK_EQ(GetRstStreamSize(), builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateSettings( + const SettingsMap& values) const { + SpdySettingsIR settings; + for (SettingsMap::const_iterator it = values.begin(); + it != values.end(); + ++it) { + settings.AddSetting(it->first, + (it->second.first & SETTINGS_FLAG_PLEASE_PERSIST) != 0, + (it->second.first & SETTINGS_FLAG_PERSISTED) != 0, + it->second.second); + } + return SerializeSettings(settings); +} + +SpdySerializedFrame* SpdyFramer::SerializeSettings( + const SpdySettingsIR& settings) const { + uint8 flags = 0; + if (settings.clear_settings()) { + flags |= SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS; + } + const SpdySettingsIR::ValueMap* values = &(settings.values()); + + // Size, in bytes, of this SETTINGS frame. + const size_t size = GetSettingsMinimumSize() + (values->size() * 8); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, SETTINGS, flags); + } else { + builder.WriteFramePrefix(*this, SETTINGS, flags, 0); + } + builder.WriteUInt32(values->size()); + DCHECK_EQ(GetSettingsMinimumSize(), builder.length()); + for (SpdySettingsIR::ValueMap::const_iterator it = values->begin(); + it != values->end(); + ++it) { + uint8 setting_flags = 0; + if (it->second.persist_value) { + setting_flags |= SETTINGS_FLAG_PLEASE_PERSIST; + } + if (it->second.persisted) { + setting_flags |= SETTINGS_FLAG_PERSISTED; + } + SettingsFlagsAndId flags_and_id(setting_flags, it->first); + uint32 id_and_flags_wire = flags_and_id.GetWireFormat(protocol_version()); + builder.WriteBytes(&id_and_flags_wire, 4); + builder.WriteUInt32(it->second.value); + } + DCHECK_EQ(size, builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::SerializeBlocked(const SpdyBlockedIR& blocked) const { + DCHECK_LE(4, protocol_version()); + SpdyFrameBuilder builder(GetBlockedSize()); + builder.WriteFramePrefix(*this, BLOCKED, kNoFlags, blocked.stream_id()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const { + SpdyPingIR ping(unique_id); + return SerializePing(ping); +} + +SpdySerializedFrame* SpdyFramer::SerializePing(const SpdyPingIR& ping) const { + SpdyFrameBuilder builder(GetPingSize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, PING, kNoFlags); + } else { + builder.WriteFramePrefix(*this, PING, 0, 0); + } + builder.WriteUInt32(ping.id()); + DCHECK_EQ(GetPingSize(), builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateGoAway( + SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) const { + SpdyGoAwayIR goaway(last_accepted_stream_id, status); + return SerializeGoAway(goaway); +} + +SpdySerializedFrame* SpdyFramer::SerializeGoAway( + const SpdyGoAwayIR& goaway) const { + SpdyFrameBuilder builder(GetGoAwaySize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, GOAWAY, kNoFlags); + } else { + builder.WriteFramePrefix(*this, GOAWAY, 0, 0); + } + builder.WriteUInt32(goaway.last_good_stream_id()); + if (protocol_version() >= 3) { + builder.WriteUInt32(goaway.status()); + } + DCHECK_EQ(GetGoAwaySize(), builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateHeaders( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* header_block) { + // Basically the same as CreateSynReply(). + DCHECK_EQ(0, flags & (!CONTROL_FLAG_FIN)); + DCHECK_EQ(enable_compression_, compressed); + + SpdyHeadersIR headers(stream_id); + headers.set_fin(flags & CONTROL_FLAG_FIN); + // TODO(hkhalil): Avoid copy here. + *(headers.GetMutableNameValueBlock()) = *header_block; + + return SerializeHeaders(headers); +} + +SpdySerializedFrame* SpdyFramer::SerializeHeaders( + const SpdyHeadersIR& headers) { + uint8 flags = 0; + if (headers.fin()) { + flags |= CONTROL_FLAG_FIN; + } + + // The size of this frame, including variable-length name-value block. + size_t size = GetHeadersMinimumSize() + + GetSerializedLength(headers.name_value_block()); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, HEADERS, flags); + builder.WriteUInt32(headers.stream_id()); + } else { + builder.WriteFramePrefix(*this, + HEADERS, + flags, + headers.stream_id()); + } + if (protocol_version() < 3) { + builder.WriteUInt16(0); // Unused. + } + DCHECK_EQ(GetHeadersMinimumSize(), builder.length()); + + SerializeNameValueBlock(&builder, headers); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(headers.name_value_block())); + debug_visitor_->OnSendCompressedFrame(headers.stream_id(), + HEADERS, + payload_len, + builder.length()); + } + + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) const { + SpdyWindowUpdateIR window_update(stream_id, delta_window_size); + return SerializeWindowUpdate(window_update); +} + +SpdySerializedFrame* SpdyFramer::SerializeWindowUpdate( + const SpdyWindowUpdateIR& window_update) const { + SpdyFrameBuilder builder(GetWindowUpdateSize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, WINDOW_UPDATE, kNoFlags); + builder.WriteUInt32(window_update.stream_id()); + } else { + builder.WriteFramePrefix(*this, + WINDOW_UPDATE, + kNoFlags, + window_update.stream_id()); + } + builder.WriteUInt32(window_update.delta()); + DCHECK_EQ(GetWindowUpdateSize(), builder.length()); + return builder.take(); +} + +// TODO(hkhalil): Gut with SpdyCredential removal. +SpdyFrame* SpdyFramer::CreateCredentialFrame( + const SpdyCredential& credential) const { + SpdyCredentialIR credential_ir(credential.slot); + credential_ir.set_proof(credential.proof); + for (std::vector<std::string>::const_iterator cert = credential.certs.begin(); + cert != credential.certs.end(); + ++cert) { + credential_ir.AddCertificate(*cert); + } + return SerializeCredential(credential_ir); +} + +SpdySerializedFrame* SpdyFramer::SerializeCredential( + const SpdyCredentialIR& credential) const { + size_t size = GetCredentialMinimumSize(); + size += 4 + credential.proof().length(); // Room for proof. + for (SpdyCredentialIR::CertificateList::const_iterator it = + credential.certificates()->begin(); + it != credential.certificates()->end(); + ++it) { + size += 4 + it->length(); // Room for certificate. + } + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, CREDENTIAL, kNoFlags); + } else { + builder.WriteFramePrefix(*this, CREDENTIAL, kNoFlags, 0); + } + builder.WriteUInt16(credential.slot()); + DCHECK_EQ(GetCredentialMinimumSize(), builder.length()); + builder.WriteStringPiece32(credential.proof()); + for (SpdyCredentialIR::CertificateList::const_iterator it = + credential.certificates()->begin(); + it != credential.certificates()->end(); + ++it) { + builder.WriteStringPiece32(*it); + } + DCHECK_EQ(size, builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreatePushPromise( + SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + const SpdyHeaderBlock* header_block) { + SpdyPushPromiseIR push_promise(stream_id, promised_stream_id); + // TODO(hkhalil): Avoid copy here. + *(push_promise.GetMutableNameValueBlock()) = *header_block; + + return SerializePushPromise(push_promise); +} + +SpdyFrame* SpdyFramer::SerializePushPromise( + const SpdyPushPromiseIR& push_promise) { + DCHECK_LE(4, protocol_version()); + // The size of this frame, including variable-length name-value block. + size_t size = GetPushPromiseMinimumSize() + + GetSerializedLength(push_promise.name_value_block()); + + SpdyFrameBuilder builder(size); + builder.WriteFramePrefix(*this, PUSH_PROMISE, kNoFlags, + push_promise.stream_id()); + builder.WriteUInt32(push_promise.promised_stream_id()); + DCHECK_EQ(GetPushPromiseMinimumSize(), builder.length()); + + SerializeNameValueBlock(&builder, push_promise); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(push_promise.name_value_block())); + debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), + PUSH_PROMISE, payload_len, builder.length()); + } + + return builder.take(); +} + +namespace { + +class FrameSerializationVisitor : public SpdyFrameVisitor { + public: + explicit FrameSerializationVisitor(SpdyFramer* framer) : framer_(framer) {} + virtual ~FrameSerializationVisitor() {} + + SpdySerializedFrame* ReleaseSerializedFrame() { return frame_.release(); } + + virtual void VisitData(const SpdyDataIR& data) OVERRIDE { + frame_.reset(framer_->SerializeData(data)); + } + virtual void VisitSynStream(const SpdySynStreamIR& syn_stream) OVERRIDE { + frame_.reset(framer_->SerializeSynStream(syn_stream)); + } + virtual void VisitSynReply(const SpdySynReplyIR& syn_reply) OVERRIDE { + frame_.reset(framer_->SerializeSynReply(syn_reply)); + } + virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) OVERRIDE { + frame_.reset(framer_->SerializeRstStream(rst_stream)); + } + virtual void VisitSettings(const SpdySettingsIR& settings) OVERRIDE { + frame_.reset(framer_->SerializeSettings(settings)); + } + virtual void VisitPing(const SpdyPingIR& ping) OVERRIDE { + frame_.reset(framer_->SerializePing(ping)); + } + virtual void VisitGoAway(const SpdyGoAwayIR& goaway) OVERRIDE { + frame_.reset(framer_->SerializeGoAway(goaway)); + } + virtual void VisitHeaders(const SpdyHeadersIR& headers) OVERRIDE { + frame_.reset(framer_->SerializeHeaders(headers)); + } + virtual void VisitWindowUpdate( + const SpdyWindowUpdateIR& window_update) OVERRIDE { + frame_.reset(framer_->SerializeWindowUpdate(window_update)); + } + virtual void VisitCredential(const SpdyCredentialIR& credential) OVERRIDE { + frame_.reset(framer_->SerializeCredential(credential)); + } + virtual void VisitBlocked(const SpdyBlockedIR& blocked) OVERRIDE { + frame_.reset(framer_->SerializeBlocked(blocked)); + } + virtual void VisitPushPromise( + const SpdyPushPromiseIR& push_promise) OVERRIDE { + frame_.reset(framer_->SerializePushPromise(push_promise)); + } + + private: + SpdyFramer* framer_; + scoped_ptr<SpdySerializedFrame> frame_; +}; + +} // namespace + +SpdySerializedFrame* SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) { + FrameSerializationVisitor visitor(this); + frame.Visit(&visitor); + return visitor.ReleaseSerializedFrame(); +} + +size_t SpdyFramer::GetSerializedLength(const SpdyHeaderBlock& headers) { + const size_t uncompressed_length = + GetSerializedLength(protocol_version(), &headers); + if (!enable_compression_) { + return uncompressed_length; + } + z_stream* compressor = GetHeaderCompressor(); + // Since we'll be performing lots of flushes when compressing the data, + // zlib's lower bounds may be insufficient. + return 2 * deflateBound(compressor, uncompressed_length); +} + +// The following compression setting are based on Brian Olson's analysis. See +// https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792 +// for more details. +#if defined(USE_SYSTEM_ZLIB) +// System zlib is not expected to have workaround for http://crbug.com/139744, +// so disable compression in that case. +// TODO(phajdan.jr): Remove the special case when it's no longer necessary. +static const int kCompressorLevel = 0; +#else // !defined(USE_SYSTEM_ZLIB) +static const int kCompressorLevel = 9; +#endif // !defined(USE_SYSTEM_ZLIB) +static const int kCompressorWindowSizeInBits = 11; +static const int kCompressorMemLevel = 1; + +z_stream* SpdyFramer::GetHeaderCompressor() { + if (header_compressor_.get()) + return header_compressor_.get(); // Already initialized. + + header_compressor_.reset(new z_stream); + memset(header_compressor_.get(), 0, sizeof(z_stream)); + + int success = deflateInit2(header_compressor_.get(), + kCompressorLevel, + Z_DEFLATED, + kCompressorWindowSizeInBits, + kCompressorMemLevel, + Z_DEFAULT_STRATEGY); + if (success == Z_OK) { + const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary + : kV3Dictionary; + const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize + : kV3DictionarySize; + success = deflateSetDictionary(header_compressor_.get(), + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); + } + if (success != Z_OK) { + LOG(WARNING) << "deflateSetDictionary failure: " << success; + header_compressor_.reset(NULL); + return NULL; + } + return header_compressor_.get(); +} + +z_stream* SpdyFramer::GetHeaderDecompressor() { + if (header_decompressor_.get()) + return header_decompressor_.get(); // Already initialized. + + header_decompressor_.reset(new z_stream); + memset(header_decompressor_.get(), 0, sizeof(z_stream)); + + int success = inflateInit(header_decompressor_.get()); + if (success != Z_OK) { + LOG(WARNING) << "inflateInit failure: " << success; + header_decompressor_.reset(NULL); + return NULL; + } + return header_decompressor_.get(); +} + +// Incrementally decompress the control frame's header block, feeding the +// result to the visitor in chunks. Continue this until the visitor +// indicates that it cannot process any more data, or (more commonly) we +// run out of data to deliver. +bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData( + SpdyStreamId stream_id, + const char* data, + size_t len) { + // Get a decompressor or set error. + z_stream* decomp = GetHeaderDecompressor(); + if (decomp == NULL) { + LOG(DFATAL) << "Couldn't get decompressor for handling compressed headers."; + set_error(SPDY_DECOMPRESS_FAILURE); + return false; + } + + bool processed_successfully = true; + char buffer[kHeaderDataChunkMaxSize]; + + decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data)); + decomp->avail_in = len; + // If we get a SYN_STREAM/SYN_REPLY/HEADERS frame with stream ID zero, we + // signal an error back in ProcessControlFrameBeforeHeaderBlock. So if we've + // reached this method successfully, stream_id should be nonzero. + DCHECK_LT(0u, stream_id); + while (decomp->avail_in > 0 && processed_successfully) { + decomp->next_out = reinterpret_cast<Bytef*>(buffer); + decomp->avail_out = arraysize(buffer); + + int rv = inflate(decomp, Z_SYNC_FLUSH); + if (rv == Z_NEED_DICT) { + const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary + : kV3Dictionary; + const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize + : kV3DictionarySize; + const DictionaryIds& ids = g_dictionary_ids.Get(); + const uLong dictionary_id = (spdy_version_ < 3) ? ids.v2_dictionary_id + : ids.v3_dictionary_id; + // Need to try again with the right dictionary. + if (decomp->adler == dictionary_id) { + rv = inflateSetDictionary(decomp, + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); + if (rv == Z_OK) + rv = inflate(decomp, Z_SYNC_FLUSH); + } + } + + // Inflate will generate a Z_BUF_ERROR if it runs out of input + // without producing any output. The input is consumed and + // buffered internally by zlib so we can detect this condition by + // checking if avail_in is 0 after the call to inflate. + bool input_exhausted = ((rv == Z_BUF_ERROR) && (decomp->avail_in == 0)); + if ((rv == Z_OK) || input_exhausted) { + size_t decompressed_len = arraysize(buffer) - decomp->avail_out; + if (decompressed_len > 0) { + processed_successfully = visitor_->OnControlFrameHeaderData( + stream_id, buffer, decompressed_len); + } + if (!processed_successfully) { + // Assume that the problem was the header block was too large for the + // visitor. + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + } + } else { + DLOG(WARNING) << "inflate failure: " << rv << " " << len; + set_error(SPDY_DECOMPRESS_FAILURE); + processed_successfully = false; + } + } + return processed_successfully; +} + +bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData( + SpdyStreamId stream_id, const char* data, size_t len) { + bool read_successfully = true; + while (read_successfully && len > 0) { + size_t bytes_to_deliver = std::min(len, kHeaderDataChunkMaxSize); + read_successfully = visitor_->OnControlFrameHeaderData(stream_id, data, + bytes_to_deliver); + data += bytes_to_deliver; + len -= bytes_to_deliver; + if (!read_successfully) { + // Assume that the problem was the header block was too large for the + // visitor. + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + } + } + return read_successfully; +} + +void SpdyFramer::SerializeNameValueBlockWithoutCompression( + SpdyFrameBuilder* builder, + const SpdyNameValueBlock& name_value_block) const { + // Serialize number of headers. + if (protocol_version() < 3) { + builder->WriteUInt16(name_value_block.size()); + } else { + builder->WriteUInt32(name_value_block.size()); + } + + // Serialize each header. + for (SpdyHeaderBlock::const_iterator it = name_value_block.begin(); + it != name_value_block.end(); + ++it) { + if (protocol_version() < 3) { + builder->WriteString(it->first); + builder->WriteString(it->second); + } else { + builder->WriteStringPiece32(it->first); + builder->WriteStringPiece32(it->second); + } + } +} + +void SpdyFramer::SerializeNameValueBlock( + SpdyFrameBuilder* builder, + const SpdyFrameWithNameValueBlockIR& frame) { + if (!enable_compression_) { + return SerializeNameValueBlockWithoutCompression(builder, + frame.name_value_block()); + } + + // First build an uncompressed version to be fed into the compressor. + const size_t uncompressed_len = GetSerializedLength( + protocol_version(), &(frame.name_value_block())); + SpdyFrameBuilder uncompressed_builder(uncompressed_len); + SerializeNameValueBlockWithoutCompression(&uncompressed_builder, + frame.name_value_block()); + scoped_ptr<SpdyFrame> uncompressed_payload(uncompressed_builder.take()); + + z_stream* compressor = GetHeaderCompressor(); + if (!compressor) { + LOG(DFATAL) << "Could not obtain compressor."; + return; + } + + base::StatsCounter compressed_frames("spdy.CompressedFrames"); + base::StatsCounter pre_compress_bytes("spdy.PreCompressSize"); + base::StatsCounter post_compress_bytes("spdy.PostCompressSize"); + + // Create an output frame. + // Since we'll be performing lots of flushes when compressing the data, + // zlib's lower bounds may be insufficient. + // + // TODO(akalin): Avoid the duplicate calculation with + // GetSerializedLength(const SpdyHeaderBlock&). + const int compressed_max_size = + 2 * deflateBound(compressor, uncompressed_len); + + // TODO(phajdan.jr): Clean up after we no longer need + // to workaround http://crbug.com/139744. +#if defined(USE_SYSTEM_ZLIB) + compressor->next_in = reinterpret_cast<Bytef*>(uncompressed_payload->data()); + compressor->avail_in = uncompressed_len; +#endif // defined(USE_SYSTEM_ZLIB) + compressor->next_out = reinterpret_cast<Bytef*>( + builder->GetWritableBuffer(compressed_max_size)); + compressor->avail_out = compressed_max_size; + + // TODO(phajdan.jr): Clean up after we no longer need + // to workaround http://crbug.com/139744. +#if defined(USE_SYSTEM_ZLIB) + int rv = deflate(compressor, Z_SYNC_FLUSH); + if (rv != Z_OK) { // How can we know that it compressed everything? + // This shouldn't happen, right? + LOG(WARNING) << "deflate failure: " << rv; + // TODO(akalin): Upstream this return. + return; + } +#else + WriteHeaderBlockToZ(&frame.name_value_block(), compressor); +#endif // defined(USE_SYSTEM_ZLIB) + + int compressed_size = compressed_max_size - compressor->avail_out; + builder->Seek(compressed_size); + builder->RewriteLength(*this); + + pre_compress_bytes.Add(uncompressed_len); + post_compress_bytes.Add(compressed_size); + + compressed_frames.Increment(); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_framer.h b/chromium/net/spdy/spdy_framer.h new file mode 100644 index 00000000000..fa004696a4c --- /dev/null +++ b/chromium/net/spdy/spdy_framer.h @@ -0,0 +1,716 @@ +// 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. + +#ifndef NET_SPDY_SPDY_FRAMER_H_ +#define NET_SPDY_SPDY_FRAMER_H_ + +#include <list> +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/sys_byteorder.h" +#include "net/base/net_export.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" + +typedef struct z_stream_s z_stream; // Forward declaration for zlib. + +namespace net { + +class HttpProxyClientSocketPoolTest; +class HttpNetworkLayer; +class HttpNetworkTransactionTest; +class SpdyHttpStreamTest; +class SpdyNetworkTransactionTest; +class SpdyProxyClientSocketTest; +class SpdySessionTest; +class SpdyStreamTest; +class SpdyWebSocketStreamTest; +class WebSocketJobTest; + +class SpdyFramer; +class SpdyFrameBuilder; +class SpdyFramerTest; + +namespace test { + +class TestSpdyVisitor; + +} // namespace test + +// A datastructure for holding a set of headers from a HEADERS, PUSH_PROMISE, +// SYN_STREAM, or SYN_REPLY frame. +typedef std::map<std::string, std::string> SpdyHeaderBlock; + +// A datastructure for holding the ID and flag fields for SETTINGS. +// Conveniently handles converstion to/from wire format. +class NET_EXPORT_PRIVATE SettingsFlagsAndId { + public: + static SettingsFlagsAndId FromWireFormat(int version, uint32 wire); + + SettingsFlagsAndId() : flags_(0), id_(0) {} + + // TODO(hkhalil): restrict to enums instead of free-form ints. + SettingsFlagsAndId(uint8 flags, uint32 id); + + uint32 GetWireFormat(int version) const; + + uint32 id() const { return id_; } + uint8 flags() const { return flags_; } + + private: + static void ConvertFlagsAndIdForSpdy2(uint32* val); + + uint8 flags_; + uint32 id_; +}; + +// SettingsMap has unique (flags, value) pair for given SpdySettingsIds ID. +typedef std::pair<SpdySettingsFlags, uint32> SettingsFlagsAndValue; +typedef std::map<SpdySettingsIds, SettingsFlagsAndValue> SettingsMap; + +// A datastrcture for holding the contents of a CREDENTIAL frame. +// TODO(hkhalil): Remove, use SpdyCredentialIR instead. +struct NET_EXPORT_PRIVATE SpdyCredential { + SpdyCredential(); + ~SpdyCredential(); + + uint16 slot; + std::vector<std::string> certs; + std::string proof; +}; + +// Scratch space necessary for processing SETTINGS frames. +struct NET_EXPORT_PRIVATE SpdySettingsScratch { + SpdySettingsScratch() { Reset(); } + + void Reset() { + setting_buf_len = 0; + last_setting_id = 0; + } + + // Buffer contains up to one complete key/value pair. + char setting_buf[8]; + + // The amount of the buffer that is filled with valid data. + size_t setting_buf_len; + + // The ID of the last setting that was processed in the current SETTINGS + // frame. Used for detecting out-of-order or duplicate keys within a settings + // frame. Set to 0 before first key/value pair is processed. + uint32 last_setting_id; +}; + +// SpdyFramerVisitorInterface is a set of callbacks for the SpdyFramer. +// Implement this interface to receive event callbacks as frames are +// decoded from the framer. +// +// Control frames that contain SPDY header blocks (SYN_STREAM, SYN_REPLY, +// HEADER, and PUSH_PROMISE) are processed in fashion that allows the +// decompressed header block to be delivered in chunks to the visitor. +// The following steps are followed: +// 1. OnSynStream, OnSynReply, OnHeaders, or OnPushPromise is called. +// 2. Repeated: OnControlFrameHeaderData is called with chunks of the +// decompressed header block. In each call the len parameter is greater +// than zero. +// 3. OnControlFrameHeaderData is called with len set to zero, indicating +// that the full header block has been delivered for the control frame. +// During step 2 the visitor may return false, indicating that the chunk of +// header data could not be handled by the visitor (typically this indicates +// resource exhaustion). If this occurs the framer will discontinue +// delivering chunks to the visitor, set a SPDY_CONTROL_PAYLOAD_TOO_LARGE +// error, and clean up appropriately. Note that this will cause the header +// decompressor to lose synchronization with the sender's header compressor, +// making the SPDY session unusable for future work. The visitor's OnError +// function should deal with this condition by closing the SPDY connection. +class NET_EXPORT_PRIVATE SpdyFramerVisitorInterface { + public: + virtual ~SpdyFramerVisitorInterface() {} + + // Called if an error is detected in the SpdyFrame protocol. + virtual void OnError(SpdyFramer* framer) = 0; + + // Called when a data frame header is received. The frame's data + // payload will be provided via subsequent calls to + // OnStreamFrameData(). + virtual void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) = 0; + + // Called when data is received. + // |stream_id| The stream receiving data. + // |data| A buffer containing the data received. + // |len| The length of the data buffer. + // When the other side has finished sending data on this stream, + // this method will be called with a zero-length buffer. + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) = 0; + + // Called when a chunk of header data is available. This is called + // after OnSynStream, OnSynReply, OnHeaders(), or OnPushPromise. + // |stream_id| The stream receiving the header data. + // |header_data| A buffer containing the header data chunk received. + // |len| The length of the header data buffer. A length of zero indicates + // that the header data block has been completely sent. + // When this function returns true the visitor indicates that it accepted + // all of the data. Returning false indicates that that an unrecoverable + // error has occurred, such as bad header data or resource exhaustion. + virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) = 0; + + // Called when a SYN_STREAM frame is received. + // Note that header block data is not included. See + // OnControlFrameHeaderData(). + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional) = 0; + + // Called when a SYN_REPLY frame is received. + // Note that header block data is not included. See + // OnControlFrameHeaderData(). + virtual void OnSynReply(SpdyStreamId stream_id, bool fin) = 0; + + // Called when a RST_STREAM frame has been parsed. + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) = 0; + + // Called when a SETTINGS frame is received. + // |clear_persisted| True if the respective flag is set on the SETTINGS frame. + virtual void OnSettings(bool clear_persisted) {} + + // Called when a complete setting within a SETTINGS frame has been parsed and + // validated. + virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0; + + // Called when a PING frame has been parsed. + virtual void OnPing(uint32 unique_id) = 0; + + // Called when a GOAWAY frame has been parsed. + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) = 0; + + // Called when a HEADERS frame is received. + // Note that header block data is not included. See + // OnControlFrameHeaderData(). + virtual void OnHeaders(SpdyStreamId stream_id, bool fin) = 0; + + // Called when a WINDOW_UPDATE frame has been parsed. + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) = 0; + + // Called when a chunk of payload data for a credential frame is available. + // |header_data| A buffer containing the header data chunk received. + // |len| The length of the header data buffer. A length of zero indicates + // that the header data block has been completely sent. + // When this function returns true the visitor indicates that it accepted + // all of the data. Returning false indicates that that an unrecoverable + // error has occurred, such as bad header data or resource exhaustion. + virtual bool OnCredentialFrameData(const char* credential_data, + size_t len) = 0; + + // Called when a BLOCKED frame has been parsed. + virtual void OnBlocked(SpdyStreamId stream_id) {} + + // Called when a PUSH_PROMISE frame is received. + // Note that header block data is not included. See + // OnControlFrameHeaderData(). + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) = 0; +}; + +// Optionally, and in addition to SpdyFramerVisitorInterface, a class supporting +// SpdyFramerDebugVisitorInterface may be used in conjunction with SpdyFramer in +// order to extract debug/internal information about the SpdyFramer as it +// operates. +// +// Most SPDY implementations need not bother with this interface at all. +class NET_EXPORT_PRIVATE SpdyFramerDebugVisitorInterface { + public: + virtual ~SpdyFramerDebugVisitorInterface() {} + + // Called after compressing a frame with a payload of + // a list of name-value pairs. + // |payload_len| is the uncompressed payload size. + // |frame_len| is the compressed frame size. + virtual void OnSendCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) {} + + // Called when a frame containing a compressed payload of + // name-value pairs is received. + // |frame_len| is the compressed frame size. + virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len) {} +}; + +class NET_EXPORT_PRIVATE SpdyFramer { + public: + // SPDY states. + // TODO(mbelshe): Can we move these into the implementation + // and avoid exposing through the header. (Needed for test) + enum SpdyState { + SPDY_ERROR, + SPDY_RESET, + SPDY_AUTO_RESET, + SPDY_READING_COMMON_HEADER, + SPDY_CONTROL_FRAME_PAYLOAD, + SPDY_IGNORE_REMAINING_PAYLOAD, + SPDY_FORWARD_STREAM_FRAME, + SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, + SPDY_CONTROL_FRAME_HEADER_BLOCK, + SPDY_CREDENTIAL_FRAME_PAYLOAD, + SPDY_SETTINGS_FRAME_PAYLOAD, + }; + + // SPDY error codes. + enum SpdyError { + SPDY_NO_ERROR, + SPDY_INVALID_CONTROL_FRAME, // Control frame is mal-formatted. + SPDY_CONTROL_PAYLOAD_TOO_LARGE, // Control frame payload was too large. + SPDY_ZLIB_INIT_FAILURE, // The Zlib library could not initialize. + SPDY_UNSUPPORTED_VERSION, // Control frame has unsupported version. + SPDY_DECOMPRESS_FAILURE, // There was an error decompressing. + SPDY_COMPRESS_FAILURE, // There was an error compressing. + SPDY_CREDENTIAL_FRAME_CORRUPT, // CREDENTIAL frame could not be parsed. + SPDY_INVALID_DATA_FRAME_FLAGS, // Data frame has invalid flags. + SPDY_INVALID_CONTROL_FRAME_FLAGS, // Control frame has invalid flags. + + LAST_ERROR, // Must be the last entry in the enum. + }; + + // Constant for invalid (or unknown) stream IDs. + static const SpdyStreamId kInvalidStream; + + // The maximum size of header data chunks delivered to the framer visitor + // through OnControlFrameHeaderData. (It is exposed here for unit test + // purposes.) + static const size_t kHeaderDataChunkMaxSize; + + // Serializes a SpdyHeaderBlock. + static void WriteHeaderBlock(SpdyFrameBuilder* frame, + const int spdy_version, + const SpdyHeaderBlock* headers); + + // Retrieve serialized length of SpdyHeaderBlock. + // TODO(hkhalil): Remove, or move to quic code. + static size_t GetSerializedLength(const int spdy_version, + const SpdyHeaderBlock* headers); + + // Create a new Framer, provided a SPDY version. + explicit SpdyFramer(SpdyMajorVersion version); + virtual ~SpdyFramer(); + + // Set callbacks to be called from the framer. A visitor must be set, or + // else the framer will likely crash. It is acceptable for the visitor + // to do nothing. If this is called multiple times, only the last visitor + // will be used. + void set_visitor(SpdyFramerVisitorInterface* visitor) { + visitor_ = visitor; + } + + // Set debug callbacks to be called from the framer. The debug visitor is + // completely optional and need not be set in order for normal operation. + // If this is called multiple times, only the last visitor will be used. + void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor) { + debug_visitor_ = debug_visitor; + } + + // Pass data into the framer for parsing. + // Returns the number of bytes consumed. It is safe to pass more bytes in + // than may be consumed. + size_t ProcessInput(const char* data, size_t len); + + // Resets the framer state after a frame has been successfully decoded. + // TODO(mbelshe): can we make this private? + void Reset(); + + // Check the state of the framer. + SpdyError error_code() const { return error_code_; } + SpdyState state() const { return state_; } + bool HasError() const { return state_ == SPDY_ERROR; } + + // Given a buffer containing a decompressed header block in SPDY + // serialized format, parse out a SpdyHeaderBlock, putting the results + // in the given header block. + // Returns number of bytes consumed if successfully parsed, 0 otherwise. + size_t ParseHeaderBlockInBuffer(const char* header_data, + size_t header_length, + SpdyHeaderBlock* block) const; + + // Create a data frame. + // |stream_id| is the stream for this frame + // |data| is the data to be included in the frame. + // |len| is the length of the data + // |flags| is the flags to use with the data. + // To mark this frame as the last data frame, enable DATA_FLAG_FIN. + SpdyFrame* CreateDataFrame(SpdyStreamId stream_id, const char* data, + uint32 len, SpdyDataFlags flags) const; + SpdySerializedFrame* SerializeData(const SpdyDataIR& data) const; + // Serializes just the data frame header, excluding actual data payload. + SpdySerializedFrame* SerializeDataFrameHeader(const SpdyDataIR& data) const; + + // Creates and serializes a SYN_STREAM frame. + // |stream_id| is the id for this stream. + // |associated_stream_id| is the associated stream id for this stream. + // |priority| is the priority (GetHighestPriority()-GetLowestPriority) for + // this stream. + // |credential_slot| is the CREDENTIAL slot to be used for this request. + // |flags| is the flags to use with the data. + // To mark this frame as the last frame, enable CONTROL_FLAG_FIN. + // |compressed| specifies whether the frame should be compressed. + // |headers| is the header block to include in the frame. + SpdyFrame* CreateSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers); + SpdySerializedFrame* SerializeSynStream(const SpdySynStreamIR& syn_stream); + + // Create a SYN_REPLY SpdyFrame. + // |stream_id| is the stream for this frame. + // |flags| is the flags to use with the data. + // To mark this frame as the last frame, enable CONTROL_FLAG_FIN. + // |compressed| specifies whether the frame should be compressed. + // |headers| is the header block to include in the frame. + SpdyFrame* CreateSynReply(SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers); + SpdySerializedFrame* SerializeSynReply(const SpdySynReplyIR& syn_reply); + + SpdyFrame* CreateRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) const; + SpdySerializedFrame* SerializeRstStream( + const SpdyRstStreamIR& rst_stream) const; + + // Creates and serializes a SETTINGS frame. The SETTINGS frame is + // used to communicate name/value pairs relevant to the communication channel. + SpdyFrame* CreateSettings(const SettingsMap& values) const; + SpdySerializedFrame* SerializeSettings(const SpdySettingsIR& settings) const; + + // Creates and serializes a PING frame. The unique_id is used to + // identify the ping request/response. + SpdyFrame* CreatePingFrame(uint32 unique_id) const; + SpdySerializedFrame* SerializePing(const SpdyPingIR& ping) const; + + // Creates and serializes a GOAWAY frame. The GOAWAY frame is used + // prior to the shutting down of the TCP connection, and includes the + // stream_id of the last stream the sender of the frame is willing to process + // to completion. + SpdyFrame* CreateGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) const; + SpdySerializedFrame* SerializeGoAway(const SpdyGoAwayIR& goaway) const; + + // Creates and serializes a HEADERS frame. The HEADERS frame is used + // for sending additional headers outside of a SYN_STREAM/SYN_REPLY. The + // arguments are the same as for CreateSynReply. + SpdyFrame* CreateHeaders(SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers); + SpdySerializedFrame* SerializeHeaders(const SpdyHeadersIR& headers); + + // Creates and serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE + // frame is used to implement per stream flow control in SPDY. + SpdyFrame* CreateWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) const; + SpdySerializedFrame* SerializeWindowUpdate( + const SpdyWindowUpdateIR& window_update) const; + + // Creates and serializes a CREDENTIAL frame. The CREDENTIAL + // frame is used to send a client certificate to the server when + // request more than one origin are sent over the same SPDY session. + SpdyFrame* CreateCredentialFrame(const SpdyCredential& credential) const; + SpdySerializedFrame* SerializeCredential( + const SpdyCredentialIR& credential) const; + + // Serializes a BLOCKED frame. The BLOCKED frame is used to indicate to the + // remote endpoint that this endpoint believes itself to be flow-control + // blocked but otherwise ready to send data. The BLOCKED frame is purely + // advisory and optional. + SpdySerializedFrame* SerializeBlocked(const SpdyBlockedIR& blocked) const; + + // Creates and serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used + // to inform the client that it will be receiving an additional stream + // in response to the original request. The frame includes synthesized + // headers to explain the upcoming data. + SpdyFrame* CreatePushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + const SpdyHeaderBlock* headers); + SpdySerializedFrame* SerializePushPromise( + const SpdyPushPromiseIR& push_promise); + + // Given a CREDENTIAL frame's payload, extract the credential. + // Returns true on successful parse, false otherwise. + // TODO(hkhalil): Implement CREDENTIAL frame parsing in SpdyFramer + // and eliminate this method. + static bool ParseCredentialData(const char* data, size_t len, + SpdyCredential* credential); + + // Serialize a frame of unknown type. + SpdySerializedFrame* SerializeFrame(const SpdyFrameIR& frame); + + // NOTES about frame compression. + // We want spdy to compress headers across the entire session. As long as + // the session is over TCP, frames are sent serially. The client & server + // can each compress frames in the same order and then compress them in that + // order, and the remote can do the reverse. However, we ultimately want + // the creation of frames to be less sensitive to order so that they can be + // placed over a UDP based protocol and yet still benefit from some + // compression. We don't know of any good compression protocol which does + // not build its state in a serial (stream based) manner.... For now, we're + // using zlib anyway. + + // Compresses a SpdyFrame. + // On success, returns a new SpdyFrame with the payload compressed. + // Compression state is maintained as part of the SpdyFramer. + // Returned frame must be freed with "delete". + // On failure, returns NULL. + SpdyFrame* CompressFrame(const SpdyFrame& frame); + + // For ease of testing and experimentation we can tweak compression on/off. + void set_enable_compression(bool value) { + enable_compression_ = value; + } + + // Used only in log messages. + void set_display_protocol(const std::string& protocol) { + display_protocol_ = protocol; + } + + // Returns the (minimum) size of frames (sans variable-length portions). + size_t GetDataFrameMinimumSize() const; + size_t GetControlFrameHeaderSize() const; + size_t GetSynStreamMinimumSize() const; + size_t GetSynReplyMinimumSize() const; + size_t GetRstStreamSize() const; + size_t GetSettingsMinimumSize() const; + size_t GetPingSize() const; + size_t GetGoAwaySize() const; + size_t GetHeadersMinimumSize() const; + size_t GetWindowUpdateSize() const; + size_t GetCredentialMinimumSize() const; + size_t GetBlockedSize() const; + size_t GetPushPromiseMinimumSize() const; + + // Returns the minimum size a frame can be (data or control). + size_t GetFrameMinimumSize() const; + + // Returns the maximum size a frame can be (data or control). + size_t GetFrameMaximumSize() const; + + // Returns the maximum payload size of a DATA frame. + size_t GetDataFrameMaximumPayload() const; + + // For debugging. + static const char* StateToString(int state); + static const char* ErrorCodeToString(int error_code); + static const char* StatusCodeToString(int status_code); + static const char* FrameTypeToString(SpdyFrameType type); + + SpdyMajorVersion protocol_version() const { return spdy_version_; } + + bool probable_http_response() const { return probable_http_response_; } + + SpdyPriority GetLowestPriority() const { return spdy_version_ < 3 ? 3 : 7; } + SpdyPriority GetHighestPriority() const { return 0; } + + // Deliver the given control frame's compressed headers block to the visitor + // in decompressed form, in chunks. Returns true if the visitor has + // accepted all of the chunks. + bool IncrementallyDecompressControlFrameHeaderData( + SpdyStreamId stream_id, + const char* data, + size_t len); + + protected: + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, BasicCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameSizesAreValidated); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HeaderCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, DecompressUncompressedFrame); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ExpandBuffer_HeapSmash); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HugeHeaderBlock); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, + UnclosedStreamDataCompressorsOneByteAtATime); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, + UncompressLargerThanFrameBufferInitialSize); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ReadLargeSettingsFrame); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, + ReadLargeSettingsFrameInSmallChunks); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameAtMaxSizeLimit); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameTooLarge); + friend class net::HttpNetworkLayer; // This is temporary for the server. + friend class net::HttpNetworkTransactionTest; + friend class net::HttpProxyClientSocketPoolTest; + friend class net::SpdyHttpStreamTest; + friend class net::SpdyNetworkTransactionTest; + friend class net::SpdyProxyClientSocketTest; + friend class net::SpdySessionTest; + friend class net::SpdyStreamTest; + friend class net::SpdyWebSocketStreamTest; + friend class net::WebSocketJobTest; + friend class test::TestSpdyVisitor; + + private: + // Internal breakouts from ProcessInput. Each returns the number of bytes + // consumed from the data. + size_t ProcessCommonHeader(const char* data, size_t len); + size_t ProcessControlFramePayload(const char* data, size_t len); + size_t ProcessCredentialFramePayload(const char* data, size_t len); + size_t ProcessControlFrameBeforeHeaderBlock(const char* data, size_t len); + size_t ProcessControlFrameHeaderBlock(const char* data, size_t len); + size_t ProcessSettingsFramePayload(const char* data, size_t len); + size_t ProcessDataFramePayload(const char* data, size_t len); + + // Helpers for above internal breakouts from ProcessInput. + void ProcessControlFrameHeader(uint16 control_frame_type_field); + bool ProcessSetting(const char* data); // Always passed exactly 8 bytes. + + // Retrieve serialized length of SpdyHeaderBlock. If compression is enabled, a + // maximum estimate is returned. + size_t GetSerializedLength(const SpdyHeaderBlock& headers); + + // Get (and lazily initialize) the ZLib state. + z_stream* GetHeaderCompressor(); + z_stream* GetHeaderDecompressor(); + + private: + // Deliver the given control frame's uncompressed headers block to the + // visitor in chunks. Returns true if the visitor has accepted all of the + // chunks. + bool IncrementallyDeliverControlFrameHeaderData(SpdyStreamId stream_id, + const char* data, + size_t len); + + // Utility to copy the given data block to the current frame buffer, up + // to the given maximum number of bytes, and update the buffer + // data (pointer and length). Returns the number of bytes + // read, and: + // *data is advanced the number of bytes read. + // *len is reduced by the number of bytes read. + size_t UpdateCurrentFrameBuffer(const char** data, size_t* len, + size_t max_bytes); + + void WriteHeaderBlockToZ(const SpdyHeaderBlock* headers, + z_stream* out) const; + + void SerializeNameValueBlockWithoutCompression( + SpdyFrameBuilder* builder, + const SpdyNameValueBlock& name_value_block) const; + + // Compresses automatically according to enable_compression_. + void SerializeNameValueBlock( + SpdyFrameBuilder* builder, + const SpdyFrameWithNameValueBlockIR& frame); + + // Set the error code and moves the framer into the error state. + void set_error(SpdyError error); + + size_t GoAwaySize() const; + + // The maximum size of the control frames that we support. + // This limit is arbitrary. We can enforce it here or at the application + // layer. We chose the framing layer, but this can be changed (or removed) + // if necessary later down the line. + size_t GetControlFrameBufferMaxSize() const { + // The theoretical maximum for SPDY3 and earlier is (2^24 - 1) + + // 8, since the length field does not count the size of the + // header. + if (spdy_version_ == SPDY2) { + return 64 * 1024; + } + if (spdy_version_ == SPDY3) { + return 16 * 1024 * 1024; + } + // The theoretical maximum for SPDY4 is 2^16 - 1, as the length + // field does count the size of the header. + return 16 * 1024; + } + + // The size of the control frame buffer. + // Since this is only used for control frame headers, the maximum control + // frame header size (SYN_STREAM) is sufficient; all remaining control + // frame data is streamed to the visitor. + static const size_t kControlFrameBufferSize; + + SpdyState state_; + SpdyState previous_state_; + SpdyError error_code_; + size_t remaining_data_length_; + + // The number of bytes remaining to read from the current control frame's + // headers. Note that header data blocks (for control types that have them) + // are part of the frame's payload, and not the frame's headers. + size_t remaining_control_header_; + + scoped_ptr<char[]> current_frame_buffer_; + // Number of bytes read into the current_frame_buffer_. + size_t current_frame_buffer_length_; + + // The type of the frame currently being read. + SpdyFrameType current_frame_type_; + + // The flags field of the frame currently being read. + uint8 current_frame_flags_; + + // The total length of the frame currently being read, including frame header. + uint32 current_frame_length_; + + // The stream ID field of the frame currently being read, if applicable. + SpdyStreamId current_frame_stream_id_; + + // Scratch space for handling SETTINGS frames. + // TODO(hkhalil): Unify memory for this scratch space with + // current_frame_buffer_. + SpdySettingsScratch settings_scratch_; + + bool enable_compression_; // Controls all compression + // SPDY header compressors. + scoped_ptr<z_stream> header_compressor_; + scoped_ptr<z_stream> header_decompressor_; + + SpdyFramerVisitorInterface* visitor_; + SpdyFramerDebugVisitorInterface* debug_visitor_; + + std::string display_protocol_; + + // The major SPDY version to be spoken/understood by this framer. + const SpdyMajorVersion spdy_version_; + + // Tracks if we've ever gotten far enough in framing to see a control frame of + // type SYN_STREAM or SYN_REPLY. + // + // If we ever get something which looks like a data frame before we've had a + // SYN, we explicitly check to see if it looks like we got an HTTP response to + // a SPDY request. This boolean lets us do that. + bool syn_frame_processed_; + + // If we ever get a data frame before a SYN frame, we check to see if it + // starts with HTTP. If it does, we likely have an HTTP response. This + // isn't guaranteed though: we could have gotten a settings frame and then + // corrupt data that just looks like HTTP, but deterministic checking requires + // a lot more state. + bool probable_http_response_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_FRAMER_H_ diff --git a/chromium/net/spdy/spdy_framer_test.cc b/chromium/net/spdy/spdy_framer_test.cc new file mode 100644 index 00000000000..d731b3adae9 --- /dev/null +++ b/chromium/net/spdy/spdy_framer_test.cc @@ -0,0 +1,4592 @@ +// 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 <algorithm> +#include <iostream> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/platform_test.h" + +using std::string; +using std::max; +using std::min; +using std::numeric_limits; +using testing::_; + +namespace net { + +namespace test { + +static const size_t kMaxDecompressedSize = 1024; + +// TODO(akalin): Make sure expectations on mocks are set before mock +// functions are called, as interleaving expectations and calls is +// undefined. +class MockVisitor : public SpdyFramerVisitorInterface { + public: + MOCK_METHOD1(OnError, void(SpdyFramer* framer)); + MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId stream_id, + size_t length, + bool fin)); + MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin)); + MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id, + const char* header_data, + size_t len)); + MOCK_METHOD6(OnSynStream, void(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 slot, + bool fin, + bool unidirectional)); + MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin)); + MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id, + SpdyRstStreamStatus status)); + MOCK_METHOD1(OnSettings, void(bool clear_persisted)); + MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value)); + MOCK_METHOD1(OnPing, void(uint32 unique_id)); + MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status)); + MOCK_METHOD2(OnHeaders, void(SpdyStreamId stream_id, bool fin)); + MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id, + uint32 delta_window_size)); + MOCK_METHOD2(OnCredentialFrameData, bool(const char* credential_data, + size_t len)); + MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id)); + MOCK_METHOD2(OnPushPromise, void(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id)); +}; + +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)); +}; + +class SpdyFramerTestUtil { + public: + // Decompress a single frame using the decompression context held by + // the SpdyFramer. The implemention is meant for use only in tests + // and will CHECK fail if the input is anything other than a single, + // well-formed compressed frame. + // + // Returns a new decompressed SpdyFrame. + template<class SpdyFrameType> static SpdyFrame* DecompressFrame( + SpdyFramer* framer, const SpdyFrameType& frame) { + DecompressionVisitor visitor(framer->protocol_version()); + framer->set_visitor(&visitor); + CHECK_EQ(frame.size(), framer->ProcessInput(frame.data(), frame.size())); + CHECK_EQ(SpdyFramer::SPDY_RESET, framer->state()); + framer->set_visitor(NULL); + + char* buffer = visitor.ReleaseBuffer(); + CHECK(buffer != NULL); + SpdyFrame* decompressed_frame = new SpdyFrame(buffer, visitor.size(), true); + if (framer->protocol_version() == 4) { + SetFrameLength(decompressed_frame, + visitor.size(), + framer->protocol_version()); + } else { + SetFrameLength(decompressed_frame, + visitor.size() - framer->GetControlFrameHeaderSize(), + framer->protocol_version()); + } + return decompressed_frame; + } + + class DecompressionVisitor : public SpdyFramerVisitorInterface { + public: + explicit DecompressionVisitor(SpdyMajorVersion version) + : version_(version), size_(0), finished_(false) {} + + void ResetBuffer() { + CHECK(buffer_.get() == NULL); + CHECK_EQ(0u, size_); + CHECK(!finished_); + buffer_.reset(new char[kMaxDecompressedSize]); + } + + virtual void OnError(SpdyFramer* framer) OVERRIDE { LOG(FATAL); } + virtual void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) OVERRIDE { + LOG(FATAL) << "Unexpected data frame header"; + } + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) OVERRIDE { + LOG(FATAL); + } + + virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) OVERRIDE { + CHECK(buffer_.get() != NULL); + CHECK_GE(kMaxDecompressedSize, size_ + len); + CHECK(!finished_); + if (len != 0) { + memcpy(buffer_.get() + size_, header_data, len); + size_ += len; + } else { + // Done. + finished_ = true; + } + return true; + } + + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 slot, + bool fin, + bool unidirectional) OVERRIDE { + SpdyFramer framer(version_); + framer.set_enable_compression(false); + const SpdyHeaderBlock null_headers; + int flags = CONTROL_FLAG_NONE; + if (fin) { + flags &= CONTROL_FLAG_FIN; + } + if (unidirectional) { + flags &= CONTROL_FLAG_UNIDIRECTIONAL; + } + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(stream_id, + associated_stream_id, + priority, + slot, + static_cast<SpdyControlFlags>(flags), + false, + &null_headers)); + ResetBuffer(); + memcpy(buffer_.get(), frame->data(), framer.GetSynStreamMinimumSize()); + size_ += framer.GetSynStreamMinimumSize(); + } + + virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE { + SpdyFramer framer(version_); + framer.set_enable_compression(false); + const SpdyHeaderBlock null_headers; + int flags = CONTROL_FLAG_NONE; + if (fin) { + flags &= CONTROL_FLAG_FIN; + } + scoped_ptr<SpdyFrame> frame( + framer.CreateHeaders(stream_id, + static_cast<SpdyControlFlags>(flags), + false, + &null_headers)); + ResetBuffer(); + memcpy(buffer_.get(), frame->data(), framer.GetHeadersMinimumSize()); + size_ += framer.GetSynStreamMinimumSize(); + } + + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) OVERRIDE { + LOG(FATAL); + } + virtual void OnSetting(SpdySettingsIds id, + uint8 flags, + uint32 value) OVERRIDE { + LOG(FATAL); + } + virtual void OnPing(uint32 unique_id) OVERRIDE { + LOG(FATAL); + } + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) OVERRIDE { + LOG(FATAL); + } + + virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE { + SpdyFramer framer(version_); + framer.set_enable_compression(false); + const SpdyHeaderBlock null_headers; + int flags = CONTROL_FLAG_NONE; + if (fin) { + flags &= CONTROL_FLAG_FIN; + } + scoped_ptr<SpdyFrame> frame( + framer.CreateHeaders(stream_id, + static_cast<SpdyControlFlags>(flags), + false, + &null_headers)); + ResetBuffer(); + memcpy(buffer_.get(), frame->data(), framer.GetHeadersMinimumSize()); + size_ += framer.GetHeadersMinimumSize(); + } + + virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) { + LOG(FATAL); + } + virtual bool OnCredentialFrameData(const char* /*credential_data*/, + size_t /*len*/) OVERRIDE { + LOG(FATAL) << "Unexpected CREDENTIAL Frame"; + return false; + } + + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) OVERRIDE { + SpdyFramer framer(version_); + framer.set_enable_compression(false); + const SpdyHeaderBlock null_headers; + scoped_ptr<SpdyFrame> frame( + framer.CreatePushPromise(stream_id, promised_stream_id, + &null_headers)); + ResetBuffer(); + memcpy(buffer_.get(), frame->data(), framer.GetPushPromiseMinimumSize()); + size_ += framer.GetPushPromiseMinimumSize(); + } + + char* ReleaseBuffer() { + CHECK(finished_); + return buffer_.release(); + } + + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) OVERRIDE { + LOG(FATAL); + } + + size_t size() const { + CHECK(finished_); + return size_; + } + + private: + SpdyMajorVersion version_; + scoped_ptr<char[]> buffer_; + size_t size_; + bool finished_; + + DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor); + }; + + private: + DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil); +}; + +class TestSpdyVisitor : public SpdyFramerVisitorInterface, + public SpdyFramerDebugVisitorInterface { + public: + static const size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024; + static const size_t kDefaultCredentialBufferSize = 16 * 1024; + + explicit TestSpdyVisitor(SpdyMajorVersion version) + : framer_(version), + use_compression_(false), + error_count_(0), + syn_frame_count_(0), + syn_reply_frame_count_(0), + headers_frame_count_(0), + goaway_count_(0), + setting_count_(0), + 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), + zero_length_data_frame_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_(-1), + header_control_type_(DATA), + header_buffer_valid_(false), + credential_buffer_(new char[kDefaultCredentialBufferSize]), + credential_buffer_length_(0), + credential_buffer_size_(kDefaultCredentialBufferSize) { + } + + virtual void OnError(SpdyFramer* f) OVERRIDE { + LOG(INFO) << "SpdyFramer Error: " + << SpdyFramer::ErrorCodeToString(f->error_code()); + error_count_++; + } + + virtual void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) OVERRIDE { + data_frame_count_++; + header_stream_id_ = stream_id; + } + + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) OVERRIDE { + EXPECT_EQ(header_stream_id_, stream_id); + if (len == 0) + ++zero_length_data_frame_count_; + + data_bytes_ += len; + std::cerr << "OnStreamFrameData(" << stream_id << ", \""; + if (len > 0) { + for (size_t i = 0 ; i < len; ++i) { + std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec; + } + } + std::cerr << "\", " << len << ")\n"; + } + + virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id, + const char* header_data, + size_t len) OVERRIDE { + ++control_frame_header_data_count_; + CHECK_EQ(header_stream_id_, stream_id); + if (len == 0) { + ++zero_length_control_frame_header_data_count_; + // Indicates end-of-header-block. + CHECK(header_buffer_valid_); + size_t parsed_length = framer_.ParseHeaderBlockInBuffer( + header_buffer_.get(), header_buffer_length_, &headers_); + DCHECK_EQ(header_buffer_length_, parsed_length); + return true; + } + const size_t available = header_buffer_size_ - header_buffer_length_; + if (len > available) { + header_buffer_valid_ = false; + return false; + } + memcpy(header_buffer_.get() + header_buffer_length_, header_data, len); + header_buffer_length_ += len; + return true; + } + + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional) OVERRIDE { + syn_frame_count_++; + InitHeaderStreaming(SYN_STREAM, stream_id); + if (fin) { + fin_flag_count_++; + } + } + + virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE { + syn_reply_frame_count_++; + InitHeaderStreaming(SYN_REPLY, stream_id); + if (fin) { + fin_flag_count_++; + } + } + + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) OVERRIDE { + fin_frame_count_++; + } + + virtual void OnSetting(SpdySettingsIds id, + uint8 flags, + uint32 value) OVERRIDE { + setting_count_++; + } + + virtual void OnPing(uint32 unique_id) OVERRIDE { + DLOG(FATAL); + } + + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) OVERRIDE { + goaway_count_++; + } + + virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE { + headers_frame_count_++; + InitHeaderStreaming(HEADERS, stream_id); + if (fin) { + fin_flag_count_++; + } + } + + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) OVERRIDE { + last_window_update_stream_ = stream_id; + last_window_update_delta_ = delta_window_size; + } + + virtual bool OnCredentialFrameData(const char* credential_data, + size_t len) OVERRIDE { + if (len == 0) { + if (!framer_.ParseCredentialData(credential_buffer_.get(), + credential_buffer_length_, + &credential_)) { + LOG(INFO) << "Error parsing credential data."; + ++error_count_; + } + return true; + } + const size_t available = + credential_buffer_size_ - credential_buffer_length_; + if (len > available) { + return false; + } + memcpy(credential_buffer_.get() + credential_buffer_length_, + credential_data, len); + credential_buffer_length_ += len; + return true; + } + + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) OVERRIDE { + InitHeaderStreaming(PUSH_PROMISE, stream_id); + last_push_promise_stream_ = stream_id; + last_push_promise_promised_stream_ = promised_stream_id; + } + + virtual void OnSendCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) OVERRIDE { + last_payload_len_ = payload_len; + last_frame_len_ = frame_len; + } + + virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len) OVERRIDE { + last_frame_len_ = frame_len; + } + + // Convenience function which runs a framer simulation with particular input. + void SimulateInFramer(const unsigned char* input, size_t size) { + framer_.set_enable_compression(use_compression_); + framer_.set_visitor(this); + size_t input_remaining = size; + const char* input_ptr = reinterpret_cast<const char*>(input); + while (input_remaining > 0 && + framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) { + // To make the tests more interesting, we feed random (amd small) chunks + // into the framer. This simulates getting strange-sized reads from + // the socket. + const size_t kMaxReadSize = 32; + size_t bytes_read = + (rand() % min(input_remaining, kMaxReadSize)) + 1; + size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read); + input_remaining -= bytes_processed; + input_ptr += bytes_processed; + } + } + + void InitHeaderStreaming(SpdyFrameType header_control_type, + SpdyStreamId stream_id) { + DCHECK_GE(header_control_type, FIRST_CONTROL_TYPE); + DCHECK_LE(header_control_type, LAST_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; + DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream); + } + + // 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]); + } + + static size_t header_data_chunk_max_size() { + return SpdyFramer::kHeaderDataChunkMaxSize; + } + + SpdyFramer framer_; + bool use_compression_; + + // Counters from the visitor callbacks. + int error_count_; + int syn_frame_count_; + int syn_reply_frame_count_; + int headers_frame_count_; + int goaway_count_; + int setting_count_; + SpdyStreamId last_window_update_stream_; + uint32 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 zero_length_data_frame_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: + scoped_ptr<char[]> header_buffer_; + size_t header_buffer_length_; + size_t header_buffer_size_; + SpdyStreamId header_stream_id_; + SpdyFrameType header_control_type_; + bool header_buffer_valid_; + SpdyHeaderBlock headers_; + + scoped_ptr<char[]> credential_buffer_; + size_t credential_buffer_length_; + size_t credential_buffer_size_; + SpdyCredential credential_; +}; + +// Retrieves serialized headers from SYN_STREAM frame. +// Does not check that the given frame is a SYN_STREAM. +base::StringPiece GetSerializedHeaders(const SpdyFrame* frame, + const SpdyFramer& framer) { + return base::StringPiece(frame->data() + framer.GetSynStreamMinimumSize(), + frame->size() - framer.GetSynStreamMinimumSize()); +} + +} // namespace test + +} // namespace net + +using net::test::SetFrameLength; +using net::test::SetFrameFlags; +using net::test::CompareCharArraysWithHexError; +using net::test::SpdyFramerTestUtil; +using net::test::TestSpdyVisitor; +using net::test::GetSerializedHeaders; + +namespace net { + +class SpdyFramerTest : public ::testing::TestWithParam<SpdyMajorVersion> { + protected: + virtual void SetUp() { + spdy_version_ = GetParam(); + spdy_version_ch_ = static_cast<unsigned char>(spdy_version_); + } + + void CompareFrame(const string& description, + const SpdyFrame& 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); + } + + void CompareFrames(const string& description, + const SpdyFrame& expected_frame, + const SpdyFrame& actual_frame) { + CompareCharArraysWithHexError( + description, + reinterpret_cast<const unsigned char*>(expected_frame.data()), + expected_frame.size(), + reinterpret_cast<const unsigned char*>(actual_frame.data()), + actual_frame.size()); + } + + // Returns true if the two header blocks have equivalent content. + bool CompareHeaderBlocks(const SpdyHeaderBlock* expected, + const SpdyHeaderBlock* actual) { + if (expected->size() != actual->size()) { + LOG(ERROR) << "Expected " << expected->size() << " headers; actually got " + << actual->size() << "."; + return false; + } + for (SpdyHeaderBlock::const_iterator it = expected->begin(); + it != expected->end(); + ++it) { + SpdyHeaderBlock::const_iterator it2 = actual->find(it->first); + if (it2 == actual->end()) { + LOG(ERROR) << "Expected header name '" << it->first << "'."; + return false; + } + if (it->second.compare(it2->second) != 0) { + LOG(ERROR) << "Expected header named '" << it->first + << "' to have a value of '" << it->second + << "'. The actual value received was '" << it2->second + << "'."; + return false; + } + } + return true; + } + + void AddSpdySettingFromWireFormat(SettingsMap* settings, + uint32 key, + uint32 value) { + SettingsFlagsAndId flags_and_id = + SettingsFlagsAndId::FromWireFormat(spdy_version_, key); + SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id()); + SpdySettingsFlags flags = + static_cast<SpdySettingsFlags>(flags_and_id.flags()); + CHECK(settings->find(id) == settings->end()); + settings->insert(std::make_pair(id, SettingsFlagsAndValue(flags, value))); + } + + bool IsSpdy2() { return spdy_version_ == SPDY2; } + bool IsSpdy3() { return spdy_version_ == SPDY3; } + bool IsSpdy4() { return spdy_version_ == SPDY4; } + + // Version of SPDY protocol to be used. + SpdyMajorVersion spdy_version_; + unsigned char spdy_version_ch_; +}; + +// All tests are run with 3 different SPDY versions: SPDY/2, SPDY/3, SPDY/4. +INSTANTIATE_TEST_CASE_P(SpdyFramerTests, + SpdyFramerTest, + ::testing::Values(SPDY2, SPDY3, SPDY4)); + +// Test that we can encode and decode a SpdyHeaderBlock in serialized form. +TEST_P(SpdyFramerTest, HeaderBlockInBuffer) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "charlie"; + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + // Encode the header block into a SynStream frame. + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + EXPECT_TRUE(frame.get() != NULL); + base::StringPiece serialized_headers = + GetSerializedHeaders(frame.get(), framer); + SpdyHeaderBlock new_headers; + EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size(), + &new_headers)); + + EXPECT_EQ(headers.size(), new_headers.size()); + EXPECT_EQ(headers["alpha"], new_headers["alpha"]); + EXPECT_EQ(headers["gamma"], new_headers["gamma"]); +} + +// Test that if there's not a full frame, we fail to parse it. +TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "charlie"; + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + // Encode the header block into a SynStream frame. + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + EXPECT_TRUE(frame.get() != NULL); + + base::StringPiece serialized_headers = + GetSerializedHeaders(frame.get(), framer); + SpdyHeaderBlock new_headers; + EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size() - 2, + &new_headers)); +} + +TEST_P(SpdyFramerTest, OutOfOrderHeaders) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + if (spdy_version_ < 4) { + frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE); + frame.WriteUInt32(3); // stream_id + } else { + frame.WriteFramePrefix(framer, SYN_STREAM, CONTROL_FLAG_NONE, 3); + } + + frame.WriteUInt32(0); // Associated stream id + frame.WriteUInt16(0); // Priority. + + if (IsSpdy2()) { + frame.WriteUInt16(2); // Number of headers. + frame.WriteString("gamma"); + frame.WriteString("gamma"); + frame.WriteString("alpha"); + frame.WriteString("alpha"); + } else { + frame.WriteUInt32(2); // Number of headers. + frame.WriteStringPiece32("gamma"); + frame.WriteStringPiece32("gamma"); + frame.WriteStringPiece32("alpha"); + frame.WriteStringPiece32("alpha"); + } + // write the length + frame.RewriteLength(framer); + + SpdyHeaderBlock new_headers; + scoped_ptr<SpdyFrame> control_frame(frame.take()); + base::StringPiece serialized_headers = + GetSerializedHeaders(control_frame.get(), framer); + EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size(), + &new_headers)); +} + +// Test that if we receive a SYN_STREAM with stream ID zero, we signal an error +// (but don't crash). +TEST_P(SpdyFramerTest, SynStreamWithStreamIdZero) { + testing::StrictMock<net::test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + scoped_ptr<SpdySerializedFrame> frame( + framer.CreateSynStream(0, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); + ASSERT_TRUE(frame.get() != NULL); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(testing::Eq(&framer))); + EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size())); + EXPECT_TRUE(framer.HasError()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +// Test that if we receive a SYN_REPLY with stream ID zero, we signal an error +// (but don't crash). +TEST_P(SpdyFramerTest, SynReplyWithStreamIdZero) { + testing::StrictMock<net::test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + scoped_ptr<SpdySerializedFrame> frame( + framer.CreateSynReply(0, // stream id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + ASSERT_TRUE(frame.get() != NULL); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(testing::Eq(&framer))); + EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size())); + EXPECT_TRUE(framer.HasError()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +// 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<net::test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + scoped_ptr<SpdySerializedFrame> frame( + framer.CreateHeaders(0, // stream id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + ASSERT_TRUE(frame.get() != NULL); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(testing::Eq(&framer))); + EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size())); + EXPECT_TRUE(framer.HasError()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal an +// error (but don't crash). +TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) { + if (spdy_version_ < SPDY4) { + return; + } + + testing::StrictMock<net::test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + scoped_ptr<SpdySerializedFrame> frame( + framer.CreatePushPromise(0, // stream id + 4, // promised stream id + &headers)); + ASSERT_TRUE(frame.get() != NULL); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(testing::Eq(&framer))); + EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size())); + EXPECT_TRUE(framer.HasError()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we +// signal an error (but don't crash). +TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) { + if (spdy_version_ < SPDY4) { + return; + } + + testing::StrictMock<net::test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + scoped_ptr<SpdySerializedFrame> frame( + framer.CreatePushPromise(3, // stream id + 0, // promised stream id + &headers)); + ASSERT_TRUE(frame.get() != NULL); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(testing::Eq(&framer))); + EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size())); + EXPECT_TRUE(framer.HasError()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +TEST_P(SpdyFramerTest, CreateCredential) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "CREDENTIAL frame"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x0A, + 0x00, 0x00, 0x00, 0x33, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x3b, 0x0A, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } +} + +TEST_P(SpdyFramerTest, ParseCredentialFrameData) { + SpdyFramer framer(spdy_version_); + + { + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x0A, + 0x00, 0x00, 0x00, 0x33, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x37, 0x0A, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, + 0x00, 0x05, 'p', 'r', + 'o', 'o', 'f', 0x00, + 0x00, 0x00, 0x06, 'a', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0C, 'a', 'n', 'o', + 't', 'h', 'e', 'r', + ' ', 'c', 'e', 'r', + 't', 0x00, 0x00, 0x00, + 0x0A, 'f', 'i', 'n', + 'a', 'l', ' ', 'c', + 'e', 'r', 't', + }; + + SpdyCredential credential; + if (IsSpdy4()) { + EXPECT_TRUE(SpdyFramer::ParseCredentialData( + reinterpret_cast<const char*>(kV4FrameData) + + framer.GetControlFrameHeaderSize(), + arraysize(kV4FrameData) - framer.GetControlFrameHeaderSize(), + &credential)); + } else { + EXPECT_TRUE(SpdyFramer::ParseCredentialData( + reinterpret_cast<const char*>(kV3FrameData) + + framer.GetControlFrameHeaderSize(), + arraysize(kV3FrameData) - framer.GetControlFrameHeaderSize(), + &credential)); + } + EXPECT_EQ(3u, credential.slot); + EXPECT_EQ("proof", credential.proof); + EXPECT_EQ("a cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_EQ("another cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_EQ("final cert", credential.certs.front()); + credential.certs.erase(credential.certs.begin()); + EXPECT_TRUE(credential.certs.empty()); + } +} + +TEST_P(SpdyFramerTest, DuplicateHeader) { + SpdyFramer framer(spdy_version_); + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + if (spdy_version_ < 4) { + frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE); + frame.WriteUInt32(3); // stream_id + } else { + frame.WriteFramePrefix(framer, SYN_STREAM, CONTROL_FLAG_NONE, 3); + } + + frame.WriteUInt32(0); // associated stream id + frame.WriteUInt16(0); // Priority. + + if (IsSpdy2()) { + frame.WriteUInt16(2); // Number of headers. + frame.WriteString("name"); + frame.WriteString("value1"); + frame.WriteString("name"); + frame.WriteString("value2"); + } else { + frame.WriteUInt32(2); // Number of headers. + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32("value1"); + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32("value2"); + } + // write the length + frame.RewriteLength(framer); + + SpdyHeaderBlock new_headers; + framer.set_enable_compression(false); + scoped_ptr<SpdyFrame> control_frame(frame.take()); + base::StringPiece serialized_headers = + GetSerializedHeaders(control_frame.get(), framer); + // This should fail because duplicate headers are verboten by the spec. + EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size(), + &new_headers)); +} + +TEST_P(SpdyFramerTest, MultiValueHeader) { + SpdyFramer framer(spdy_version_); + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + if (spdy_version_ < 4) { + frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE); + frame.WriteUInt32(3); // stream_id + } else { + frame.WriteFramePrefix(framer, SYN_STREAM, CONTROL_FLAG_NONE, 3); + } + + frame.WriteUInt32(0); // associated stream id + frame.WriteUInt16(0); // Priority. + + string value("value1\0value2"); + if (IsSpdy2()) { + frame.WriteUInt16(1); // Number of headers. + frame.WriteString("name"); + frame.WriteString(value); + } else { + frame.WriteUInt32(1); // Number of headers. + frame.WriteStringPiece32("name"); + frame.WriteStringPiece32(value); + } + // write the length + frame.RewriteLength(framer); + + SpdyHeaderBlock new_headers; + framer.set_enable_compression(false); + scoped_ptr<SpdyFrame> control_frame(frame.take()); + base::StringPiece serialized_headers = + GetSerializedHeaders(control_frame.get(), framer); + EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size(), + &new_headers)); + EXPECT_TRUE(new_headers.find("name") != new_headers.end()); + EXPECT_EQ(value, new_headers.find("name")->second); +} + +TEST_P(SpdyFramerTest, BasicCompression) { + SpdyHeaderBlock headers; + headers["server"] = "SpdyServer 1.0"; + headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; + headers["status"] = "200"; + headers["version"] = "HTTP/1.1"; + headers["content-type"] = "text/html"; + headers["content-length"] = "12"; + + scoped_ptr<TestSpdyVisitor> visitor(new TestSpdyVisitor(spdy_version_)); + SpdyFramer framer(spdy_version_); + framer.set_debug_visitor(visitor.get()); + scoped_ptr<SpdyFrame> frame1( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); + size_t uncompressed_size1 = visitor->last_payload_len_; + size_t compressed_size1 = + visitor->last_frame_len_ - framer.GetSynStreamMinimumSize(); + if (IsSpdy2()) { + EXPECT_EQ(139u, uncompressed_size1); +#if defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(155u, compressed_size1); +#else // !defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(135u, compressed_size1); +#endif // !defined(USE_SYSTEM_ZLIB) + } else { + EXPECT_EQ(165u, uncompressed_size1); +#if defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(181u, compressed_size1); +#else // !defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(117u, compressed_size1); +#endif // !defined(USE_SYSTEM_ZLIB) + } + scoped_ptr<SpdyFrame> frame2( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); + size_t uncompressed_size2 = visitor->last_payload_len_; + size_t compressed_size2 = + visitor->last_frame_len_ - framer.GetSynStreamMinimumSize(); + + // Expect the second frame to be more compact than the first. + EXPECT_LE(frame2->size(), frame1->size()); + + // Decompress the first frame + scoped_ptr<SpdyFrame> frame3(SpdyFramerTestUtil::DecompressFrame( + &framer, *frame1.get())); + + // Decompress the second frame + visitor.reset(new TestSpdyVisitor(spdy_version_)); + framer.set_debug_visitor(visitor.get()); + scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame( + &framer, *frame2.get())); + size_t uncompressed_size4 = + frame4->size() - framer.GetSynStreamMinimumSize(); + size_t compressed_size4 = + visitor->last_frame_len_ - framer.GetSynStreamMinimumSize(); + if (IsSpdy2()) { + EXPECT_EQ(139u, uncompressed_size4); +#if defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(149u, compressed_size4); +#else // !defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(101u, compressed_size4); +#endif // !defined(USE_SYSTEM_ZLIB) + } else { + EXPECT_EQ(165u, uncompressed_size4); +#if defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(175u, compressed_size4); +#else // !defined(USE_SYSTEM_ZLIB) + EXPECT_EQ(102u, compressed_size4); +#endif // !defined(USE_SYSTEM_ZLIB) + } + + EXPECT_EQ(uncompressed_size1, uncompressed_size2); + EXPECT_EQ(uncompressed_size1, uncompressed_size4); + EXPECT_EQ(compressed_size2, compressed_size4); + + // Expect frames 3 & 4 to be the same. + CompareFrames("Uncompressed SYN_STREAM", *frame3, *frame4); + + // Expect frames 3 to be the same as a uncompressed frame created + // from scratch. + framer.set_enable_compression(false); + scoped_ptr<SpdyFrame> uncompressed_frame( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + CompareFrames("Uncompressed SYN_STREAM", *frame3, *uncompressed_frame); +} + +TEST_P(SpdyFramerTest, CompressEmptyHeaders) { + // See crbug.com/172383 + SpdyHeaderBlock headers; + headers["server"] = "SpdyServer 1.0"; + headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; + headers["status"] = "200"; + headers["version"] = "HTTP/1.1"; + headers["content-type"] = "text/html"; + headers["content-length"] = "12"; + headers["x-empty-header"] = ""; + + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(true); + scoped_ptr<SpdyFrame> frame1( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); +} + +TEST_P(SpdyFramerTest, Basic) { + const unsigned char kV2Input[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'h', 'h', + 0x00, 0x02, 'v', 'v', + + 0x80, spdy_version_ch_, 0x00, 0x08, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 'h', '2', + 0x00, 0x02, 'v', '2', + 0x00, 0x02, 'h', '3', + 0x00, 0x02, 'v', '3', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #3 + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #1 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x00, + + 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + }; + + const unsigned char kV3Input[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, spdy_version_ch_, 0x00, 0x08, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x02, + 'h', '2', + 0x00, 0x00, 0x00, 0x02, + 'v', '2', 0x00, 0x00, + 0x00, 0x02, 'h', '3', + 0x00, 0x00, 0x00, 0x02, + 'v', '3', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #3 + 0x00, 0x00, 0x00, 0x0e, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + + 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #1 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x00, + + 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #3 + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + }; + + const unsigned char kV4Input[] = { + 0x00, 0x1e, 0x01, 0x00, // SYN_STREAM #1 + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x00, 0x24, 0x08, 0x00, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x02, + 'h', '2', 0x00, 0x00, + 0x00, 0x02, 'v', '2', + 0x00, 0x00, 0x00, 0x02, + 'h', '3', 0x00, 0x00, + 0x00, 0x02, 'v', '3', + + 0x00, 0x14, 0x00, 0x00, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x01, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x12, 0x01, 0x00, // SYN Stream #3 + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + + 0x00, 0x10, 0x00, 0x00, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x03, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x0c, 0x00, 0x00, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x01, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x0c, 0x03, 0x00, // RST_STREAM on Stream #1 + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x08, 0x00, 0x00, // DATA on Stream #3 + 0x00, 0x00, 0x00, 0x03, + + 0x00, 0x0c, 0x03, 0x00, // RST_STREAM on Stream #3 + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + }; + + TestSpdyVisitor visitor(spdy_version_); + if (IsSpdy2()) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else if (IsSpdy3()) { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } else { + visitor.SimulateInFramer(kV4Input, sizeof(kV4Input)); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(2, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(24, visitor.data_bytes_); + EXPECT_EQ(2, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(0, visitor.zero_length_data_frame_count_); + EXPECT_EQ(4, visitor.data_frame_count_); +} + +// Test that the FIN flag on a data frame signifies EOF. +TEST_P(SpdyFramerTest, FinOnDataFrame) { + const unsigned char kV2Input[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'h', 'h', + 0x00, 0x02, 'v', 'v', + + 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1 + 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'a', 'a', + 0x00, 0x02, 'b', 'b', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF + 0x01, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + }; + const unsigned char kV3Input[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 'a', 'a', 0x00, 0x00, + 0x00, 0x02, 'b', 'b', + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x0c, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF + 0x01, 0x00, 0x00, 0x04, + 0xde, 0xad, 0xbe, 0xef, + }; + const unsigned char kV4Input[] = { + 0x00, 0x1e, 0x01, 0x00, // SYN_STREAM #1 + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x00, 0x18, 0x02, 0x00, // SYN REPLY Stream #1 + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 'a', 'a', 0x00, 0x00, + 0x00, 0x02, 'b', 'b', + + 0x00, 0x14, 0x00, 0x00, // DATA on Stream #1 + 0x00, 0x00, 0x00, 0x01, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, + + 0x00, 0x0c, 0x00, 0x01, // DATA on Stream #1, with FIN + 0x00, 0x00, 0x00, 0x01, + 0xde, 0xad, 0xbe, 0xef, + }; + + TestSpdyVisitor visitor(spdy_version_); + if (IsSpdy2()) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else if (IsSpdy3()) { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } else { + visitor.SimulateInFramer(kV4Input, sizeof(kV4Input)); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, 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.zero_length_data_frame_count_); + EXPECT_EQ(2, visitor.data_frame_count_); +} + +// Test that the FIN flag on a SYN reply frame signifies EOF. +TEST_P(SpdyFramerTest, FinOnSynReplyFrame) { + const unsigned char kV2Input[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'h', 'h', + 0x00, 0x02, 'v', 'v', + + 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1 + 0x01, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 'a', 'a', + 0x00, 0x02, 'b', 'b', + }; + const unsigned char kV3Input[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1 + 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1 + 0x01, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 'a', 'a', 0x00, 0x00, + 0x00, 0x02, 'b', 'b', + }; + const unsigned char kV4Input[] = { + 0x00, 0x1e, 0x01, 0x00, // SYN_STREAM #1 + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x02, 'h', 'h', + 0x00, 0x00, 0x00, 0x02, + 'v', 'v', + + 0x00, 0x18, 0x02, 0x01, // SYN_REPLY #1, with FIN + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 'a', 'a', 0x00, 0x00, + 0x00, 0x02, 'b', 'b', + }; + + TestSpdyVisitor visitor(spdy_version_); + if (IsSpdy2()) { + visitor.SimulateInFramer(kV2Input, sizeof(kV2Input)); + } else if (IsSpdy3()) { + visitor.SimulateInFramer(kV3Input, sizeof(kV3Input)); + } else { + visitor.SimulateInFramer(kV4Input, sizeof(kV4Input)); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, 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.zero_length_data_frame_count_); + EXPECT_EQ(0, visitor.data_frame_count_); +} + +TEST_P(SpdyFramerTest, HeaderCompression) { + SpdyFramer send_framer(spdy_version_); + SpdyFramer recv_framer(spdy_version_); + + send_framer.set_enable_compression(true); + recv_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kHeader3[] = "header3"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + const char kValue3[] = "value3"; + + // SYN_STREAM #1 + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + SpdySynStreamIR syn_ir_1(1); + syn_ir_1.SetHeader(kHeader1, kValue1); + syn_ir_1.SetHeader(kHeader2, kValue2); + scoped_ptr<SpdyFrame> syn_frame_1(send_framer.SerializeFrame(syn_ir_1)); + EXPECT_TRUE(syn_frame_1.get() != NULL); + + // SYN_STREAM #2 + block[kHeader3] = kValue3; + scoped_ptr<SpdyFrame> syn_frame_2( + send_framer.CreateSynStream(3, // stream id + 0, // associated stream id + 0, // priority + 0, // credential slot + flags, + true, // compress + &block)); + EXPECT_TRUE(syn_frame_2.get() != NULL); + + // Now start decompressing + scoped_ptr<SpdyFrame> decompressed; + scoped_ptr<SpdyFrame> uncompressed; + base::StringPiece serialized_headers; + SpdyHeaderBlock decompressed_headers; + + // Decompress SYN_STREAM #1 + decompressed.reset(SpdyFramerTestUtil::DecompressFrame( + &recv_framer, *syn_frame_1.get())); + EXPECT_TRUE(decompressed.get() != NULL); + serialized_headers = GetSerializedHeaders(decompressed.get(), send_framer); + EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size(), + &decompressed_headers)); + EXPECT_EQ(2u, decompressed_headers.size()); + EXPECT_EQ(kValue1, decompressed_headers[kHeader1]); + EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); + + // Decompress SYN_STREAM #2 + decompressed.reset(SpdyFramerTestUtil::DecompressFrame( + &recv_framer, *syn_frame_2.get())); + EXPECT_TRUE(decompressed.get() != NULL); + serialized_headers = GetSerializedHeaders(decompressed.get(), send_framer); + decompressed_headers.clear(); + EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers.data(), + serialized_headers.size(), + &decompressed_headers)); + EXPECT_EQ(3u, decompressed_headers.size()); + EXPECT_EQ(kValue1, decompressed_headers[kHeader1]); + EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); + EXPECT_EQ(kValue3, decompressed_headers[kHeader3]); +} + +// Verify we don't leak when we leave streams unclosed +TEST_P(SpdyFramerTest, UnclosedStreamDataCompressors) { + SpdyFramer send_framer(spdy_version_); + + send_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + scoped_ptr<SpdyFrame> syn_frame( + send_framer.CreateSynStream(1, // stream id + 0, // associated stream id + 0, // priority + 0, // credential slot + flags, + true, // compress + &block)); + EXPECT_TRUE(syn_frame.get() != NULL); + + const char bytes[] = "this is a test test test test test!"; + scoped_ptr<SpdyFrame> send_frame( + send_framer.CreateDataFrame( + 1, bytes, arraysize(bytes), + static_cast<SpdyDataFlags>(DATA_FLAG_FIN))); + EXPECT_TRUE(send_frame.get() != NULL); + + // Run the inputs through the framer. + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + const unsigned char* data; + data = reinterpret_cast<const unsigned char*>(syn_frame->data()); + visitor.SimulateInFramer(data, syn_frame->size()); + data = reinterpret_cast<const unsigned char*>(send_frame->data()); + visitor.SimulateInFramer(data, send_frame->size()); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(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.zero_length_data_frame_count_); + EXPECT_EQ(1, 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) { + SpdyFramer send_framer(spdy_version_); + + send_framer.set_enable_compression(true); + + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + + SpdyHeaderBlock block; + block[kHeader1] = kValue1; + block[kHeader2] = kValue2; + SpdyControlFlags flags(CONTROL_FLAG_NONE); + scoped_ptr<SpdyFrame> syn_frame( + send_framer.CreateSynStream(1, // stream id + 0, // associated stream id + 0, // priority + 0, // credential slot + flags, + true, // compress + &block)); + EXPECT_TRUE(syn_frame.get() != NULL); + + const char bytes[] = "this is a test test test test test!"; + scoped_ptr<SpdyFrame> send_frame( + send_framer.CreateDataFrame( + 1, bytes, arraysize(bytes), + static_cast<SpdyDataFlags>(DATA_FLAG_FIN))); + EXPECT_TRUE(send_frame.get() != NULL); + + // Run the inputs through the framer. + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + const unsigned char* data; + data = reinterpret_cast<const unsigned char*>(syn_frame->data()); + for (size_t idx = 0; idx < syn_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.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_EQ(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.zero_length_data_frame_count_); + EXPECT_EQ(1, visitor.data_frame_count_); +} + +TEST_P(SpdyFramerTest, WindowUpdateFrame) { + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x12345678)); + + const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78 + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78 + }; + + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreateDataFrame) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "'hello' data frame, no FIN"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x05, + 'h', 'e', 'l', 'l', + 'o' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 'h', 'e', 'l', 'l', + 'o' + }; + const char bytes[] = "hello"; + + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, bytes, strlen(bytes), DATA_FLAG_NONE)); + if (IsSpdy4()) { + CompareFrame( + kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame( + kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + + SpdyDataIR data_ir(1); + data_ir.SetDataShallow(base::StringPiece(bytes, strlen(bytes))); + frame.reset(framer.SerializeDataFrameHeader(data_ir)); + CompareCharArraysWithHexError( + kDescription, + reinterpret_cast<const unsigned char*>(frame->data()), + framer.GetDataFrameMinimumSize(), + IsSpdy4() ? kV4FrameData : kV3FrameData, + framer.GetDataFrameMinimumSize()); + } + + { + const char kDescription[] = "Data frame with negative data byte, no FIN"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0xff + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0xff + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "\xff", 1, DATA_FLAG_NONE)); + if (IsSpdy4()) { + CompareFrame( + kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame( + kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "'hello' data frame, with FIN"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x05, + 'h', 'e', 'l', 'l', + 'o' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0d, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 'h', 'e', 'l', 'l', + 'o' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "hello", 5, DATA_FLAG_FIN)); + if (IsSpdy4()) { + CompareFrame( + kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame( + kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "Empty data frame"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, "", 0, DATA_FLAG_NONE)); + if (IsSpdy4()) { + CompareFrame( + kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame( + kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "Data frame with max stream ID"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x7f, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x05, + 'h', 'e', 'l', 'l', + 'o' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0d, 0x00, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 'h', 'e', 'l', 'l', + 'o' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 0x7fffffff, "hello", 5, DATA_FLAG_FIN)); + if (IsSpdy4()) { + CompareFrame( + kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame( + kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + if (!IsSpdy4()) { + // This test does not apply to SPDY 4 because the max frame size is smaller + // than 4MB. + const char kDescription[] = "Large data frame"; + const int kDataSize = 4 * 1024 * 1024; // 4 MB + const string kData(kDataSize, 'A'); + const unsigned char kFrameHeader[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x40, 0x00, 0x00, + }; + + const int kFrameSize = arraysize(kFrameHeader) + kDataSize; + scoped_ptr<unsigned char[]> expected_frame_data( + new unsigned char[kFrameSize]); + memcpy(expected_frame_data.get(), kFrameHeader, arraysize(kFrameHeader)); + memset(expected_frame_data.get() + arraysize(kFrameHeader), 'A', kDataSize); + + scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( + 1, kData.data(), kData.size(), DATA_FLAG_FIN)); + CompareFrame(kDescription, *frame, expected_frame_data.get(), kFrameSize); + } +} + +TEST_P(SpdyFramerTest, CreateSynStreamUncompressed) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + { + const char kDescription[] = "SYN_STREAM frame, lowest pri, slot 2, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kPri = IsSpdy2() ? 0xC0 : 0xE0; + const unsigned char kCre = IsSpdy2() ? 0 : 2; + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + kPri, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x2a, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + kPri, kCre, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'b', + 'a', 'r' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x2e, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + kPri, kCre, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'b', + 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + framer.GetLowestPriority(), + kCre, // credential slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = + "SYN_STREAM frame with a 0-length header name, highest pri, FIN, " + "max stream ID"; + + SpdyHeaderBlock headers; + headers[std::string()] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x1D, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x27, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x2b, 0x01, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(0x7fffffff, // stream id + 0x7fffffff, // associated stream id + framer.GetHighestPriority(), + 0, // credential slot + CONTROL_FLAG_FIN, + false, // compress + &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = + "SYN_STREAM frame with a 0-length header val, high pri, FIN, " + "max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kPri = IsSpdy2() ? 0x40 : 0x20; + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x1D, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + kPri, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x27, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + kPri, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x00 + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x2b, 0x01, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0xff, 0xff, + kPri, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x00, 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x00 + }; + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(0x7fffffff, // stream id + 0x7fffffff, // associated stream id + 1, // priority + 0, // credential slot + CONTROL_FLAG_FIN, + false, // compress + &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} + +// TODO(phajdan.jr): Clean up after we no longer need +// to workaround http://crbug.com/139744. +#if !defined(USE_SYSTEM_ZLIB) +TEST_P(SpdyFramerTest, CreateSynStreamCompressed) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(true); + + { + const char kDescription[] = + "SYN_STREAM frame, low pri, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const SpdyPriority priority = IsSpdy2() ? 2 : 4; + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x06, 0x08, 0xa0, + 0xb4, 0xfc, 0x7c, 0x80, + 0x00, 0x62, 0x60, 0x4e, + 0xcb, 0xcf, 0x67, 0x60, + 0x06, 0x08, 0xa0, 0xa4, + 0xc4, 0x22, 0x80, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0xff, 0xff, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x37, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x38, 0xEA, + 0xE3, 0xC6, 0xA7, 0xC2, + 0x02, 0xE5, 0x0E, 0x50, + 0xC2, 0x4B, 0x4A, 0x04, + 0xE5, 0x0B, 0x66, 0x80, + 0x00, 0x4A, 0xCB, 0xCF, + 0x07, 0x08, 0x20, 0x10, + 0x95, 0x96, 0x9F, 0x0F, + 0xA2, 0x00, 0x02, 0x28, + 0x29, 0xB1, 0x08, 0x20, + 0x80, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x3b, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x38, 0xea, + 0xe3, 0xc6, 0xa7, 0xc2, + 0x02, 0xe5, 0x0e, 0x50, + 0xc2, 0x4b, 0x4a, 0x04, + 0xe5, 0x0b, 0x66, 0x80, + 0x00, 0x4a, 0xcb, 0xcf, + 0x07, 0x08, 0x20, 0x10, + 0x95, 0x96, 0x9f, 0x0f, + 0xa2, 0x00, 0x02, 0x28, + 0x29, 0xb1, 0x08, 0x20, + 0x80, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, + }; + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(1, // stream id + 0, // associated stream id + priority, + 0, // credential slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} +#endif // !defined(USE_SYSTEM_ZLIB) + +TEST_P(SpdyFramerTest, CreateSynReplyUncompressed) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + { + const char kDescription[] = "SYN_REPLY frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x28, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 1, CONTROL_FLAG_NONE, false, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = + "SYN_REPLY frame with a 0-length header name, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers[std::string()] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x25, 0x02, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = + "SYN_REPLY frame with a 0-length header val, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x25, 0x02, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} + +// TODO(phajdan.jr): Clean up after we no longer need +// to workaround http://crbug.com/139744. +#if !defined(USE_SYSTEM_ZLIB) +TEST_P(SpdyFramerTest, CreateSynReplyCompressed) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(true); + + { + const char kDescription[] = "SYN_REPLY frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x06, 0x08, 0xa0, + 0xb4, 0xfc, 0x7c, 0x80, + 0x00, 0x62, 0x60, 0x4e, + 0xcb, 0xcf, 0x67, 0x60, + 0x06, 0x08, 0xa0, 0xa4, + 0xc4, 0x22, 0x80, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0xff, 0xff, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x31, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0x66, 0x80, 0x00, 0x4a, + 0xcb, 0xcf, 0x07, 0x08, + 0x20, 0x10, 0x95, 0x96, + 0x9f, 0x0f, 0xa2, 0x00, + 0x02, 0x28, 0x29, 0xb1, + 0x08, 0x20, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0xff, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x35, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0x66, 0x80, 0x00, 0x4a, + 0xcb, 0xcf, 0x07, 0x08, + 0x20, 0x10, 0x95, 0x96, + 0x9f, 0x0f, 0xa2, 0x00, + 0x02, 0x28, 0x29, 0xb1, + 0x08, 0x20, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0xff, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( + 1, CONTROL_FLAG_NONE, true, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} +#endif // !defined(USE_SYSTEM_ZLIB) + +TEST_P(SpdyFramerTest, CreateRstStream) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "RST_STREAM frame"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame( + framer.CreateRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "RST_STREAM frame with max stream ID"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x03, 0x00, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateRstStream( + 0x7FFFFFFF, RST_STREAM_PROTOCOL_ERROR)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "RST_STREAM frame with max status code"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x06, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x03, 0x00, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x06, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateRstStream( + 0x7FFFFFFF, RST_STREAM_INTERNAL_ERROR)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } +} + +TEST_P(SpdyFramerTest, CreateSettings) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "Network byte order SETTINGS frame"; + + uint32 kValue = 0x0a0b0c0d; + SpdySettingsFlags kFlags = static_cast<SpdySettingsFlags>(0x01); + SpdySettingsIds kId = static_cast<SpdySettingsIds>(0x020304); + + SettingsMap settings; + settings[kId] = SettingsFlagsAndValue(kFlags, kValue); + + EXPECT_EQ(kFlags, settings[kId].first); + EXPECT_EQ(kValue, settings[kId].second); + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, + 0x04, 0x03, 0x02, 0x01, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x02, 0x03, 0x04, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x14, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x02, 0x03, 0x04, + 0x0a, 0x0b, 0x0c, 0x0d, + }; + + scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = "Basic SETTINGS frame"; + + SettingsMap settings; + AddSpdySettingFromWireFormat( + &settings, 0x00000000, 0x00000001); // 1st Setting + AddSpdySettingFromWireFormat( + &settings, 0x01000001, 0x00000002); // 2nd Setting + AddSpdySettingFromWireFormat( + &settings, 0x02000002, 0x00000003); // 3rd Setting + AddSpdySettingFromWireFormat( + &settings, 0x03000003, 0xff000004); // 4th Setting + + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, // 1st Setting + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x01, // 2nd Setting + 0x00, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x02, // 3rd Setting + 0x00, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x03, // 4th Setting + 0xff, 0x00, 0x00, 0x04, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x2c, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, // 1st Setting + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x01, // 2nd Setting + 0x00, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x02, // 3rd Setting + 0x00, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x03, // 4th Setting + 0xff, 0x00, 0x00, 0x04, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "Empty SETTINGS frame"; + + SettingsMap settings; + + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } +} + +TEST_P(SpdyFramerTest, CreatePingFrame) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "PING frame"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x04, + 0x12, 0x34, 0x56, 0x78, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x12, 0x34, 0x56, 0x78, + }; + scoped_ptr<SpdyFrame> frame(framer.CreatePingFrame(0x12345678u)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } +} + +TEST_P(SpdyFramerTest, CreateGoAway) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "GOAWAY frame"; + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x10, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0, GOAWAY_OK)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = "GOAWAY frame with max stream ID, status"; + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x04, + 0x7f, 0xff, 0xff, 0xff, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x10, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF, + GOAWAY_INTERNAL_ERROR)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} + +TEST_P(SpdyFramerTest, CreateHeadersUncompressed) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x28, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 1, CONTROL_FLAG_NONE, false, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header name, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers[std::string()] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x25, 0x08, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'b', 'a', + 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x21, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x25, 0x08, 0x01, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x00 + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} + +// TODO(phajdan.jr): Clean up after we no longer need +// to workaround http://crbug.com/139744. +#if !defined(USE_SYSTEM_ZLIB) +TEST_P(SpdyFramerTest, CreateHeadersCompressed) { + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(true); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x06, 0x08, 0xa0, + 0xb4, 0xfc, 0x7c, 0x80, + 0x00, 0x62, 0x60, 0x4e, + 0xcb, 0xcf, 0x67, 0x60, + 0x06, 0x08, 0xa0, 0xa4, + 0xc4, 0x22, 0x80, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0xff, 0xff, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x31, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0x66, 0x80, 0x00, 0x4a, + 0xcb, 0xcf, 0x07, 0x08, + 0x20, 0x10, 0x95, 0x96, + 0x9f, 0x0f, 0xa2, 0x00, + 0x02, 0x28, 0x29, 0xb1, + 0x08, 0x20, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0xff, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x35, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x38, 0xea, 0xe3, 0xc6, + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0x66, 0x80, 0x00, 0x4a, + 0xcb, 0xcf, 0x07, 0x08, + 0x20, 0x10, 0x95, 0x96, + 0x9f, 0x0f, 0xa2, 0x00, + 0x02, 0x28, 0x29, 0xb1, + 0x08, 0x20, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 1, CONTROL_FLAG_NONE, true, &headers)); + if (IsSpdy2()) { + CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } else { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } + } +} +#endif // !defined(USE_SYSTEM_ZLIB) + +TEST_P(SpdyFramerTest, CreateWindowUpdate) { + SpdyFramer framer(spdy_version_); + + { + const char kDescription[] = "WINDOW_UPDATE frame"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame( + framer.CreateWindowUpdate(1, 1)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "WINDOW_UPDATE frame with max stream ID"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x09, 0x00, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } + + { + const char kDescription[] = "WINDOW_UPDATE frame with max window delta"; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x7f, 0xff, 0xff, 0xff, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x0c, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x7f, 0xff, 0xff, 0xff, + }; + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF)); + if (IsSpdy4()) { + CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData)); + } else { + CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData)); + } + } +} + +TEST_P(SpdyFramerTest, SerializeBlocked) { + if (spdy_version_ < SPDY4) { + return; + } + + SpdyFramer framer(spdy_version_); + + const char kDescription[] = "BLOCKED frame"; + const unsigned char kFrameData[] = { + 0x00, 0x08, 0x0b, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + SpdyBlockedIR blocked_ir(0); + scoped_ptr<SpdySerializedFrame> frame(framer.SerializeFrame(blocked_ir)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + +} + +TEST_P(SpdyFramerTest, CreatePushPromiseUncompressed) { + if (spdy_version_ < SPDY4) { + return; + } + + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + + const char kDescription[] = "PUSH_PROMISE frame"; + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kFrameData[] = { + 0x00, 0x2C, 0x0C, 0x00, // length = 44, type = 12, flags = 0 + 0x00, 0x00, 0x00, 0x2A, // stream id = 42 + 0x00, 0x00, 0x00, 0x39, // promised stream id = 57 + 0x00, 0x00, 0x00, 0x02, // start of uncompressed header block + 0x00, 0x00, 0x00, 0x03, + 'b', 'a', 'r', 0x00, + 0x00, 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x00, + 0x00, 0x03, 'f', 'o', + 'o', 0x00, 0x00, 0x00, + 0x03, 'b', 'a', 'r' // end of uncompressed header block + }; + + scoped_ptr<SpdySerializedFrame> frame(framer.CreatePushPromise( + 42, 57, &headers)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); +} + +TEST_P(SpdyFramerTest, CreatePushPromiseCompressed) { + if (spdy_version_ < SPDY4) { + return; + } + + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(true); + + const char kDescription[] = "PUSH_PROMISE frame"; + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kFrameData[] = { + 0x00, 0x39, 0x0C, 0x00, // length = 57, type = 12, flags = 0 + 0x00, 0x00, 0x00, 0x2A, // stream id = 42 + 0x00, 0x00, 0x00, 0x39, // promised stream id = 57 + 0x38, 0xea, 0xe3, 0xc6, // start of compressed header block + 0xa7, 0xc2, 0x02, 0xe5, + 0x0e, 0x50, 0xc2, 0x4b, + 0x4a, 0x04, 0xe5, 0x0b, + 0x66, 0x80, 0x00, 0x4a, + 0xcb, 0xcf, 0x07, 0x08, + 0x20, 0x10, 0x95, 0x96, + 0x9f, 0x0f, 0xa2, 0x00, + 0x02, 0x28, 0x29, 0xb1, + 0x08, 0x20, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0xff // end of compressed header block + }; + + scoped_ptr<SpdySerializedFrame> frame(framer.CreatePushPromise( + 42, 57, &headers)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); +} + +TEST_P(SpdyFramerTest, ReadCompressedSynStreamHeaderBlock) { + SpdyHeaderBlock headers; + headers["aa"] = "vv"; + headers["bb"] = "ww"; + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(SpdyFramerTest, ReadCompressedSynReplyHeaderBlock) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynReply(1, // stream_id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateHeaders(1, // stream_id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(1, visitor.headers_frame_count_); + // control_frame_header_data_count_ depends on the random sequence + // produced by rand(), so adding, removing or running single tests + // alters this value. The best we can do is assert that it happens + // at least twice. + EXPECT_LE(2, visitor.control_frame_header_data_count_); + EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(0, visitor.zero_length_data_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) { + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateHeaders(1, // stream_id + CONTROL_FLAG_FIN, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(1, visitor.headers_frame_count_); + // control_frame_header_data_count_ depends on the random sequence + // produced by rand(), so adding, removing or running single tests + // alters this value. The best we can do is assert that it happens + // at least twice. + EXPECT_LE(2, visitor.control_frame_header_data_count_); + EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(1, visitor.zero_length_data_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(SpdyFramerTest, ControlFrameAtMaxSizeLimit) { + // First find the size of the header value in order to just reach the control + // frame max size. + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + SpdyHeaderBlock headers; + headers["aa"] = ""; + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + const size_t kBigValueSize = + framer.GetControlFrameBufferMaxSize() - control_frame->size(); + + // Create a frame at exatly that size. + string big_value(kBigValueSize, 'x'); + headers["aa"] = big_value.c_str(); + control_frame.reset( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + EXPECT_EQ(framer.GetControlFrameBufferMaxSize(), control_frame->size()); + + TestSpdyVisitor visitor(spdy_version_); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(0, visitor.zero_length_data_frame_count_); + EXPECT_LT(kBigValueSize, visitor.header_buffer_length_); +} + +TEST_P(SpdyFramerTest, ControlFrameTooLarge) { + // First find the size of the header value in order to just reach the control + // frame max size. + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + SpdyHeaderBlock headers; + headers["aa"] = ""; + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + const size_t kBigValueSize = + framer.GetControlFrameBufferMaxSize() - control_frame->size() + 1; + + // Create a frame at exatly that size. + string big_value(kBigValueSize, 'x'); + headers["aa"] = big_value.c_str(); + control_frame.reset( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + EXPECT_EQ(framer.GetControlFrameBufferMaxSize() + 1, + control_frame->size()); + + TestSpdyVisitor visitor(spdy_version_); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_FALSE(visitor.header_buffer_valid_); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE, + visitor.framer_.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + EXPECT_EQ(0, visitor.syn_frame_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); +} + +// Check that the framer stops delivering header data chunks once the visitor +// declares it doesn't want any more. This is important to guard against +// "zip bomb" types of attacks. +TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) { + SpdyHeaderBlock headers; + const size_t kHeaderBufferChunks = 4; + const size_t kHeaderBufferSize = + TestSpdyVisitor::header_data_chunk_max_size() * kHeaderBufferChunks; + const size_t kBigValueSize = kHeaderBufferSize * 2; + string big_value(kBigValueSize, 'x'); + headers["aa"] = big_value.c_str(); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_FIN, // half close + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.set_header_buffer_size(kHeaderBufferSize); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_FALSE(visitor.header_buffer_valid_); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE, + visitor.framer_.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + + // The framer should have stoped delivering chunks after the visitor + // signaled "stop" by returning false from OnControlFrameHeaderData(). + // + // control_frame_header_data_count_ depends on the random sequence + // produced by rand(), so adding, removing or running single tests + // alters this value. The best we can do is assert that it happens + // at least kHeaderBufferChunks + 1. + EXPECT_LE(kHeaderBufferChunks + 1, + static_cast<unsigned>(visitor.control_frame_header_data_count_)); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + // The framer should not have sent half-close to the visitor. + EXPECT_EQ(0, visitor.zero_length_data_frame_count_); +} + +TEST_P(SpdyFramerTest, DecompressCorruptHeaderBlock) { + SpdyHeaderBlock headers; + headers["aa"] = "alpha beta gamma delta"; + SpdyFramer framer(spdy_version_); + framer.set_enable_compression(false); + // Construct a SYN_STREAM control frame without compressing the header block, + // and have the framer try to decompress it. This will cause the framer to + // deal with a decompression error. + scoped_ptr<SpdyFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + 0, // credential_slot + CONTROL_FLAG_NONE, + false, // compress + &headers)); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(SpdyFramer::SPDY_DECOMPRESS_FAILURE, visitor.framer_.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + EXPECT_EQ(0u, visitor.header_buffer_length_); +} + +TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) { + // Create a GoAway frame that has a few extra bytes at the end. + // We create enough overhead to overflow the framer's control frame buffer. + ASSERT_GE(250u, SpdyFramer::kControlFrameBufferSize); + const unsigned char length = 1 + SpdyFramer::kControlFrameBufferSize; + const unsigned char kV3FrameData[] = { // Also applies for V2. + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, length, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + const unsigned char kV4FrameData[] = { + 0x00, static_cast<uint8>(length + 4), 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + SpdyFramer framer(spdy_version_); + const size_t pad_length = + length + framer.GetControlFrameHeaderSize() - + (IsSpdy4() ? sizeof(kV4FrameData) : sizeof(kV3FrameData)); + string pad('A', pad_length); + TestSpdyVisitor visitor(spdy_version_); + + if (IsSpdy4()) { + visitor.SimulateInFramer(kV4FrameData, sizeof(kV4FrameData)); + } else { + visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData)); + } + visitor.SimulateInFramer( + reinterpret_cast<const unsigned char*>(pad.c_str()), + pad.length()); + + EXPECT_EQ(1, visitor.error_count_); // This generated an error. + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, + visitor.framer_.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + EXPECT_EQ(0, visitor.goaway_count_); // Frame not parsed. +} + +TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) { + SpdyFramer framer(spdy_version_); + SettingsMap settings; + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + SetFrameLength(control_frame.get(), 0, spdy_version_); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + framer.GetControlFrameHeaderSize()); + // Should generate an error, since zero-len settings frames are unsupported. + EXPECT_EQ(1, visitor.error_count_); +} + +// Tests handling of SETTINGS frames with invalid length. +TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) { + SpdyFramer framer(spdy_version_); + SettingsMap settings; + // Add a setting to pad the frame so that we don't get a buffer overflow when + // calling SimulateInFramer() below. + settings[SETTINGS_UPLOAD_BANDWIDTH] = + SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, 0x00000002); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + const size_t kNewLength = 5; + SetFrameLength(control_frame.get(), kNewLength, spdy_version_); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + framer.GetControlFrameHeaderSize() + kNewLength); + // Should generate an error, since zero-len settings frames are unsupported. + EXPECT_EQ(1, visitor.error_count_); +} + +// Tests handling of SETTINGS frames larger than the frame buffer size. +TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) { + SpdyFramer framer(spdy_version_); + SettingsMap settings; + SpdySettingsFlags flags = SETTINGS_FLAG_PLEASE_PERSIST; + settings[SETTINGS_UPLOAD_BANDWIDTH] = + SettingsFlagsAndValue(flags, 0x00000002); + settings[SETTINGS_DOWNLOAD_BANDWIDTH] = + SettingsFlagsAndValue(flags, 0x00000003); + settings[SETTINGS_ROUND_TRIP_TIME] = SettingsFlagsAndValue(flags, 0x00000004); + scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings)); + EXPECT_LT(SpdyFramer::kControlFrameBufferSize, + control_frame->size()); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + + // Read all at once. + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(settings.size(), static_cast<unsigned>(visitor.setting_count_)); + + // Read data in small chunks. + size_t framed_data = 0; + size_t unframed_data = control_frame->size(); + size_t kReadChunkSize = 5; // Read five bytes at a time. + while (unframed_data > 0) { + size_t to_read = min(kReadChunkSize, unframed_data); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data() + framed_data), + to_read); + unframed_data -= to_read; + framed_data += to_read; + } + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(settings.size() * 2, static_cast<unsigned>(visitor.setting_count_)); +} + +// Tests handling of SETTINGS frame with duplicate entries. +TEST_P(SpdyFramerTest, ReadDuplicateSettings) { + SpdyFramer framer(spdy_version_); + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x03, + 0x01, 0x00, 0x00, 0x00, // 1st Setting + 0x00, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x00, // 2nd (duplicate) Setting + 0x00, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x00, // 3rd (unprocessed) Setting + 0x00, 0x00, 0x00, 0x03, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, // 1st Setting + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, // 2nd (duplicate) Setting + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x03, // 3rd (unprocessed) Setting + 0x00, 0x00, 0x00, 0x03, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x24, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, // 1st Setting + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, // 2nd (duplicate) Setting + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x03, // 3rd (unprocessed) Setting + 0x00, 0x00, 0x00, 0x03, + }; + + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + if (IsSpdy2()) { + visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData)); + } else if (IsSpdy3()) { + visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData)); + } else { + visitor.SimulateInFramer(kV4FrameData, sizeof(kV4FrameData)); + } + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); +} + +// Tests handling of SETTINGS frame with entries out of order. +TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) { + SpdyFramer framer(spdy_version_); + + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x03, + 0x02, 0x00, 0x00, 0x00, // 1st Setting + 0x00, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x00, // 2nd (out of order) Setting + 0x00, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x00, // 3rd (unprocessed) Setting + 0x00, 0x00, 0x00, 0x03, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x02, // 1st Setting + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, // 2nd (out of order) Setting + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x01, 0x03, // 3rd (unprocessed) Setting + 0x00, 0x00, 0x00, 0x03, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x24, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x02, // 1st Setting + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, // 2nd (out of order) Setting + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x01, 0x03, // 3rd (unprocessed) Setting + 0x00, 0x00, 0x00, 0x03, + }; + + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + if (IsSpdy2()) { + visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData)); + } else if (IsSpdy3()) { + visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData)); + } else { + visitor.SimulateInFramer(kV4FrameData, sizeof(kV4FrameData)); + } + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); +} + +TEST_P(SpdyFramerTest, ReadWindowUpdate) { + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateWindowUpdate(1, 2)); + TestSpdyVisitor visitor(spdy_version_); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(1u, visitor.last_window_update_stream_); + EXPECT_EQ(2u, visitor.last_window_update_delta_); +} + +TEST_P(SpdyFramerTest, ReadCredentialFrame) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame->data()), + control_frame->size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(control_frame->size() - framer.GetControlFrameHeaderSize(), + visitor.credential_buffer_length_); + EXPECT_EQ(credential.slot, visitor.credential_.slot); + EXPECT_EQ(credential.proof, visitor.credential_.proof); + EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size()); + for (size_t i = 0; i < credential.certs.size(); i++) { + EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]); + } +} + +TEST_P(SpdyFramerTest, ReadCredentialFrameOneByteAtATime) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + // Read one byte at a time to make sure we handle edge cases + unsigned char* data = + reinterpret_cast<unsigned char*>(control_frame->data()); + for (size_t idx = 0; idx < control_frame->size(); ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(control_frame->size() - framer.GetControlFrameHeaderSize(), + visitor.credential_buffer_length_); + EXPECT_EQ(credential.slot, visitor.credential_.slot); + EXPECT_EQ(credential.proof, visitor.credential_.proof); + EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size()); + for (size_t i = 0; i < credential.certs.size(); i++) { + EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]); + } +} + +TEST_P(SpdyFramerTest, ReadCredentialFrameWithNoPayload) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + SetFrameLength(control_frame.get(), 0, spdy_version_); + unsigned char* data = + reinterpret_cast<unsigned char*>(control_frame->data()); + visitor.SimulateInFramer(data, framer.GetControlFrameHeaderSize()); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ReadCredentialFrameWithCorruptProof) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + unsigned char* data = + reinterpret_cast<unsigned char*>(control_frame->data()); + size_t offset = framer.GetControlFrameHeaderSize() + 4; + data[offset] = 0xFF; // Proof length is past the end of the frame + visitor.SimulateInFramer( + data, control_frame->size()); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ReadCredentialFrameWithCorruptCertificate) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> control_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(control_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + unsigned char* data = + reinterpret_cast<unsigned char*>(control_frame->data()); + size_t offset = framer.GetCredentialMinimumSize() + 1; + data[offset] = 0xFF; // Proof length is past the end of the frame + visitor.SimulateInFramer( + data, control_frame->size()); + EXPECT_EQ(1, visitor.error_count_); +} + +// Regression test for parsing issue found in b/8278897. +TEST_P(SpdyFramerTest, ReadCredentialFrameFollowedByAnotherFrame) { + SpdyCredential credential; + credential.slot = 3; + credential.proof = "proof"; + credential.certs.push_back("a cert"); + credential.certs.push_back("another cert"); + credential.certs.push_back("final cert"); + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> credential_frame( + framer.CreateCredentialFrame(credential)); + EXPECT_TRUE(credential_frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + string multiple_frame_data(credential_frame->data(), + credential_frame->size()); + scoped_ptr<SpdyFrame> goaway_frame(framer.CreateGoAway(0, GOAWAY_OK)); + multiple_frame_data.append(string(goaway_frame->data(), + goaway_frame->size())); + visitor.SimulateInFramer( + reinterpret_cast<unsigned const char*>(multiple_frame_data.data()), + multiple_frame_data.length()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(credential_frame->size() - framer.GetControlFrameHeaderSize(), + visitor.credential_buffer_length_); + EXPECT_EQ(credential.slot, visitor.credential_.slot); + EXPECT_EQ(credential.proof, visitor.credential_.proof); + EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size()); + for (size_t i = 0; i < credential.certs.size(); i++) { + EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]); + } +} + +TEST_P(SpdyFramerTest, ReadCompressedPushPromise) { + if (spdy_version_ < 4) { + return; + } + + SpdyHeaderBlock headers; + headers["foo"] = "bar"; + headers["bar"] = "foofoo"; + SpdyFramer framer(spdy_version_); + scoped_ptr<SpdyFrame> frame(framer.CreatePushPromise(42, 57, &headers)); + EXPECT_TRUE(frame.get() != NULL); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = true; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(frame->data()), + frame->size()); + EXPECT_EQ(42u, visitor.last_push_promise_stream_); + EXPECT_EQ(57u, visitor.last_push_promise_promised_stream_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_P(SpdyFramerTest, ReadGarbage) { + SpdyFramer framer(spdy_version_); + unsigned char garbage_frame[256]; + memset(garbage_frame, ~0, sizeof(garbage_frame)); + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ReadGarbageWithValidVersion) { + if (IsSpdy4()) { + // Not valid for SPDY 4 since there is no version field. + return; + } + SpdyFramer framer(spdy_version_); + const unsigned char kFrameData[] = { + 0x80, spdy_version_ch_, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + }; + TestSpdyVisitor visitor(spdy_version_); + visitor.use_compression_ = false; + visitor.SimulateInFramer(kFrameData, arraysize(kFrameData)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, SizesTest) { + SpdyFramer framer(spdy_version_); + EXPECT_EQ(8u, framer.GetDataFrameMinimumSize()); + if (IsSpdy4()) { + EXPECT_EQ(8u, framer.GetSynReplyMinimumSize()); + EXPECT_EQ(12u, framer.GetRstStreamSize()); + EXPECT_EQ(12u, framer.GetSettingsMinimumSize()); + EXPECT_EQ(12u, framer.GetPingSize()); + EXPECT_EQ(16u, framer.GetGoAwaySize()); + EXPECT_EQ(8u, framer.GetHeadersMinimumSize()); + EXPECT_EQ(12u, framer.GetWindowUpdateSize()); + EXPECT_EQ(10u, framer.GetCredentialMinimumSize()); + EXPECT_EQ(8u, framer.GetBlockedSize()); + EXPECT_EQ(12u, framer.GetPushPromiseMinimumSize()); + EXPECT_EQ(8u, framer.GetFrameMinimumSize()); + EXPECT_EQ(65535u, framer.GetFrameMaximumSize()); + EXPECT_EQ(65527u, framer.GetDataFrameMaximumPayload()); + } else { + EXPECT_EQ(8u, framer.GetControlFrameHeaderSize()); + EXPECT_EQ(18u, framer.GetSynStreamMinimumSize()); + EXPECT_EQ(IsSpdy2() ? 14u : 12u, framer.GetSynReplyMinimumSize()); + EXPECT_EQ(16u, framer.GetRstStreamSize()); + EXPECT_EQ(12u, framer.GetSettingsMinimumSize()); + EXPECT_EQ(12u, framer.GetPingSize()); + EXPECT_EQ(IsSpdy2() ? 12u : 16u, framer.GetGoAwaySize()); + EXPECT_EQ(IsSpdy2() ? 14u : 12u, framer.GetHeadersMinimumSize()); + EXPECT_EQ(16u, framer.GetWindowUpdateSize()); + EXPECT_EQ(10u, framer.GetCredentialMinimumSize()); + EXPECT_EQ(8u, framer.GetFrameMinimumSize()); + EXPECT_EQ(16777215u, framer.GetFrameMaximumSize()); + EXPECT_EQ(16777207u, framer.GetDataFrameMaximumPayload()); + } +} + +TEST_P(SpdyFramerTest, StateToStringTest) { + EXPECT_STREQ("ERROR", + SpdyFramer::StateToString(SpdyFramer::SPDY_ERROR)); + EXPECT_STREQ("AUTO_RESET", + SpdyFramer::StateToString(SpdyFramer::SPDY_AUTO_RESET)); + EXPECT_STREQ("RESET", + SpdyFramer::StateToString(SpdyFramer::SPDY_RESET)); + EXPECT_STREQ("READING_COMMON_HEADER", + SpdyFramer::StateToString( + SpdyFramer::SPDY_READING_COMMON_HEADER)); + EXPECT_STREQ("CONTROL_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CONTROL_FRAME_PAYLOAD)); + EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_IGNORE_REMAINING_PAYLOAD)); + EXPECT_STREQ("FORWARD_STREAM_FRAME", + SpdyFramer::StateToString( + SpdyFramer::SPDY_FORWARD_STREAM_FRAME)); + EXPECT_STREQ("SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK)); + EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CONTROL_FRAME_HEADER_BLOCK)); + EXPECT_STREQ("SPDY_CREDENTIAL_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_CREDENTIAL_FRAME_PAYLOAD)); + EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD", + SpdyFramer::StateToString( + SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD)); + EXPECT_STREQ("UNKNOWN_STATE", + SpdyFramer::StateToString( + SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD + 1)); +} + +TEST_P(SpdyFramerTest, ErrorCodeToStringTest) { + EXPECT_STREQ("NO_ERROR", + SpdyFramer::ErrorCodeToString(SpdyFramer::SPDY_NO_ERROR)); + EXPECT_STREQ("INVALID_CONTROL_FRAME", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_INVALID_CONTROL_FRAME)); + EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE)); + EXPECT_STREQ("ZLIB_INIT_FAILURE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_ZLIB_INIT_FAILURE)); + EXPECT_STREQ("UNSUPPORTED_VERSION", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_UNSUPPORTED_VERSION)); + EXPECT_STREQ("DECOMPRESS_FAILURE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_DECOMPRESS_FAILURE)); + EXPECT_STREQ("COMPRESS_FAILURE", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_COMPRESS_FAILURE)); + EXPECT_STREQ("SPDY_INVALID_DATA_FRAME_FLAGS", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS)); + EXPECT_STREQ("SPDY_INVALID_CONTROL_FRAME_FLAGS", + SpdyFramer::ErrorCodeToString( + SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS)); + EXPECT_STREQ("UNKNOWN_ERROR", + SpdyFramer::ErrorCodeToString(SpdyFramer::LAST_ERROR)); +} + +TEST_P(SpdyFramerTest, StatusCodeToStringTest) { + EXPECT_STREQ("INVALID", + SpdyFramer::StatusCodeToString(RST_STREAM_INVALID)); + EXPECT_STREQ("PROTOCOL_ERROR", + SpdyFramer::StatusCodeToString(RST_STREAM_PROTOCOL_ERROR)); + EXPECT_STREQ("INVALID_STREAM", + SpdyFramer::StatusCodeToString(RST_STREAM_INVALID_STREAM)); + EXPECT_STREQ("REFUSED_STREAM", + SpdyFramer::StatusCodeToString(RST_STREAM_REFUSED_STREAM)); + EXPECT_STREQ("UNSUPPORTED_VERSION", + SpdyFramer::StatusCodeToString(RST_STREAM_UNSUPPORTED_VERSION)); + EXPECT_STREQ("CANCEL", + SpdyFramer::StatusCodeToString(RST_STREAM_CANCEL)); + EXPECT_STREQ("INTERNAL_ERROR", + SpdyFramer::StatusCodeToString(RST_STREAM_INTERNAL_ERROR)); + EXPECT_STREQ("FLOW_CONTROL_ERROR", + SpdyFramer::StatusCodeToString(RST_STREAM_FLOW_CONTROL_ERROR)); + EXPECT_STREQ("UNKNOWN_STATUS", + SpdyFramer::StatusCodeToString(RST_STREAM_NUM_STATUS_CODES)); +} + +TEST_P(SpdyFramerTest, FrameTypeToStringTest) { + EXPECT_STREQ("DATA", + SpdyFramer::FrameTypeToString(DATA)); + EXPECT_STREQ("SYN_STREAM", + SpdyFramer::FrameTypeToString(SYN_STREAM)); + EXPECT_STREQ("SYN_REPLY", + SpdyFramer::FrameTypeToString(SYN_REPLY)); + EXPECT_STREQ("RST_STREAM", + SpdyFramer::FrameTypeToString(RST_STREAM)); + EXPECT_STREQ("SETTINGS", + SpdyFramer::FrameTypeToString(SETTINGS)); + EXPECT_STREQ("NOOP", + SpdyFramer::FrameTypeToString(NOOP)); + EXPECT_STREQ("PING", + SpdyFramer::FrameTypeToString(PING)); + EXPECT_STREQ("GOAWAY", + SpdyFramer::FrameTypeToString(GOAWAY)); + EXPECT_STREQ("HEADERS", + SpdyFramer::FrameTypeToString(HEADERS)); + EXPECT_STREQ("WINDOW_UPDATE", + SpdyFramer::FrameTypeToString(WINDOW_UPDATE)); + EXPECT_STREQ("PUSH_PROMISE", + SpdyFramer::FrameTypeToString(PUSH_PROMISE)); + EXPECT_STREQ("CREDENTIAL", + SpdyFramer::FrameTypeToString(CREDENTIAL)); +} + +TEST_P(SpdyFramerTest, CatchProbableHttpResponse) { + if (IsSpdy4()) { + // TODO(hkhalil): catch probable HTTP response in SPDY 4? + return; + } + { + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnError(_)); + framer.ProcessInput("HTTP/1.1", 8); + EXPECT_TRUE(framer.probable_http_response()); + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + { + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnError(_)); + framer.ProcessInput("HTTP/1.0", 8); + EXPECT_TRUE(framer.probable_http_response()); + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } +} + +TEST_P(SpdyFramerTest, DataFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + scoped_ptr<SpdyFrame> frame( + framer.CreateDataFrame(1, "hello", 5, DATA_FLAG_NONE)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags & ~DATA_FLAG_FIN) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, flags & DATA_FLAG_FIN)); + EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5, false)); + if (flags & DATA_FLAG_FIN) { + EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true)); + } + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags & ~DATA_FLAG_FIN) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, SynStreamFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + testing::StrictMock<test::MockDebugVisitor> debug_visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + framer.set_debug_visitor(&debug_visitor); + + EXPECT_CALL(debug_visitor, OnSendCompressedFrame(8, SYN_STREAM, _, _)); + + SpdyHeaderBlock headers; + headers["foo"] = "bar"; + scoped_ptr<SpdyFrame> frame( + framer.CreateSynStream(8, 3, 1, 0, CONTROL_FLAG_NONE, true, &headers)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags & ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(8, SYN_STREAM, _)); + EXPECT_CALL(visitor, OnSynStream(8, 3, 1, 0, flags & CONTROL_FLAG_FIN, + flags & CONTROL_FLAG_UNIDIRECTIONAL)); + EXPECT_CALL(visitor, OnControlFrameHeaderData(8, _, _)) + .WillRepeatedly(testing::Return(true)); + if (flags & DATA_FLAG_FIN) { + EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true)); + } + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags & ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, SynReplyFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["foo"] = "bar"; + scoped_ptr<SpdyFrame> frame( + framer.CreateSynReply(37, CONTROL_FLAG_NONE, true, &headers)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags & ~CONTROL_FLAG_FIN) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnSynReply(37, flags & CONTROL_FLAG_FIN)); + EXPECT_CALL(visitor, OnControlFrameHeaderData(37, _, _)) + .WillRepeatedly(testing::Return(true)); + if (flags & DATA_FLAG_FIN) { + EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true)); + } + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags & ~CONTROL_FLAG_FIN) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, RstStreamFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(13, RST_STREAM_CANCEL)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags != 0) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnRstStream(13, RST_STREAM_CANCEL)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags != 0) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, SettingsFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SettingsMap settings; + settings[SETTINGS_UPLOAD_BANDWIDTH] = + std::make_pair(SETTINGS_FLAG_NONE, 54321); + scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags & ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnSettings( + flags & SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS)); + EXPECT_CALL(visitor, OnSetting(SETTINGS_UPLOAD_BANDWIDTH, + SETTINGS_FLAG_NONE, 54321)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags & ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, GoawayFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(97, GOAWAY_OK)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags != 0) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnGoAway(97, GOAWAY_OK)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags != 0) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, HeadersFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyHeaderBlock headers; + headers["foo"] = "bar"; + scoped_ptr<SpdyFrame> frame( + framer.CreateHeaders(57, CONTROL_FLAG_NONE, true, &headers)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags & ~CONTROL_FLAG_FIN) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnHeaders(57, flags & CONTROL_FLAG_FIN)); + EXPECT_CALL(visitor, OnControlFrameHeaderData(57, _, _)) + .WillRepeatedly(testing::Return(true)); + if (flags & DATA_FLAG_FIN) { + EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true)); + } + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags & ~CONTROL_FLAG_FIN) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, PingFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + scoped_ptr<SpdyFrame> frame(framer.CreatePingFrame(42)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags != 0) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnPing(42)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags != 0) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, WindowUpdateFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(4, 1024)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags != 0) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnWindowUpdate(4, 1024)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags != 0) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, PushPromiseFrameFlags) { + if (spdy_version_ < SPDY4) { + return; + } + + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<net::test::MockVisitor> visitor; + testing::StrictMock<net::test::MockDebugVisitor> debug_visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + framer.set_debug_visitor(&debug_visitor); + + EXPECT_CALL(debug_visitor, OnSendCompressedFrame(42, PUSH_PROMISE, _, _)); + + SpdyHeaderBlock headers; + headers["foo"] = "bar"; + scoped_ptr<SpdyFrame> frame(framer.CreatePushPromise(42, 57, &headers)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags != 0) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(42, PUSH_PROMISE, _)); + EXPECT_CALL(visitor, OnPushPromise(42, 57)); + EXPECT_CALL(visitor, OnControlFrameHeaderData(42, _, _)) + .WillRepeatedly(testing::Return(true)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags != 0) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, CredentialFrameFlags) { + for (int flags = 0; flags < 256; ++flags) { + SCOPED_TRACE(testing::Message() << "Flags " << flags); + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + SpdyCredential credential; + scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential)); + SetFrameFlags(frame.get(), flags, spdy_version_); + + if (flags != 0) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnCredentialFrameData(_, _)) + .WillRepeatedly(testing::Return(true)); + } + + framer.ProcessInput(frame->data(), frame->size()); + if (flags != 0) { + EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS, + framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } else { + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + } + } +} + +TEST_P(SpdyFramerTest, EmptySynStream) { + SpdyHeaderBlock headers; + + testing::StrictMock<test::MockVisitor> visitor; + testing::StrictMock<test::MockDebugVisitor> debug_visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + framer.set_debug_visitor(&debug_visitor); + + EXPECT_CALL(debug_visitor, OnSendCompressedFrame(1, SYN_STREAM, _, _)); + + scoped_ptr<SpdyFrame> + frame(framer.CreateSynStream(1, 0, 1, 0, CONTROL_FLAG_NONE, true, + &headers)); + // Adjust size to remove the name/value block. + if (IsSpdy4()) { + SetFrameLength( + frame.get(), + framer.GetSynStreamMinimumSize(), + spdy_version_); + } else { + SetFrameLength( + frame.get(), + framer.GetSynStreamMinimumSize() - framer.GetControlFrameHeaderSize(), + spdy_version_); + } + + EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(1, SYN_STREAM, _)); + EXPECT_CALL(visitor, OnSynStream(1, 0, 1, 0, false, false)); + EXPECT_CALL(visitor, OnControlFrameHeaderData(1, NULL, 0)); + + framer.ProcessInput(frame->data(), framer.GetSynStreamMinimumSize()); + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +TEST_P(SpdyFramerTest, SettingsFlagsAndId) { + const uint32 kId = 0x020304; + const uint32 kFlags = 0x01; + const uint32 kWireFormat = htonl(IsSpdy2() ? 0x04030201 : 0x01020304); + + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(spdy_version_, kWireFormat); + EXPECT_EQ(kId, id_and_flags.id()); + EXPECT_EQ(kFlags, id_and_flags.flags()); + EXPECT_EQ(kWireFormat, id_and_flags.GetWireFormat(spdy_version_)); +} + +// Test handling of a RST_STREAM with out-of-bounds status codes. +TEST_P(SpdyFramerTest, RstStreamStatusBounds) { + DCHECK_GE(0xff, RST_STREAM_NUM_STATUS_CODES); + + const unsigned char kV3RstStreamInvalid[] = { + 0x80, spdy_version_ch_, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, RST_STREAM_INVALID + }; + const unsigned char kV4RstStreamInvalid[] = { + 0x00, 0x0c, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, RST_STREAM_INVALID + }; + + const unsigned char kV3RstStreamNumStatusCodes[] = { + 0x80, spdy_version_ch_, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, RST_STREAM_NUM_STATUS_CODES + }; + const unsigned char kV4RstStreamNumStatusCodes[] = { + 0x00, 0x0c, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, RST_STREAM_NUM_STATUS_CODES + }; + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnRstStream(1, RST_STREAM_INVALID)); + if (IsSpdy4()) { + framer.ProcessInput(reinterpret_cast<const char*>(kV4RstStreamInvalid), + arraysize(kV4RstStreamInvalid)); + } else { + framer.ProcessInput(reinterpret_cast<const char*>(kV3RstStreamInvalid), + arraysize(kV3RstStreamInvalid)); + } + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); + + EXPECT_CALL(visitor, OnRstStream(1, RST_STREAM_INVALID)); + if (IsSpdy4()) { + framer.ProcessInput( + reinterpret_cast<const char*>(kV4RstStreamNumStatusCodes), + arraysize(kV4RstStreamNumStatusCodes)); + } else { + framer.ProcessInput( + reinterpret_cast<const char*>(kV3RstStreamNumStatusCodes), + arraysize(kV3RstStreamNumStatusCodes)); + } + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +// Tests handling of a GOAWAY frame with out-of-bounds stream ID. +TEST_P(SpdyFramerTest, GoAwayStreamIdBounds) { + const unsigned char kV2FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x04, + 0xff, 0xff, 0xff, 0xff, + }; + const unsigned char kV3FrameData[] = { + 0x80, spdy_version_ch_, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x08, + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + }; + const unsigned char kV4FrameData[] = { + 0x00, 0x10, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + }; + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnGoAway(0x7fffffff, GOAWAY_OK)); + if (IsSpdy2()) { + framer.ProcessInput(reinterpret_cast<const char*>(kV2FrameData), + arraysize(kV2FrameData)); + } else if (IsSpdy3()) { + framer.ProcessInput(reinterpret_cast<const char*>(kV3FrameData), + arraysize(kV3FrameData)); + } else { + framer.ProcessInput(reinterpret_cast<const char*>(kV4FrameData), + arraysize(kV4FrameData)); + } + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +TEST_P(SpdyFramerTest, OnBlocked) { + if (spdy_version_ < SPDY4) { + return; + } + + const SpdyStreamId kStreamId = 0; + + testing::StrictMock<test::MockVisitor> visitor; + SpdyFramer framer(spdy_version_); + framer.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnBlocked(kStreamId)); + + SpdyBlockedIR blocked_ir(0); + scoped_ptr<SpdySerializedFrame> frame(framer.SerializeFrame(blocked_ir)); + framer.ProcessInput(frame->data(), framer.GetBlockedSize()); + + EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state()); + EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code()) + << SpdyFramer::ErrorCodeToString(framer.error_code()); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_header_block.cc b/chromium/net/spdy/spdy_header_block.cc new file mode 100644 index 00000000000..ecc22a45324 --- /dev/null +++ b/chromium/net/spdy/spdy_header_block.cc @@ -0,0 +1,52 @@ +// 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 "net/spdy/spdy_header_block.h" + +#include "base/values.h" +#include "net/spdy/spdy_http_utils.h" + +namespace net { + +base::Value* SpdyHeaderBlockNetLogCallback( + const SpdyHeaderBlock* headers, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + base::DictionaryValue* headers_dict = new base::DictionaryValue(); + for (SpdyHeaderBlock::const_iterator it = headers->begin(); + it != headers->end(); ++it) { + headers_dict->SetWithoutPathExpansion( + it->first, + new base::StringValue( + ShouldShowHttpHeaderValue(it->first) ? it->second : "[elided]")); + } + dict->Set("headers", headers_dict); + return dict; +} + +bool SpdyHeaderBlockFromNetLogParam( + const base::Value* event_param, + SpdyHeaderBlock* headers) { + headers->clear(); + + const base::DictionaryValue* dict = NULL; + const base::DictionaryValue* header_dict = NULL; + + if (!event_param || + !event_param->GetAsDictionary(&dict) || + !dict->GetDictionary("headers", &header_dict)) { + return false; + } + + for (base::DictionaryValue::Iterator it(*header_dict); !it.IsAtEnd(); + it.Advance()) { + if (!it.value().GetAsString(&(*headers)[it.key()])) { + headers->clear(); + return false; + } + } + return true; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_header_block.h b/chromium/net/spdy/spdy_header_block.h new file mode 100644 index 00000000000..8ae8fa385e8 --- /dev/null +++ b/chromium/net/spdy/spdy_header_block.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef NET_SPDY_SPDY_HEADER_BLOCK_H_ +#define NET_SPDY_SPDY_HEADER_BLOCK_H_ + +#include <map> +#include <string> + +#include "net/base/net_export.h" +#include "net/base/net_log.h" + +namespace net { + +// A data structure for holding a set of headers from either a +// SYN_STREAM or SYN_REPLY frame. +typedef std::map<std::string, std::string> SpdyHeaderBlock; + +// Converts a SpdyHeaderBlock into NetLog event parameters. Caller takes +// ownership of returned value. +NET_EXPORT base::Value* SpdyHeaderBlockNetLogCallback( + const SpdyHeaderBlock* headers, + NetLog::LogLevel log_level); + +// Converts NetLog event parameters into a SPDY header block and writes them +// to |headers|. |event_param| must have been created by +// SpdyHeaderBlockNetLogCallback. On failure, returns false and clears +// |headers|. +NET_EXPORT bool SpdyHeaderBlockFromNetLogParam( + const base::Value* event_param, + SpdyHeaderBlock* headers); + +} // namespace net + +#endif // NET_SPDY_SPDY_HEADER_BLOCK_H_ diff --git a/chromium/net/spdy/spdy_header_block_unittest.cc b/chromium/net/spdy/spdy_header_block_unittest.cc new file mode 100644 index 00000000000..3cfef16e999 --- /dev/null +++ b/chromium/net/spdy/spdy_header_block_unittest.cc @@ -0,0 +1,31 @@ +// 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 "net/spdy/spdy_header_block.h" + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "net/base/net_log.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +TEST(SpdyHeaderBlockTest, ToNetLogParamAndBackAgain) { + SpdyHeaderBlock headers; + headers["A"] = "a"; + headers["B"] = "b"; + + scoped_ptr<Value> event_param( + SpdyHeaderBlockNetLogCallback(&headers, NetLog::LOG_ALL_BUT_BYTES)); + + SpdyHeaderBlock headers2; + ASSERT_TRUE(SpdyHeaderBlockFromNetLogParam(event_param.get(), &headers2)); + EXPECT_EQ(headers, headers2); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/spdy/spdy_http_stream.cc b/chromium/net/spdy/spdy_http_stream.cc new file mode 100644 index 00000000000..4d9117514ab --- /dev/null +++ b/chromium/net/spdy/spdy_http_stream.cc @@ -0,0 +1,532 @@ +// 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 "net/spdy/spdy_http_stream.h" + +#include <algorithm> +#include <list> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_log.h" +#include "net/base/net_util.h" +#include "net/base/upload_data_stream.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session.h" + +namespace net { + +SpdyHttpStream::SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session, + bool direct) + : weak_factory_(this), + spdy_session_(spdy_session), + is_reused_(spdy_session_->IsReused()), + stream_closed_(false), + closed_stream_status_(ERR_FAILED), + closed_stream_id_(0), + request_info_(NULL), + response_info_(NULL), + response_headers_status_(RESPONSE_HEADERS_ARE_INCOMPLETE), + user_buffer_len_(0), + request_body_buf_size_(0), + buffered_read_callback_pending_(false), + more_read_data_pending_(false), + direct_(direct) { + DCHECK(spdy_session_.get()); +} + +SpdyHttpStream::~SpdyHttpStream() { + if (stream_.get()) { + stream_->DetachDelegate(); + DCHECK(!stream_.get()); + } +} + +int SpdyHttpStream::InitializeStream(const HttpRequestInfo* request_info, + RequestPriority priority, + const BoundNetLog& stream_net_log, + const CompletionCallback& callback) { + DCHECK(!stream_); + if (!spdy_session_) + return ERR_CONNECTION_CLOSED; + + request_info_ = request_info; + if (request_info_->method == "GET") { + int error = spdy_session_->GetPushStream(request_info_->url, &stream_, + stream_net_log); + if (error != OK) + return error; + + // |stream_| may be NULL even if OK was returned. + if (stream_.get()) { + DCHECK_EQ(stream_->type(), SPDY_PUSH_STREAM); + stream_->SetDelegate(this); + return OK; + } + } + + int rv = stream_request_.StartRequest( + SPDY_REQUEST_RESPONSE_STREAM, spdy_session_, request_info_->url, + priority, stream_net_log, + base::Bind(&SpdyHttpStream::OnStreamCreated, + weak_factory_.GetWeakPtr(), callback)); + + if (rv == OK) { + stream_ = stream_request_.ReleaseStream(); + stream_->SetDelegate(this); + } + + return rv; +} + +const HttpResponseInfo* SpdyHttpStream::GetResponseInfo() const { + return response_info_; +} + +UploadProgress SpdyHttpStream::GetUploadProgress() const { + if (!request_info_ || !HasUploadData()) + return UploadProgress(); + + return UploadProgress(request_info_->upload_data_stream->position(), + request_info_->upload_data_stream->size()); +} + +int SpdyHttpStream::ReadResponseHeaders(const CompletionCallback& callback) { + CHECK(!callback.is_null()); + if (stream_closed_) + return closed_stream_status_; + + CHECK(stream_.get()); + + // Check if we already have the response headers. If so, return synchronously. + if (response_headers_status_ == RESPONSE_HEADERS_ARE_COMPLETE) { + CHECK(stream_->IsIdle()); + return OK; + } + + // Still waiting for the response, return IO_PENDING. + CHECK(callback_.is_null()); + callback_ = callback; + return ERR_IO_PENDING; +} + +int SpdyHttpStream::ReadResponseBody( + IOBuffer* buf, int buf_len, const CompletionCallback& callback) { + if (stream_.get()) + CHECK(stream_->IsIdle()); + + CHECK(buf); + CHECK(buf_len); + CHECK(!callback.is_null()); + + // If we have data buffered, complete the IO immediately. + if (!response_body_queue_.IsEmpty()) { + return response_body_queue_.Dequeue(buf->data(), buf_len); + } else if (stream_closed_) { + return closed_stream_status_; + } + + CHECK(callback_.is_null()); + CHECK(!user_buffer_.get()); + CHECK_EQ(0, user_buffer_len_); + + callback_ = callback; + user_buffer_ = buf; + user_buffer_len_ = buf_len; + return ERR_IO_PENDING; +} + +void SpdyHttpStream::Close(bool not_reusable) { + // Note: the not_reusable flag has no meaning for SPDY streams. + + Cancel(); + DCHECK(!stream_.get()); +} + +HttpStream* SpdyHttpStream::RenewStreamForAuth() { + return NULL; +} + +bool SpdyHttpStream::IsResponseBodyComplete() const { + return stream_closed_; +} + +bool SpdyHttpStream::CanFindEndOfResponse() const { + return true; +} + +bool SpdyHttpStream::IsConnectionReused() const { + return is_reused_; +} + +void SpdyHttpStream::SetConnectionReused() { + // SPDY doesn't need an indicator here. +} + +bool SpdyHttpStream::IsConnectionReusable() const { + // SPDY streams aren't considered reusable. + return false; +} + +bool SpdyHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const { + if (stream_closed_) { + if (!closed_stream_has_load_timing_info_) + return false; + *load_timing_info = closed_stream_load_timing_info_; + return true; + } + + // If |stream_| has yet to be created, or does not yet have an ID, fail. + // The reused flag can only be correctly set once a stream has an ID. Streams + // get their IDs once the request has been successfully sent, so this does not + // behave that differently from other stream types. + if (!stream_ || stream_->stream_id() == 0) + return false; + + return stream_->GetLoadTimingInfo(load_timing_info); +} + +int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers, + HttpResponseInfo* response, + const CompletionCallback& callback) { + if (stream_closed_) { + if (stream_->type() == SPDY_PUSH_STREAM) + return closed_stream_status_; + + return (closed_stream_status_ == OK) ? ERR_FAILED : closed_stream_status_; + } + + base::Time request_time = base::Time::Now(); + CHECK(stream_.get()); + + stream_->SetRequestTime(request_time); + // This should only get called in the case of a request occurring + // during server push that has already begun but hasn't finished, + // so we set the response's request time to be the actual one + if (response_info_) + response_info_->request_time = request_time; + + CHECK(!request_body_buf_.get()); + if (HasUploadData()) { + // Use kMaxSpdyFrameChunkSize as the buffer size, since the request + // body data is written with this size at a time. + request_body_buf_ = new IOBufferWithSize(kMaxSpdyFrameChunkSize); + // The request body buffer is empty at first. + request_body_buf_size_ = 0; + } + + CHECK(!callback.is_null()); + CHECK(response); + + // SendRequest can be called in two cases. + // + // a) A client initiated request. In this case, |response_info_| should be + // NULL to start with. + // b) A client request which matches a response that the server has already + // pushed. + if (push_response_info_.get()) { + *response = *(push_response_info_.get()); + push_response_info_.reset(); + } else { + DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_); + } + + response_info_ = response; + + // Put the peer's IP address and port into the response. + IPEndPoint address; + int result = stream_->GetPeerAddress(&address); + if (result != OK) + return result; + response_info_->socket_address = HostPortPair::FromIPEndPoint(address); + + if (stream_->type() == SPDY_PUSH_STREAM) { + // Pushed streams do not send any data, and should always be + // idle. However, we still want to return ERR_IO_PENDING to mimic + // non-push behavior. The callback will be called when the + // response is received. + result = ERR_IO_PENDING; + } else { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock); + CreateSpdyHeadersFromHttpRequest( + *request_info_, request_headers, + headers.get(), stream_->GetProtocolVersion(), + direct_); + stream_->net_log().AddEvent( + NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS, + base::Bind(&SpdyHeaderBlockNetLogCallback, headers.get())); + result = + stream_->SendRequestHeaders( + headers.Pass(), + HasUploadData() ? MORE_DATA_TO_SEND : NO_MORE_DATA_TO_SEND); + } + + if (result == ERR_IO_PENDING) { + CHECK(callback_.is_null()); + callback_ = callback; + } + return result; +} + +void SpdyHttpStream::Cancel() { + callback_.Reset(); + if (stream_.get()) { + stream_->Cancel(); + DCHECK(!stream_.get()); + } +} + +void SpdyHttpStream::OnRequestHeadersSent() { + if (!callback_.is_null()) + DoCallback(OK); + + // TODO(akalin): Do this immediately after sending the request + // headers. + if (HasUploadData()) + ReadAndSendRequestBodyData(); +} + +SpdyResponseHeadersStatus SpdyHttpStream::OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) { + CHECK_EQ(response_headers_status_, RESPONSE_HEADERS_ARE_INCOMPLETE); + + if (!response_info_) { + DCHECK_EQ(stream_->type(), SPDY_PUSH_STREAM); + push_response_info_.reset(new HttpResponseInfo); + response_info_ = push_response_info_.get(); + } + + if (!SpdyHeadersToHttpResponse( + response_headers, stream_->GetProtocolVersion(), response_info_)) { + // We do not have complete headers yet. + return RESPONSE_HEADERS_ARE_INCOMPLETE; + } + + response_info_->response_time = stream_->response_time(); + response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE; + // Don't store the SSLInfo in the response here, HttpNetworkTransaction + // will take care of that part. + SSLInfo ssl_info; + NextProto protocol_negotiated = kProtoUnknown; + stream_->GetSSLInfo(&ssl_info, + &response_info_->was_npn_negotiated, + &protocol_negotiated); + response_info_->npn_negotiated_protocol = + SSLClientSocket::NextProtoToString(protocol_negotiated); + response_info_->request_time = stream_->GetRequestTime(); + response_info_->connection_info = + HttpResponseInfo::ConnectionInfoFromNextProto(stream_->GetProtocol()); + response_info_->vary_data + .Init(*request_info_, *response_info_->headers.get()); + + if (!callback_.is_null()) + DoCallback(OK); + + return RESPONSE_HEADERS_ARE_COMPLETE; +} + +void SpdyHttpStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) { + CHECK_EQ(response_headers_status_, RESPONSE_HEADERS_ARE_COMPLETE); + + // Note that data may be received for a SpdyStream prior to the user calling + // ReadResponseBody(), therefore user_buffer_ may be NULL. This may often + // happen for server initiated streams. + DCHECK(stream_.get()); + DCHECK(!stream_->IsClosed() || stream_->type() == SPDY_PUSH_STREAM); + if (buffer) { + response_body_queue_.Enqueue(buffer.Pass()); + + if (user_buffer_.get()) { + // Handing small chunks of data to the caller creates measurable overhead. + // We buffer data in short time-spans and send a single read notification. + ScheduleBufferedReadCallback(); + } + } +} + +void SpdyHttpStream::OnDataSent() { + request_body_buf_size_ = 0; + ReadAndSendRequestBodyData(); +} + +void SpdyHttpStream::OnClose(int status) { + if (stream_.get()) { + stream_closed_ = true; + closed_stream_status_ = status; + closed_stream_id_ = stream_->stream_id(); + closed_stream_has_load_timing_info_ = + stream_->GetLoadTimingInfo(&closed_stream_load_timing_info_); + } + stream_.reset(); + bool invoked_callback = false; + if (status == net::OK) { + // We need to complete any pending buffered read now. + invoked_callback = DoBufferedReadCallback(); + } + if (!invoked_callback && !callback_.is_null()) + DoCallback(status); +} + +bool SpdyHttpStream::HasUploadData() const { + CHECK(request_info_); + return + request_info_->upload_data_stream && + ((request_info_->upload_data_stream->size() > 0) || + request_info_->upload_data_stream->is_chunked()); +} + +void SpdyHttpStream::OnStreamCreated( + const CompletionCallback& callback, + int rv) { + if (rv == OK) { + stream_ = stream_request_.ReleaseStream(); + stream_->SetDelegate(this); + } + callback.Run(rv); +} + +void SpdyHttpStream::ReadAndSendRequestBodyData() { + CHECK(HasUploadData()); + CHECK_EQ(request_body_buf_size_, 0); + + if (request_info_->upload_data_stream->IsEOF()) + return; + + // Read the data from the request body stream. + const int rv = request_info_->upload_data_stream + ->Read(request_body_buf_.get(), + request_body_buf_->size(), + base::Bind(&SpdyHttpStream::OnRequestBodyReadCompleted, + weak_factory_.GetWeakPtr())); + + if (rv != ERR_IO_PENDING) { + // ERR_IO_PENDING is the only possible error. + CHECK_GE(rv, 0); + OnRequestBodyReadCompleted(rv); + } +} + +void SpdyHttpStream::OnRequestBodyReadCompleted(int status) { + CHECK_GE(status, 0); + request_body_buf_size_ = status; + const bool eof = request_info_->upload_data_stream->IsEOF(); + if (eof) { + CHECK_GE(request_body_buf_size_, 0); + } else { + CHECK_GT(request_body_buf_size_, 0); + } + stream_->SendData(request_body_buf_.get(), + request_body_buf_size_, + eof ? NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND); +} + +void SpdyHttpStream::ScheduleBufferedReadCallback() { + // If there is already a scheduled DoBufferedReadCallback, don't issue + // another one. Mark that we have received more data and return. + if (buffered_read_callback_pending_) { + more_read_data_pending_ = true; + return; + } + + more_read_data_pending_ = false; + buffered_read_callback_pending_ = true; + const base::TimeDelta kBufferTime = base::TimeDelta::FromMilliseconds(1); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&SpdyHttpStream::DoBufferedReadCallback), + weak_factory_.GetWeakPtr()), + kBufferTime); +} + +// Checks to see if we should wait for more buffered data before notifying +// the caller. Returns true if we should wait, false otherwise. +bool SpdyHttpStream::ShouldWaitForMoreBufferedData() const { + // If the response is complete, there is no point in waiting. + if (stream_closed_) + return false; + + DCHECK_GT(user_buffer_len_, 0); + return response_body_queue_.GetTotalSize() < + static_cast<size_t>(user_buffer_len_); +} + +bool SpdyHttpStream::DoBufferedReadCallback() { + buffered_read_callback_pending_ = false; + + // If the transaction is cancelled or errored out, we don't need to complete + // the read. + if (!stream_.get() && !stream_closed_) + return false; + + int stream_status = + stream_closed_ ? closed_stream_status_ : stream_->response_status(); + if (stream_status != OK) + return false; + + // When more_read_data_pending_ is true, it means that more data has + // arrived since we started waiting. Wait a little longer and continue + // to buffer. + if (more_read_data_pending_ && ShouldWaitForMoreBufferedData()) { + ScheduleBufferedReadCallback(); + return false; + } + + int rv = 0; + if (user_buffer_.get()) { + rv = ReadResponseBody(user_buffer_.get(), user_buffer_len_, callback_); + CHECK_NE(rv, ERR_IO_PENDING); + user_buffer_ = NULL; + user_buffer_len_ = 0; + DoCallback(rv); + return true; + } + return false; +} + +void SpdyHttpStream::DoCallback(int rv) { + CHECK_NE(rv, ERR_IO_PENDING); + CHECK(!callback_.is_null()); + + // Since Run may result in being called back, clear user_callback_ in advance. + CompletionCallback c = callback_; + callback_.Reset(); + c.Run(rv); +} + +void SpdyHttpStream::GetSSLInfo(SSLInfo* ssl_info) { + DCHECK(stream_.get()); + bool using_npn; + NextProto protocol_negotiated = kProtoUnknown; + stream_->GetSSLInfo(ssl_info, &using_npn, &protocol_negotiated); +} + +void SpdyHttpStream::GetSSLCertRequestInfo( + SSLCertRequestInfo* cert_request_info) { + DCHECK(stream_.get()); + stream_->GetSSLCertRequestInfo(cert_request_info); +} + +bool SpdyHttpStream::IsSpdyHttpStream() const { + return true; +} + +void SpdyHttpStream::Drain(HttpNetworkSession* session) { + Close(false); + delete this; +} + +void SpdyHttpStream::SetPriority(RequestPriority priority) { + // TODO(akalin): Plumb this through to |stream_request_| and + // |stream_|. +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_http_stream.h b/chromium/net/spdy/spdy_http_stream.h new file mode 100644 index 00000000000..f6e39cd102e --- /dev/null +++ b/chromium/net/spdy/spdy_http_stream.h @@ -0,0 +1,167 @@ +// 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. + +#ifndef NET_SPDY_SPDY_HTTP_STREAM_H_ +#define NET_SPDY_SPDY_HTTP_STREAM_H_ + +#include <list> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "net/base/completion_callback.h" +#include "net/base/net_log.h" +#include "net/http/http_stream.h" +#include "net/spdy/spdy_read_queue.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_stream.h" + +namespace net { + +class DrainableIOBuffer; +struct HttpRequestInfo; +class HttpResponseInfo; +class IOBuffer; +class SpdySession; +class UploadDataStream; + +// The SpdyHttpStream is a HTTP-specific type of stream known to a SpdySession. +class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate, + public HttpStream { + public: + // |spdy_session| must not be NULL. + SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session, bool direct); + virtual ~SpdyHttpStream(); + + SpdyStream* stream() { return stream_.get(); } + + // Cancels any callbacks from being invoked and deletes the stream. + void Cancel(); + + // HttpStream implementation. + + virtual int InitializeStream(const HttpRequestInfo* request_info, + RequestPriority priority, + const BoundNetLog& net_log, + const CompletionCallback& callback) OVERRIDE; + + virtual int SendRequest(const HttpRequestHeaders& headers, + HttpResponseInfo* response, + const CompletionCallback& callback) OVERRIDE; + virtual UploadProgress GetUploadProgress() const OVERRIDE; + virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE; + virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE; + virtual int ReadResponseBody(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual void Close(bool not_reusable) OVERRIDE; + virtual HttpStream* RenewStreamForAuth() OVERRIDE; + virtual bool IsResponseBodyComplete() const OVERRIDE; + virtual bool CanFindEndOfResponse() const OVERRIDE; + + // Must not be called if a NULL SpdySession was pssed into the + // constructor. + virtual bool IsConnectionReused() const OVERRIDE; + + virtual void SetConnectionReused() OVERRIDE; + virtual bool IsConnectionReusable() const OVERRIDE; + virtual bool GetLoadTimingInfo( + LoadTimingInfo* load_timing_info) const OVERRIDE; + virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; + virtual void GetSSLCertRequestInfo( + SSLCertRequestInfo* cert_request_info) OVERRIDE; + virtual bool IsSpdyHttpStream() const OVERRIDE; + virtual void Drain(HttpNetworkSession* session) OVERRIDE; + virtual void SetPriority(RequestPriority priority) OVERRIDE; + + // SpdyStream::Delegate implementation. + virtual void OnRequestHeadersSent() OVERRIDE; + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE; + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE; + virtual void OnDataSent() OVERRIDE; + virtual void OnClose(int status) OVERRIDE; + + private: + // Must be called only when |request_info_| is non-NULL. + bool HasUploadData() const; + + void OnStreamCreated(const CompletionCallback& callback, int rv); + + // Reads the remaining data (whether chunked or not) from the + // request body stream and sends it if there's any. The read and + // subsequent sending may happen asynchronously. Must be called only + // when HasUploadData() is true. + void ReadAndSendRequestBodyData(); + + // Called when data has just been read from the request body stream; + // does the actual sending of data. + void OnRequestBodyReadCompleted(int status); + + // Call the user callback. + void DoCallback(int rv); + + void ScheduleBufferedReadCallback(); + + // Returns true if the callback is invoked. + bool DoBufferedReadCallback(); + bool ShouldWaitForMoreBufferedData() const; + + base::WeakPtrFactory<SpdyHttpStream> weak_factory_; + + const base::WeakPtr<SpdySession> spdy_session_; + bool is_reused_; + SpdyStreamRequest stream_request_; + base::WeakPtr<SpdyStream> stream_; + + bool stream_closed_; + + // Set only when |stream_closed_| is true. + int closed_stream_status_; + SpdyStreamId closed_stream_id_; + bool closed_stream_has_load_timing_info_; + LoadTimingInfo closed_stream_load_timing_info_; + + // The request to send. + const HttpRequestInfo* request_info_; + + // |response_info_| is the HTTP response data object which is filled in + // when a SYN_REPLY comes in for the stream. + // It is not owned by this stream object, or point to |push_response_info_|. + HttpResponseInfo* response_info_; + + scoped_ptr<HttpResponseInfo> push_response_info_; + + // We don't use SpdyStream's |response_header_status_| as we + // sometimes call back into our delegate before it is updated. + SpdyResponseHeadersStatus response_headers_status_; + + // We buffer the response body as it arrives asynchronously from the stream. + SpdyReadQueue response_body_queue_; + + CompletionCallback callback_; + + // User provided buffer for the ReadResponseBody() response. + scoped_refptr<IOBuffer> user_buffer_; + int user_buffer_len_; + + // Temporary buffer used to read the request body from UploadDataStream. + scoped_refptr<IOBufferWithSize> request_body_buf_; + int request_body_buf_size_; + + // Is there a scheduled read callback pending. + bool buffered_read_callback_pending_; + // Has more data been received from the network during the wait for the + // scheduled read callback. + bool more_read_data_pending_; + + // Is this spdy stream direct to the origin server (or to a proxy). + bool direct_; + + DISALLOW_COPY_AND_ASSIGN(SpdyHttpStream); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_HTTP_STREAM_H_ diff --git a/chromium/net/spdy/spdy_http_stream_unittest.cc b/chromium/net/spdy/spdy_http_stream_unittest.cc new file mode 100644 index 00000000000..d88c963cd9a --- /dev/null +++ b/chromium/net/spdy/spdy_http_stream_unittest.cc @@ -0,0 +1,898 @@ +// 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 "net/spdy/spdy_http_stream.h" + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "crypto/signature_creator.h" +#include "net/base/capturing_net_log.h" +#include "net/base/load_timing_info.h" +#include "net/base/load_timing_info_test_util.h" +#include "net/base/upload_data_stream.h" +#include "net/base/upload_element_reader.h" +#include "net/cert/asn1_util.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/socket/next_proto.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_credential_builder.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_test_util_common.h" +#include "net/ssl/default_server_bound_cert_store.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +// Tests the load timing of a stream that's connected and is not the first +// request sent on a connection. +void TestLoadTimingReused(const HttpStream& stream) { + LoadTimingInfo load_timing_info; + EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info)); + + EXPECT_TRUE(load_timing_info.socket_reused); + EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id); + + ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing); + ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); +} + +// Tests the load timing of a stream that's connected and using a fresh +// connection. +void TestLoadTimingNotReused(const HttpStream& stream) { + LoadTimingInfo load_timing_info; + EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info)); + + EXPECT_FALSE(load_timing_info.socket_reused); + EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id); + + ExpectConnectTimingHasTimes(load_timing_info.connect_timing, + CONNECT_TIMING_HAS_DNS_TIMES); + ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); +} + +} // namespace + +class SpdyHttpStreamTest : public testing::Test, + public testing::WithParamInterface<NextProto> { + public: + SpdyHttpStreamTest() + : spdy_util_(GetParam()), + session_deps_(GetParam()) { + session_deps_.net_log = &net_log_; + } + + DeterministicSocketData* deterministic_data() { + return deterministic_data_.get(); + } + + OrderedSocketData* data() { return data_.get(); } + + protected: + virtual void TearDown() OVERRIDE { + crypto::ECSignatureCreator::SetFactoryForTesting(NULL); + base::MessageLoop::current()->RunUntilIdle(); + } + + // Initializes the session using DeterministicSocketData. It's advisable + // to use this function rather than the OrderedSocketData, since the + // DeterministicSocketData behaves in a reasonable manner. + void InitSessionDeterministic(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count, + const SpdySessionKey& key) { + deterministic_data_.reset( + new DeterministicSocketData(reads, reads_count, writes, writes_count)); + session_deps_.deterministic_socket_factory->AddSocketDataProvider( + deterministic_data_.get()); + http_session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + session_ = CreateInsecureSpdySession(http_session_, key, BoundNetLog()); + } + + // Initializes the session using the finicky OrderedSocketData class. + void InitSession(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count, + const SpdySessionKey& key) { + data_.reset(new OrderedSocketData(reads, reads_count, + writes, writes_count)); + session_deps_.socket_factory->AddSocketDataProvider(data_.get()); + http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + session_ = CreateInsecureSpdySession(http_session_, key, BoundNetLog()); + } + + void TestSendCredentials( + ServerBoundCertService* server_bound_cert_service, + const std::string& cert, + const std::string& proof); + + SpdyTestUtil spdy_util_; + CapturingNetLog net_log_; + SpdySessionDependencies session_deps_; + scoped_ptr<OrderedSocketData> data_; + scoped_ptr<DeterministicSocketData> deterministic_data_; + scoped_refptr<HttpNetworkSession> http_session_; + base::WeakPtr<SpdySession> session_; + + private: + MockECSignatureCreatorFactory ec_signature_creator_factory_; +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + SpdyHttpStreamTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +// SpdyHttpStream::GetUploadProgress() should still work even before the +// stream is initialized. +TEST_P(SpdyHttpStreamTest, GetUploadProgressBeforeInitialization) { + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + InitSession(reads, arraysize(reads), NULL, 0, key); + + SpdyHttpStream stream(session_, false); + UploadProgress progress = stream.GetUploadProgress(); + EXPECT_EQ(0u, progress.size()); + EXPECT_EQ(0u, progress.position()); +} + +TEST_P(SpdyHttpStreamTest, SendRequest) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(SYNCHRONOUS, 0, 3) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + InitSession(reads, arraysize(reads), writes, arraysize(writes), key); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + TestCompletionCallback callback; + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + // Make sure getting load timing information the stream early does not crash. + LoadTimingInfo load_timing_info; + EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info)); + + ASSERT_EQ( + OK, + http_stream->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info)); + + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, + callback.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info)); + + // This triggers the MockWrite and read 2 + callback.WaitForResult(); + + // Can get timing information once the stream connects. + TestLoadTimingNotReused(*http_stream); + + // This triggers read 3. The empty read causes the session to shut down. + data()->CompleteRead(); + + // Because we abandoned the stream, we don't expect to find a session in the + // pool anymore. + EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); + + TestLoadTimingNotReused(*http_stream); + http_stream->Close(true); + // Test that there's no crash when trying to get the load timing after the + // stream has been closed. + TestLoadTimingNotReused(*http_stream); +} + +TEST_P(SpdyHttpStreamTest, LoadTimingTwoRequests) { + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + scoped_ptr<SpdyFrame> resp1( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body1( + spdy_util_.ConstructSpdyBodyFrame(1, "", 0, true)); + scoped_ptr<SpdyFrame> resp2( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2( + spdy_util_.ConstructSpdyBodyFrame(3, "", 0, true)); + MockRead reads[] = { + CreateMockRead(*resp1, 2), + CreateMockRead(*body1, 3), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2, 5), + MockRead(ASYNC, 0, 6) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + InitSessionDeterministic(reads, arraysize(reads), + writes, arraysize(writes), + key); + + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("http://www.google.com/"); + TestCompletionCallback callback1; + HttpResponseInfo response1; + HttpRequestHeaders headers1; + scoped_ptr<SpdyHttpStream> http_stream1(new SpdyHttpStream(session_, true)); + + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("http://www.google.com/"); + TestCompletionCallback callback2; + HttpResponseInfo response2; + HttpRequestHeaders headers2; + scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true)); + + // First write. + ASSERT_EQ(OK, + http_stream1->InitializeStream(&request1, DEFAULT_PRIORITY, + BoundNetLog(), + CompletionCallback())); + EXPECT_EQ(ERR_IO_PENDING, http_stream1->SendRequest(headers1, &response1, + callback1.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + deterministic_data()->RunFor(1); + EXPECT_LE(0, callback1.WaitForResult()); + + TestLoadTimingNotReused(*http_stream1); + LoadTimingInfo load_timing_info1; + LoadTimingInfo load_timing_info2; + EXPECT_TRUE(http_stream1->GetLoadTimingInfo(&load_timing_info1)); + EXPECT_FALSE(http_stream2->GetLoadTimingInfo(&load_timing_info2)); + + // Second write. + ASSERT_EQ(OK, + http_stream2->InitializeStream(&request2, DEFAULT_PRIORITY, + BoundNetLog(), + CompletionCallback())); + EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers2, &response2, + callback2.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + deterministic_data()->RunFor(1); + EXPECT_LE(0, callback2.WaitForResult()); + TestLoadTimingReused(*http_stream2); + EXPECT_TRUE(http_stream2->GetLoadTimingInfo(&load_timing_info2)); + EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id); + + // All the reads. + deterministic_data()->RunFor(6); + + // Read stream 1 to completion, before making sure we can still read load + // timing from both streams. + scoped_refptr<IOBuffer> buf1(new IOBuffer(1)); + ASSERT_EQ( + 0, http_stream1->ReadResponseBody(buf1.get(), 1, callback1.callback())); + + // Stream 1 has been read to completion. + TestLoadTimingNotReused(*http_stream1); + // Stream 2 still has queued body data. + TestLoadTimingReused(*http_stream2); +} + +TEST_P(SpdyHttpStreamTest, SendChunkedPost) { + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<SpdyFrame> body( + framer.CreateDataFrame(1, kUploadData, kUploadDataSize, DATA_FLAG_FIN)); + std::vector<MockWrite> writes; + int seq = 0; + writes.push_back(CreateMockWrite(*req, seq++)); + writes.push_back(CreateMockWrite(*body, seq++)); // POST upload frame + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + std::vector<MockRead> reads; + reads.push_back(CreateMockRead(*resp, seq++)); + reads.push_back(CreateMockRead(*body, seq++)); + reads.push_back(MockRead(SYNCHRONOUS, 0, seq++)); // EOF + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + InitSession(vector_as_array(&reads), reads.size(), + vector_as_array(&writes), writes.size(), + key); + EXPECT_EQ(spdy_util_.spdy_version(), session_->GetProtocolVersion()); + + UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0); + const int kFirstChunkSize = kUploadDataSize/2; + upload_stream.AppendChunk(kUploadData, kFirstChunkSize, false); + upload_stream.AppendChunk(kUploadData + kFirstChunkSize, + kUploadDataSize - kFirstChunkSize, true); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_stream; + + ASSERT_EQ(OK, upload_stream.Init(CompletionCallback())); + + TestCompletionCallback callback; + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + SpdyHttpStream http_stream(session_, true); + ASSERT_EQ( + OK, + http_stream.InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + + EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest( + headers, &response, callback.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + // This results in writing the post body and reading the response headers. + callback.WaitForResult(); + + // This triggers reading the body and the EOF, causing the session to shut + // down. + data()->CompleteRead(); + base::MessageLoop::current()->RunUntilIdle(); + + // Because we abandoned the stream, we don't expect to find a session in the + // pool anymore. + EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +// Test to ensure the SpdyStream state machine does not get confused when a +// chunk becomes available while a write is pending. +TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPost) { + const char kUploadData1[] = "12345678"; + const int kUploadData1Size = arraysize(kUploadData1)-1; + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> chunk2( + spdy_util_.ConstructSpdyBodyFrame( + 1, kUploadData1, kUploadData1Size, false)); + scoped_ptr<SpdyFrame> chunk3(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0), + CreateMockWrite(*chunk1, 1), // POST upload frames + CreateMockWrite(*chunk2, 2), + CreateMockWrite(*chunk3, 3), + }; + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp, 4), + CreateMockRead(*chunk1, 5), + CreateMockRead(*chunk2, 6), + CreateMockRead(*chunk3, 7), + MockRead(ASYNC, 0, 8) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + InitSessionDeterministic(reads, arraysize(reads), + writes, arraysize(writes), + key); + + UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_stream; + + ASSERT_EQ(OK, upload_stream.Init(CompletionCallback())); + upload_stream.AppendChunk(kUploadData, kUploadDataSize, false); + + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + + TestCompletionCallback callback; + HttpRequestHeaders headers; + HttpResponseInfo response; + // This will attempt to Write() the initial request and headers, which will + // complete asynchronously. + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, + callback.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + // Complete the initial request write and the first chunk. + deterministic_data()->RunFor(2); + ASSERT_TRUE(callback.have_result()); + EXPECT_EQ(OK, callback.WaitForResult()); + + // Now append the final two chunks which will enqueue two more writes. + upload_stream.AppendChunk(kUploadData1, kUploadData1Size, false); + upload_stream.AppendChunk(kUploadData, kUploadDataSize, true); + + // Finish writing all the chunks. + deterministic_data()->RunFor(2); + + // Read response headers. + deterministic_data()->RunFor(1); + ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback())); + + // Read and check |chunk1| response. + deterministic_data()->RunFor(1); + scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize)); + ASSERT_EQ(kUploadDataSize, + http_stream->ReadResponseBody( + buf1.get(), kUploadDataSize, callback.callback())); + EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize)); + + // Read and check |chunk2| response. + deterministic_data()->RunFor(1); + scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size)); + ASSERT_EQ(kUploadData1Size, + http_stream->ReadResponseBody( + buf2.get(), kUploadData1Size, callback.callback())); + EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size)); + + // Read and check |chunk3| response. + deterministic_data()->RunFor(1); + scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize)); + ASSERT_EQ(kUploadDataSize, + http_stream->ReadResponseBody( + buf3.get(), kUploadDataSize, callback.callback())); + EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize)); + + // Finish reading the |EOF|. + deterministic_data()->RunFor(1); + ASSERT_TRUE(response.headers.get()); + ASSERT_EQ(200, response.headers->response_code()); + EXPECT_TRUE(deterministic_data()->at_read_eof()); + EXPECT_TRUE(deterministic_data()->at_write_eof()); +} + +// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058 +TEST_P(SpdyHttpStreamTest, SpdyURLTest) { + const char * const full_url = "http://www.google.com/foo?query=what#anchor"; + const char * const base_url = "http://www.google.com/foo?query=what"; + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(base_url, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(SYNCHRONOUS, 0, 3) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + InitSession(reads, arraysize(reads), writes, arraysize(writes), key); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(full_url); + TestCompletionCallback callback; + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + ASSERT_EQ(OK, + http_stream->InitializeStream( + &request, DEFAULT_PRIORITY, net_log, CompletionCallback())); + + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, + callback.callback())); + + EXPECT_EQ(base_url, http_stream->stream()->GetUrlFromHeaders().spec()); + + // This triggers the MockWrite and read 2 + callback.WaitForResult(); + + // This triggers read 3. The empty read causes the session to shut down. + data()->CompleteRead(); + + // Because we abandoned the stream, we don't expect to find a session in the + // pool anymore. + EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +namespace { + +void GetECServerBoundCertAndProof( + const std::string& host, + ServerBoundCertService* server_bound_cert_service, + std::string* cert, + std::string* proof) { + TestCompletionCallback callback; + std::string key; + ServerBoundCertService::RequestHandle request_handle; + int rv = server_bound_cert_service->GetOrCreateDomainBoundCert( + host, &key, cert, callback.callback(), + &request_handle); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + SpdyCredential credential; + EXPECT_EQ(OK, + SpdyCredentialBuilder::Build( + MockClientSocket::kTlsUnique, key, *cert, 2, &credential)); + + ASSERT_FALSE(credential.certs.empty()); + cert->assign(credential.certs[0]); + proof->assign(credential.proof); +} + +// Constructs a standard SPDY SYN_STREAM frame for a GET request with +// a credential set. +SpdyFrame* ConstructCredentialRequestFrame(NextProto next_proto, + size_t slot, const GURL& url, + SpdyStreamId stream_id) { + SpdyTestUtil util(next_proto); + + const SpdyHeaderInfo syn_headers = { + SYN_STREAM, + stream_id, + 0, + ConvertRequestPriorityToSpdyPriority(LOWEST, 3), + slot, + CONTROL_FLAG_FIN, + false, + RST_STREAM_INVALID, + NULL, + 0, + DATA_FLAG_NONE + }; + + scoped_ptr<SpdyHeaderBlock> headers(util.ConstructGetHeaderBlock(url.spec())); + return util.ConstructSpdyFrame(syn_headers, headers.Pass()); +} + +} // namespace + +// TODO(rch): When openssl supports server bound certifictes, this +// guard can be removed +#if !defined(USE_OPENSSL) +// Test that if we request a resource for a new origin on a session that +// used domain bound certificates, that we send a CREDENTIAL frame for +// the new domain before we send the new request. +void SpdyHttpStreamTest::TestSendCredentials( + ServerBoundCertService* server_bound_cert_service, + const std::string& cert, + const std::string& proof) { + const char* kUrl1 = "https://www.google.com/"; + const char* kUrl2 = "https://www.gmail.com/"; + + SpdyCredential cred; + cred.slot = 2; + cred.proof = proof; + cred.certs.push_back(cert); + + scoped_ptr<SpdyFrame> req(ConstructCredentialRequestFrame( + GetParam(), 1, GURL(kUrl1), 1)); + scoped_ptr<SpdyFrame> credential( + spdy_util_.ConstructSpdyCredential(cred)); + scoped_ptr<SpdyFrame> req2(ConstructCredentialRequestFrame( + GetParam(), 2, GURL(kUrl2), 3)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0), + CreateMockWrite(*credential.get(), 2), + CreateMockWrite(*req2.get(), 3), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*resp2, 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + HostPortPair host_port_pair(HostPortPair::FromURL(GURL(kUrl1))); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + DeterministicMockClientSocketFactory* socket_factory = + session_deps_.deterministic_socket_factory.get(); + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + socket_factory->AddSocketDataProvider(&data); + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.channel_id_sent = true; + ssl.server_bound_cert_service = server_bound_cert_service; + ssl.protocol_negotiated = GetParam(); + socket_factory->AddSSLSocketDataProvider(&ssl); + http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + &session_deps_); + session_ = CreateSecureSpdySession(http_session_, key, BoundNetLog()); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(kUrl1); + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + ASSERT_EQ( + OK, + http_stream->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + + // EXPECT_FALSE(session_->NeedsCredentials(request.url)); + // GURL new_origin(kUrl2); + // EXPECT_TRUE(session_->NeedsCredentials(new_origin)); + + TestCompletionCallback callback; + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, + callback.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + data.RunFor(2); + callback.WaitForResult(); + + // Start up second request for resource on a new origin. + scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true)); + request.url = GURL(kUrl2); + ASSERT_EQ( + OK, + http_stream2->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, &response, + callback.callback())); + data.RunFor(2); + callback.WaitForResult(); + + EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders( + callback.callback())); + data.RunFor(1); + EXPECT_EQ(OK, callback.WaitForResult()); + ASSERT_TRUE(response.headers.get() != NULL); + ASSERT_EQ(200, response.headers->response_code()); +} + +// The tests below are only for SPDY/3 and above. + +// Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be +// made available is handled correctly. +TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPostWithWindowUpdate) { + if (GetParam() < kProtoSPDY3) + return; + + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0), + CreateMockWrite(*chunk1, 1), + }; + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize)); + MockRead reads[] = { + CreateMockRead(*window_update, 2), + CreateMockRead(*resp, 3), + CreateMockRead(*chunk1, 4), + MockRead(ASYNC, 0, 5) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + InitSessionDeterministic(reads, arraysize(reads), + writes, arraysize(writes), + key); + + UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_stream; + + ASSERT_EQ(OK, upload_stream.Init(CompletionCallback())); + upload_stream.AppendChunk(kUploadData, kUploadDataSize, true); + + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + + HttpRequestHeaders headers; + HttpResponseInfo response; + // This will attempt to Write() the initial request and headers, which will + // complete asynchronously. + TestCompletionCallback callback; + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, + callback.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + // Complete the initial request write and first chunk. + deterministic_data_->RunFor(2); + ASSERT_TRUE(callback.have_result()); + EXPECT_EQ(OK, callback.WaitForResult()); + + // Verify that the window size has decreased. + ASSERT_TRUE(http_stream->stream() != NULL); + EXPECT_NE(static_cast<int>(kSpdyStreamInitialWindowSize), + http_stream->stream()->send_window_size()); + + // Read window update. + deterministic_data_->RunFor(1); + + // Verify the window update. + ASSERT_TRUE(http_stream->stream() != NULL); + EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize), + http_stream->stream()->send_window_size()); + + // Read response headers. + deterministic_data_->RunFor(1); + ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback())); + + // Read and check |chunk1| response. + deterministic_data_->RunFor(1); + scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize)); + ASSERT_EQ(kUploadDataSize, + http_stream->ReadResponseBody( + buf1.get(), kUploadDataSize, callback.callback())); + EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize)); + + // Finish reading the |EOF|. + deterministic_data_->RunFor(1); + ASSERT_TRUE(response.headers.get()); + ASSERT_EQ(200, response.headers->response_code()); + EXPECT_TRUE(deterministic_data_->at_read_eof()); + EXPECT_TRUE(deterministic_data_->at_write_eof()); +} + +TEST_P(SpdyHttpStreamTest, SendCredentialsEC) { + if (GetParam() < kProtoSPDY3) + return; + + scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool = + new base::SequencedWorkerPool(1, "SpdyHttpStreamSpdy3Test"); + scoped_ptr<ServerBoundCertService> server_bound_cert_service( + new ServerBoundCertService(new DefaultServerBoundCertStore(NULL), + sequenced_worker_pool)); + std::string cert; + std::string proof; + GetECServerBoundCertAndProof("www.gmail.com", + server_bound_cert_service.get(), + &cert, &proof); + + TestSendCredentials(server_bound_cert_service.get(), cert, proof); + + sequenced_worker_pool->Shutdown(); +} + +TEST_P(SpdyHttpStreamTest, DontSendCredentialsForHttpUrlsEC) { + if (GetParam() < kProtoSPDY3) + return; + + scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool = + new base::SequencedWorkerPool(1, "SpdyHttpStreamSpdy3Test"); + scoped_ptr<ServerBoundCertService> server_bound_cert_service( + new ServerBoundCertService(new DefaultServerBoundCertStore(NULL), + sequenced_worker_pool)); + std::string cert; + std::string proof; + GetECServerBoundCertAndProof("proxy.google.com", + server_bound_cert_service.get(), + &cert, &proof); + + const char* kUrl1 = "http://www.google.com/"; + const char* kUrl2 = "http://www.gmail.com/"; + + SpdyCredential cred; + cred.slot = 2; + cred.proof = proof; + cred.certs.push_back(cert); + + scoped_ptr<SpdyFrame> req(ConstructCredentialRequestFrame( + GetParam(), 0, GURL(kUrl1), 1)); + scoped_ptr<SpdyFrame> req2(ConstructCredentialRequestFrame( + GetParam(), 0, GURL(kUrl2), 3)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0), + CreateMockWrite(*req2.get(), 2), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*resp2, 3), + MockRead(ASYNC, 0, 4) // EOF + }; + + HostPortPair host_port_pair(HostPortPair::FromURL(GURL(kUrl1))); + SpdySessionKey key(host_port_pair, + ProxyServer::FromURI("proxy.google.com", + ProxyServer::SCHEME_HTTPS), + kPrivacyModeDisabled); + InitSessionDeterministic(reads, arraysize(reads), + writes, arraysize(writes), + key); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(kUrl1); + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + ASSERT_EQ( + OK, + http_stream->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + + TestCompletionCallback callback; + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, + callback.callback())); + EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); + + deterministic_data_->RunFor(2); + EXPECT_EQ(OK, callback.WaitForResult()); + + // Start up second request for resource on a new origin. + scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true)); + request.url = GURL(kUrl2); + ASSERT_EQ( + OK, + http_stream2->InitializeStream(&request, DEFAULT_PRIORITY, + net_log, CompletionCallback())); + EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, &response, + callback.callback())); + deterministic_data_->RunFor(1); + EXPECT_EQ(OK, callback.WaitForResult()); + + EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders( + callback.callback())); + deterministic_data_->RunFor(1); + EXPECT_EQ(OK, callback.WaitForResult()); + ASSERT_TRUE(response.headers.get() != NULL); + ASSERT_EQ(200, response.headers->response_code()); + deterministic_data_->RunFor(1); + sequenced_worker_pool->Shutdown(); +} + +#endif // !defined(USE_OPENSSL) + +// TODO(willchan): Write a longer test for SpdyStream that exercises all +// methods. + +} // namespace net diff --git a/chromium/net/spdy/spdy_http_utils.cc b/chromium/net/spdy/spdy_http_utils.cc new file mode 100644 index 00000000000..21012f23fc1 --- /dev/null +++ b/chromium/net/spdy/spdy_http_utils.cc @@ -0,0 +1,203 @@ +// 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 "net/spdy/spdy_http_utils.h" + +#include <string> + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" +#include "net/base/escape.h" +#include "net/base/load_flags.h" +#include "net/base/net_util.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/http/http_util.h" + +namespace net { + +bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers, + int protocol_version, + HttpResponseInfo* response) { + std::string status_key = (protocol_version >= 3) ? ":status" : "status"; + std::string version_key = (protocol_version >= 3) ? ":version" : "version"; + std::string version; + std::string status; + + // The "status" and "version" headers are required. + SpdyHeaderBlock::const_iterator it; + it = headers.find(status_key); + if (it == headers.end()) + return false; + status = it->second; + + it = headers.find(version_key); + if (it == headers.end()) + return false; + version = it->second; + + std::string raw_headers(version); + raw_headers.push_back(' '); + raw_headers.append(status); + raw_headers.push_back('\0'); + for (it = headers.begin(); it != headers.end(); ++it) { + // For each value, if the server sends a NUL-separated + // list of values, we separate that back out into + // individual headers for each value in the list. + // e.g. + // Set-Cookie "foo\0bar" + // becomes + // Set-Cookie: foo\0 + // Set-Cookie: bar\0 + std::string value = it->second; + size_t start = 0; + size_t end = 0; + do { + end = value.find('\0', start); + std::string tval; + if (end != value.npos) + tval = value.substr(start, (end - start)); + else + tval = value.substr(start); + if (protocol_version >= 3 && it->first[0] == ':') + raw_headers.append(it->first.substr(1)); + else + raw_headers.append(it->first); + raw_headers.push_back(':'); + raw_headers.append(tval); + raw_headers.push_back('\0'); + start = end + 1; + } while (end != value.npos); + } + + response->headers = new HttpResponseHeaders(raw_headers); + response->was_fetched_via_spdy = true; + return true; +} + +void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info, + const HttpRequestHeaders& request_headers, + SpdyHeaderBlock* headers, + int protocol_version, + bool direct) { + + HttpRequestHeaders::Iterator it(request_headers); + while (it.GetNext()) { + std::string name = StringToLowerASCII(it.name()); + if (name == "connection" || name == "proxy-connection" || + name == "transfer-encoding") { + continue; + } + if (headers->find(name) == headers->end()) { + (*headers)[name] = it.value(); + } else { + std::string new_value = (*headers)[name]; + new_value.append(1, '\0'); // +=() doesn't append 0's + new_value += it.value(); + (*headers)[name] = new_value; + } + } + static const char kHttpProtocolVersion[] = "HTTP/1.1"; + + if (protocol_version < 3) { + (*headers)["version"] = kHttpProtocolVersion; + (*headers)["method"] = info.method; + (*headers)["host"] = GetHostAndOptionalPort(info.url); + (*headers)["scheme"] = info.url.scheme(); + if (direct) + (*headers)["url"] = HttpUtil::PathForRequest(info.url); + else + (*headers)["url"] = HttpUtil::SpecForRequest(info.url); + } else { + (*headers)[":version"] = kHttpProtocolVersion; + (*headers)[":method"] = info.method; + (*headers)[":host"] = GetHostAndOptionalPort(info.url); + (*headers)[":scheme"] = info.url.scheme(); + (*headers)[":path"] = HttpUtil::PathForRequest(info.url); + headers->erase("host"); // this is kinda insane, spdy 3 spec. + } + +} + +COMPILE_ASSERT(HIGHEST - LOWEST < 4 && + HIGHEST - MINIMUM_PRIORITY < 5, + request_priority_incompatible_with_spdy); + +SpdyPriority ConvertRequestPriorityToSpdyPriority( + const RequestPriority priority, + int protocol_version) { + DCHECK_GE(priority, MINIMUM_PRIORITY); + DCHECK_LT(priority, NUM_PRIORITIES); + if (protocol_version == 2) { + // SPDY 2 only has 2 bits of priority, but we have 5 RequestPriorities. + // Map IDLE => 3, LOWEST => 2, LOW => 2, MEDIUM => 1, HIGHEST => 0. + if (priority > LOWEST) { + return static_cast<SpdyPriority>(HIGHEST - priority); + } else { + return static_cast<SpdyPriority>(HIGHEST - priority - 1); + } + } else { + return static_cast<SpdyPriority>(HIGHEST - priority); + } +} + +NET_EXPORT_PRIVATE RequestPriority ConvertSpdyPriorityToRequestPriority( + SpdyPriority priority, + int protocol_version) { + // Handle invalid values gracefully, and pick LOW to map 2 back + // to for SPDY/2. + SpdyPriority idle_cutoff = (protocol_version == 2) ? 3 : 5; + return (priority >= idle_cutoff) ? + IDLE : static_cast<RequestPriority>(HIGHEST - priority); +} + +GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers, + int protocol_version, + bool pushed) { + // SPDY 2 server push urls are specified in a single "url" header. + if (pushed && protocol_version == 2) { + std::string url; + SpdyHeaderBlock::const_iterator it; + it = headers.find("url"); + if (it != headers.end()) + url = it->second; + return GURL(url); + } + + const char* scheme_header = protocol_version >= 3 ? ":scheme" : "scheme"; + const char* host_header = protocol_version >= 3 ? ":host" : "host"; + const char* path_header = protocol_version >= 3 ? ":path" : "url"; + + std::string scheme; + std::string host_port; + std::string path; + SpdyHeaderBlock::const_iterator it; + it = headers.find(scheme_header); + if (it != headers.end()) + scheme = it->second; + it = headers.find(host_header); + if (it != headers.end()) + host_port = it->second; + it = headers.find(path_header); + if (it != headers.end()) + path = it->second; + + std::string url = (scheme.empty() || host_port.empty() || path.empty()) + ? std::string() + : scheme + "://" + host_port + path; + return GURL(url); +} + +bool ShouldShowHttpHeaderValue(const std::string& header_name) { +#if defined(SPDY_PROXY_AUTH_ORIGIN) + if (header_name == "proxy-authorization") + return false; +#endif + return true; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_http_utils.h b/chromium/net/spdy/spdy_http_utils.h new file mode 100644 index 00000000000..26511a77834 --- /dev/null +++ b/chromium/net/spdy/spdy_http_utils.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef NET_SPDY_SPDY_HTTP_UTILS_H_ +#define NET_SPDY_SPDY_HTTP_UTILS_H_ + +#include "net/base/net_export.h" +#include "net/base/request_priority.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" +#include "url/gurl.h" + +namespace net { + +class HttpResponseInfo; +struct HttpRequestInfo; +class HttpRequestHeaders; + +// Convert a SpdyHeaderBlock into an HttpResponseInfo. +// |headers| input parameter with the SpdyHeaderBlock. +// |response| output parameter for the HttpResponseInfo. +// Returns true if successfully converted. False if the SpdyHeaderBlock is +// incomplete (e.g. missing 'status' or 'version'). +bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers, + int protocol_version, + HttpResponseInfo* response); + +// Create a SpdyHeaderBlock for a Spdy SYN_STREAM Frame from +// HttpRequestInfo and HttpRequestHeaders. +void NET_EXPORT_PRIVATE CreateSpdyHeadersFromHttpRequest( + const HttpRequestInfo& info, + const HttpRequestHeaders& request_headers, + SpdyHeaderBlock* headers, + int protocol_version, + bool direct); + +// Returns the URL associated with the |headers| by assembling the +// scheme, host and path from the protocol specific keys. +GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers, + int protocol_version, + bool pushed); + +// Returns true if the value of this header should be displayed. +NET_EXPORT_PRIVATE bool ShouldShowHttpHeaderValue( + const std::string& header_name); + +NET_EXPORT_PRIVATE SpdyPriority ConvertRequestPriorityToSpdyPriority( + RequestPriority priority, + int protocol_version); + +NET_EXPORT_PRIVATE RequestPriority ConvertSpdyPriorityToRequestPriority( + SpdyPriority priority, + int protocol_version); + +} // namespace net + +#endif // NET_SPDY_SPDY_HTTP_UTILS_H_ diff --git a/chromium/net/spdy/spdy_http_utils_unittest.cc b/chromium/net/spdy/spdy_http_utils_unittest.cc new file mode 100644 index 00000000000..b198808c9b1 --- /dev/null +++ b/chromium/net/spdy/spdy_http_utils_unittest.cc @@ -0,0 +1,67 @@ +// 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 "net/spdy/spdy_http_utils.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy2Priority) { + EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST, 2)); + EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM, 2)); + EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW, 2)); + EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOWEST, 2)); + EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(IDLE, 2)); +} + +TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy3Priority) { + EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST, 3)); + EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM, 3)); + EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW, 3)); + EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(LOWEST, 3)); + EXPECT_EQ(4, ConvertRequestPriorityToSpdyPriority(IDLE, 3)); +} + +TEST(SpdyHttpUtilsTest, ConvertSpdy2PriorityToRequestPriority) { + EXPECT_EQ(HIGHEST, ConvertSpdyPriorityToRequestPriority(0, 2)); + EXPECT_EQ(MEDIUM, ConvertSpdyPriorityToRequestPriority(1, 2)); + EXPECT_EQ(LOW, ConvertSpdyPriorityToRequestPriority(2, 2)); + EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(3, 2)); + // These are invalid values, but we should still handle them + // gracefully. + for (int i = 4; i < kuint8max; ++i) { + EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(i, 2)); + } +} + +TEST(SpdyHttpUtilsTest, ConvertSpdy3PriorityToRequestPriority) { + EXPECT_EQ(HIGHEST, ConvertSpdyPriorityToRequestPriority(0, 3)); + EXPECT_EQ(MEDIUM, ConvertSpdyPriorityToRequestPriority(1, 3)); + EXPECT_EQ(LOW, ConvertSpdyPriorityToRequestPriority(2, 3)); + EXPECT_EQ(LOWEST, ConvertSpdyPriorityToRequestPriority(3, 3)); + EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(4, 3)); + // These are invalid values, but we should still handle them + // gracefully. + for (int i = 5; i < kuint8max; ++i) { + EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(i, 3)); + } +} + +TEST(SpdyHttpUtilsTest, ShowHttpHeaderValue){ +#if defined(SPDY_PROXY_AUTH_ORIGIN) + EXPECT_FALSE(ShouldShowHttpHeaderValue("proxy-authorization")); + EXPECT_TRUE(ShouldShowHttpHeaderValue("accept-encoding")); +#else + EXPECT_TRUE(ShouldShowHttpHeaderValue("proxy-authorization")); + EXPECT_TRUE(ShouldShowHttpHeaderValue("accept-encoding")); +#endif +} + +} // namespace + +} // namespace net diff --git a/chromium/net/spdy/spdy_network_transaction_unittest.cc b/chromium/net/spdy/spdy_network_transaction_unittest.cc new file mode 100644 index 00000000000..f3f9f344995 --- /dev/null +++ b/chromium/net/spdy/spdy_network_transaction_unittest.cc @@ -0,0 +1,6405 @@ +// 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 <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "net/base/auth.h" +#include "net/base/net_log_unittest.h" +#include "net/base/request_priority.h" +#include "net/base/upload_bytes_element_reader.h" +#include "net/base/upload_data_stream.h" +#include "net/base/upload_file_element_reader.h" +#include "net/http/http_network_session_peer.h" +#include "net/http/http_network_transaction.h" +#include "net/http/http_server_properties.h" +#include "net/http/http_transaction_unittest.h" +#include "net/socket/client_socket_pool_base.h" +#include "net/socket/next_proto.h" +#include "net/spdy/buffered_spdy_framer.h" +#include "net/spdy/spdy_http_stream.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_common.h" +#include "net/spdy/spdy_test_utils.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/platform_test.h" + +//----------------------------------------------------------------------------- + +namespace net { + +namespace { +const char kRequestUrl[] = "http://www.google.com/"; + +enum SpdyNetworkTransactionTestSSLType { + SPDYNPN, + SPDYNOSSL, + SPDYSSL, +}; + +struct SpdyNetworkTransactionTestParams { + SpdyNetworkTransactionTestParams() + : protocol(kProtoSPDY2), + ssl_type(SPDYNPN) {} + + SpdyNetworkTransactionTestParams( + NextProto protocol, + SpdyNetworkTransactionTestSSLType ssl_type) + : protocol(protocol), + ssl_type(ssl_type) {} + + NextProto protocol; + SpdyNetworkTransactionTestSSLType ssl_type; +}; + +SpdySessionDependencies* CreateSpdySessionDependencies( + SpdyNetworkTransactionTestParams test_params) { + return new SpdySessionDependencies(test_params.protocol); +} + +SpdySessionDependencies* CreateSpdySessionDependencies( + SpdyNetworkTransactionTestParams test_params, + ProxyService* proxy_service) { + return new SpdySessionDependencies(test_params.protocol, proxy_service); +} + +} // namespace + +class SpdyNetworkTransactionTest + : public ::testing::TestWithParam<SpdyNetworkTransactionTestParams> { + protected: + SpdyNetworkTransactionTest() : spdy_util_(GetParam().protocol) { + } + + virtual ~SpdyNetworkTransactionTest() { + // UploadDataStream posts deletion tasks back to the message loop on + // destruction. + upload_data_stream_.reset(); + base::RunLoop().RunUntilIdle(); + } + + virtual void SetUp() { + google_get_request_initialized_ = false; + google_post_request_initialized_ = false; + google_chunked_post_request_initialized_ = false; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + struct TransactionHelperResult { + int rv; + std::string status_line; + std::string response_data; + HttpResponseInfo response_info; + }; + + // A helper class that handles all the initial npn/ssl setup. + class NormalSpdyTransactionHelper { + public: + NormalSpdyTransactionHelper(const HttpRequestInfo& request, + RequestPriority priority, + const BoundNetLog& log, + SpdyNetworkTransactionTestParams test_params, + SpdySessionDependencies* session_deps) + : request_(request), + priority_(priority), + session_deps_(session_deps == NULL ? + CreateSpdySessionDependencies(test_params) : + session_deps), + session_(SpdySessionDependencies::SpdyCreateSession( + session_deps_.get())), + log_(log), + test_params_(test_params), + deterministic_(false), + spdy_enabled_(true) { + switch (test_params_.ssl_type) { + case SPDYNOSSL: + case SPDYSSL: + port_ = 80; + break; + case SPDYNPN: + port_ = 443; + break; + default: + NOTREACHED(); + } + } + + ~NormalSpdyTransactionHelper() { + // Any test which doesn't close the socket by sending it an EOF will + // have a valid session left open, which leaks the entire session pool. + // This is just fine - in fact, some of our tests intentionally do this + // so that we can check consistency of the SpdySessionPool as the test + // finishes. If we had put an EOF on the socket, the SpdySession would + // have closed and we wouldn't be able to check the consistency. + + // Forcefully close existing sessions here. + session()->spdy_session_pool()->CloseAllSessions(); + } + + void SetDeterministic() { + session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + session_deps_.get()); + deterministic_ = true; + } + + void SetSpdyDisabled() { + spdy_enabled_ = false; + port_ = 80; + } + + void RunPreTestSetup() { + if (!session_deps_.get()) + session_deps_.reset(CreateSpdySessionDependencies(test_params_)); + if (!session_.get()) + session_ = SpdySessionDependencies::SpdyCreateSession( + session_deps_.get()); + HttpStreamFactory::set_use_alternate_protocols(false); + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(false); + + std::vector<NextProto> next_protos = SpdyNextProtos(); + + switch (test_params_.ssl_type) { + case SPDYNPN: + session_->http_server_properties()->SetAlternateProtocol( + HostPortPair("www.google.com", 80), 443, + AlternateProtocolFromNextProto(test_params_.protocol)); + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(next_protos); + break; + case SPDYNOSSL: + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + break; + case SPDYSSL: + HttpStreamFactory::set_force_spdy_over_ssl(true); + HttpStreamFactory::set_force_spdy_always(true); + break; + default: + NOTREACHED(); + } + + // We're now ready to use SSL-npn SPDY. + trans_.reset(new HttpNetworkTransaction(priority_, session_.get())); + } + + // Start the transaction, read some data, finish. + void RunDefaultTest() { + if (!StartDefaultTest()) + return; + FinishDefaultTest(); + } + + bool StartDefaultTest() { + output_.rv = trans_->Start(&request_, callback.callback(), log_); + + // We expect an IO Pending or some sort of error. + EXPECT_LT(output_.rv, 0); + return output_.rv == ERR_IO_PENDING; + } + + void FinishDefaultTest() { + output_.rv = callback.WaitForResult(); + if (output_.rv != OK) { + session_->spdy_session_pool()->CloseCurrentSessions(net::ERR_ABORTED); + return; + } + + // Verify responses. + const HttpResponseInfo* response = trans_->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy); + if (HttpStreamFactory::spdy_enabled()) { + EXPECT_EQ( + HttpResponseInfo::ConnectionInfoFromNextProto( + test_params_.protocol), + response->connection_info); + } else { + EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP1, + response->connection_info); + } + if (test_params_.ssl_type == SPDYNPN && spdy_enabled_) { + EXPECT_TRUE(response->was_npn_negotiated); + } else { + EXPECT_TRUE(!response->was_npn_negotiated); + } + // If SPDY is not enabled, a HTTP request should not be diverted + // over a SSL session. + if (!spdy_enabled_) { + EXPECT_EQ(request_.url.SchemeIs("https"), + response->was_npn_negotiated); + } + EXPECT_EQ("127.0.0.1", response->socket_address.host()); + EXPECT_EQ(port_, response->socket_address.port()); + output_.status_line = response->headers->GetStatusLine(); + output_.response_info = *response; // Make a copy so we can verify. + output_.rv = ReadTransaction(trans_.get(), &output_.response_data); + } + + // Most tests will want to call this function. In particular, the MockReads + // should end with an empty read, and that read needs to be processed to + // ensure proper deletion of the spdy_session_pool. + void VerifyDataConsumed() { + for (DataVector::iterator it = data_vector_.begin(); + it != data_vector_.end(); ++it) { + EXPECT_TRUE((*it)->at_read_eof()) << "Read count: " + << (*it)->read_count() + << " Read index: " + << (*it)->read_index(); + EXPECT_TRUE((*it)->at_write_eof()) << "Write count: " + << (*it)->write_count() + << " Write index: " + << (*it)->write_index(); + } + } + + // Occasionally a test will expect to error out before certain reads are + // processed. In that case we want to explicitly ensure that the reads were + // not processed. + void VerifyDataNotConsumed() { + for (DataVector::iterator it = data_vector_.begin(); + it != data_vector_.end(); ++it) { + EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: " + << (*it)->read_count() + << " Read index: " + << (*it)->read_index(); + EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: " + << (*it)->write_count() + << " Write index: " + << (*it)->write_index(); + } + } + + void RunToCompletion(StaticSocketDataProvider* data) { + RunPreTestSetup(); + AddData(data); + RunDefaultTest(); + VerifyDataConsumed(); + } + + void AddData(StaticSocketDataProvider* data) { + DCHECK(!deterministic_); + data_vector_.push_back(data); + SSLSocketDataProvider* ssl_provider = + new SSLSocketDataProvider(ASYNC, OK); + if (test_params_.ssl_type == SPDYNPN) + ssl_provider->SetNextProto(test_params_.protocol); + + ssl_vector_.push_back(ssl_provider); + if (test_params_.ssl_type == SPDYNPN || test_params_.ssl_type == SPDYSSL) + session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_provider); + + session_deps_->socket_factory->AddSocketDataProvider(data); + if (test_params_.ssl_type == SPDYNPN) { + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider* hanging_non_alternate_protocol_socket = + new StaticSocketDataProvider(NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket->set_connect_data( + never_finishing_connect); + session_deps_->socket_factory->AddSocketDataProvider( + hanging_non_alternate_protocol_socket); + alternate_vector_.push_back(hanging_non_alternate_protocol_socket); + } + } + + void AddDeterministicData(DeterministicSocketData* data) { + DCHECK(deterministic_); + data_vector_.push_back(data); + SSLSocketDataProvider* ssl_provider = + new SSLSocketDataProvider(ASYNC, OK); + if (test_params_.ssl_type == SPDYNPN) + ssl_provider->SetNextProto(test_params_.protocol); + + ssl_vector_.push_back(ssl_provider); + if (test_params_.ssl_type == SPDYNPN || + test_params_.ssl_type == SPDYSSL) { + session_deps_->deterministic_socket_factory-> + AddSSLSocketDataProvider(ssl_provider); + } + session_deps_->deterministic_socket_factory->AddSocketDataProvider(data); + if (test_params_.ssl_type == SPDYNPN) { + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + DeterministicSocketData* hanging_non_alternate_protocol_socket = + new DeterministicSocketData(NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket->set_connect_data( + never_finishing_connect); + session_deps_->deterministic_socket_factory->AddSocketDataProvider( + hanging_non_alternate_protocol_socket); + alternate_deterministic_vector_.push_back( + hanging_non_alternate_protocol_socket); + } + } + + void SetSession(const scoped_refptr<HttpNetworkSession>& session) { + session_ = session; + } + HttpNetworkTransaction* trans() { return trans_.get(); } + void ResetTrans() { trans_.reset(); } + TransactionHelperResult& output() { return output_; } + const HttpRequestInfo& request() const { return request_; } + const scoped_refptr<HttpNetworkSession>& session() const { + return session_; + } + scoped_ptr<SpdySessionDependencies>& session_deps() { + return session_deps_; + } + int port() const { return port_; } + SpdyNetworkTransactionTestParams test_params() const { + return test_params_; + } + + private: + typedef std::vector<StaticSocketDataProvider*> DataVector; + typedef ScopedVector<SSLSocketDataProvider> SSLVector; + typedef ScopedVector<StaticSocketDataProvider> AlternateVector; + typedef ScopedVector<DeterministicSocketData> AlternateDeterministicVector; + HttpRequestInfo request_; + RequestPriority priority_; + scoped_ptr<SpdySessionDependencies> session_deps_; + scoped_refptr<HttpNetworkSession> session_; + TransactionHelperResult output_; + scoped_ptr<StaticSocketDataProvider> first_transaction_; + SSLVector ssl_vector_; + TestCompletionCallback callback; + scoped_ptr<HttpNetworkTransaction> trans_; + scoped_ptr<HttpNetworkTransaction> trans_http_; + DataVector data_vector_; + AlternateVector alternate_vector_; + AlternateDeterministicVector alternate_deterministic_vector_; + const BoundNetLog& log_; + SpdyNetworkTransactionTestParams test_params_; + int port_; + bool deterministic_; + bool spdy_enabled_; + }; + + void ConnectStatusHelperWithExpectedStatus(const MockRead& status, + int expected_status); + + void ConnectStatusHelper(const MockRead& status); + + const HttpRequestInfo& CreateGetPushRequest() { + google_get_push_request_.method = "GET"; + google_get_push_request_.url = GURL("http://www.google.com/foo.dat"); + google_get_push_request_.load_flags = 0; + return google_get_push_request_; + } + + const HttpRequestInfo& CreateGetRequest() { + if (!google_get_request_initialized_) { + google_get_request_.method = "GET"; + google_get_request_.url = GURL(kDefaultURL); + google_get_request_.load_flags = 0; + google_get_request_initialized_ = true; + } + return google_get_request_; + } + + const HttpRequestInfo& CreateGetRequestWithUserAgent() { + if (!google_get_request_initialized_) { + google_get_request_.method = "GET"; + google_get_request_.url = GURL(kDefaultURL); + google_get_request_.load_flags = 0; + google_get_request_.extra_headers.SetHeader("User-Agent", "Chrome"); + google_get_request_initialized_ = true; + } + return google_get_request_; + } + + const HttpRequestInfo& CreatePostRequest() { + if (!google_post_request_initialized_) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back( + new UploadBytesElementReader(kUploadData, kUploadDataSize)); + upload_data_stream_.reset(new UploadDataStream(&element_readers, 0)); + + google_post_request_.method = "POST"; + google_post_request_.url = GURL(kDefaultURL); + google_post_request_.upload_data_stream = upload_data_stream_.get(); + google_post_request_initialized_ = true; + } + return google_post_request_; + } + + const HttpRequestInfo& CreateFilePostRequest() { + if (!google_post_request_initialized_) { + base::FilePath file_path; + CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path)); + CHECK_EQ(static_cast<int>(kUploadDataSize), + file_util::WriteFile(file_path, kUploadData, kUploadDataSize)); + + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back( + new UploadFileElementReader(base::MessageLoopProxy::current().get(), + file_path, + 0, + kUploadDataSize, + base::Time())); + upload_data_stream_.reset(new UploadDataStream(&element_readers, 0)); + + google_post_request_.method = "POST"; + google_post_request_.url = GURL(kDefaultURL); + google_post_request_.upload_data_stream = upload_data_stream_.get(); + google_post_request_initialized_ = true; + } + return google_post_request_; + } + + const HttpRequestInfo& CreateComplexPostRequest() { + if (!google_post_request_initialized_) { + const int kFileRangeOffset = 1; + const int kFileRangeLength = 3; + CHECK_LT(kFileRangeOffset + kFileRangeLength, kUploadDataSize); + + base::FilePath file_path; + CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path)); + CHECK_EQ(static_cast<int>(kUploadDataSize), + file_util::WriteFile(file_path, kUploadData, kUploadDataSize)); + + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back( + new UploadBytesElementReader(kUploadData, kFileRangeOffset)); + element_readers.push_back( + new UploadFileElementReader(base::MessageLoopProxy::current().get(), + file_path, + kFileRangeOffset, + kFileRangeLength, + base::Time())); + element_readers.push_back(new UploadBytesElementReader( + kUploadData + kFileRangeOffset + kFileRangeLength, + kUploadDataSize - (kFileRangeOffset + kFileRangeLength))); + upload_data_stream_.reset(new UploadDataStream(&element_readers, 0)); + + google_post_request_.method = "POST"; + google_post_request_.url = GURL(kDefaultURL); + google_post_request_.upload_data_stream = upload_data_stream_.get(); + google_post_request_initialized_ = true; + } + return google_post_request_; + } + + const HttpRequestInfo& CreateChunkedPostRequest() { + if (!google_chunked_post_request_initialized_) { + upload_data_stream_.reset( + new UploadDataStream(UploadDataStream::CHUNKED, 0)); + google_chunked_post_request_.method = "POST"; + google_chunked_post_request_.url = GURL(kDefaultURL); + google_chunked_post_request_.upload_data_stream = + upload_data_stream_.get(); + google_chunked_post_request_initialized_ = true; + } + return google_chunked_post_request_; + } + + // Read the result of a particular transaction, knowing that we've got + // multiple transactions in the read pipeline; so as we read, we may have + // to skip over data destined for other transactions while we consume + // the data for |trans|. + int ReadResult(HttpNetworkTransaction* trans, + StaticSocketDataProvider* data, + std::string* result) { + const int kSize = 3000; + + int bytes_read = 0; + scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(kSize)); + TestCompletionCallback callback; + while (true) { + int rv = trans->Read(buf.get(), kSize, callback.callback()); + if (rv == ERR_IO_PENDING) { + // Multiple transactions may be in the data set. Keep pulling off + // reads until we complete our callback. + while (!callback.have_result()) { + data->CompleteRead(); + base::RunLoop().RunUntilIdle(); + } + rv = callback.WaitForResult(); + } else if (rv <= 0) { + break; + } + result->append(buf->data(), rv); + bytes_read += rv; + } + return bytes_read; + } + + void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) { + // This lengthy block is reaching into the pool to dig out the active + // session. Once we have the session, we verify that the streams are + // all closed and not leaked at this point. + const GURL& url = helper.request().url; + int port = helper.test_params().ssl_type == SPDYNPN ? 443 : 80; + HostPortPair host_port_pair(url.host(), port); + SpdySessionKey key(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + BoundNetLog log; + const scoped_refptr<HttpNetworkSession>& session = helper.session(); + base::WeakPtr<SpdySession> spdy_session = + session->spdy_session_pool()->FindAvailableSession(key, log); + ASSERT_TRUE(spdy_session != NULL); + EXPECT_EQ(0u, spdy_session->num_active_streams()); + EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams()); + } + + void RunServerPushTest(OrderedSocketData* data, + HttpResponseInfo* response, + HttpResponseInfo* push_response, + const std::string& expected) { + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Request the pushed path. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + base::RunLoop().RunUntilIdle(); + + // The data for the pushed path may be coming in more than 1 frame. Compile + // the results into a single string. + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected), 0) << "Received data: " + << result2 + << "||||| Expected data: " + << expected; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + *response = *trans->GetResponseInfo(); + *push_response = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + } + + static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper, + int result) { + helper->ResetTrans(); + } + + static void StartTransactionCallback( + const scoped_refptr<HttpNetworkSession>& session, + int result) { + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get())); + TestCompletionCallback callback; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + callback.WaitForResult(); + } + + SpdyTestUtil spdy_util_; + + private: + scoped_ptr<UploadDataStream> upload_data_stream_; + bool google_get_request_initialized_; + bool google_post_request_initialized_; + bool google_chunked_post_request_initialized_; + HttpRequestInfo google_get_request_; + HttpRequestInfo google_post_request_; + HttpRequestInfo google_chunked_post_request_; + HttpRequestInfo google_get_push_request_; + base::ScopedTempDir temp_dir_; +}; + +//----------------------------------------------------------------------------- +// All tests are run with three different connection types: SPDY after NPN +// negotiation, SPDY without SSL, and SPDY with SSL. +// +// TODO(akalin): Use ::testing::Combine() when we are able to use +// <tr1/tuple>. +INSTANTIATE_TEST_CASE_P( + Spdy, + SpdyNetworkTransactionTest, + ::testing::Values( + SpdyNetworkTransactionTestParams(kProtoSPDY2, SPDYNOSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY2, SPDYSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY2, SPDYNPN), + SpdyNetworkTransactionTestParams(kProtoSPDY3, SPDYNOSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY3, SPDYSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY3, SPDYNPN), + SpdyNetworkTransactionTestParams(kProtoSPDY31, SPDYNOSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY31, SPDYSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY31, SPDYNPN), + SpdyNetworkTransactionTestParams(kProtoSPDY4a2, SPDYNOSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY4a2, SPDYSSL), + SpdyNetworkTransactionTestParams(kProtoSPDY4a2, SPDYNPN), + SpdyNetworkTransactionTestParams(kProtoHTTP2Draft04, SPDYNOSSL), + SpdyNetworkTransactionTestParams(kProtoHTTP2Draft04, SPDYSSL), + SpdyNetworkTransactionTestParams(kProtoHTTP2Draft04, SPDYNPN))); + +// Verify HttpNetworkTransaction constructor. +TEST_P(SpdyNetworkTransactionTest, Constructor) { + scoped_ptr<SpdySessionDependencies> session_deps( + CreateSpdySessionDependencies(GetParam())); + scoped_refptr<HttpNetworkSession> session( + SpdySessionDependencies::SpdyCreateSession(session_deps.get())); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get())); +} + +TEST_P(SpdyNetworkTransactionTest, Get) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionTest, GetAtEachPriority) { + for (RequestPriority p = MINIMUM_PRIORITY; p < NUM_PRIORITIES; + p = RequestPriority(p + 1)) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, p, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + SpdyPriority spdy_prio = 0; + EXPECT_TRUE(GetSpdyPriority(spdy_util_.spdy_version(), *req, &spdy_prio)); + // this repeats the RequestPriority-->SpdyPriority mapping from + // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make + // sure it's being done right. + if (spdy_util_.spdy_version() < SPDY3) { + switch(p) { + case HIGHEST: + EXPECT_EQ(0, spdy_prio); + break; + case MEDIUM: + EXPECT_EQ(1, spdy_prio); + break; + case LOW: + case LOWEST: + EXPECT_EQ(2, spdy_prio); + break; + case IDLE: + EXPECT_EQ(3, spdy_prio); + break; + default: + FAIL(); + } + } else { + switch(p) { + case HIGHEST: + EXPECT_EQ(0, spdy_prio); + break; + case MEDIUM: + EXPECT_EQ(1, spdy_prio); + break; + case LOW: + EXPECT_EQ(2, spdy_prio); + break; + case LOWEST: + EXPECT_EQ(3, spdy_prio); + break; + case IDLE: + EXPECT_EQ(4, spdy_prio); + break; + default: + FAIL(); + } + } + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + HttpRequestInfo http_req = CreateGetRequest(); + + NormalSpdyTransactionHelper helper(http_req, p, BoundNetLog(), + GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + } +} + +// Start three gets simultaniously; making sure that multiplexed +// streams work properly. + +// This can't use the TransactionHelper method, since it only +// handles a single transaction, and finishes them as soon +// as it launches them. + +// TODO(gavinp): create a working generalized TransactionHelper that +// can allow multiple streams in flight. + +TEST_P(SpdyNetworkTransactionTest, ThreeGets) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false)); + scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<SpdyFrame> req3( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, LOWEST, true)); + scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, false)); + scoped_ptr<SpdyFrame> fbody3(spdy_util_.ConstructSpdyBodyFrame(5, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req3), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*resp3, 7), + CreateMockRead(*body3), + + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + + trans2->GetResponseInfo(); + + out.rv = ReadTransaction(trans1.get(), &out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBinding) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false)); + scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + data_placeholder.set_connect_data(never_finishing_connect); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + // We require placeholder data because two get requests are sent out, so + // there needs to be two sets of SSL connection data. + helper.AddData(&data_placeholder); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + EXPECT_TRUE(response2->headers.get() != NULL); + EXPECT_TRUE(response2->was_fetched_via_spdy); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); +} + +TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBindingFromPreconnect) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false)); + scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + OrderedSocketData preconnect_data(reads, arraysize(reads), + writes, arraysize(writes)); + + MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING); + + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + data_placeholder.set_connect_data(never_finishing_connect); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&preconnect_data); + // We require placeholder data because 3 connections are attempted (first is + // the preconnect, 2nd and 3rd are the never finished connections. + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + + HttpRequestInfo httpreq = CreateGetRequest(); + + // Preconnect the first. + SSLConfig preconnect_ssl_config; + helper.session()->ssl_config_service()->GetSSLConfig(&preconnect_ssl_config); + HttpStreamFactory* http_stream_factory = + helper.session()->http_stream_factory(); + if (http_stream_factory->has_next_protos()) { + preconnect_ssl_config.next_protos = http_stream_factory->next_protos(); + } + + http_stream_factory->PreconnectStreams( + 1, httpreq, DEFAULT_PRIORITY, + preconnect_ssl_config, preconnect_ssl_config); + + out.rv = trans1->Start(&httpreq, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + EXPECT_TRUE(response2->headers.get() != NULL); + EXPECT_TRUE(response2->was_fetched_via_spdy); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); +} + +// Similar to ThreeGets above, however this test adds a SETTINGS +// frame. The SETTINGS frame is read during the IO loop waiting on +// the first transaction completion, and sets a maximum concurrent +// stream limit of 1. This means that our IO loop exists after the +// second transaction completes, so we can assert on read_index(). +TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrent) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false)); + scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<SpdyFrame> req3( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, LOWEST, true)); + scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, false)); + scoped_ptr<SpdyFrame> fbody3(spdy_util_.ConstructSpdyBodyFrame(5, true)); + + SettingsMap settings; + const uint32 max_concurrent_streams = 1; + settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req3), + }; + + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + CreateMockRead(*resp3, 12), + CreateMockRead(*body3), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + + BoundNetLog log; + TransactionHelperResult out; + { + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // Run transaction 1 through quickly to force a read of our SETTINGS + // frame. + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + EXPECT_EQ(7U, data.read_index()); // i.e. the third trans was queued + + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response3 = trans3->GetResponseInfo(); + out.status_line = response3->headers->GetStatusLine(); + out.response_info = *response3; + out.rv = ReadTransaction(trans3.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); + } + EXPECT_EQ(OK, out.rv); +} + +// Similar to ThreeGetsWithMaxConcurrent above, however this test adds +// a fourth transaction. The third and fourth transactions have +// different data ("hello!" vs "hello!hello!") and because of the +// user specified priority, we expect to see them inverted in +// the response from the server. +TEST_P(SpdyNetworkTransactionTest, FourGetsWithMaxConcurrentPriority) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false)); + scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<SpdyFrame> req4( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, HIGHEST, true)); + scoped_ptr<SpdyFrame> resp4(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<SpdyFrame> fbody4(spdy_util_.ConstructSpdyBodyFrame(5, true)); + + scoped_ptr<SpdyFrame> req3( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 7, LOWEST, true)); + scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 7)); + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(7, false)); + scoped_ptr<SpdyFrame> fbody3(spdy_util_.ConstructSpdyBodyFrame(7, true)); + + SettingsMap settings; + const uint32 max_concurrent_streams = 1; + settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req4), + CreateMockWrite(*req3), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + CreateMockRead(*resp4, 13), + CreateMockRead(*fbody4), + CreateMockRead(*resp3, 16), + CreateMockRead(*body3), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + // We require placeholder data because four get requests are sent out, so + // there needs to be four sets of SSL connection data. + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans4( + new HttpNetworkTransaction(HIGHEST, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + TestCompletionCallback callback4; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + HttpRequestInfo httpreq4 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + // Run transaction 1 through quickly to force a read of our SETTINGS frame. + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans4->Start(&httpreq4, callback4.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + EXPECT_EQ(data.read_index(), 7U); // i.e. the third & fourth trans queued + + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + // notice: response3 gets two hellos, response4 gets one + // hello, so we know dequeuing priority was respected. + const HttpResponseInfo* response3 = trans3->GetResponseInfo(); + out.status_line = response3->headers->GetStatusLine(); + out.response_info = *response3; + out.rv = ReadTransaction(trans3.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + out.rv = callback4.WaitForResult(); + EXPECT_EQ(OK, out.rv); + const HttpResponseInfo* response4 = trans4->GetResponseInfo(); + out.status_line = response4->headers->GetStatusLine(); + out.response_info = *response4; + out.rv = ReadTransaction(trans4.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); +} + +// Similar to ThreeGetsMaxConcurrrent above, however, this test +// deletes a session in the middle of the transaction to insure +// that we properly remove pendingcreatestream objects from +// the spdy_session +TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentDelete) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false)); + scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + SettingsMap settings; + const uint32 max_concurrent_streams = 1; + settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // Run transaction 1 through quickly to force a read of our SETTINGS frame. + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + delete trans3.release(); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + EXPECT_EQ(8U, data.read_index()); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); +} + +namespace { + +// The KillerCallback will delete the transaction on error as part of the +// callback. +class KillerCallback : public TestCompletionCallbackBase { + public: + explicit KillerCallback(HttpNetworkTransaction* transaction) + : transaction_(transaction), + callback_(base::Bind(&KillerCallback::OnComplete, + base::Unretained(this))) { + } + + virtual ~KillerCallback() {} + + const CompletionCallback& callback() const { return callback_; } + + private: + void OnComplete(int result) { + if (result < 0) + delete transaction_; + + SetResult(result); + } + + HttpNetworkTransaction* transaction_; + CompletionCallback callback_; +}; + +} // namespace + +// Similar to ThreeGetsMaxConcurrrentDelete above, however, this test +// closes the socket while we have a pending transaction waiting for +// a pending stream creation. http://crbug.com/52901 +TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentSocketClose) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> fin_body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + + SettingsMap settings; + const uint32 max_concurrent_streams = 1; + settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fin_body), + CreateMockRead(*resp2, 7), + MockRead(ASYNC, ERR_CONNECTION_RESET, 0), // Abort! + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data_placeholder(NULL, 0, NULL, 0); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(&data_placeholder); + helper.AddData(&data_placeholder); + HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session().get()); + HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session().get()); + HttpNetworkTransaction* trans3( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + KillerCallback callback3(trans3); + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1.Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // Run transaction 1 through quickly to force a read of our SETTINGS frame. + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2.Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback3.WaitForResult(); + ASSERT_EQ(ERR_ABORTED, out.rv); + + EXPECT_EQ(6U, data.read_index()); + + const HttpResponseInfo* response1 = trans1.GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(&trans1, &out.response_data); + EXPECT_EQ(OK, out.rv); + + const HttpResponseInfo* response2 = trans2.GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(&trans2, &out.response_data); + EXPECT_EQ(ERR_CONNECTION_RESET, out.rv); + + helper.VerifyDataConsumed(); +} + +// Test that a simple PUT request works. +TEST_P(SpdyNetworkTransactionTest, Put) { + // Setup the request + HttpRequestInfo request; + request.method = "PUT"; + request.url = GURL("http://www.google.com/"); + + const SpdyHeaderInfo kSynStartHeader = { + SYN_STREAM, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Length + DATA_FLAG_NONE // Data Flags + }; + scoped_ptr<SpdyHeaderBlock> put_headers( + spdy_util_.ConstructPutHeaderBlock("http://www.google.com", 0)); + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame( + kSynStartHeader, put_headers.Pass())); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + const SpdyHeaderInfo kSynReplyHeader = { + SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Length + DATA_FLAG_NONE // Data Flags + }; + scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); + (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; + (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + (*reply_headers)["content-length"] = "1234"; + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyFrame( + kSynReplyHeader, reply_headers.Pass())); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); +} + +// Test that a simple HEAD request works. +TEST_P(SpdyNetworkTransactionTest, Head) { + // Setup the request + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + + const SpdyHeaderInfo kSynStartHeader = { + SYN_STREAM, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Length + DATA_FLAG_NONE // Data Flags + }; + scoped_ptr<SpdyHeaderBlock> head_headers( + spdy_util_.ConstructHeadHeaderBlock("http://www.google.com", 0)); + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame( + kSynStartHeader, head_headers.Pass())); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + const SpdyHeaderInfo kSynReplyHeader = { + SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Length + DATA_FLAG_NONE // Data Flags + }; + scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); + (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; + (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + (*reply_headers)["content-length"] = "1234"; + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyFrame( + kSynReplyHeader, + reply_headers.Pass())); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); +} + +// Test that a simple POST works. +TEST_P(SpdyNetworkTransactionTest, Post) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), // POST upload frame + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(2, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreatePostRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a POST with a file works. +TEST_P(SpdyNetworkTransactionTest, FilePost) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), // POST upload frame + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(2, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateFilePostRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a complex POST works. +TEST_P(SpdyNetworkTransactionTest, ComplexPost) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), // POST upload frame + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(2, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateComplexPostRequest(), + DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a chunked POST works. +TEST_P(SpdyNetworkTransactionTest, ChunkedPost) { + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(2, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(), + DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + // These chunks get merged into a single frame when being sent. + const int kFirstChunkSize = kUploadDataSize/2; + helper.request().upload_data_stream->AppendChunk( + kUploadData, kFirstChunkSize, false); + helper.request().upload_data_stream->AppendChunk( + kUploadData + kFirstChunkSize, kUploadDataSize - kFirstChunkSize, true); + + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ(kUploadData, out.response_data); +} + +// Test that a chunked POST works with chunks appended after transaction starts. +TEST_P(SpdyNetworkTransactionTest, DelayedChunkedPost) { + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> chunk2(spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> chunk3(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*chunk1), + CreateMockWrite(*chunk2), + CreateMockWrite(*chunk3), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*chunk1), + CreateMockRead(*chunk2), + CreateMockRead(*chunk3), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(4, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(), + DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + helper.request().upload_data_stream->AppendChunk( + kUploadData, kUploadDataSize, false); + + helper.RunPreTestSetup(); + helper.AddData(&data); + ASSERT_TRUE(helper.StartDefaultTest()); + + base::RunLoop().RunUntilIdle(); + helper.request().upload_data_stream->AppendChunk( + kUploadData, kUploadDataSize, false); + base::RunLoop().RunUntilIdle(); + helper.request().upload_data_stream->AppendChunk( + kUploadData, kUploadDataSize, true); + + helper.FinishDefaultTest(); + helper.VerifyDataConsumed(); + + std::string expected_response; + expected_response += kUploadData; + expected_response += kUploadData; + expected_response += kUploadData; + + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ(expected_response, out.response_data); +} + +// Test that a POST without any post data works. +TEST_P(SpdyNetworkTransactionTest, NullPost) { + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL(kRequestUrl); + // Create an empty UploadData. + request.upload_data_stream = NULL; + + // When request.upload_data_stream is NULL for post, content-length is + // expected to be 0. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost(kRequestUrl, 1, 0, LOWEST, NULL, 0)); + // Set the FIN bit since there will be no body. + test::SetFrameFlags(req.get(), CONTROL_FLAG_FIN, spdy_util_.spdy_version()); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a simple POST works. +TEST_P(SpdyNetworkTransactionTest, EmptyPost) { + // Create an empty UploadDataStream. + ScopedVector<UploadElementReader> element_readers; + UploadDataStream stream(&element_readers, 0); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL(kRequestUrl); + request.upload_data_stream = &stream; + + const uint64 kContentLength = 0; + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kContentLength, LOWEST, NULL, 0)); + // Set the FIN bit since there will be no body. + test::SetFrameFlags(req.get(), CONTROL_FLAG_FIN, spdy_util_.spdy_version()); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// While we're doing a post, the server sends back a SYN_REPLY. +TEST_P(SpdyNetworkTransactionTest, PostWithEarlySynReply) { + static const char upload[] = { "hello!" }; + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back( + new UploadBytesElementReader(upload, sizeof(upload))); + UploadDataStream stream(&element_readers, 0); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL(kRequestUrl); + request.upload_data_stream = &stream; + + scoped_ptr<SpdyFrame> stream_reply( + spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*stream_reply, 1), + MockRead(ASYNC, 0, 4) // EOF + }; + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*body, 2), + CreateMockWrite(*rst, 3) + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreatePostRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreatePostRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data.RunFor(4); + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + data.RunFor(1); +} + +// The client upon cancellation tries to send a RST_STREAM frame. The mock +// socket causes the TCP write to return zero. This test checks that the client +// tries to queue up the RST_STREAM frame again. +TEST_P(SpdyNetworkTransactionTest, SocketWriteReturnsZero) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0, SYNCHRONOUS), + MockWrite(SYNCHRONOUS, 0, 0, 2), + CreateMockWrite(*rst.get(), 3, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 1, ASYNC), + MockRead(ASYNC, 0, 0, 4) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data.SetStop(2); + data.Run(); + helper.ResetTrans(); + data.SetStop(20); + data.Run(); + + helper.VerifyDataConsumed(); +} + +// Test that the transaction doesn't crash when we don't have a reply. +TEST_P(SpdyNetworkTransactionTest, ResponseWithoutSynReply) { + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), NULL, 0); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +// Test that the transaction doesn't crash when we get two replies on the same +// stream ID. See http://crbug.com/45639. +TEST_P(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_STREAM_IN_USE)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + + helper.VerifyDataConsumed(); +} + +TEST_P(SpdyNetworkTransactionTest, ResetReplyWithTransferEncoding) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + const char* const headers[] = { + "transfer-encoding", "chunked" + }; + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyGetSynReply(headers, 1, 1)); + scoped_ptr<SpdyFrame> body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + + helper.session()->spdy_session_pool()->CloseAllSessions(); + helper.VerifyDataConsumed(); +} + +TEST_P(SpdyNetworkTransactionTest, ResetPushWithTransferEncoding) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + const char* const headers[] = { + "transfer-encoding", "chunked" + }; + scoped_ptr<SpdyFrame> push( + spdy_util_.ConstructSpdyPush(headers, arraysize(headers) / 2, + 2, 1, "http://www.google.com/1")); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*push), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + helper.session()->spdy_session_pool()->CloseAllSessions(); + helper.VerifyDataConsumed(); +} + +TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + // This following read isn't used by the test, except during the + // RunUntilIdle() call at the end since the SpdySession survives the + // HttpNetworkTransaction and still tries to continue Read()'ing. Any + // MockRead will do here. + MockRead(ASYNC, 0, 0) // EOF + }; + + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + helper.ResetTrans(); // Cancel the transaction. + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + base::RunLoop().RunUntilIdle(); + helper.VerifyDataNotConsumed(); +} + +// Verify that the client sends a Rst Frame upon cancelling the stream. +TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 0, SYNCHRONOUS), + CreateMockWrite(*rst, 2, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 0, 3) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), + GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data.SetStop(2); + data.Run(); + helper.ResetTrans(); + data.SetStop(20); + data.Run(); + + helper.VerifyDataConsumed(); +} + +// Verify that the client can correctly deal with the user callback attempting +// to start another transaction on a session that is closing down. See +// http://crbug.com/47455 +TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + MockWrite writes2[] = { CreateMockWrite(*req) }; + + // The indicated length of this frame is longer than its actual length. When + // the session receives an empty frame after this one, it shuts down the + // session, and calls the read callback with the incomplete data. + const uint8 kGetBodyFrame2[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x07, + 'h', 'e', 'l', 'l', 'o', '!', + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2), + arraysize(kGetBodyFrame2), 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + MockRead(ASYNC, 0, 0, 6), // EOF + }; + MockRead reads2[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3), // EOF + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + DelayedSocketData data2(1, reads2, arraysize(reads2), + writes2, arraysize(writes2)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + helper.AddData(&data2); + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); + rv = trans->Read( + buf.get(), + kSize, + base::Bind(&SpdyNetworkTransactionTest::StartTransactionCallback, + helper.session())); + // This forces an err_IO_pending, which sets the callback. + data.CompleteRead(); + // This finishes the read. + data.CompleteRead(); + helper.VerifyDataConsumed(); +} + +// Verify that the client can correctly deal with the user callback deleting the +// transaction. Failures will usually be valgrind errors. See +// http://crbug.com/46925 +TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 2), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), // EOF + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Setup a user callback which will delete the session, and clear out the + // memory holding the stream object. Note that the callback deletes trans. + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); + rv = trans->Read( + buf.get(), + kSize, + base::Bind(&SpdyNetworkTransactionTest::DeleteSessionCallback, + base::Unretained(&helper))); + ASSERT_EQ(ERR_IO_PENDING, rv); + data.CompleteRead(); + + // Finish running rest of tasks. + base::RunLoop().RunUntilIdle(); + helper.VerifyDataConsumed(); +} + +// Send a spdy request to www.google.com that gets redirected to www.foo.com. +TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { + const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM); + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); + (*headers)["user-agent"] = ""; + (*headers)["accept-encoding"] = "gzip,deflate"; + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php")); + (*headers2)["user-agent"] = ""; + (*headers2)["accept-encoding"] = "gzip,deflate"; + + // Setup writes/reads to www.google.com + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame( + kSynStartHeader, headers.Pass())); + scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyFrame( + kSynStartHeader, headers2.Pass())); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReplyRedirect(1)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3) // EOF + }; + + // Setup writes/reads to www.foo.com + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(ASYNC, 0, 0, 4) // EOF + }; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data2(reads2, arraysize(reads2), + writes2, arraysize(writes2)); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + TestDelegate d; + { + SpdyURLRequestContext spdy_url_request_context(GetParam().protocol); + net::URLRequest r( + GURL("http://www.google.com/"), &d, &spdy_url_request_context); + spdy_url_request_context.socket_factory(). + AddSocketDataProvider(&data); + spdy_url_request_context.socket_factory(). + AddSocketDataProvider(&data2); + + d.set_quit_on_redirect(true); + r.Start(); + base::RunLoop().Run(); + + EXPECT_EQ(1, d.received_redirect_count()); + + r.FollowDeferredRedirect(); + base::RunLoop().Run(); + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + } + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data2.at_read_eof()); + EXPECT_TRUE(data2.at_write_eof()); +} + +// Send a spdy request to www.google.com. Get a pushed stream that redirects to +// www.foo.com. +TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { + const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); + (*headers)["user-agent"] = ""; + (*headers)["accept-encoding"] = "gzip,deflate"; + + // Setup writes/reads to www.google.com + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers.Pass())); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> rep( + spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat", + "301 Moved Permanently", + "http://www.foo.com/index.php")); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*rst, 6), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + CreateMockRead(*body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + MockRead(ASYNC, 0, 0, 7) // EOF + }; + + // Setup writes/reads to www.foo.com + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php")); + (*headers2)["user-agent"] = ""; + (*headers2)["accept-encoding"] = "gzip,deflate"; + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers2.Pass())); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(ASYNC, 0, 0, 5) // EOF + }; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + OrderedSocketData data2(reads2, arraysize(reads2), + writes2, arraysize(writes2)); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + TestDelegate d; + TestDelegate d2; + SpdyURLRequestContext spdy_url_request_context(GetParam().protocol); + { + net::URLRequest r( + GURL("http://www.google.com/"), &d, &spdy_url_request_context); + spdy_url_request_context.socket_factory(). + AddSocketDataProvider(&data); + + r.Start(); + base::RunLoop().Run(); + + EXPECT_EQ(0, d.received_redirect_count()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + + net::URLRequest r2( + GURL("http://www.google.com/foo.dat"), &d2, &spdy_url_request_context); + spdy_url_request_context.socket_factory(). + AddSocketDataProvider(&data2); + + d2.set_quit_on_redirect(true); + r2.Start(); + base::RunLoop().Run(); + EXPECT_EQ(1, d2.received_redirect_count()); + + r2.FollowDeferredRedirect(); + base::RunLoop().Run(); + EXPECT_EQ(1, d2.response_started_count()); + EXPECT_FALSE(d2.received_data_before_response()); + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status()); + std::string contents2("hello!"); + EXPECT_EQ(contents2, d2.data_received()); + } + data.CompleteRead(); + data2.CompleteRead(); + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data2.at_read_eof()); + EXPECT_TRUE(data2.at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4, SYNCHRONOUS), + CreateMockRead(*stream2_body, 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushBeforeSynReply) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + MockRead reads[] = { + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_reply, 3), + CreateMockRead(*stream1_body, 4, SYNCHRONOUS), + CreateMockRead(*stream2_body, 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*stream1_syn, 1), }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + scoped_ptr<SpdyFrame> + stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_body, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<SpdyFrame> stream2_rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_rst, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()) << "Read count: " + << data.read_count() + << " Read index: " + << data.read_index(); + EXPECT_TRUE(data.at_write_eof()) << "Write count: " + << data.write_count() + << " Write index: " + << data.write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +// Verify that we don't leak streams and that we properly send a reset +// if the server pushes the same stream twice. +TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> stream3_rst( + spdy_util_.ConstructSpdyRstStream(4, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream3_rst, 5), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + scoped_ptr<SpdyFrame> + stream3_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 4, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream3_syn, 4), + CreateMockRead(*stream1_body, 6, SYNCHRONOUS), + CreateMockRead(*stream2_body, 7), + MockRead(ASYNC, ERR_IO_PENDING, 8), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + static const char kPushedData[] = "pushed my darling hello my baby"; + scoped_ptr<SpdyFrame> stream2_body_base( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + const size_t kChunkSize = strlen(kPushedData) / 4; + scoped_ptr<SpdyFrame> stream2_body1( + new SpdyFrame(stream2_body_base->data(), kChunkSize, false)); + scoped_ptr<SpdyFrame> stream2_body2( + new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false)); + scoped_ptr<SpdyFrame> stream2_body3( + new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize, + kChunkSize, false)); + scoped_ptr<SpdyFrame> stream2_body4( + new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize, + stream2_body_base->size() - 3 * kChunkSize, false)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_body1, 4), + CreateMockRead(*stream2_body2, 5), + CreateMockRead(*stream2_body3, 6), + CreateMockRead(*stream2_body4, 7), + CreateMockRead(*stream1_body, 8, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 9), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed my darling hello my baby"); + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, &response, &response2, kPushedData); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + static const char kPushedData[] = "pushed my darling hello my baby"; + scoped_ptr<SpdyFrame> stream2_body_base( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + const size_t kChunkSize = strlen(kPushedData) / 4; + scoped_ptr<SpdyFrame> stream2_body1( + new SpdyFrame(stream2_body_base->data(), kChunkSize, false)); + scoped_ptr<SpdyFrame> stream2_body2( + new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false)); + scoped_ptr<SpdyFrame> stream2_body3( + new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize, + kChunkSize, false)); + scoped_ptr<SpdyFrame> stream2_body4( + new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize, + stream2_body_base->size() - 3 * kChunkSize, false)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_body1, 4), + CreateMockRead(*stream2_body2, 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + CreateMockRead(*stream2_body3, 7), + CreateMockRead(*stream2_body4, 8), + CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 10) // Force a pause. + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, &response, &response2, kPushedData); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> stream2_rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 0, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()) << "Read count: " + << data.read_count() + << " Read index: " + << data.read_index(); + EXPECT_TRUE(data.at_write_eof()) << "Write count: " + << data.write_count() + << " Write index: " + << data.write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> stream2_rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_INVALID_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 9, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()) << "Read count: " + << data.read_count() + << " Read index: " + << data.read_index(); + EXPECT_TRUE(data.at_write_eof()) << "Write count: " + << data.write_count() + << " Write index: " + << data.write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> stream2_rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyHeaderBlock> incomplete_headers(new SpdyHeaderBlock()); + (*incomplete_headers)["hello"] = "bye"; + (*incomplete_headers)[spdy_util_.GetStatusKey()] = "200 OK"; + (*incomplete_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> stream2_syn( + spdy_util_.ConstructSpdyControlFrame(incomplete_headers.Pass(), + false, + 2, // Stream ID + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + // Associated stream ID + 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()) << "Read count: " + << data.read_count() + << " Read index: " + << data.read_index(); + EXPECT_TRUE(data.at_write_eof()) << "Write count: " + << data.write_count() + << " Write index: " + << data.write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +// Verify that various SynReply headers parse correctly through the +// HTTP layer. +TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { + struct SynReplyHeadersTests { + int num_headers; + const char* extra_headers[5]; + SpdyHeaderBlock expected_headers; + } test_cases[] = { + // This uses a multi-valued cookie header. + { 2, + { "cookie", "val1", + "cookie", "val2", // will get appended separated by NULL + NULL + }, + }, + // This is the minimalist set of headers. + { 0, + { NULL }, + }, + // Headers with a comma separated list. + { 1, + { "cookie", "val1,val2", + NULL + }, + } + }; + + test_cases[0].expected_headers["cookie"] = "val1"; + test_cases[0].expected_headers["cookie"] += '\0'; + test_cases[0].expected_headers["cookie"] += "val2"; + test_cases[0].expected_headers["hello"] = "bye"; + test_cases[0].expected_headers["status"] = "200"; + test_cases[0].expected_headers["version"] = "HTTP/1.1"; + + test_cases[1].expected_headers["hello"] = "bye"; + test_cases[1].expected_headers["status"] = "200"; + test_cases[1].expected_headers["version"] = "HTTP/1.1"; + + test_cases[2].expected_headers["cookie"] = "val1,val2"; + test_cases[2].expected_headers["hello"] = "bye"; + test_cases[2].expected_headers["status"] = "200"; + test_cases[2].expected_headers["version"] = "HTTP/1.1"; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyGetSynReply(test_cases[i].extra_headers, + test_cases[i].num_headers, + 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; + EXPECT_TRUE(headers.get() != NULL); + void* iter = NULL; + std::string name, value; + SpdyHeaderBlock header_block; + while (headers->EnumerateHeaderLines(&iter, &name, &value)) { + if (header_block[name].empty()) { + header_block[name] = value; + } else { + header_block[name] += '\0'; + header_block[name] += value; + } + } + EXPECT_EQ(test_cases[i].expected_headers, header_block); + } +} + +// Verify that various SynReply headers parse vary fields correctly +// through the HTTP layer, and the response matches the request. +TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) { + static const SpdyHeaderInfo syn_reply_info = { + SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Data Length + DATA_FLAG_NONE // Data Flags + }; + // Modify the following data to change/add test cases: + struct SynReplyTests { + const SpdyHeaderInfo* syn_reply; + bool vary_matches; + int num_headers[2]; + const char* extra_headers[2][16]; + } test_cases[] = { + // Test the case of a multi-valued cookie. When the value is delimited + // with NUL characters, it needs to be unfolded into multiple headers. + { + &syn_reply_info, + true, + { 1, 4 }, + { { "cookie", "val1,val2", + NULL + }, + { "vary", "cookie", + spdy_util_.GetStatusKey(), "200", + spdy_util_.GetPathKey(), "/index.php", + spdy_util_.GetVersionKey(), "HTTP/1.1", + NULL + } + } + }, { // Multiple vary fields. + &syn_reply_info, + true, + { 2, 5 }, + { { "friend", "barney", + "enemy", "snaggletooth", + NULL + }, + { "vary", "friend", + "vary", "enemy", + spdy_util_.GetStatusKey(), "200", + spdy_util_.GetPathKey(), "/index.php", + spdy_util_.GetVersionKey(), "HTTP/1.1", + NULL + } + } + }, { // Test a '*' vary field. + &syn_reply_info, + false, + { 1, 4 }, + { { "cookie", "val1,val2", + NULL + }, + { "vary", "*", + spdy_util_.GetStatusKey(), "200", + spdy_util_.GetPathKey(), "/index.php", + spdy_util_.GetVersionKey(), "HTTP/1.1", + NULL + } + } + }, { // Multiple comma-separated vary fields. + &syn_reply_info, + true, + { 2, 4 }, + { { "friend", "barney", + "enemy", "snaggletooth", + NULL + }, + { "vary", "friend,enemy", + spdy_util_.GetStatusKey(), "200", + spdy_util_.GetPathKey(), "/index.php", + spdy_util_.GetVersionKey(), "HTTP/1.1", + NULL + } + } + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + // Construct the request. + scoped_ptr<SpdyFrame> frame_req( + spdy_util_.ConstructSpdyGet(test_cases[i].extra_headers[0], + test_cases[i].num_headers[0], + false, 1, LOWEST, true)); + + MockWrite writes[] = { + CreateMockWrite(*frame_req), + }; + + // Construct the reply. + scoped_ptr<SpdyFrame> frame_reply( + spdy_util_.ConstructSpdyFrame(*test_cases[i].syn_reply, + test_cases[i].extra_headers[1], + test_cases[i].num_headers[1], + NULL, + 0)); + + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*frame_reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + // Attach the headers to the request. + int header_count = test_cases[i].num_headers[0]; + + HttpRequestInfo request = CreateGetRequest(); + for (int ct = 0; ct < header_count; ct++) { + const char* header_key = test_cases[i].extra_headers[0][ct * 2]; + const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1]; + request.extra_headers.SetHeader(header_key, header_value); + } + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv) << i; + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i; + EXPECT_EQ("hello!", out.response_data) << i; + + // Test the response information. + EXPECT_TRUE(out.response_info.response_time > + out.response_info.request_time) << i; + base::TimeDelta test_delay = out.response_info.response_time - + out.response_info.request_time; + base::TimeDelta min_expected_delay; + min_expected_delay.FromMilliseconds(10); + EXPECT_GT(test_delay.InMillisecondsF(), + min_expected_delay.InMillisecondsF()) << i; + EXPECT_EQ(out.response_info.vary_data.is_valid(), + test_cases[i].vary_matches) << i; + + // Check the headers. + scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; + ASSERT_TRUE(headers.get() != NULL) << i; + void* iter = NULL; + std::string name, value, lines; + while (headers->EnumerateHeaderLines(&iter, &name, &value)) { + lines.append(name); + lines.append(": "); + lines.append(value); + lines.append("\n"); + } + + // Construct the expected header reply string. + SpdyHeaderBlock reply_headers; + AppendToHeaderBlock(test_cases[i].extra_headers[1], + test_cases[i].num_headers[1], + &reply_headers); + std::string expected_reply = + spdy_util_.ConstructSpdyReplyString(reply_headers); + EXPECT_EQ(expected_reply, lines) << i; + } +} + +// Verify that we don't crash on invalid SynReply responses. +TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) { + const SpdyHeaderInfo kSynStartHeader = { + SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Length + DATA_FLAG_NONE // Data Flags + }; + + struct InvalidSynReplyTests { + int num_headers; + const char* headers[10]; + } test_cases[] = { + // SYN_REPLY missing status header + { 4, + { "cookie", "val1", + "cookie", "val2", + spdy_util_.GetPathKey(), "/index.php", + spdy_util_.GetVersionKey(), "HTTP/1.1", + NULL + }, + }, + // SYN_REPLY missing version header + { 2, + { "status", "200", + spdy_util_.GetPathKey(), "/index.php", + NULL + }, + }, + // SYN_REPLY with no headers + { 0, { NULL }, }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyFrame(kSynStartHeader, + NULL, 0, + test_cases[i].headers, + test_cases[i].num_headers)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + } +} + +// Verify that we don't crash on some corrupt frames. +TEST_P(SpdyNetworkTransactionTest, CorruptFrameSessionError) { + // This is the length field that's too short. + scoped_ptr<SpdyFrame> syn_reply_wrong_length( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + size_t right_size = + (spdy_util_.spdy_version() < SPDY4) ? + syn_reply_wrong_length->size() - framer.GetControlFrameHeaderSize() : + syn_reply_wrong_length->size(); + size_t wrong_size = right_size - 4; + test::SetFrameLength(syn_reply_wrong_length.get(), + wrong_size, + spdy_util_.spdy_version()); + + struct SynReplyTests { + const SpdyFrame* syn_reply; + } test_cases[] = { + { syn_reply_wrong_length.get(), }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + MockRead(ASYNC, test_cases[i].syn_reply->data(), wrong_size), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + } +} + +// Test that we shutdown correctly on write errors. +TEST_P(SpdyNetworkTransactionTest, WriteError) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { + // We'll write 10 bytes successfully + MockWrite(ASYNC, req->data(), 10), + // Followed by ERROR! + MockWrite(ASYNC, ERR_FAILED), + }; + + DelayedSocketData data(2, NULL, 0, + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_FAILED, out.rv); + data.Reset(); +} + +// Test that partial writes work. +TEST_P(SpdyNetworkTransactionTest, PartialWrite) { + // Chop the SYN_STREAM frame into 5 chunks. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + const int kChunks = 5; + scoped_ptr<MockWrite[]> writes(ChopWriteFrame(*req.get(), kChunks)); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(kChunks, reads, arraysize(reads), + writes.get(), kChunks); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// In this test, we enable compression, but get a uncompressed SynReply from +// the server. Verify that teardown is all clean. +TEST_P(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) { + scoped_ptr<SpdyFrame> compressed( + spdy_util_.ConstructSpdyGet(NULL, 0, true, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*compressed), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + SpdySessionDependencies* session_deps = + CreateSpdySessionDependencies(GetParam()); + session_deps->enable_compression = true; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), session_deps); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + data.Reset(); +} + +// Test that the NetLog contains good data for a simple GET request. +TEST_P(SpdyNetworkTransactionTest, NetLog) { + static const char* const kExtraHeaders[] = { + "user-agent", "Chrome", + }; + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(kExtraHeaders, 1, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + CapturingBoundNetLog log; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(), + DEFAULT_PRIORITY, + log.bound(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the NetLog was filled reasonably. + // This test is intentionally non-specific about the exact ordering of the + // log; instead we just check to make sure that certain events exist, and that + // they are in the right order. + net::CapturingNetLog::CapturedEntryList entries; + log.GetEntries(&entries); + + EXPECT_LT(0u, entries.size()); + int pos = 0; + pos = net::ExpectLogContainsSomewhere(entries, 0, + net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, + net::NetLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, + net::NetLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, + net::NetLog::PHASE_END); + + // Check that we logged all the headers correctly + pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM, + net::NetLog::PHASE_NONE); + + base::ListValue* header_list; + ASSERT_TRUE(entries[pos].params.get()); + ASSERT_TRUE(entries[pos].params->GetList("headers", &header_list)); + + std::vector<std::string> expected; + expected.push_back(std::string(spdy_util_.GetHostKey()) + ": www.google.com"); + expected.push_back(std::string(spdy_util_.GetPathKey()) + ": /"); + expected.push_back(std::string(spdy_util_.GetSchemeKey()) + ": http"); + expected.push_back(std::string(spdy_util_.GetVersionKey()) + ": HTTP/1.1"); + expected.push_back(std::string(spdy_util_.GetMethodKey()) + ": GET"); + expected.push_back("user-agent: Chrome"); + EXPECT_EQ(expected.size(), header_list->GetSize()); + for (std::vector<std::string>::const_iterator it = expected.begin(); + it != expected.end(); + ++it) { + base::StringValue header(*it); + EXPECT_NE(header_list->end(), header_list->Find(header)) << + "Header not found: " << *it; + } +} + +// Since we buffer the IO from the stream to the renderer, this test verifies +// that when we read out the maximum amount of data (e.g. we received 50 bytes +// on the network, but issued a Read for only 5 of those bytes) that the data +// flow still works correctly. +TEST_P(SpdyNetworkTransactionTest, BufferFull) { + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 2 data frames in a single read. + scoped_ptr<SpdyFrame> data_frame_1( + framer.CreateDataFrame(1, "goodby", 6, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> data_frame_2( + framer.CreateDataFrame(1, "e worl", 6, DATA_FLAG_NONE)); + const SpdyFrame* data_frames[2] = { + data_frame_1.get(), + data_frame_2.get(), + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + scoped_ptr<SpdyFrame> last_frame( + framer.CreateDataFrame(1, "d", 1, DATA_FLAG_FIN)); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + CreateMockRead(*last_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + TestCompletionCallback callback; + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.status_line = response->headers->GetStatusLine(); + out.response_info = *response; // Make a copy so we can verify. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + do { + // Read small chunks at a time. + const int kSmallReadSize = 3; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data.CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + content.append(buf->data(), rv); + } else if (rv < 0) { + NOTREACHED(); + } + } while (rv > 0); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + base::RunLoop().RunUntilIdle(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("goodbye world", out.response_data); +} + +// Verify that basic buffering works; when multiple data frames arrive +// at the same time, ensure that we don't notify a read completion for +// each data frame individually. +TEST_P(SpdyNetworkTransactionTest, Buffering) { + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 4 data frames in a single read. + scoped_ptr<SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> data_frame_fin( + framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN)); + const SpdyFrame* data_frames[4] = { + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame_fin.get() + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.status_line = response->headers->GetStatusLine(); + out.response_info = *response; // Make a copy so we can verify. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data.CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + EXPECT_EQ(kSmallReadSize, rv); + content.append(buf->data(), rv); + } else if (rv < 0) { + FAIL() << "Unexpected read error: " << rv; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes. + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + base::RunLoop().RunUntilIdle(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("messagemessagemessagemessage", out.response_data); +} + +// Verify the case where we buffer data but read it after it has been buffered. +TEST_P(SpdyNetworkTransactionTest, BufferedAll) { + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 5 data frames in a single read. + scoped_ptr<SpdyFrame> syn_reply( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + // turn off FIN bit + test::SetFrameFlags( + syn_reply.get(), CONTROL_FLAG_NONE, spdy_util_.spdy_version()); + scoped_ptr<SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> data_frame_fin( + framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN)); + const SpdyFrame* frames[5] = { + syn_reply.get(), + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame_fin.get() + }; + char combined_frames[200]; + int combined_frames_len = + CombineFrames(frames, arraysize(frames), + combined_frames, arraysize(combined_frames)); + + MockRead reads[] = { + MockRead(ASYNC, combined_frames, combined_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.status_line = response->headers->GetStatusLine(); + out.response_info = *response; // Make a copy so we can verify. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); + if (rv > 0) { + EXPECT_EQ(kSmallReadSize, rv); + content.append(buf->data(), rv); + } else if (rv < 0) { + FAIL() << "Unexpected read error: " << rv; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(3, reads_completed); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + base::RunLoop().RunUntilIdle(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("messagemessagemessagemessage", out.response_data); +} + +// Verify the case where we buffer data and close the connection. +TEST_P(SpdyNetworkTransactionTest, BufferedClosed) { + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // All data frames in a single read. + // NOTE: We don't FIN the stream. + scoped_ptr<SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); + const SpdyFrame* data_frames[4] = { + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame.get() + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a wait + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.status_line = response->headers->GetStatusLine(); + out.response_info = *response; // Make a copy so we can verify. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data.CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + content.append(buf->data(), rv); + } else if (rv < 0) { + // This test intentionally closes the connection, and will get an error. + EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); + break; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(0, reads_completed); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + base::RunLoop().RunUntilIdle(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Verify the case where we buffer data and cancel the transaction. +TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) { + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // NOTE: We don't FIN the stream. + scoped_ptr<SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a wait + CreateMockRead(*data_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.status_line = response->headers->GetStatusLine(); + out.response_info = *response; // Make a copy so we can verify. + + // Read Data + TestCompletionCallback read_callback; + + do { + const int kReadSize = 256; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize)); + rv = trans->Read(buf.get(), kReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + // Complete the read now, which causes buffering to start. + data.CompleteRead(); + // Destroy the transaction, causing the stream to get cancelled + // and orphaning the buffered IO task. + helper.ResetTrans(); + break; + } + // We shouldn't get here in this test. + FAIL() << "Unexpected read: " << rv; + } while (rv > 0); + + // Flush the MessageLoop; this will cause the buffered IO task + // to run for the final time. + base::RunLoop().RunUntilIdle(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Test that if the server requests persistence of settings, that we save +// the settings in the HttpServerProperties. +TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { + static const SpdyHeaderInfo kSynReplyInfo = { + SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Data Length + DATA_FLAG_NONE // Data Flags + }; + + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + net_log, GetParam(), NULL); + helper.RunPreTestSetup(); + + // Verify that no settings exist initially. + HostPortPair host_port_pair("www.google.com", helper.port()); + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).empty()); + + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // Construct the reply. + scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); + (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; + (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> reply( + spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass())); + + const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH; + unsigned int kSampleValue1 = 0x0a0a0a0a; + const SpdySettingsIds kSampleId2 = SETTINGS_DOWNLOAD_BANDWIDTH; + unsigned int kSampleValue2 = 0x0b0b0b0b; + const SpdySettingsIds kSampleId3 = SETTINGS_ROUND_TRIP_TIME; + unsigned int kSampleValue3 = 0x0c0c0c0c; + scoped_ptr<SpdyFrame> settings_frame; + { + // Construct the SETTINGS frame. + SettingsMap settings; + // First add a persisted setting. + settings[kSampleId1] = + SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue1); + // Next add a non-persisted setting. + settings[kSampleId2] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kSampleValue2); + // Next add another persisted setting. + settings[kSampleId3] = + SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue3); + settings_frame.reset(spdy_util_.ConstructSpdySettings(settings)); + } + + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply), + CreateMockRead(*body), + CreateMockRead(*settings_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + helper.AddData(&data); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + { + // Verify we had two persisted settings. + const SettingsMap& settings_map = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + ASSERT_EQ(2u, settings_map.size()); + + // Verify the first persisted setting. + SettingsMap::const_iterator it1 = settings_map.find(kSampleId1); + EXPECT_TRUE(it1 != settings_map.end()); + SettingsFlagsAndValue flags_and_value1 = it1->second; + EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first); + EXPECT_EQ(kSampleValue1, flags_and_value1.second); + + // Verify the second persisted setting. + SettingsMap::const_iterator it3 = settings_map.find(kSampleId3); + EXPECT_TRUE(it3 != settings_map.end()); + SettingsFlagsAndValue flags_and_value3 = it3->second; + EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3.first); + EXPECT_EQ(kSampleValue3, flags_and_value3.second); + } +} + +// Test that when there are settings saved that they are sent back to the +// server upon session establishment. +TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { + static const SpdyHeaderInfo kSynReplyInfo = { + SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + RST_STREAM_INVALID, // Status + NULL, // Data + 0, // Data Length + DATA_FLAG_NONE // Data Flags + }; + + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + net_log, GetParam(), NULL); + helper.RunPreTestSetup(); + + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + + SpdySessionPoolPeer pool_peer(spdy_session_pool); + pool_peer.SetEnableSendingInitialData(true); + + // Verify that no settings exist initially. + HostPortPair host_port_pair("www.google.com", helper.port()); + EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).empty()); + + const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH; + unsigned int kSampleValue1 = 0x0a0a0a0a; + const SpdySettingsIds kSampleId2 = SETTINGS_ROUND_TRIP_TIME; + unsigned int kSampleValue2 = 0x0c0c0c0c; + + // First add a persisted setting. + spdy_session_pool->http_server_properties()->SetSpdySetting( + host_port_pair, + kSampleId1, + SETTINGS_FLAG_PLEASE_PERSIST, + kSampleValue1); + + // Next add another persisted setting. + spdy_session_pool->http_server_properties()->SetSpdySetting( + host_port_pair, + kSampleId2, + SETTINGS_FLAG_PLEASE_PERSIST, + kSampleValue2); + + EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).size()); + + // Construct the initial SETTINGS frame. + SettingsMap initial_settings; + initial_settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams); + scoped_ptr<SpdyFrame> initial_settings_frame( + spdy_util_.ConstructSpdySettings(initial_settings)); + + // Construct the initial window update. + scoped_ptr<SpdyFrame> initial_window_update( + spdy_util_.ConstructSpdyWindowUpdate( + kSessionFlowControlStreamId, + kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize)); + + // Construct the persisted SETTINGS frame. + const SettingsMap& settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + + std::vector<MockWrite> writes; + if (GetParam().protocol == kProtoHTTP2Draft04) { + writes.push_back( + MockWrite(ASYNC, + kHttp2ConnectionHeaderPrefix, + kHttp2ConnectionHeaderPrefixSize)); + } + writes.push_back(CreateMockWrite(*initial_settings_frame)); + if (GetParam().protocol >= kProtoSPDY31) { + writes.push_back(CreateMockWrite(*initial_window_update)); + }; + writes.push_back(CreateMockWrite(*settings_frame)); + writes.push_back(CreateMockWrite(*req)); + + // Construct the reply. + scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); + (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; + (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> reply( + spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass())); + + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(2, reads, arraysize(reads), + vector_as_array(&writes), writes.size()); + helper.AddData(&data); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + { + // Verify we had two persisted settings. + const SettingsMap& settings_map = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + ASSERT_EQ(2u, settings_map.size()); + + // Verify the first persisted setting. + SettingsMap::const_iterator it1 = settings_map.find(kSampleId1); + EXPECT_TRUE(it1 != settings_map.end()); + SettingsFlagsAndValue flags_and_value1 = it1->second; + EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first); + EXPECT_EQ(kSampleValue1, flags_and_value1.second); + + // Verify the second persisted setting. + SettingsMap::const_iterator it2 = settings_map.find(kSampleId2); + EXPECT_TRUE(it2 != settings_map.end()); + SettingsFlagsAndValue flags_and_value2 = it2->second; + EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value2.first); + EXPECT_EQ(kSampleValue2, flags_and_value2.second); + } +} + +TEST_P(SpdyNetworkTransactionTest, GoAwayWithActiveStream) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> go_away(spdy_util_.ConstructSpdyGoAway()); + MockRead reads[] = { + CreateMockRead(*go_away), + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.AddData(&data); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_ABORTED, out.rv); +} + +TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + BoundNetLog log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + log, GetParam(), NULL); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + TransactionHelperResult out; + out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log); + + EXPECT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.rv = ReadTransaction(trans, &out.response_data); + EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Test to make sure we can correctly connect through a proxy. +TEST_P(SpdyNetworkTransactionTest, ProxyConnect) { + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.session_deps().reset(CreateSpdySessionDependencies( + GetParam(), + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"))); + helper.SetSession(make_scoped_refptr( + SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); + helper.RunPreTestSetup(); + HttpNetworkTransaction* trans = helper.trans(); + + const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + MockWrite writes_SPDYNPN[] = { + MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), + CreateMockWrite(*req, 2), + }; + MockRead reads_SPDYNPN[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp, 3), + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), + }; + + MockWrite writes_SPDYSSL[] = { + MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), + CreateMockWrite(*req, 2), + }; + MockRead reads_SPDYSSL[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp, 3), + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), + }; + + MockWrite writes_SPDYNOSSL[] = { + CreateMockWrite(*req, 0), + }; + + MockRead reads_SPDYNOSSL[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body.get(), 2), + MockRead(ASYNC, 0, 0, 3), + }; + + scoped_ptr<OrderedSocketData> data; + switch(GetParam().ssl_type) { + case SPDYNOSSL: + data.reset(new OrderedSocketData(reads_SPDYNOSSL, + arraysize(reads_SPDYNOSSL), + writes_SPDYNOSSL, + arraysize(writes_SPDYNOSSL))); + break; + case SPDYSSL: + data.reset(new OrderedSocketData(reads_SPDYSSL, + arraysize(reads_SPDYSSL), + writes_SPDYSSL, + arraysize(writes_SPDYSSL))); + break; + case SPDYNPN: + data.reset(new OrderedSocketData(reads_SPDYNPN, + arraysize(reads_SPDYNPN), + writes_SPDYNPN, + arraysize(writes_SPDYNPN))); + break; + default: + NOTREACHED(); + } + + helper.AddData(data.get()); + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans, &response_data)); + EXPECT_EQ("hello!", response_data); + helper.VerifyDataConsumed(); +} + +// Test to make sure we can correctly connect through a proxy to www.google.com, +// if there already exists a direct spdy connection to www.google.com. See +// http://crbug.com/49874 +TEST_P(SpdyNetworkTransactionTest, DirectConnectProxyReconnect) { + // When setting up the first transaction, we store the SpdySessionPool so that + // we can use the same pool in the second transaction. + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + // Use a proxy service which returns a proxy fallback list from DIRECT to + // myproxy:70. For this test there will be no fallback, so it is equivalent + // to simply DIRECT. The reason for appending the second proxy is to verify + // that the session pool key used does is just "DIRECT". + helper.session_deps().reset(CreateSpdySessionDependencies( + GetParam(), + ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70"))); + helper.SetSession(make_scoped_refptr( + SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); + + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + helper.RunPreTestSetup(); + + // Construct and send a simple GET request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause + MockRead(ASYNC, 0, 5) // EOF + }; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + TransactionHelperResult out; + out.rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.rv = ReadTransaction(trans, &out.response_data); + EXPECT_EQ(OK, out.rv); + out.status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the SpdySession is still in the SpdySessionPool. + HostPortPair host_port_pair("www.google.com", helper.port()); + SpdySessionKey session_pool_key_direct( + host_port_pair, ProxyServer::Direct(), kPrivacyModeDisabled); + EXPECT_TRUE(HasSpdySession(spdy_session_pool, session_pool_key_direct)); + SpdySessionKey session_pool_key_proxy( + host_port_pair, + ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP), + kPrivacyModeDisabled); + EXPECT_FALSE(HasSpdySession(spdy_session_pool, session_pool_key_proxy)); + + // Set up data for the proxy connection. + const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; + scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyGet( + "http://www.google.com/foo.dat", false, 1, LOWEST)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + MockWrite writes_SPDYNPN[] = { + MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), + CreateMockWrite(*req2, 2), + }; + MockRead reads_SPDYNPN[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp2, 3), + CreateMockRead(*body2, 4), + MockRead(ASYNC, 0, 5) // EOF + }; + + MockWrite writes_SPDYNOSSL[] = { + CreateMockWrite(*req2, 0), + }; + MockRead reads_SPDYNOSSL[] = { + CreateMockRead(*resp2, 1), + CreateMockRead(*body2, 2), + MockRead(ASYNC, 0, 3) // EOF + }; + + MockWrite writes_SPDYSSL[] = { + MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), + CreateMockWrite(*req2, 2), + }; + MockRead reads_SPDYSSL[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp2, 3), + CreateMockRead(*body2, 4), + MockRead(ASYNC, 0, 0, 5), + }; + + scoped_ptr<OrderedSocketData> data_proxy; + switch(GetParam().ssl_type) { + case SPDYNPN: + data_proxy.reset(new OrderedSocketData(reads_SPDYNPN, + arraysize(reads_SPDYNPN), + writes_SPDYNPN, + arraysize(writes_SPDYNPN))); + break; + case SPDYNOSSL: + data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL, + arraysize(reads_SPDYNOSSL), + writes_SPDYNOSSL, + arraysize(writes_SPDYNOSSL))); + break; + case SPDYSSL: + data_proxy.reset(new OrderedSocketData(reads_SPDYSSL, + arraysize(reads_SPDYSSL), + writes_SPDYSSL, + arraysize(writes_SPDYSSL))); + break; + default: + NOTREACHED(); + } + + // Create another request to www.google.com, but this time through a proxy. + HttpRequestInfo request_proxy; + request_proxy.method = "GET"; + request_proxy.url = GURL("http://www.google.com/foo.dat"); + request_proxy.load_flags = 0; + scoped_ptr<SpdySessionDependencies> ssd_proxy( + CreateSpdySessionDependencies(GetParam())); + // Ensure that this transaction uses the same SpdySessionPool. + scoped_refptr<HttpNetworkSession> session_proxy( + SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get())); + NormalSpdyTransactionHelper helper_proxy(request_proxy, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + HttpNetworkSessionPeer session_peer(session_proxy); + scoped_ptr<net::ProxyService> proxy_service( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); + session_peer.SetProxyService(proxy_service.get()); + helper_proxy.session_deps().swap(ssd_proxy); + helper_proxy.SetSession(session_proxy); + helper_proxy.RunPreTestSetup(); + helper_proxy.AddData(data_proxy.get()); + + HttpNetworkTransaction* trans_proxy = helper_proxy.trans(); + TestCompletionCallback callback_proxy; + int rv = trans_proxy->Start( + &request_proxy, callback_proxy.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback_proxy.WaitForResult(); + EXPECT_EQ(0, rv); + + HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo(); + EXPECT_TRUE(response_proxy.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data)); + EXPECT_EQ("hello!", response_data); + + data.CompleteRead(); + helper_proxy.VerifyDataConsumed(); +} + +// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction +// on a new connection, if the connection was previously known to be good. +// This can happen when a server reboots without saying goodbye, or when +// we're behind a NAT that masked the RST. +TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) { + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, ERR_IO_PENDING), + MockRead(ASYNC, ERR_CONNECTION_RESET), + }; + + MockRead reads2[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + // This test has a couple of variants. + enum { + // Induce the RST while waiting for our transaction to send. + VARIANT_RST_DURING_SEND_COMPLETION, + // Induce the RST while waiting for our transaction to read. + // In this case, the send completed - everything copied into the SNDBUF. + VARIANT_RST_DURING_READ_COMPLETION + }; + + for (int variant = VARIANT_RST_DURING_SEND_COMPLETION; + variant <= VARIANT_RST_DURING_READ_COMPLETION; + ++variant) { + DelayedSocketData data1(1, reads, arraysize(reads), NULL, 0); + + DelayedSocketData data2(1, reads2, arraysize(reads2), NULL, 0); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.AddData(&data1); + helper.AddData(&data2); + helper.RunPreTestSetup(); + + for (int i = 0; i < 2; ++i) { + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + + TestCompletionCallback callback; + int rv = trans->Start( + &helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // On the second transaction, we trigger the RST. + if (i == 1) { + if (variant == VARIANT_RST_DURING_READ_COMPLETION) { + // Writes to the socket complete asynchronously on SPDY by running + // through the message loop. Complete the write here. + base::RunLoop().RunUntilIdle(); + } + + // Now schedule the ERR_CONNECTION_RESET. + EXPECT_EQ(3u, data1.read_index()); + data1.CompleteRead(); + EXPECT_EQ(4u, data1.read_index()); + } + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_EQ("hello!", response_data); + } + + helper.VerifyDataConsumed(); + } +} + +// Test that turning SPDY on and off works properly. +TEST_P(SpdyNetworkTransactionTest, SpdyOnOffToggle) { + net::HttpStreamFactory::set_spdy_enabled(true); + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + net::HttpStreamFactory::set_spdy_enabled(false); + MockRead http_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello from http"), + MockRead(SYNCHRONOUS, OK), + }; + DelayedSocketData data2(1, http_reads, arraysize(http_reads), NULL, 0); + NormalSpdyTransactionHelper helper2(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper2.SetSpdyDisabled(); + helper2.RunToCompletion(&data2); + TransactionHelperResult out2 = helper2.output(); + EXPECT_EQ(OK, out2.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line); + EXPECT_EQ("hello from http", out2.response_data); + + net::HttpStreamFactory::set_spdy_enabled(true); +} + +// Tests that Basic authentication works over SPDY +TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { + net::HttpStreamFactory::set_spdy_enabled(true); + + // The first request will be a bare GET, the second request will be a + // GET with an Authorization header. + scoped_ptr<SpdyFrame> req_get( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + const char* const kExtraAuthorizationHeaders[] = { + "authorization", "Basic Zm9vOmJhcg==" + }; + scoped_ptr<SpdyFrame> req_get_authorization( + spdy_util_.ConstructSpdyGet(kExtraAuthorizationHeaders, + arraysize(kExtraAuthorizationHeaders) / 2, + false, 3, LOWEST, true)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req_get, 1), + CreateMockWrite(*req_get_authorization, 4), + }; + + // The first response is a 401 authentication challenge, and the second + // response will be a 200 response since the second request includes a valid + // Authorization header. + const char* const kExtraAuthenticationHeaders[] = { + "www-authenticate", + "Basic realm=\"MyRealm\"" + }; + scoped_ptr<SpdyFrame> resp_authentication( + spdy_util_.ConstructSpdySynReplyError( + "401 Authentication Required", + kExtraAuthenticationHeaders, + arraysize(kExtraAuthenticationHeaders) / 2, + 1)); + scoped_ptr<SpdyFrame> body_authentication( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> resp_data( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body_data(spdy_util_.ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp_authentication, 2), + CreateMockRead(*body_authentication, 3), + CreateMockRead(*resp_data, 5), + CreateMockRead(*body_data, 6), + MockRead(ASYNC, 0, 7), + }; + + OrderedSocketData data(spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes)); + HttpRequestInfo request(CreateGetRequest()); + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + net_log, GetParam(), NULL); + + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + const int rv_start = trans->Start(&request, callback.callback(), net_log); + EXPECT_EQ(ERR_IO_PENDING, rv_start); + const int rv_start_complete = callback.WaitForResult(); + EXPECT_EQ(OK, rv_start_complete); + + // Make sure the response has an auth challenge. + const HttpResponseInfo* const response_start = trans->GetResponseInfo(); + ASSERT_TRUE(response_start != NULL); + ASSERT_TRUE(response_start->headers.get() != NULL); + EXPECT_EQ(401, response_start->headers->response_code()); + EXPECT_TRUE(response_start->was_fetched_via_spdy); + AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get(); + ASSERT_TRUE(auth_challenge != NULL); + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("basic", auth_challenge->scheme); + EXPECT_EQ("MyRealm", auth_challenge->realm); + + // Restart with a username/password. + AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); + TestCompletionCallback callback_restart; + const int rv_restart = trans->RestartWithAuth( + credentials, callback_restart.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv_restart); + const int rv_restart_complete = callback_restart.WaitForResult(); + EXPECT_EQ(OK, rv_restart_complete); + // TODO(cbentzel): This is actually the same response object as before, but + // data has changed. + const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); + ASSERT_TRUE(response_restart != NULL); + ASSERT_TRUE(response_restart->headers.get() != NULL); + EXPECT_EQ(200, response_restart->headers->response_code()); + EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) { + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); + spdy_util_.AddUrlToHeaderBlock( + "http://www.google.com/foo.dat", initial_headers.get()); + scoped_ptr<SpdyFrame> stream2_syn( + spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), + false, + 2, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + 1)); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)["hello"] = "bye"; + (*late_headers)[spdy_util_.GetStatusKey()] = "200"; + (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> stream2_headers( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_headers, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + CreateMockRead(*stream2_body, 5), + MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + RunServerPushTest(&data, + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) { + // We push a stream and attempt to claim it before the headers come down. + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); + spdy_util_.AddUrlToHeaderBlock( + "http://www.google.com/foo.dat", initial_headers.get()); + scoped_ptr<SpdyFrame> stream2_syn( + spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), + false, + 2, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + 1)); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)["hello"] = "bye"; + (*late_headers)[spdy_util_.GetStatusKey()] = "200"; + (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> stream2_headers( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers, 4), + CreateMockRead(*stream2_body, 5), + MockRead(ASYNC, 0, 6), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.AddDeterministicData(&data); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // and the body of the primary stream, but before we've received the HEADERS + // for the pushed stream. + data.SetStop(3); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data.Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data.RunFor(3); + base::RunLoop().RunUntilIdle(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), &data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, &data, &result); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + // Read the final EOF (which will close the session) + data.RunFor(1); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) { + // We push a stream and attempt to claim it before the headers come down. + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); + spdy_util_.AddUrlToHeaderBlock( + "http://www.google.com/foo.dat", initial_headers.get()); + scoped_ptr<SpdyFrame> stream2_syn( + spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), + false, + 2, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + 1)); + + scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock()); + (*middle_headers)["hello"] = "bye"; + scoped_ptr<SpdyFrame> stream2_headers1( + spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)[spdy_util_.GetStatusKey()] = "200"; + (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> stream2_headers2( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers1, 4), + CreateMockRead(*stream2_headers2, 5), + CreateMockRead(*stream2_body, 6), + MockRead(ASYNC, 0, 7), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.AddDeterministicData(&data); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // the first HEADERS frame, and the body of the primary stream, but before + // we've received the final HEADERS for the pushed stream. + data.SetStop(4); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data.Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data.RunFor(3); + base::RunLoop().RunUntilIdle(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), &data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, &data, &result); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(expected_push_result, result2); + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + // Verify we got all the headers + if (spdy_util_.spdy_version() < SPDY3) { + EXPECT_TRUE(response2.headers->HasHeaderValue( + "url", + "http://www.google.com/foo.dat")); + } else { + EXPECT_TRUE(response2.headers->HasHeaderValue( + "scheme", "http")); + EXPECT_TRUE(response2.headers->HasHeaderValue( + "host", "www.google.com")); + EXPECT_TRUE(response2.headers->HasHeaderValue( + "path", "/foo.dat")); + } + EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye")); + EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200")); + EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); + + // Read the final EOF (which will close the session) + data.RunFor(1); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushWithNoStatusHeaderFrames) { + // We push a stream and attempt to claim it before the headers come down. + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); + spdy_util_.AddUrlToHeaderBlock( + "http://www.google.com/foo.dat", initial_headers.get()); + scoped_ptr<SpdyFrame> stream2_syn( + spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), + false, + 2, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + 1)); + + scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock()); + (*middle_headers)["hello"] = "bye"; + scoped_ptr<SpdyFrame> stream2_headers1( + spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers1, 4), + CreateMockRead(*stream2_body, 5), + MockRead(ASYNC, 0, 6), // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.AddDeterministicData(&data); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // the first HEADERS frame, and the body of the primary stream, but before + // we've received the final HEADERS for the pushed stream. + data.SetStop(4); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data.Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data.RunFor(2); + base::RunLoop().RunUntilIdle(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), &data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, &data, &result); + EXPECT_EQ("hello!", result); + + // Verify that we haven't received any push data. + EXPECT_EQ("", result2); + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + HttpResponseInfo response = *trans->GetResponseInfo(); + ASSERT_TRUE(trans2->GetResponseInfo() == NULL); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Read the final EOF (which will close the session). + data.RunFor(1); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); + (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK"; + (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> stream1_reply( + spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), + false, + 1, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)["hello"] = "bye"; + scoped_ptr<SpdyFrame> stream1_headers( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 1, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) { + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*rst), + }; + + scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); + (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK"; + (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; + scoped_ptr<SpdyFrame> stream1_reply( + spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), + false, + 1, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + 0)); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)["hello"] = "bye"; + scoped_ptr<SpdyFrame> stream1_headers( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 1, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, false)); + scoped_ptr<SpdyFrame> stream1_body2( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.RunToCompletion(&data); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) { + // In this test we want to verify that we can't accidentally push content + // which can't be pushed by this content server. + // This test assumes that: + // - if we're requesting http://www.foo.com/barbaz + // - the browser has made a connection to "www.foo.com". + + // A list of the URL to fetch, followed by the URL being pushed. + static const char* const kTestCases[] = { + "http://www.google.com/foo.html", + "http://www.google.com:81/foo.js", // Bad port + + "http://www.google.com/foo.html", + "https://www.google.com/foo.js", // Bad protocol + + "http://www.google.com/foo.html", + "ftp://www.google.com/foo.js", // Invalid Protocol + + "http://www.google.com/foo.html", + "http://blat.www.google.com/foo.js", // Cross subdomain + + "http://www.google.com/foo.html", + "http://www.foo.com/foo.js", // Cross domain + }; + + for (size_t index = 0; index < arraysize(kTestCases); index += 2) { + const char* url_to_fetch = kTestCases[index]; + const char* url_to_push = kTestCases[index + 1]; + + scoped_ptr<SpdyFrame> stream1_syn( + spdy_util_.ConstructSpdyGet(url_to_fetch, false, 1, LOWEST)); + scoped_ptr<SpdyFrame> stream1_body( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> push_rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*push_rst, 4), + }; + + scoped_ptr<SpdyFrame> + stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> + stream2_syn(spdy_util_.ConstructSpdyPush(NULL, + 0, + 2, + 1, + url_to_push)); + const char kPushedData[] = "pushed"; + scoped_ptr<SpdyFrame> stream2_body( + spdy_util_.ConstructSpdyBodyFrame( + 2, kPushedData, strlen(kPushedData), true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL)); + + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + CreateMockRead(*stream2_body, 6), + MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(url_to_fetch); + request.load_flags = 0; + + // Enable cross-origin push. Since we are not using a proxy, this should + // not actually enable cross-origin SPDY push. + scoped_ptr<SpdySessionDependencies> session_deps( + CreateSpdySessionDependencies(GetParam())); + session_deps->trusted_spdy_proxy = "123.45.67.89:8080"; + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), + session_deps.release()); + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Read the response body. + std::string result; + ReadResult(trans, &data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + } +} + +TEST_P(SpdyNetworkTransactionTest, RetryAfterRefused) { + // Construct the request. + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*req2, 3), + }; + + scoped_ptr<SpdyFrame> refused( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_REFUSED_STREAM)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(3, true)); + MockRead reads[] = { + CreateMockRead(*refused, 2), + CreateMockRead(*resp, 4), + CreateMockRead(*body, 5), + MockRead(ASYNC, 0, 6) // EOF + }; + + OrderedSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + + helper.RunPreTestSetup(); + helper.AddData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data.at_read_eof()) << "Read count: " + << data.read_count() + << " Read index: " + << data.read_index(); + EXPECT_TRUE(data.at_write_eof()) << "Write count: " + << data.write_count() + << " Write index: " + << data.write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, OutOfOrderSynStream) { + // This first request will start to establish the SpdySession. + // Then we will start the second (MEDIUM priority) and then third + // (HIGHEST priority) request in such a way that the third will actually + // start before the second, causing the second to be numbered differently + // than the order they were created. + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, HIGHEST, true)); + scoped_ptr<SpdyFrame> req3( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 3), + CreateMockWrite(*req3, 4), + }; + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, true)); + MockRead reads[] = { + CreateMockRead(*resp1, 1), + CreateMockRead(*body1, 2), + CreateMockRead(*resp2, 5), + CreateMockRead(*body2, 6), + CreateMockRead(*resp3, 7), + CreateMockRead(*body3, 8), + MockRead(ASYNC, 0, 9) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), LOWEST, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + + // Start the first transaction to set up the SpdySession + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + HttpRequestInfo info1 = CreateGetRequest(); + int rv = trans->Start(&info1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Run the message loop, but do not allow the write to complete. + // This leaves the SpdySession with a write pending, which prevents + // SpdySession from attempting subsequent writes until this write completes. + base::RunLoop().RunUntilIdle(); + + // Now, start both new transactions + HttpRequestInfo info2 = CreateGetRequest(); + TestCompletionCallback callback2; + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(MEDIUM, helper.session().get())); + rv = trans2->Start(&info2, callback2.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + base::RunLoop().RunUntilIdle(); + + HttpRequestInfo info3 = CreateGetRequest(); + TestCompletionCallback callback3; + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(HIGHEST, helper.session().get())); + rv = trans3->Start(&info3, callback3.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + base::RunLoop().RunUntilIdle(); + + // We now have two SYN_STREAM frames queued up which will be + // dequeued only once the first write completes, which we + // now allow to happen. + data.RunFor(2); + EXPECT_EQ(OK, callback.WaitForResult()); + + // And now we can allow everything else to run to completion. + data.SetStop(10); + data.Run(); + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ(OK, callback3.WaitForResult()); + + helper.VerifyDataConsumed(); +} + +// The tests below are only for SPDY/3 and above. + +// Test that sent data frames and received WINDOW_UPDATE frames change +// the send_window_size_ correctly. + +// WINDOW_UPDATE is different than most other frames in that it can arrive +// while the client is still sending the request body. In order to enforce +// this scenario, we feed a couple of dummy frames and give a delay of 0 to +// socket data provider, so that initial read that is done as soon as the +// stream is created, succeeds and schedules another read. This way reads +// and writes are interleaved; after doing a full frame write, SpdyStream +// will break out of DoLoop and will read and process a WINDOW_UPDATE. +// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away +// since request has not been completely written, therefore we feed +// enough number of WINDOW_UPDATEs to finish the first read and cause a +// write, leading to a complete write of request body; after that we send +// a reply with a body, to cause a graceful shutdown. + +// TODO(agayev): develop a socket data provider where both, reads and +// writes are ordered so that writing tests like these are easy and rewrite +// all these tests using it. Right now we are working around the +// limitations as described above and it's not deterministic, tests may +// fail under specific circumstances. +TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) { + if (GetParam().protocol < kProtoSPDY3) + return; + + static int kFrameCount = 2; + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body( + spdy_util_.ConstructSpdyBodyFrame( + 1, content->c_str(), content->size(), false)); + scoped_ptr<SpdyFrame> body_end( + spdy_util_.ConstructSpdyBodyFrame( + 1, content->c_str(), content->size(), true)); + + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*body, 1), + CreateMockWrite(*body_end, 2), + }; + + static const int32 kDeltaWindowSize = 0xff; + static const int kDeltaCount = 4; + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<SpdyFrame> window_update_dummy( + spdy_util_.ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update_dummy, 3), + CreateMockRead(*window_update_dummy, 4), + CreateMockRead(*window_update_dummy, 5), + CreateMockRead(*window_update, 6), // Four updates, therefore window + CreateMockRead(*window_update, 7), // size should increase by + CreateMockRead(*window_update, 8), // kDeltaWindowSize * 4 + CreateMockRead(*window_update, 9), + CreateMockRead(*resp, 10), + CreateMockRead(*body_end, 11), + MockRead(ASYNC, 0, 0, 12) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + ScopedVector<UploadElementReader> element_readers; + for (int i = 0; i < kFrameCount; ++i) { + element_readers.push_back( + new UploadBytesElementReader(content->c_str(), content->size())); + } + UploadDataStream upload_data_stream(&element_readers, 0); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL(kDefaultURL); + request.upload_data_stream = &upload_data_stream; + + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.AddDeterministicData(&data); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + + data.RunFor(11); + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize) + + kDeltaWindowSize * kDeltaCount - + kMaxSpdyFrameChunkSize * kFrameCount, + stream->stream()->send_window_size()); + + data.RunFor(1); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + helper.VerifyDataConsumed(); +} + +// Test that received data frames and sent WINDOW_UPDATE frames change +// the recv_window_size_ correctly. +TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) { + if (GetParam().protocol < kProtoSPDY3) + return; + + // Set the data in the body frame large enough to trigger sending a + // WINDOW_UPDATE by the stream. + const std::string body_data(kSpdyStreamInitialWindowSize / 2 + 1, 'x'); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> session_window_update( + spdy_util_.ConstructSpdyWindowUpdate(0, body_data.size())); + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate(1, body_data.size())); + + std::vector<MockWrite> writes; + writes.push_back(CreateMockWrite(*req)); + if (GetParam().protocol >= kProtoSPDY31) + writes.push_back(CreateMockWrite(*session_window_update)); + writes.push_back(CreateMockWrite(*window_update)); + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body_no_fin( + spdy_util_.ConstructSpdyBodyFrame( + 1, body_data.data(), body_data.size(), false)); + scoped_ptr<SpdyFrame> body_fin( + spdy_util_.ConstructSpdyBodyFrame(1, NULL, 0, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body_no_fin), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause + CreateMockRead(*body_fin), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause + MockRead(ASYNC, 0, 0) // EOF + }; + + DelayedSocketData data(1, reads, arraysize(reads), + vector_as_array(&writes), writes.size()); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.AddData(&data); + helper.RunPreTestSetup(); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SpdyHttpStream* stream = + static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + + EXPECT_EQ( + static_cast<int>(kSpdyStreamInitialWindowSize - body_data.size()), + stream->stream()->recv_window_size()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + + // Issue a read which will cause a WINDOW_UPDATE to be sent and window + // size increased to default. + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(body_data.size())); + rv = trans->Read(buf.get(), body_data.size(), CompletionCallback()); + EXPECT_EQ(static_cast<int>(body_data.size()), rv); + std::string content(buf->data(), buf->data() + body_data.size()); + EXPECT_EQ(body_data, content); + + // Schedule the reading of empty data frame with FIN + data.CompleteRead(); + + // Force write of WINDOW_UPDATE which was scheduled during the above + // read. + base::RunLoop().RunUntilIdle(); + + // Read EOF. + data.CompleteRead(); + + helper.VerifyDataConsumed(); +} + +// Test that WINDOW_UPDATE frame causing overflow is handled correctly. +TEST_P(SpdyNetworkTransactionTest, WindowUpdateOverflow) { + if (GetParam().protocol < kProtoSPDY3) + return; + + // Number of full frames we hope to write (but will not, used to + // set content-length header correctly) + static int kFrameCount = 3; + + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body( + spdy_util_.ConstructSpdyBodyFrame( + 1, content->c_str(), content->size(), false)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR)); + + // We're not going to write a data frame with FIN, we'll receive a bad + // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame. + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*body, 2), + CreateMockWrite(*rst, 3), + }; + + static const int32 kDeltaWindowSize = 0x7fffffff; // cause an overflow + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + MockRead reads[] = { + CreateMockRead(*window_update, 1), + MockRead(ASYNC, 0, 4) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + + ScopedVector<UploadElementReader> element_readers; + for (int i = 0; i < kFrameCount; ++i) { + element_readers.push_back( + new UploadBytesElementReader(content->c_str(), content->size())); + } + UploadDataStream upload_data_stream(&element_readers, 0); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_data_stream; + + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, rv); + + data.RunFor(5); + ASSERT_TRUE(callback.have_result()); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, callback.WaitForResult()); + helper.VerifyDataConsumed(); +} + +// Test that after hitting a send window size of 0, the write process +// stalls and upon receiving WINDOW_UPDATE frame write resumes. + +// This test constructs a POST request followed by enough data frames +// containing 'a' that would make the window size 0, followed by another +// data frame containing default content (which is "hello!") and this frame +// also contains a FIN flag. DelayedSocketData is used to enforce all +// writes go through before a read could happen. However, the last frame +// ("hello!") is not supposed to go through since by the time its turn +// arrives, window size is 0. At this point MessageLoop::Run() called via +// callback would block. Therefore we call MessageLoop::RunUntilIdle() +// which returns after performing all possible writes. We use DCHECKS to +// ensure that last data frame is still there and stream has stalled. +// After that, next read is artifically enforced, which causes a +// WINDOW_UPDATE to be read and I/O process resumes. +TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { + if (GetParam().protocol < kProtoSPDY3) + return; + + // Number of frames we need to send to zero out the window size: data + // frames plus SYN_STREAM plus the last data frame; also we need another + // data frame that we will send once the WINDOW_UPDATE is received, + // therefore +3. + size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; + + // Calculate last frame's size; 0 size data frame is legal. + size_t last_frame_size = + kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; + + // Construct content for a data frame of maximum size. + std::string content(kMaxSpdyFrameChunkSize, 'a'); + + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, + LOWEST, NULL, 0)); + + // Full frames. + scoped_ptr<SpdyFrame> body1( + spdy_util_.ConstructSpdyBodyFrame( + 1, content.c_str(), content.size(), false)); + + // Last frame to zero out the window size. + scoped_ptr<SpdyFrame> body2( + spdy_util_.ConstructSpdyBodyFrame( + 1, content.c_str(), last_frame_size, false)); + + // Data frame to be sent once WINDOW_UPDATE frame is received. + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + // Fill in mock writes. + scoped_ptr<MockWrite[]> writes(new MockWrite[num_writes]); + size_t i = 0; + writes[i] = CreateMockWrite(*req); + for (i = 1; i < num_writes - 2; i++) + writes[i] = CreateMockWrite(*body1); + writes[i++] = CreateMockWrite(*body2); + writes[i] = CreateMockWrite(*body3); + + // Construct read frame, give enough space to upload the rest of the + // data. + scoped_ptr<SpdyFrame> session_window_update( + spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize)); + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize)); + scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*session_window_update), + CreateMockRead(*session_window_update), + CreateMockRead(*window_update), + CreateMockRead(*window_update), + CreateMockRead(*reply), + CreateMockRead(*body2), + CreateMockRead(*body3), + MockRead(ASYNC, 0, 0) // EOF + }; + + // Skip the session window updates unless we're using SPDY/3.1 and + // above. + size_t read_offset = (GetParam().protocol >= kProtoSPDY31) ? 0 : 2; + size_t num_reads = arraysize(reads) - read_offset; + + // Force all writes to happen before any read, last write will not + // actually queue a frame, due to window size being 0. + DelayedSocketData data(num_writes, reads + read_offset, num_reads, + writes.get(), num_writes); + + ScopedVector<UploadElementReader> element_readers; + std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); + upload_data_string.append(kUploadData, kUploadDataSize); + element_readers.push_back(new UploadBytesElementReader( + upload_data_string.c_str(), upload_data_string.size())); + UploadDataStream upload_data_stream(&element_readers, 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_data_stream; + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.AddData(&data); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + base::RunLoop().RunUntilIdle(); // Write as much as we can. + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(0, stream->stream()->send_window_size()); + // All the body data should have been read. + // TODO(satorux): This is because of the weirdness in reading the request + // body in OnSendBodyComplete(). See crbug.com/113107. + EXPECT_TRUE(upload_data_stream.IsEOF()); + // But the body is not yet fully sent (kUploadData is not yet sent) + // since we're send-stalled. + EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); + + data.ForceNextRead(); // Read in WINDOW_UPDATE frame. + rv = callback.WaitForResult(); + helper.VerifyDataConsumed(); +} + +// Test we correctly handle the case where the SETTINGS frame results in +// unstalling the send window. +TEST_P(SpdyNetworkTransactionTest, FlowControlStallResumeAfterSettings) { + if (GetParam().protocol < kProtoSPDY3) + return; + + // Number of frames we need to send to zero out the window size: data + // frames plus SYN_STREAM plus the last data frame; also we need another + // data frame that we will send once the SETTING is received, therefore +3. + size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; + + // Calculate last frame's size; 0 size data frame is legal. + size_t last_frame_size = + kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; + + // Construct content for a data frame of maximum size. + std::string content(kMaxSpdyFrameChunkSize, 'a'); + + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, + LOWEST, NULL, 0)); + + // Full frames. + scoped_ptr<SpdyFrame> body1( + spdy_util_.ConstructSpdyBodyFrame( + 1, content.c_str(), content.size(), false)); + + // Last frame to zero out the window size. + scoped_ptr<SpdyFrame> body2( + spdy_util_.ConstructSpdyBodyFrame( + 1, content.c_str(), last_frame_size, false)); + + // Data frame to be sent once SETTINGS frame is received. + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + // Fill in mock reads/writes. + std::vector<MockRead> reads; + std::vector<MockWrite> writes; + size_t i = 0; + writes.push_back(CreateMockWrite(*req, i++)); + while (i < num_writes - 2) + writes.push_back(CreateMockWrite(*body1, i++)); + writes.push_back(CreateMockWrite(*body2, i++)); + + // Construct read frame for SETTINGS that gives enough space to upload the + // rest of the data. + SettingsMap settings; + settings[SETTINGS_INITIAL_WINDOW_SIZE] = + SettingsFlagsAndValue( + SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize * 2); + scoped_ptr<SpdyFrame> settings_frame_large( + spdy_util_.ConstructSpdySettings(settings)); + + reads.push_back(CreateMockRead(*settings_frame_large, i++)); + + scoped_ptr<SpdyFrame> session_window_update( + spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize)); + if (GetParam().protocol >= kProtoSPDY31) + reads.push_back(CreateMockRead(*session_window_update, i++)); + + writes.push_back(CreateMockWrite(*body3, i++)); + + scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + reads.push_back(CreateMockRead(*reply, i++)); + reads.push_back(CreateMockRead(*body2, i++)); + reads.push_back(CreateMockRead(*body3, i++)); + reads.push_back(MockRead(ASYNC, 0, i++)); // EOF + + // Force all writes to happen before any read, last write will not + // actually queue a frame, due to window size being 0. + DeterministicSocketData data(vector_as_array(&reads), reads.size(), + vector_as_array(&writes), writes.size()); + + ScopedVector<UploadElementReader> element_readers; + std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); + upload_data_string.append(kUploadData, kUploadDataSize); + element_readers.push_back(new UploadBytesElementReader( + upload_data_string.c_str(), upload_data_string.size())); + UploadDataStream upload_data_stream(&element_readers, 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_data_stream; + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data.RunFor(num_writes - 1); // Write as much as we can. + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(0, stream->stream()->send_window_size()); + + // All the body data should have been read. + // TODO(satorux): This is because of the weirdness in reading the request + // body in OnSendBodyComplete(). See crbug.com/113107. + EXPECT_TRUE(upload_data_stream.IsEOF()); + // But the body is not yet fully sent (kUploadData is not yet sent) + // since we're send-stalled. + EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); + + data.RunFor(6); // Read in SETTINGS frame to unstall. + rv = callback.WaitForResult(); + helper.VerifyDataConsumed(); + // If stream is NULL, that means it was unstalled and closed. + EXPECT_TRUE(stream->stream() == NULL); +} + +// Test we correctly handle the case where the SETTINGS frame results in a +// negative send window size. +TEST_P(SpdyNetworkTransactionTest, FlowControlNegativeSendWindowSize) { + if (GetParam().protocol < kProtoSPDY3) + return; + + // Number of frames we need to send to zero out the window size: data + // frames plus SYN_STREAM plus the last data frame; also we need another + // data frame that we will send once the SETTING is received, therefore +3. + size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; + + // Calculate last frame's size; 0 size data frame is legal. + size_t last_frame_size = + kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; + + // Construct content for a data frame of maximum size. + std::string content(kMaxSpdyFrameChunkSize, 'a'); + + scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( + kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, + LOWEST, NULL, 0)); + + // Full frames. + scoped_ptr<SpdyFrame> body1( + spdy_util_.ConstructSpdyBodyFrame( + 1, content.c_str(), content.size(), false)); + + // Last frame to zero out the window size. + scoped_ptr<SpdyFrame> body2( + spdy_util_.ConstructSpdyBodyFrame( + 1, content.c_str(), last_frame_size, false)); + + // Data frame to be sent once SETTINGS frame is received. + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + // Fill in mock reads/writes. + std::vector<MockRead> reads; + std::vector<MockWrite> writes; + size_t i = 0; + writes.push_back(CreateMockWrite(*req, i++)); + while (i < num_writes - 2) + writes.push_back(CreateMockWrite(*body1, i++)); + writes.push_back(CreateMockWrite(*body2, i++)); + + // Construct read frame for SETTINGS that makes the send_window_size + // negative. + SettingsMap new_settings; + new_settings[SETTINGS_INITIAL_WINDOW_SIZE] = + SettingsFlagsAndValue( + SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize / 2); + scoped_ptr<SpdyFrame> settings_frame_small( + spdy_util_.ConstructSpdySettings(new_settings)); + // Construct read frames for WINDOW_UPDATE that makes the send_window_size + // positive. + scoped_ptr<SpdyFrame> session_window_update_init_size( + spdy_util_.ConstructSpdyWindowUpdate(0, kSpdyStreamInitialWindowSize)); + scoped_ptr<SpdyFrame> window_update_init_size( + spdy_util_.ConstructSpdyWindowUpdate(1, kSpdyStreamInitialWindowSize)); + + reads.push_back(CreateMockRead(*settings_frame_small, i++)); + + if (GetParam().protocol >= kProtoSPDY3) + reads.push_back(CreateMockRead(*session_window_update_init_size, i++)); + + reads.push_back(CreateMockRead(*window_update_init_size, i++)); + + writes.push_back(CreateMockWrite(*body3, i++)); + + scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + reads.push_back(CreateMockRead(*reply, i++)); + reads.push_back(CreateMockRead(*body2, i++)); + reads.push_back(CreateMockRead(*body3, i++)); + reads.push_back(MockRead(ASYNC, 0, i++)); // EOF + + // Force all writes to happen before any read, last write will not + // actually queue a frame, due to window size being 0. + DeterministicSocketData data(vector_as_array(&reads), reads.size(), + vector_as_array(&writes), writes.size()); + + ScopedVector<UploadElementReader> element_readers; + std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); + upload_data_string.append(kUploadData, kUploadDataSize); + element_readers.push_back(new UploadBytesElementReader( + upload_data_string.c_str(), upload_data_string.size())); + UploadDataStream upload_data_stream(&element_readers, 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data_stream = &upload_data_stream; + NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, + BoundNetLog(), GetParam(), NULL); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(&data); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data.RunFor(num_writes - 1); // Write as much as we can. + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(0, stream->stream()->send_window_size()); + + // All the body data should have been read. + // TODO(satorux): This is because of the weirdness in reading the request + // body in OnSendBodyComplete(). See crbug.com/113107. + EXPECT_TRUE(upload_data_stream.IsEOF()); + // But the body is not yet fully sent (kUploadData is not yet sent) + // since we're send-stalled. + EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); + + // Read in WINDOW_UPDATE or SETTINGS frame. + data.RunFor((GetParam().protocol >= kProtoSPDY31) ? 8 : 7); + rv = callback.WaitForResult(); + helper.VerifyDataConsumed(); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_priority_forest.h b/chromium/net/spdy/spdy_priority_forest.h new file mode 100644 index 00000000000..4f00f566caf --- /dev/null +++ b/chromium/net/spdy/spdy_priority_forest.h @@ -0,0 +1,527 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_PRIORITY_FOREST_H_ +#define NET_SPDY_SPDY_PRIORITY_FOREST_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/rand_util.h" + +namespace net { + +// This data structure implements the SPDY prioriziation data structures +// defined in this document: http://go/spdy4-prioritization +// +// Nodes can be added and removed, and dependencies between them defined. Each +// node can have at most one parent and at most one child (forming a list), but +// there can be multiple lists, with each list root having its own priority. +// Individual nodes can also be marked as ready to read/write, and then the +// whole structure can be queried to pick the next node to read/write out of +// those ready. +// +// The NodeId and Priority types must be POD that support comparison (most +// likely, they will be numbers). +template <typename NodeId, typename Priority> +class SpdyPriorityForest { + public: + SpdyPriorityForest(); + ~SpdyPriorityForest(); + + // Return the number of nodes currently in the forest. + int num_nodes() const; + + // Return true if the forest contains a node with the given ID. + bool NodeExists(NodeId node_id) const; + + // Add a new root node to the forest, with the given priority. Returns true + // on success, or false if the node_id already exists within the forest. + bool AddRootNode(NodeId node_id, Priority priority); + + // Add a new node to the forest, with the given parent. Returns true on + // success. Returns false and has no effect if the new node already exists, + // or if the parent doesn't exist, or if the parent already has a child. + bool AddNonRootNode(NodeId node_id, NodeId parent_id, bool unordered); + + // Remove an existing node from the forest. Returns true on success, or + // false if the node doesn't exist. + bool RemoveNode(NodeId node_id); + + // Get the priority of the given node. If the node doesn't exist, or is not + // a root node (and thus has no priority), returns Priority(). + Priority GetPriority(NodeId node_id) const; + + // Get the parent of the given node. If the node doesn't exist, or is a root + // node (and thus has no parent), returns NodeId(). + NodeId GetParent(NodeId node_id) const; + + // Determine if the given node is unordered with respect to its parent. If + // the node doesn't exist, or is a root node (and thus has no parent), + // returns false. + bool IsNodeUnordered(NodeId node_id) const; + + // Get the child of the given node. If the node doesn't exist, or has no + // child, returns NodeId(). + NodeId GetChild(NodeId node_id) const; + + // Set the priority of the given node. If the node was not already a root + // node, this makes it a root node. Returns true on success, or false if the + // node doesn't exist. + bool SetPriority(NodeId node_id, Priority priority); + + // Set the parent of the given node. If the node was a root node, this makes + // it no longer a root. Returns true on success. Returns false and has no + // effect if (1) the node and/or the parent doesn't exist, (2) the new parent + // already has a different child than the node, or (3) if the new parent is a + // descendant of the node (so this would have created a cycle). + bool SetParent(NodeId node_id, NodeId parent_id, bool unordered); + + // Check if a node is marked as ready to read. Returns false if the node + // doesn't exist. + bool IsMarkedReadyToRead(NodeId node_id) const; + // Mark the node as ready or not ready to read. Returns true on success, or + // false if the node doesn't exist. + bool MarkReadyToRead(NodeId node_id); + bool MarkNoLongerReadyToRead(NodeId node_id); + // Return the ID of the next node that we should read, or return NodeId() if + // no node in the forest is ready to read. + NodeId NextNodeToRead(); + + // Check if a node is marked as ready to write. Returns false if the node + // doesn't exist. + bool IsMarkedReadyToWrite(NodeId node_id) const; + // Mark the node as ready or not ready to write. Returns true on success, or + // false if the node doesn't exist. + bool MarkReadyToWrite(NodeId node_id); + bool MarkNoLongerReadyToWrite(NodeId node_id); + // Return the ID of the next node that we should write, or return NodeId() if + // no node in the forest is ready to write. + NodeId NextNodeToWrite(); + + // Return true if all internal invariants hold (useful for unit tests). + // Unless there are bugs, this should always return true. + bool ValidateInvariantsForTests() const; + + private: + enum NodeType { ROOT_NODE, NONROOT_ORDERED, NONROOT_UNORDERED }; + struct Node { + Node() : type(ROOT_NODE), flags(0), child() { + depends_on.priority = Priority(); + } + NodeType type; + unsigned int flags; // bitfield of flags + union { + Priority priority; // used for root nodes + NodeId parent_id; // used for non-root nodes + } depends_on; + NodeId child; // node ID of child (or NodeId() for no child) + }; + + typedef base::hash_map<NodeId, Node> NodeMap; + + // Constants for the Node.flags bitset: + // kReadToRead: set for nodes that are ready for reading + static const unsigned int kReadyToRead = (1 << 0); + // kReadToWrite: set for nodes that are ready for writing + static const unsigned int kReadyToWrite = (1 << 1); + + // Common code for IsMarkedReadyToRead and IsMarkedReadyToWrite. + bool IsMarked(NodeId node_id, unsigned int flag) const; + // Common code for MarkReadyToRead and MarkReadyToWrite. + bool Mark(NodeId node_id, unsigned int flag); + // Common code for MarkNoLongerReadyToRead and MarkNoLongerReadyToWrite. + bool Unmark(NodeId node_id, unsigned int flag); + // Common code for NextNodeToRead and NextNodeToWrite; + NodeId FirstMarkedNode(unsigned int flag); + // Get the given node, or return NULL if it doesn't exist. + const Node* FindNode(NodeId node_id) const; + + NodeMap all_nodes_; // maps from node IDs to Node objects + + DISALLOW_COPY_AND_ASSIGN(SpdyPriorityForest); +}; + +template <typename NodeId, typename Priority> +SpdyPriorityForest<NodeId, Priority>::SpdyPriorityForest() {} + +template <typename NodeId, typename Priority> +SpdyPriorityForest<NodeId, Priority>::~SpdyPriorityForest() {} + +template <typename NodeId, typename Priority> +int SpdyPriorityForest<NodeId, Priority>::num_nodes() const { + return all_nodes_.size(); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::NodeExists(NodeId node_id) const { + return all_nodes_.count(node_id) != 0; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::AddRootNode( + NodeId node_id, Priority priority) { + if (NodeExists(node_id)) { + return false; + } + Node* new_node = &all_nodes_[node_id]; + new_node->type = ROOT_NODE; + new_node->depends_on.priority = priority; + return true; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::AddNonRootNode( + NodeId node_id, NodeId parent_id, bool unordered) { + if (NodeExists(node_id) || !NodeExists(parent_id)) { + return false; + } + + Node* parent = &all_nodes_[parent_id]; + if (parent->child != NodeId()) { + return false; + } + + Node* new_node = &all_nodes_[node_id]; + new_node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED); + new_node->depends_on.parent_id = parent_id; + parent->child = node_id; + return true; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::RemoveNode(NodeId node_id) { + if (!NodeExists(node_id)) { + return false; + } + const Node& node = all_nodes_[node_id]; + + // If the node to be removed is not a root node, we need to change its + // parent's child ID. + if (node.type != ROOT_NODE) { + DCHECK(NodeExists(node.depends_on.parent_id)); + Node* parent = &all_nodes_[node.depends_on.parent_id]; + DCHECK_EQ(node_id, parent->child); + parent->child = node.child; + } + + // If the node has a child, we need to change the child's priority or parent. + if (node.child != NodeId()) { + DCHECK(NodeExists(node.child)); + Node* child = &all_nodes_[node.child]; + DCHECK_NE(ROOT_NODE, child->type); + DCHECK_EQ(node_id, child->depends_on.parent_id); + // Make the child's new depends_on be the node's depends_on (whether that + // be a priority or a parent node ID). + child->depends_on = node.depends_on; + // If the removed node was a root, its child is now a root. Otherwise, the + // child will be be unordered if and only if it was already unordered and + // the removed not is also not ordered. + if (node.type == ROOT_NODE) { + child->type = ROOT_NODE; + } else if (node.type == NONROOT_ORDERED) { + child->type = NONROOT_ORDERED; + } + } + + // Delete the node. + all_nodes_.erase(node_id); + return true; +} + +template <typename NodeId, typename Priority> +Priority SpdyPriorityForest<NodeId, Priority>::GetPriority( + NodeId node_id) const { + const Node* node = FindNode(node_id); + if (node != NULL && node->type == ROOT_NODE) { + return node->depends_on.priority; + } else { + return Priority(); + } +} + +template <typename NodeId, typename Priority> +NodeId SpdyPriorityForest<NodeId, Priority>::GetParent(NodeId node_id) const { + const Node* node = FindNode(node_id); + if (node != NULL && node->type != ROOT_NODE) { + return node->depends_on.parent_id; + } else { + return NodeId(); + } +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::IsNodeUnordered( + NodeId node_id) const { + const Node* node = FindNode(node_id); + return node != NULL && node->type == NONROOT_UNORDERED; +} + +template <typename NodeId, typename Priority> +NodeId SpdyPriorityForest<NodeId, Priority>::GetChild(NodeId node_id) const { + const Node* node = FindNode(node_id); + if (node != NULL) { + return node->child; + } else { + return NodeId(); + } +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::SetPriority( + NodeId node_id, Priority priority) { + if (!NodeExists(node_id)) { + return false; + } + + Node* node = &all_nodes_[node_id]; + // If this is not already a root node, we need to make it be a root node. + if (node->type != ROOT_NODE) { + DCHECK(NodeExists(node->depends_on.parent_id)); + Node* parent = &all_nodes_[node->depends_on.parent_id]; + parent->child = NodeId(); + node->type = ROOT_NODE; + } + + node->depends_on.priority = priority; + return true; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::SetParent( + NodeId node_id, NodeId parent_id, bool unordered) { + if (!NodeExists(node_id) || !NodeExists(parent_id)) { + return false; + } + + Node* node = &all_nodes_[node_id]; + Node* new_parent = &all_nodes_[parent_id]; + // If the new parent is already the node's parent, all we have to do is + // update the node type and we're done. + if (new_parent->child == node_id) { + node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED); + return true; + } + // Otherwise, if the new parent already has a child, we fail. + if (new_parent->child != NodeId()) { + return false; + } + + // Next, make sure we won't create a cycle. + if (node_id == parent_id) return false; + Node* last = node; + NodeId last_id = node_id; + while (last->child != NodeId()) { + if (last->child == parent_id) return false; + last_id = last->child; + DCHECK(NodeExists(last_id)); + last = &all_nodes_[last_id]; + } + + // If the node is not a root, we need clear its old parent's child field + // (unless the old parent is the same as the new parent). + if (node->type != ROOT_NODE) { + const NodeId old_parent_id = node->depends_on.parent_id; + DCHECK(NodeExists(old_parent_id)); + DCHECK(old_parent_id != parent_id); + Node* old_parent = &all_nodes_[old_parent_id]; + DCHECK_EQ(node_id, old_parent->child); + old_parent->child = NodeId(); + } + + // Make the change. + node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED); + node->depends_on.parent_id = parent_id; + new_parent->child = node_id; + return true; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::IsMarkedReadyToRead( + NodeId node_id) const { + return IsMarked(node_id, kReadyToRead); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::MarkReadyToRead(NodeId node_id) { + return Mark(node_id, kReadyToRead); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::MarkNoLongerReadyToRead( + NodeId node_id) { + return Unmark(node_id, kReadyToRead); +} + +template <typename NodeId, typename Priority> +NodeId SpdyPriorityForest<NodeId, Priority>::NextNodeToRead() { + return FirstMarkedNode(kReadyToRead); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::IsMarkedReadyToWrite( + NodeId node_id) const { + return IsMarked(node_id, kReadyToWrite); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::MarkReadyToWrite(NodeId node_id) { + return Mark(node_id, kReadyToWrite); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::MarkNoLongerReadyToWrite( + NodeId node_id) { + return Unmark(node_id, kReadyToWrite); +} + +template <typename NodeId, typename Priority> +NodeId SpdyPriorityForest<NodeId, Priority>::NextNodeToWrite() { + return FirstMarkedNode(kReadyToWrite); +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::IsMarked( + NodeId node_id, unsigned int flag) const { + const Node* node = FindNode(node_id); + return node != NULL && (node->flags & flag) != 0; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::Mark( + NodeId node_id, unsigned int flag) { + if (!NodeExists(node_id)) { + return false; + } + all_nodes_[node_id].flags |= flag; + return true; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::Unmark( + NodeId node_id, unsigned int flag) { + if (!NodeExists(node_id)) { + return false; + } + all_nodes_[node_id].flags &= ~flag; + return true; +} + +template <typename NodeId, typename Priority> +NodeId SpdyPriorityForest<NodeId, Priority>::FirstMarkedNode( + unsigned int flag) { + // TODO(mdsteele): This is an *incredibly* stupid brute force solution. + + // Get all root nodes that have at least one marked child. + uint64 total_weight = 0; + std::map<uint64, NodeId> roots; // maps cumulative weight to root node ID + for (typename NodeMap::const_iterator iter = all_nodes_.begin(); + iter != all_nodes_.end(); ++iter) { + const NodeId root_id = iter->first; + const Node& root = iter->second; + if (root.type == ROOT_NODE) { + // See if there is at least one marked node in this root's chain. + for (const Node* node = &root; ; node = &all_nodes_[node->child]) { + if ((node->flags & flag) != 0) { + total_weight += static_cast<uint64>(root.depends_on.priority); + roots[total_weight] = root_id; + break; + } + if (node->child == NodeId()) { + break; + } + DCHECK(NodeExists(node->child)); + } + } + } + + // If there are no ready nodes, then return NodeId(). + if (total_weight == 0) { + DCHECK(roots.empty()); + return NodeId(); + } else { + DCHECK(!roots.empty()); + } + + // Randomly select a tree to use. + typename std::map<uint64, NodeId>::const_iterator root_iter = + roots.upper_bound(base::RandGenerator(total_weight)); + DCHECK(root_iter != roots.end()); + const NodeId root_id = root_iter->second; + + // Find the first node in the chain that is ready. + NodeId node_id = root_id; + while (true) { + DCHECK(NodeExists(node_id)); + Node* node = &all_nodes_[node_id]; + if ((node->flags & flag) != 0) { + // There might be more nodes that are ready and that are linked to this + // one in an unordered chain. Find all of them, then pick one randomly. + std::vector<NodeId> group; + group.push_back(node_id); + for (Node* next = node; next->child != NodeId();) { + DCHECK(NodeExists(next->child)); + Node *child = &all_nodes_[next->child]; + DCHECK_NE(ROOT_NODE, child->type); + if (child->type != NONROOT_UNORDERED) { + break; + } + if ((child->flags & flag) != 0) { + group.push_back(next->child); + } + next = child; + } + return group[base::RandGenerator(group.size())]; + } + node_id = node->child; + } +} + +template <typename NodeId, typename Priority> +const typename SpdyPriorityForest<NodeId, Priority>::Node* +SpdyPriorityForest<NodeId, Priority>::FindNode(NodeId node_id) const { + typename NodeMap::const_iterator iter = all_nodes_.find(node_id); + if (iter == all_nodes_.end()) { + return NULL; + } + return &iter->second; +} + +template <typename NodeId, typename Priority> +bool SpdyPriorityForest<NodeId, Priority>::ValidateInvariantsForTests() const { + for (typename NodeMap::const_iterator iter = all_nodes_.begin(); + iter != all_nodes_.end(); ++iter) { + const NodeId node_id = iter->first; + const Node& node = iter->second; + if (node.type != ROOT_NODE && + (!NodeExists(node.depends_on.parent_id) || + GetChild(node.depends_on.parent_id) != node_id)) { + return false; + } + if (node.child != NodeId()) { + if (!NodeExists(node.child) || node_id != GetParent(node.child)) { + return false; + } + } + + NodeId child_id = node.child; + int count = 0; + while (child_id != NodeId()) { + if (count > num_nodes() || node_id == child_id) { + return false; + } + child_id = GetChild(child_id); + ++count; + } + } + return true; +} + +} // namespace net + +#endif // NET_SPDY_SPDY_PRIORITY_FOREST_H_ diff --git a/chromium/net/spdy/spdy_priority_forest_test.cc b/chromium/net/spdy/spdy_priority_forest_test.cc new file mode 100644 index 00000000000..58b21d469e3 --- /dev/null +++ b/chromium/net/spdy/spdy_priority_forest_test.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_priority_forest.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(SpdyPriorityForestTest, AddAndRemoveNodes) { + SpdyPriorityForest<uint32,int16> forest; + EXPECT_EQ(0, forest.num_nodes()); + EXPECT_FALSE(forest.NodeExists(1)); + + EXPECT_TRUE(forest.AddRootNode(1, 1000)); + EXPECT_EQ(1, forest.num_nodes()); + ASSERT_TRUE(forest.NodeExists(1)); + EXPECT_EQ(1000, forest.GetPriority(1)); + EXPECT_FALSE(forest.NodeExists(5)); + + EXPECT_TRUE(forest.AddRootNode(5, 50)); + EXPECT_FALSE(forest.AddRootNode(5, 500)); + EXPECT_EQ(2, forest.num_nodes()); + EXPECT_TRUE(forest.NodeExists(1)); + ASSERT_TRUE(forest.NodeExists(5)); + EXPECT_EQ(50, forest.GetPriority(5)); + EXPECT_FALSE(forest.NodeExists(13)); + + EXPECT_TRUE(forest.AddRootNode(13, 130)); + EXPECT_EQ(3, forest.num_nodes()); + EXPECT_TRUE(forest.NodeExists(1)); + EXPECT_TRUE(forest.NodeExists(5)); + ASSERT_TRUE(forest.NodeExists(13)); + EXPECT_EQ(130, forest.GetPriority(13)); + + EXPECT_TRUE(forest.RemoveNode(5)); + EXPECT_FALSE(forest.RemoveNode(5)); + EXPECT_EQ(2, forest.num_nodes()); + EXPECT_TRUE(forest.NodeExists(1)); + EXPECT_FALSE(forest.NodeExists(5)); + EXPECT_TRUE(forest.NodeExists(13)); + + // The parent node 19 doesn't exist, so this should fail: + EXPECT_FALSE(forest.AddNonRootNode(7, 19, false)); + // This should succed, creating node 7: + EXPECT_TRUE(forest.AddNonRootNode(7, 13, false)); + // Now node 7 already exists, so this should fail: + EXPECT_FALSE(forest.AddNonRootNode(7, 1, false)); + // Node 13 already has a child (7), so this should fail: + EXPECT_FALSE(forest.AddNonRootNode(17, 13, false)); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +TEST(SpdyPriorityForestTest, SetParent) { + SpdyPriorityForest<uint32,int16> forest; + forest.AddRootNode(1, 1000); + forest.AddNonRootNode(2, 1, false); + forest.AddNonRootNode(3, 2, false); + forest.AddNonRootNode(5, 3, false); + forest.AddNonRootNode(7, 5, false); + forest.AddNonRootNode(9, 7, false); + forest.AddRootNode(11, 2000); + forest.AddNonRootNode(13, 11, false); + // We can't set the parent of a nonexistent node, or set the parent of an + // existing node to a nonexistent node. + EXPECT_FALSE(forest.SetParent(99, 13, false)); + EXPECT_FALSE(forest.SetParent(5, 99, false)); + // We can't make a node a child a node that already has a child: + EXPECT_FALSE(forest.SetParent(13, 7, false)); + EXPECT_FALSE(forest.SetParent(3, 11, false)); + // These would create cycles: + EXPECT_FALSE(forest.SetParent(11, 13, false)); + EXPECT_FALSE(forest.SetParent(1, 9, false)); + EXPECT_FALSE(forest.SetParent(3, 9, false)); + // But this change is legit: + EXPECT_EQ(7u, forest.GetChild(5)); + EXPECT_TRUE(forest.SetParent(7, 13, false)); + EXPECT_EQ(0u, forest.GetChild(5)); + EXPECT_EQ(13u, forest.GetParent(7)); + EXPECT_EQ(7u, forest.GetChild(13)); + // So is this change (now that 9 is no longer a descendant of 1): + EXPECT_TRUE(forest.SetParent(1, 9, false)); + EXPECT_EQ(9u, forest.GetParent(1)); + EXPECT_EQ(1u, forest.GetChild(9)); + // We must allow setting the parent of a node to its same parent (even though + // that parent of course has a child already), so that we can change + // orderedness. + EXPECT_EQ(1u, forest.GetParent(2)); + EXPECT_EQ(2u, forest.GetChild(1)); + EXPECT_FALSE(forest.IsNodeUnordered(2)); + EXPECT_TRUE(forest.SetParent(2, 1, true)); + EXPECT_EQ(1u, forest.GetParent(2)); + EXPECT_EQ(2u, forest.GetChild(1)); + EXPECT_TRUE(forest.IsNodeUnordered(2)); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +TEST(SpdyPriorityForestTest, RemoveNodesFromMiddleOfChain) { + SpdyPriorityForest<uint32,int16> forest; + forest.AddRootNode(1, 1000); + forest.AddNonRootNode(2, 1, false); + forest.AddNonRootNode(3, 2, true); + forest.AddNonRootNode(5, 3, false); + forest.AddNonRootNode(7, 5, true); + forest.AddNonRootNode(9, 7, true); + + // Remove a node from the middle, with unordered links on both sides. The + // new merged link should also be unordered. + EXPECT_TRUE(forest.NodeExists(7)); + EXPECT_EQ(7u, forest.GetChild(5)); + EXPECT_EQ(7u, forest.GetParent(9)); + EXPECT_TRUE(forest.IsNodeUnordered(9)); + EXPECT_TRUE(forest.RemoveNode(7)); + EXPECT_FALSE(forest.NodeExists(7)); + EXPECT_EQ(9u, forest.GetChild(5)); + EXPECT_EQ(5u, forest.GetParent(9)); + EXPECT_TRUE(forest.IsNodeUnordered(9)); + + // Remove another node from the middle, with an unordered link on only one + // side. The new merged link should be ordered. + EXPECT_TRUE(forest.NodeExists(2)); + EXPECT_EQ(2u, forest.GetChild(1)); + EXPECT_EQ(2u, forest.GetParent(3)); + EXPECT_FALSE(forest.IsNodeUnordered(2)); + EXPECT_TRUE(forest.IsNodeUnordered(3)); + EXPECT_TRUE(forest.RemoveNode(2)); + EXPECT_FALSE(forest.NodeExists(2)); + EXPECT_EQ(3u, forest.GetChild(1)); + EXPECT_EQ(1u, forest.GetParent(3)); + EXPECT_FALSE(forest.IsNodeUnordered(3)); + + // Try removing the root. + EXPECT_TRUE(forest.NodeExists(1)); + EXPECT_EQ(0u, forest.GetParent(1)); + EXPECT_EQ(1000, forest.GetPriority(1)); + EXPECT_EQ(1u, forest.GetParent(3)); + EXPECT_EQ(0, forest.GetPriority(3)); + EXPECT_TRUE(forest.RemoveNode(1)); + EXPECT_FALSE(forest.NodeExists(1)); + EXPECT_EQ(0u, forest.GetParent(3)); + EXPECT_EQ(1000, forest.GetPriority(3)); + + // Now try removing the tail. + EXPECT_TRUE(forest.NodeExists(9)); + EXPECT_EQ(9u, forest.GetChild(5)); + EXPECT_TRUE(forest.RemoveNode(9)); + EXPECT_FALSE(forest.NodeExists(9)); + EXPECT_EQ(0u, forest.GetChild(5)); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +TEST(SpdyPriorityForestTest, MergeOrderedAndUnorderedLinks1) { + SpdyPriorityForest<uint32,int16> forest; + forest.AddRootNode(1, 1000); + forest.AddNonRootNode(2, 1, true); + forest.AddNonRootNode(3, 2, false); + + EXPECT_EQ(2u, forest.GetChild(1)); + EXPECT_EQ(3u, forest.GetChild(2)); + EXPECT_EQ(1u, forest.GetParent(2)); + EXPECT_EQ(2u, forest.GetParent(3)); + EXPECT_TRUE(forest.IsNodeUnordered(2)); + EXPECT_FALSE(forest.IsNodeUnordered(3)); + EXPECT_TRUE(forest.RemoveNode(2)); + EXPECT_FALSE(forest.NodeExists(2)); + EXPECT_EQ(3u, forest.GetChild(1)); + EXPECT_EQ(1u, forest.GetParent(3)); + EXPECT_FALSE(forest.IsNodeUnordered(3)); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +TEST(SpdyPriorityForestTest, MergeOrderedAndUnorderedLinks2) { + SpdyPriorityForest<uint32,int16> forest; + forest.AddRootNode(1, 1000); + forest.AddNonRootNode(2, 1, false); + forest.AddNonRootNode(3, 2, true); + + EXPECT_EQ(2u, forest.GetChild(1)); + EXPECT_EQ(3u, forest.GetChild(2)); + EXPECT_EQ(1u, forest.GetParent(2)); + EXPECT_EQ(2u, forest.GetParent(3)); + EXPECT_FALSE(forest.IsNodeUnordered(2)); + EXPECT_TRUE(forest.IsNodeUnordered(3)); + EXPECT_TRUE(forest.RemoveNode(2)); + EXPECT_FALSE(forest.NodeExists(2)); + EXPECT_EQ(3u, forest.GetChild(1)); + EXPECT_EQ(1u, forest.GetParent(3)); + EXPECT_FALSE(forest.IsNodeUnordered(3)); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +TEST(SpdyPriorityForestTest, WeightedSelectionOfForests) { + SpdyPriorityForest<uint32,int16> forest; + forest.AddRootNode(1, 10); + forest.AddRootNode(3, 20); + forest.AddRootNode(5, 70); + EXPECT_EQ(70, forest.GetPriority(5)); + EXPECT_TRUE(forest.SetPriority(5, 40)); + EXPECT_FALSE(forest.SetPriority(7, 80)); + EXPECT_EQ(40, forest.GetPriority(5)); + forest.AddNonRootNode(7, 3, false); + EXPECT_FALSE(forest.IsMarkedReadyToRead(1)); + EXPECT_TRUE(forest.MarkReadyToRead(1)); + EXPECT_TRUE(forest.IsMarkedReadyToRead(1)); + EXPECT_TRUE(forest.MarkReadyToRead(5)); + EXPECT_TRUE(forest.MarkReadyToRead(7)); + EXPECT_FALSE(forest.MarkReadyToRead(99)); + + int counts[8] = {0}; + for (int i = 0; i < 7000; ++i) { + const uint32 node_id = forest.NextNodeToRead(); + ASSERT_TRUE(node_id == 1 || node_id == 5 || node_id == 7) + << "node_id is " << node_id; + ++counts[node_id]; + } + + // In theory, these could fail even if the weighted random selection is + // implemented correctly, but it's very unlikely. + EXPECT_GE(counts[1], 800); EXPECT_LE(counts[1], 1200); + EXPECT_GE(counts[7], 1600); EXPECT_LE(counts[7], 2400); + EXPECT_GE(counts[5], 3200); EXPECT_LE(counts[5], 4800); + + // If we unmark all but one node, then we know for sure that that node will + // be selected next. + EXPECT_TRUE(forest.MarkNoLongerReadyToRead(1)); + EXPECT_TRUE(forest.MarkNoLongerReadyToRead(7)); + EXPECT_FALSE(forest.MarkNoLongerReadyToRead(99)); + EXPECT_EQ(5u, forest.NextNodeToRead()); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +TEST(SpdyPriorityForestTest, SelectionBetweenUnorderedNodes) { + SpdyPriorityForest<uint32,int16> forest; + forest.AddRootNode(1, 1000); + forest.AddNonRootNode(2, 1, false); + forest.AddNonRootNode(3, 2, true); + forest.AddNonRootNode(4, 3, true); + forest.AddNonRootNode(5, 4, true); + forest.AddNonRootNode(6, 5, true); + forest.AddNonRootNode(7, 6, false); + EXPECT_FALSE(forest.IsMarkedReadyToWrite(2)); + EXPECT_TRUE(forest.MarkReadyToWrite(2)); + EXPECT_TRUE(forest.IsMarkedReadyToWrite(2)); + EXPECT_TRUE(forest.MarkReadyToWrite(4)); + EXPECT_TRUE(forest.MarkReadyToWrite(6)); + EXPECT_TRUE(forest.MarkReadyToWrite(7)); + EXPECT_FALSE(forest.MarkReadyToWrite(99)); + + int counts[8] = {0}; + for (int i = 0; i < 6000; ++i) { + const uint32 node_id = forest.NextNodeToWrite(); + ASSERT_TRUE(node_id == 2 || node_id == 4 || node_id == 6) + << "node_id is " << node_id; + ++counts[node_id]; + } + + // In theory, these could fail even if the random selection is implemented + // correctly, but it's very unlikely. + EXPECT_GE(counts[2], 1600); EXPECT_LE(counts[2], 2400); + EXPECT_GE(counts[4], 1600); EXPECT_LE(counts[4], 2400); + EXPECT_GE(counts[6], 1600); EXPECT_LE(counts[6], 2400); + + // Once we unmark that group of nodes, the next node up should be node 7, + // which has an ordered dependency on said group. + EXPECT_TRUE(forest.MarkNoLongerReadyToWrite(2)); + EXPECT_TRUE(forest.MarkNoLongerReadyToWrite(4)); + EXPECT_TRUE(forest.MarkNoLongerReadyToWrite(6)); + EXPECT_FALSE(forest.MarkNoLongerReadyToWrite(99)); + EXPECT_EQ(7u, forest.NextNodeToWrite()); + + ASSERT_TRUE(forest.ValidateInvariantsForTests()); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_protocol.cc b/chromium/net/spdy/spdy_protocol.cc new file mode 100644 index 00000000000..39031ebd9f5 --- /dev/null +++ b/chromium/net/spdy/spdy_protocol.cc @@ -0,0 +1,82 @@ +// 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 "net/spdy/spdy_protocol.h" + +namespace net { + +SpdyFrameWithNameValueBlockIR::SpdyFrameWithNameValueBlockIR( + SpdyStreamId stream_id) : SpdyFrameWithFinIR(stream_id) {} + +SpdyFrameWithNameValueBlockIR::~SpdyFrameWithNameValueBlockIR() {} + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const base::StringPiece& data) + : SpdyFrameWithFinIR(stream_id) { + SetDataDeep(data); +} + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id) + : SpdyFrameWithFinIR(stream_id) {} + +SpdyDataIR::~SpdyDataIR() {} + +void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitData(*this); +} + +void SpdySynStreamIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitSynStream(*this); +} + +void SpdySynReplyIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitSynReply(*this); +} + +void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitRstStream(*this); +} + +SpdySettingsIR::SpdySettingsIR() : clear_settings_(false) {} + +SpdySettingsIR::~SpdySettingsIR() {} + +void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitSettings(*this); +} + +void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitPing(*this); +} + +void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitGoAway(*this); +} + +void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitHeaders(*this); +} + +void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitWindowUpdate(*this); +} + +SpdyCredentialIR::SpdyCredentialIR(int16 slot) { + set_slot(slot); +} + +SpdyCredentialIR::~SpdyCredentialIR() {} + +void SpdyCredentialIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitCredential(*this); +} + +void SpdyBlockedIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitBlocked(*this); +} + +void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitPushPromise(*this); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_protocol.h b/chromium/net/spdy/spdy_protocol.h new file mode 100644 index 00000000000..438d9d7f517 --- /dev/null +++ b/chromium/net/spdy/spdy_protocol.h @@ -0,0 +1,804 @@ +// 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. + +// This file contains some protocol structures for use with SPDY 2 and 3 +// The SPDY 2 spec can be found at: +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 +// The SPDY 3 spec can be found at: +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 + +#ifndef NET_SPDY_SPDY_PROTOCOL_H_ +#define NET_SPDY_SPDY_PROTOCOL_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/sys_byteorder.h" +#include "net/base/net_export.h" +#include "net/spdy/spdy_bitmasks.h" + +namespace net { + +// The major versions of SPDY. Major version differences indicate +// framer-layer incompatibility, as opposed to minor version numbers +// which indicate application-layer incompatibility. It is guaranteed +// that the enum value SPDYn maps to the integer n. +enum SpdyMajorVersion { + SPDY2 = 2, + SPDY_MIN_VERSION = SPDY2, + SPDY3 = 3, + SPDY4 = 4, + SPDY_MAX_VERSION = SPDY4 +}; + +// A SPDY stream id is a 31 bit entity. +typedef uint32 SpdyStreamId; + +// Specifies the stream ID used to denote the current session (for +// flow control). +const SpdyStreamId kSessionFlowControlStreamId = 0; + +// Initial window size for a Spdy stream. +const int32 kSpdyStreamInitialWindowSize = 64 * 1024; // 64 KBytes + +// Initial window size for a Spdy session. +const int32 kSpdySessionInitialWindowSize = 64 * 1024; // 64 KBytes + +// Maximum window size for a Spdy stream or session. +const int32 kSpdyMaximumWindowSize = 0x7FFFFFFF; // Max signed 32bit int + +// SPDY 2 dictionary. +// This is just a hacked dictionary to use for shrinking HTTP-like headers. +const char kV2Dictionary[] = + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + "-agent10010120020120220320420520630030130230330430530630740040140240340440" + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + ".1statusversionurl"; +const int kV2DictionarySize = arraysize(kV2Dictionary); + +// SPDY 3 dictionary. +const char kV3Dictionary[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete.... + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace... + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept. + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding.... + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage. + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age. + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding.... + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content- + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation.. + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5... + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range.. + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type.. + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date.. + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag.. + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match. + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince.... + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none- + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match... + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if- + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation... + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards... + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma. + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate... + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion.... + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range... + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after. + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te. + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding. + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via. + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate.. + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get. + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion.. + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive. + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204. + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503. + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service. + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept. + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov. + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu. + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun.. + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text. + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859- + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-.. + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0. +}; +const int kV3DictionarySize = arraysize(kV3Dictionary); + +// The HTTP/2 connection header prefix, which must be the first bytes +// sent by the client upon starting an HTTP/2 connection, and which +// must be followed by a SETTINGS frame. +// +// Equivalent to the string "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +// (without the null terminator). +const char kHttp2ConnectionHeaderPrefix[] = { + 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, // PRI * HT + 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, // TP/2.0.. + 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a // ..SM.... +}; +const int kHttp2ConnectionHeaderPrefixSize = + arraysize(kHttp2ConnectionHeaderPrefix); + +// Types of SPDY frames. +enum SpdyFrameType { + DATA = 0, + SYN_STREAM = 1, + FIRST_CONTROL_TYPE = SYN_STREAM, + SYN_REPLY, + RST_STREAM, + SETTINGS, + NOOP, // Because it is valid in SPDY/2, kept for identifiability/enum order. + PING, + GOAWAY, + HEADERS, + WINDOW_UPDATE, + CREDENTIAL, + BLOCKED, + PUSH_PROMISE, + LAST_CONTROL_TYPE = PUSH_PROMISE +}; + +// Flags on data packets. +enum SpdyDataFlags { + DATA_FLAG_NONE = 0, + DATA_FLAG_FIN = 1, +}; + +// Flags on control packets +enum SpdyControlFlags { + CONTROL_FLAG_NONE = 0, + CONTROL_FLAG_FIN = 1, + CONTROL_FLAG_UNIDIRECTIONAL = 2 +}; + +// Flags on the SETTINGS control frame. +enum SpdySettingsControlFlags { + SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1 +}; + +// Flags for settings within a SETTINGS frame. +enum SpdySettingsFlags { + SETTINGS_FLAG_NONE = 0x0, + SETTINGS_FLAG_PLEASE_PERSIST = 0x1, + SETTINGS_FLAG_PERSISTED = 0x2 +}; + +// List of known settings. +enum SpdySettingsIds { + SETTINGS_UPLOAD_BANDWIDTH = 0x1, + SETTINGS_DOWNLOAD_BANDWIDTH = 0x2, + // Network round trip time in milliseconds. + SETTINGS_ROUND_TRIP_TIME = 0x3, + SETTINGS_MAX_CONCURRENT_STREAMS = 0x4, + // TCP congestion window in packets. + SETTINGS_CURRENT_CWND = 0x5, + // Downstream byte retransmission rate in percentage. + SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6, + // Initial window size in bytes + SETTINGS_INITIAL_WINDOW_SIZE = 0x7 +}; + +// Status codes for RST_STREAM frames. +enum SpdyRstStreamStatus { + RST_STREAM_INVALID = 0, + RST_STREAM_PROTOCOL_ERROR = 1, + RST_STREAM_INVALID_STREAM = 2, + RST_STREAM_REFUSED_STREAM = 3, + RST_STREAM_UNSUPPORTED_VERSION = 4, + RST_STREAM_CANCEL = 5, + RST_STREAM_INTERNAL_ERROR = 6, + RST_STREAM_FLOW_CONTROL_ERROR = 7, + RST_STREAM_STREAM_IN_USE = 8, + RST_STREAM_STREAM_ALREADY_CLOSED = 9, + RST_STREAM_INVALID_CREDENTIALS = 10, + RST_STREAM_FRAME_TOO_LARGE = 11, + RST_STREAM_NUM_STATUS_CODES = 12 +}; + +// Status codes for GOAWAY frames. +enum SpdyGoAwayStatus { + GOAWAY_INVALID = -1, + GOAWAY_OK = 0, + GOAWAY_PROTOCOL_ERROR = 1, + GOAWAY_INTERNAL_ERROR = 2, + GOAWAY_NUM_STATUS_CODES = 3 +}; + +// A SPDY priority is a number between 0 and 7 (inclusive). +// SPDY priority range is version-dependant. For SPDY 2 and below, priority is a +// number between 0 and 3. +typedef uint8 SpdyPriority; + +typedef uint8 SpdyCredentialSlot; + +typedef std::map<std::string, std::string> SpdyNameValueBlock; + +typedef uint32 SpdyPingId; + +class SpdyFrame; +typedef SpdyFrame SpdySerializedFrame; + +class SpdyFrameVisitor; + +// Intermediate representation for SPDY frames. +// TODO(hkhalil): Rename this class to SpdyFrame when the existing SpdyFrame is +// gone. +class SpdyFrameIR { + public: + virtual ~SpdyFrameIR() {} + + virtual void Visit(SpdyFrameVisitor* visitor) const = 0; + + protected: + SpdyFrameIR() {} + + private: + DISALLOW_COPY_AND_ASSIGN(SpdyFrameIR); +}; + +// Abstract class intended to be inherited by IRs that have a stream associated +// to them. +class SpdyFrameWithStreamIdIR : public SpdyFrameIR { + public: + virtual ~SpdyFrameWithStreamIdIR() {} + SpdyStreamId stream_id() const { return stream_id_; } + void set_stream_id(SpdyStreamId stream_id) { + DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + stream_id_ = stream_id; + } + + protected: + explicit SpdyFrameWithStreamIdIR(SpdyStreamId stream_id) { + set_stream_id(stream_id); + } + + private: + SpdyStreamId stream_id_; + + DISALLOW_COPY_AND_ASSIGN(SpdyFrameWithStreamIdIR); +}; + +// Abstract class intended to be inherited by IRs that have the option of a FIN +// flag. Implies SpdyFrameWithStreamIdIR. +class SpdyFrameWithFinIR : public SpdyFrameWithStreamIdIR { + public: + virtual ~SpdyFrameWithFinIR() {} + bool fin() const { return fin_; } + void set_fin(bool fin) { fin_ = fin; } + + protected: + explicit SpdyFrameWithFinIR(SpdyStreamId stream_id) + : SpdyFrameWithStreamIdIR(stream_id), + fin_(false) {} + + private: + bool fin_; + + DISALLOW_COPY_AND_ASSIGN(SpdyFrameWithFinIR); +}; + +// Abstract class intended to be inherited by IRs that contain a name-value +// block. Implies SpdyFrameWithFinIR. +class NET_EXPORT_PRIVATE SpdyFrameWithNameValueBlockIR + : public NON_EXPORTED_BASE(SpdyFrameWithFinIR) { + public: + const SpdyNameValueBlock& name_value_block() const { + return name_value_block_; + } + SpdyNameValueBlock* GetMutableNameValueBlock() { return &name_value_block_; } + void SetHeader(const base::StringPiece& name, + const base::StringPiece& value) { + name_value_block_[name.as_string()] = value.as_string(); + } + + protected: + explicit SpdyFrameWithNameValueBlockIR(SpdyStreamId stream_id); + virtual ~SpdyFrameWithNameValueBlockIR(); + + private: + SpdyNameValueBlock name_value_block_; + + DISALLOW_COPY_AND_ASSIGN(SpdyFrameWithNameValueBlockIR); +}; + +class NET_EXPORT_PRIVATE SpdyDataIR + : public NON_EXPORTED_BASE(SpdyFrameWithFinIR) { + public: + // Performs deep copy on data. + SpdyDataIR(SpdyStreamId stream_id, const base::StringPiece& data); + + // Use in conjunction with SetDataShallow() for shallow-copy on data. + explicit SpdyDataIR(SpdyStreamId stream_id); + + virtual ~SpdyDataIR(); + + base::StringPiece data() const { return data_; } + + // Deep-copy of data (keep private copy). + void SetDataDeep(const base::StringPiece& data) { + data_store_.reset(new std::string(data.data(), data.length())); + data_ = *(data_store_.get()); + } + + // Shallow-copy of data (do not keep private copy). + void SetDataShallow(const base::StringPiece& data) { + data_store_.reset(); + data_ = data; + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + // Used to store data that this SpdyDataIR should own. + scoped_ptr<std::string> data_store_; + base::StringPiece data_; + + DISALLOW_COPY_AND_ASSIGN(SpdyDataIR); +}; + +class NET_EXPORT_PRIVATE SpdySynStreamIR + : public SpdyFrameWithNameValueBlockIR { + public: + explicit SpdySynStreamIR(SpdyStreamId stream_id) + : SpdyFrameWithNameValueBlockIR(stream_id), + associated_to_stream_id_(0), + priority_(0), + slot_(0), + unidirectional_(false) {} + SpdyStreamId associated_to_stream_id() const { + return associated_to_stream_id_; + } + void set_associated_to_stream_id(SpdyStreamId stream_id) { + associated_to_stream_id_ = stream_id; + } + SpdyPriority priority() const { return priority_; } + void set_priority(SpdyPriority priority) { priority_ = priority; } + SpdyCredentialSlot slot() const { return slot_; } + void set_slot(SpdyCredentialSlot slot) { slot_ = slot; } + bool unidirectional() const { return unidirectional_; } + void set_unidirectional(bool unidirectional) { + unidirectional_ = unidirectional; + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + SpdyStreamId associated_to_stream_id_; + SpdyPriority priority_; + SpdyCredentialSlot slot_; + bool unidirectional_; + + DISALLOW_COPY_AND_ASSIGN(SpdySynStreamIR); +}; + +class SpdySynReplyIR : public SpdyFrameWithNameValueBlockIR { + public: + explicit SpdySynReplyIR(SpdyStreamId stream_id) + : SpdyFrameWithNameValueBlockIR(stream_id) {} + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(SpdySynReplyIR); +}; + +class SpdyRstStreamIR : public SpdyFrameWithStreamIdIR { + public: + SpdyRstStreamIR(SpdyStreamId stream_id, SpdyRstStreamStatus status) + : SpdyFrameWithStreamIdIR(stream_id) { + set_status(status); + } + SpdyRstStreamStatus status() const { + return status_; + } + void set_status(SpdyRstStreamStatus status) { + DCHECK_NE(status, RST_STREAM_INVALID); + DCHECK_LT(status, RST_STREAM_NUM_STATUS_CODES); + status_ = status; + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + SpdyRstStreamStatus status_; + + DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamIR); +}; + +class SpdySettingsIR : public SpdyFrameIR { + public: + // Associates flags with a value. + struct Value { + Value() : persist_value(false), + persisted(false), + value(0) {} + bool persist_value; + bool persisted; + int32 value; + }; + typedef std::map<SpdySettingsIds, Value> ValueMap; + + SpdySettingsIR(); + + virtual ~SpdySettingsIR(); + + // Overwrites as appropriate. + const ValueMap& values() const { return values_; } + void AddSetting(SpdySettingsIds id, + bool persist_value, + bool persisted, + int32 value) { + // TODO(hkhalil): DCHECK_LE(SETTINGS_UPLOAD_BANDWIDTH, id); + // TODO(hkhalil): DCHECK_GE(SETTINGS_INITIAL_WINDOW_SIZE, id); + values_[id].persist_value = persist_value; + values_[id].persisted = persisted; + values_[id].value = value; + } + bool clear_settings() const { return clear_settings_; } + void set_clear_settings(bool clear_settings) { + clear_settings_ = clear_settings; + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + ValueMap values_; + bool clear_settings_; + + DISALLOW_COPY_AND_ASSIGN(SpdySettingsIR); +}; + +class SpdyPingIR : public SpdyFrameIR { + public: + explicit SpdyPingIR(SpdyPingId id) : id_(id) {} + SpdyPingId id() const { return id_; } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + SpdyPingId id_; + + DISALLOW_COPY_AND_ASSIGN(SpdyPingIR); +}; + +class SpdyGoAwayIR : public SpdyFrameIR { + public: + SpdyGoAwayIR(SpdyStreamId last_good_stream_id, SpdyGoAwayStatus status) { + set_last_good_stream_id(last_good_stream_id); + set_status(status); + } + SpdyStreamId last_good_stream_id() const { return last_good_stream_id_; } + void set_last_good_stream_id(SpdyStreamId last_good_stream_id) { + DCHECK_LE(0u, last_good_stream_id); + DCHECK_EQ(0u, last_good_stream_id & ~kStreamIdMask); + last_good_stream_id_ = last_good_stream_id; + } + SpdyGoAwayStatus status() const { return status_; } + void set_status(SpdyGoAwayStatus status) { + // TODO(hkhalil): Check valid ranges of status? + status_ = status; + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + SpdyStreamId last_good_stream_id_; + SpdyGoAwayStatus status_; + + DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayIR); +}; + +class SpdyHeadersIR : public SpdyFrameWithNameValueBlockIR { + public: + explicit SpdyHeadersIR(SpdyStreamId stream_id) + : SpdyFrameWithNameValueBlockIR(stream_id) {} + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(SpdyHeadersIR); +}; + +class SpdyWindowUpdateIR : public SpdyFrameWithStreamIdIR { + public: + SpdyWindowUpdateIR(SpdyStreamId stream_id, int32 delta) + : SpdyFrameWithStreamIdIR(stream_id) { + set_delta(delta); + } + int32 delta() const { return delta_; } + void set_delta(int32 delta) { + DCHECK_LT(0, delta); + DCHECK_LE(delta, kSpdyMaximumWindowSize); + delta_ = delta; + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + int32 delta_; + + DISALLOW_COPY_AND_ASSIGN(SpdyWindowUpdateIR); +}; + +class SpdyCredentialIR : public SpdyFrameIR { + public: + typedef std::vector<std::string> CertificateList; + + explicit SpdyCredentialIR(int16 slot); + virtual ~SpdyCredentialIR(); + + int16 slot() const { return slot_; } + void set_slot(int16 slot) { + // TODO(hkhalil): Verify valid slot range? + slot_ = slot; + } + base::StringPiece proof() const { return proof_; } + void set_proof(const base::StringPiece& proof) { + proof.CopyToString(&proof_); + } + const CertificateList* certificates() const { return &certificates_; } + void AddCertificate(const base::StringPiece& certificate) { + certificates_.push_back(certificate.as_string()); + } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + int16 slot_; + std::string proof_; + CertificateList certificates_; + + DISALLOW_COPY_AND_ASSIGN(SpdyCredentialIR); +}; + +class NET_EXPORT_PRIVATE SpdyBlockedIR + : public NON_EXPORTED_BASE(SpdyFrameWithStreamIdIR) { + public: + explicit SpdyBlockedIR(SpdyStreamId stream_id) + : SpdyFrameWithStreamIdIR(stream_id) {} + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(SpdyBlockedIR); +}; + +class SpdyPushPromiseIR : public SpdyFrameWithNameValueBlockIR { + public: + SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id) + : SpdyFrameWithNameValueBlockIR(stream_id), + promised_stream_id_(promised_stream_id) {} + SpdyStreamId promised_stream_id() const { return promised_stream_id_; } + void set_promised_stream_id(SpdyStreamId id) { promised_stream_id_ = id; } + + virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE; + + private: + SpdyStreamId promised_stream_id_; + DISALLOW_COPY_AND_ASSIGN(SpdyPushPromiseIR); +}; + + +// ------------------------------------------------------------------------- +// Wrapper classes for various SPDY frames. + +// All Spdy Frame types derive from this SpdyFrame class. +class SpdyFrame { + public: + // Create a SpdyFrame using a pre-created buffer. + // If |owns_buffer| is true, this class takes ownership of the buffer + // and will delete it on cleanup. The buffer must have been created using + // new char[]. + // If |owns_buffer| is false, the caller retains ownership of the buffer and + // is responsible for making sure the buffer outlives this frame. In other + // words, this class does NOT create a copy of the buffer. + SpdyFrame(char* data, size_t size, bool owns_buffer) + : frame_(data), + size_(size), + owns_buffer_(owns_buffer) { + DCHECK(frame_); + } + + ~SpdyFrame() { + if (owns_buffer_) { + delete [] frame_; + } + frame_ = NULL; + } + + // Provides access to the frame bytes, which is a buffer containing + // the frame packed as expected for sending over the wire. + char* data() const { return frame_; } + + // Returns the actual size of the underlying buffer. + size_t size() const { return size_; } + + protected: + char* frame_; + + private: + size_t size_; + bool owns_buffer_; + DISALLOW_COPY_AND_ASSIGN(SpdyFrame); +}; + +// This interface is for classes that want to process SpdyFrameIRs without +// having to know what type they are. An instance of this interface can be +// passed to a SpdyFrameIR's Visit method, and the appropriate type-specific +// method of this class will be called. +class SpdyFrameVisitor { + public: + virtual void VisitSynStream(const SpdySynStreamIR& syn_stream) = 0; + virtual void VisitSynReply(const SpdySynReplyIR& syn_reply) = 0; + virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) = 0; + virtual void VisitSettings(const SpdySettingsIR& settings) = 0; + virtual void VisitPing(const SpdyPingIR& ping) = 0; + virtual void VisitGoAway(const SpdyGoAwayIR& goaway) = 0; + virtual void VisitHeaders(const SpdyHeadersIR& headers) = 0; + virtual void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) = 0; + virtual void VisitCredential(const SpdyCredentialIR& credential) = 0; + virtual void VisitBlocked(const SpdyBlockedIR& blocked) = 0; + virtual void VisitPushPromise(const SpdyPushPromiseIR& push_promise) = 0; + virtual void VisitData(const SpdyDataIR& data) = 0; + + protected: + SpdyFrameVisitor() {} + virtual ~SpdyFrameVisitor() {} + + private: + DISALLOW_COPY_AND_ASSIGN(SpdyFrameVisitor); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_PROTOCOL_H_ diff --git a/chromium/net/spdy/spdy_protocol_test.cc b/chromium/net/spdy/spdy_protocol_test.cc new file mode 100644 index 00000000000..006dfe15ca5 --- /dev/null +++ b/chromium/net/spdy/spdy_protocol_test.cc @@ -0,0 +1,86 @@ +// 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 "net/spdy/spdy_protocol.h" + +#include <limits> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "net/spdy/spdy_bitmasks.h" +#include "net/spdy/spdy_framer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +enum SpdyProtocolTestTypes { + SPDY2 = 2, + SPDY3 = 3, +}; + +} // namespace + +namespace net { + +class SpdyProtocolTest + : public ::testing::TestWithParam<SpdyProtocolTestTypes> { + protected: + virtual void SetUp() { + spdy_version_ = GetParam(); + } + + bool IsSpdy2() { return spdy_version_ == SPDY2; } + + // Version of SPDY protocol to be used. + int spdy_version_; +}; + +// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3. +INSTANTIATE_TEST_CASE_P(SpdyProtocolTests, + SpdyProtocolTest, + ::testing::Values(SPDY2, SPDY3)); + +// Test our protocol constants +TEST_P(SpdyProtocolTest, ProtocolConstants) { + EXPECT_EQ(1, SYN_STREAM); + EXPECT_EQ(2, SYN_REPLY); + EXPECT_EQ(3, RST_STREAM); + EXPECT_EQ(4, SETTINGS); + EXPECT_EQ(5, NOOP); + EXPECT_EQ(6, PING); + EXPECT_EQ(7, GOAWAY); + EXPECT_EQ(8, HEADERS); + EXPECT_EQ(9, WINDOW_UPDATE); + EXPECT_EQ(10, CREDENTIAL); + EXPECT_EQ(11, BLOCKED); + EXPECT_EQ(12, PUSH_PROMISE); + EXPECT_EQ(12, LAST_CONTROL_TYPE); + EXPECT_EQ(std::numeric_limits<int32>::max(), kSpdyMaximumWindowSize); +} + +class SpdyProtocolDeathTest : public SpdyProtocolTest {}; + +// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3. +INSTANTIATE_TEST_CASE_P(SpdyProtocolDeathTests, + SpdyProtocolDeathTest, + ::testing::Values(SPDY2, SPDY3)); + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +TEST_P(SpdyProtocolDeathTest, TestSpdySettingsAndIdOutOfBounds) { + scoped_ptr<SettingsFlagsAndId> flags_and_id; + + EXPECT_DEBUG_DEATH( + { + flags_and_id.reset(new SettingsFlagsAndId(1, ~0)); + }, + "SPDY setting ID too large."); + // Make sure that we get expected values in opt mode. + if (flags_and_id.get() != NULL) { + EXPECT_EQ(1, flags_and_id->flags()); + EXPECT_EQ(static_cast<SpdyPingId>(0xffffff), flags_and_id->id()); + } +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +} // namespace net diff --git a/chromium/net/spdy/spdy_proxy_client_socket.cc b/chromium/net/spdy/spdy_proxy_client_socket.cc new file mode 100644 index 00000000000..fa3300f3d49 --- /dev/null +++ b/chromium/net/spdy/spdy_proxy_client_socket.cc @@ -0,0 +1,520 @@ +// 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 "net/spdy/spdy_proxy_client_socket.h" + +#include <algorithm> // min + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "net/base/auth.h" +#include "net/base/io_buffer.h" +#include "net/base/net_util.h" +#include "net/http/http_auth_cache.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_response_headers.h" +#include "net/http/proxy_connect_redirect_http_stream.h" +#include "net/spdy/spdy_http_utils.h" +#include "url/gurl.h" + +namespace net { + +SpdyProxyClientSocket::SpdyProxyClientSocket( + const base::WeakPtr<SpdyStream>& spdy_stream, + const std::string& user_agent, + const HostPortPair& endpoint, + const GURL& url, + const HostPortPair& proxy_server, + const BoundNetLog& source_net_log, + HttpAuthCache* auth_cache, + HttpAuthHandlerFactory* auth_handler_factory) + : next_state_(STATE_DISCONNECTED), + spdy_stream_(spdy_stream), + endpoint_(endpoint), + auth_( + new HttpAuthController(HttpAuth::AUTH_PROXY, + GURL("https://" + proxy_server.ToString()), + auth_cache, + auth_handler_factory)), + user_buffer_len_(0), + write_buffer_len_(0), + was_ever_used_(false), + redirect_has_load_timing_info_(false), + weak_factory_(this), + net_log_(BoundNetLog::Make(spdy_stream->net_log().net_log(), + NetLog::SOURCE_PROXY_CLIENT_SOCKET)) { + request_.method = "CONNECT"; + request_.url = url; + if (!user_agent.empty()) + request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, + user_agent); + + net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, + source_net_log.source().ToEventParametersCallback()); + net_log_.AddEvent( + NetLog::TYPE_SPDY_PROXY_CLIENT_SESSION, + spdy_stream->net_log().source().ToEventParametersCallback()); + + spdy_stream_->SetDelegate(this); + was_ever_used_ = spdy_stream_->WasEverUsed(); +} + +SpdyProxyClientSocket::~SpdyProxyClientSocket() { + Disconnect(); + net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); +} + +const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const { + return response_.headers.get() ? &response_ : NULL; +} + +const scoped_refptr<HttpAuthController>& +SpdyProxyClientSocket::GetAuthController() const { + return auth_; +} + +int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) { + // A SPDY Stream can only handle a single request, so the underlying + // stream may not be reused and a new SpdyProxyClientSocket must be + // created (possibly on top of the same SPDY Session). + next_state_ = STATE_DISCONNECTED; + return OK; +} + +bool SpdyProxyClientSocket::IsUsingSpdy() const { + return true; +} + +NextProto SpdyProxyClientSocket::GetProtocolNegotiated() const { + // Save the negotiated protocol + SSLInfo ssl_info; + bool was_npn_negotiated; + NextProto protocol_negotiated; + spdy_stream_->GetSSLInfo(&ssl_info, &was_npn_negotiated, + &protocol_negotiated); + return protocol_negotiated; +} + +HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() { + return new ProxyConnectRedirectHttpStream( + redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL); +} + +// Sends a SYN_STREAM frame to the proxy with a CONNECT request +// for the specified endpoint. Waits for the server to send back +// a SYN_REPLY frame. OK will be returned if the status is 200. +// ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status. +// In any of these cases, Read() may be called to retrieve the HTTP +// response body. Any other return values should be considered fatal. +// TODO(rch): handle 407 proxy auth requested correctly, perhaps +// by creating a new stream for the subsequent request. +// TODO(rch): create a more appropriate error code to disambiguate +// the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure. +int SpdyProxyClientSocket::Connect(const CompletionCallback& callback) { + DCHECK(read_callback_.is_null()); + if (next_state_ == STATE_OPEN) + return OK; + + DCHECK_EQ(STATE_DISCONNECTED, next_state_); + next_state_ = STATE_GENERATE_AUTH_TOKEN; + + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + read_callback_ = callback; + return rv; +} + +void SpdyProxyClientSocket::Disconnect() { + read_buffer_queue_.Clear(); + user_buffer_ = NULL; + user_buffer_len_ = 0; + read_callback_.Reset(); + + write_buffer_len_ = 0; + write_callback_.Reset(); + + next_state_ = STATE_DISCONNECTED; + + if (spdy_stream_.get()) { + // This will cause OnClose to be invoked, which takes care of + // cleaning up all the internal state. + spdy_stream_->Cancel(); + DCHECK(!spdy_stream_.get()); + } +} + +bool SpdyProxyClientSocket::IsConnected() const { + return next_state_ == STATE_OPEN; +} + +bool SpdyProxyClientSocket::IsConnectedAndIdle() const { + return IsConnected() && read_buffer_queue_.IsEmpty() && + spdy_stream_->IsIdle(); +} + +const BoundNetLog& SpdyProxyClientSocket::NetLog() const { + return net_log_; +} + +void SpdyProxyClientSocket::SetSubresourceSpeculation() { + // TODO(rch): what should this implementation be? +} + +void SpdyProxyClientSocket::SetOmniboxSpeculation() { + // TODO(rch): what should this implementation be? +} + +bool SpdyProxyClientSocket::WasEverUsed() const { + return was_ever_used_ || (spdy_stream_.get() && spdy_stream_->WasEverUsed()); +} + +bool SpdyProxyClientSocket::UsingTCPFastOpen() const { + return false; +} + +bool SpdyProxyClientSocket::WasNpnNegotiated() const { + return false; +} + +NextProto SpdyProxyClientSocket::GetNegotiatedProtocol() const { + return kProtoUnknown; +} + +bool SpdyProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) { + bool was_npn_negotiated; + NextProto protocol_negotiated; + return spdy_stream_->GetSSLInfo(ssl_info, &was_npn_negotiated, + &protocol_negotiated); +} + +int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) { + DCHECK(read_callback_.is_null()); + DCHECK(!user_buffer_.get()); + + if (next_state_ == STATE_DISCONNECTED) + return ERR_SOCKET_NOT_CONNECTED; + + if (next_state_ == STATE_CLOSED && read_buffer_queue_.IsEmpty()) { + return 0; + } + + DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED); + DCHECK(buf); + size_t result = PopulateUserReadBuffer(buf->data(), buf_len); + if (result == 0) { + user_buffer_ = buf; + user_buffer_len_ = static_cast<size_t>(buf_len); + DCHECK(!callback.is_null()); + read_callback_ = callback; + return ERR_IO_PENDING; + } + user_buffer_ = NULL; + return result; +} + +size_t SpdyProxyClientSocket::PopulateUserReadBuffer(char* data, size_t len) { + return read_buffer_queue_.Dequeue(data, len); +} + +int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) { + DCHECK(write_callback_.is_null()); + if (next_state_ != STATE_OPEN) + return ERR_SOCKET_NOT_CONNECTED; + + DCHECK(spdy_stream_.get()); + spdy_stream_->SendData(buf, buf_len, MORE_DATA_TO_SEND); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, + buf_len, buf->data()); + write_callback_ = callback; + write_buffer_len_ = buf_len; + return ERR_IO_PENDING; +} + +bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size) { + // Since this StreamSocket sits on top of a shared SpdySession, it + // is not safe for callers to set change this underlying socket. + return false; +} + +bool SpdyProxyClientSocket::SetSendBufferSize(int32 size) { + // Since this StreamSocket sits on top of a shared SpdySession, it + // is not safe for callers to set change this underlying socket. + return false; +} + +int SpdyProxyClientSocket::GetPeerAddress(IPEndPoint* address) const { + if (!IsConnected()) + return ERR_SOCKET_NOT_CONNECTED; + return spdy_stream_->GetPeerAddress(address); +} + +int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint* address) const { + if (!IsConnected()) + return ERR_SOCKET_NOT_CONNECTED; + return spdy_stream_->GetLocalAddress(address); +} + +void SpdyProxyClientSocket::LogBlockedTunnelResponse() const { + ProxyClientSocket::LogBlockedTunnelResponse( + response_.headers->response_code(), + request_.url, + /* is_https_proxy = */ true); +} + +void SpdyProxyClientSocket::OnIOComplete(int result) { + DCHECK_NE(STATE_DISCONNECTED, next_state_); + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) { + CompletionCallback c = read_callback_; + read_callback_.Reset(); + c.Run(rv); + } +} + +int SpdyProxyClientSocket::DoLoop(int last_io_result) { + DCHECK_NE(next_state_, STATE_DISCONNECTED); + int rv = last_io_result; + do { + State state = next_state_; + next_state_ = STATE_DISCONNECTED; + switch (state) { + case STATE_GENERATE_AUTH_TOKEN: + DCHECK_EQ(OK, rv); + rv = DoGenerateAuthToken(); + break; + case STATE_GENERATE_AUTH_TOKEN_COMPLETE: + rv = DoGenerateAuthTokenComplete(rv); + break; + case STATE_SEND_REQUEST: + DCHECK_EQ(OK, rv); + net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST); + rv = DoSendRequest(); + break; + case STATE_SEND_REQUEST_COMPLETE: + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv); + rv = DoSendRequestComplete(rv); + if (rv >= 0 || rv == ERR_IO_PENDING) { + // Emit extra event so can use the same events as + // HttpProxyClientSocket. + net_log_.BeginEvent( + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS); + } + break; + case STATE_READ_REPLY_COMPLETE: + rv = DoReadReplyComplete(rv); + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv); + break; + default: + NOTREACHED() << "bad state"; + rv = ERR_UNEXPECTED; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED && + next_state_ != STATE_OPEN); + return rv; +} + +int SpdyProxyClientSocket::DoGenerateAuthToken() { + next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE; + return auth_->MaybeGenerateAuthToken( + &request_, + base::Bind(&SpdyProxyClientSocket::OnIOComplete, + weak_factory_.GetWeakPtr()), + net_log_); +} + +int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + if (result == OK) + next_state_ = STATE_SEND_REQUEST; + return result; +} + +int SpdyProxyClientSocket::DoSendRequest() { + next_state_ = STATE_SEND_REQUEST_COMPLETE; + + // Add Proxy-Authentication header if necessary. + HttpRequestHeaders authorization_headers; + if (auth_->HaveAuth()) { + auth_->AddAuthorizationHeader(&authorization_headers); + } + + std::string request_line; + HttpRequestHeaders request_headers; + BuildTunnelRequest(request_, authorization_headers, endpoint_, &request_line, + &request_headers); + + net_log_.AddEvent( + NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + base::Bind(&HttpRequestHeaders::NetLogCallback, + base::Unretained(&request_headers), + &request_line)); + + request_.extra_headers.MergeFrom(request_headers); + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + CreateSpdyHeadersFromHttpRequest(request_, request_headers, headers.get(), + spdy_stream_->GetProtocolVersion(), true); + // Reset the URL to be the endpoint of the connection + if (spdy_stream_->GetProtocolVersion() > 2) { + (*headers)[":path"] = endpoint_.ToString(); + headers->erase(":scheme"); + } else { + (*headers)["url"] = endpoint_.ToString(); + headers->erase("scheme"); + } + + return spdy_stream_->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND); +} + +int SpdyProxyClientSocket::DoSendRequestComplete(int result) { + if (result < 0) + return result; + + // Wait for SYN_REPLY frame from the server + next_state_ = STATE_READ_REPLY_COMPLETE; + return ERR_IO_PENDING; +} + +int SpdyProxyClientSocket::DoReadReplyComplete(int result) { + // We enter this method directly from DoSendRequestComplete, since + // we are notified by a callback when the SYN_REPLY frame arrives + + if (result < 0) + return result; + + // Require the "HTTP/1.x" status line for SSL CONNECT. + if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) + return ERR_TUNNEL_CONNECTION_FAILED; + + net_log_.AddEvent( + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers)); + + switch (response_.headers->response_code()) { + case 200: // OK + next_state_ = STATE_OPEN; + return OK; + + case 302: // Found / Moved Temporarily + // Try to return a sanitized response so we can follow auth redirects. + // If we can't, fail the tunnel connection. + if (SanitizeProxyRedirect(&response_, request_.url)) { + redirect_has_load_timing_info_ = + spdy_stream_->GetLoadTimingInfo(&redirect_load_timing_info_); + spdy_stream_->DetachDelegate(); + next_state_ = STATE_DISCONNECTED; + return ERR_HTTPS_PROXY_TUNNEL_RESPONSE; + } else { + LogBlockedTunnelResponse(); + return ERR_TUNNEL_CONNECTION_FAILED; + } + + case 407: // Proxy Authentication Required + next_state_ = STATE_OPEN; + return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_); + + default: + // Ignore response to avoid letting the proxy impersonate the target + // server. (See http://crbug.com/137891.) + LogBlockedTunnelResponse(); + return ERR_TUNNEL_CONNECTION_FAILED; + } +} + +// SpdyStream::Delegate methods: +// Called when SYN frame has been sent. +// Returns true if no more data to be sent after SYN frame. +void SpdyProxyClientSocket::OnRequestHeadersSent() { + DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE); + + OnIOComplete(OK); +} + +SpdyResponseHeadersStatus SpdyProxyClientSocket::OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) { + // If we've already received the reply, existing headers are too late. + // TODO(mbelshe): figure out a way to make HEADERS frames useful after the + // initial response. + if (next_state_ != STATE_READ_REPLY_COMPLETE) + return RESPONSE_HEADERS_ARE_COMPLETE; + + // Save the response + if (!SpdyHeadersToHttpResponse( + response_headers, spdy_stream_->GetProtocolVersion(), &response_)) + return RESPONSE_HEADERS_ARE_INCOMPLETE; + + OnIOComplete(OK); + return RESPONSE_HEADERS_ARE_COMPLETE; +} + +// Called when data is received or on EOF (if |buffer| is NULL). +void SpdyProxyClientSocket::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) { + if (buffer) { + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, + buffer->GetRemainingSize(), + buffer->GetRemainingData()); + read_buffer_queue_.Enqueue(buffer.Pass()); + } else { + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, 0, NULL); + } + + if (!read_callback_.is_null()) { + int rv = PopulateUserReadBuffer(user_buffer_->data(), user_buffer_len_); + CompletionCallback c = read_callback_; + read_callback_.Reset(); + user_buffer_ = NULL; + user_buffer_len_ = 0; + c.Run(rv); + } +} + +void SpdyProxyClientSocket::OnDataSent() { + DCHECK(!write_callback_.is_null()); + + int rv = write_buffer_len_; + write_buffer_len_ = 0; + ResetAndReturn(&write_callback_).Run(rv); +} + +void SpdyProxyClientSocket::OnClose(int status) { + was_ever_used_ = spdy_stream_->WasEverUsed(); + spdy_stream_.reset(); + + bool connecting = next_state_ != STATE_DISCONNECTED && + next_state_ < STATE_OPEN; + if (next_state_ == STATE_OPEN) + next_state_ = STATE_CLOSED; + else + next_state_ = STATE_DISCONNECTED; + + base::WeakPtr<SpdyProxyClientSocket> weak_ptr = weak_factory_.GetWeakPtr(); + CompletionCallback write_callback = write_callback_; + write_callback_.Reset(); + write_buffer_len_ = 0; + + // If we're in the middle of connecting, we need to make sure + // we invoke the connect callback. + if (connecting) { + DCHECK(!read_callback_.is_null()); + CompletionCallback read_callback = read_callback_; + read_callback_.Reset(); + read_callback.Run(status); + } else if (!read_callback_.is_null()) { + // If we have a read_callback_, the we need to make sure we call it back. + OnDataReceived(scoped_ptr<SpdyBuffer>()); + } + // This may have been deleted by read_callback_, so check first. + if (weak_ptr.get() && !write_callback.is_null()) + write_callback.Run(ERR_CONNECTION_CLOSED); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_proxy_client_socket.h b/chromium/net/spdy/spdy_proxy_client_socket.h new file mode 100644 index 00000000000..3d7933110b4 --- /dev/null +++ b/chromium/net/spdy/spdy_proxy_client_socket.h @@ -0,0 +1,175 @@ +// 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. + +#ifndef NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_ +#define NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_ + +#include <list> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/load_timing_info.h" +#include "net/base/net_log.h" +#include "net/http/http_auth_controller.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/http/proxy_client_socket.h" +#include "net/spdy/spdy_http_stream.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_read_queue.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_stream.h" + + +class GURL; + +namespace net { + +class AddressList; +class HttpStream; +class IOBuffer; +class SpdyStream; + +class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket, + public SpdyStream::Delegate { + public: + // Create a socket on top of the |spdy_stream| by sending a SYN_STREAM + // CONNECT frame for |endpoint|. After the SYN_REPLY is received, + // any data read/written to the socket will be transferred in data + // frames. This object will set itself as |spdy_stream|'s delegate. + SpdyProxyClientSocket(const base::WeakPtr<SpdyStream>& spdy_stream, + const std::string& user_agent, + const HostPortPair& endpoint, + const GURL& url, + const HostPortPair& proxy_server, + const BoundNetLog& source_net_log, + HttpAuthCache* auth_cache, + HttpAuthHandlerFactory* auth_handler_factory); + + + // On destruction Disconnect() is called. + virtual ~SpdyProxyClientSocket(); + + // ProxyClientSocket methods: + virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE; + virtual HttpStream* CreateConnectResponseStream() OVERRIDE; + virtual const scoped_refptr<HttpAuthController>& GetAuthController() const + OVERRIDE; + virtual int RestartWithAuth(const CompletionCallback& callback) OVERRIDE; + virtual bool IsUsingSpdy() const OVERRIDE; + virtual NextProto GetProtocolNegotiated() const OVERRIDE; + + // StreamSocket implementation. + virtual int Connect(const CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual bool IsConnected() const OVERRIDE; + virtual bool IsConnectedAndIdle() const OVERRIDE; + virtual const BoundNetLog& NetLog() const OVERRIDE; + virtual void SetSubresourceSpeculation() OVERRIDE; + virtual void SetOmniboxSpeculation() OVERRIDE; + virtual bool WasEverUsed() const OVERRIDE; + virtual bool UsingTCPFastOpen() const OVERRIDE; + virtual bool WasNpnNegotiated() const OVERRIDE; + virtual NextProto GetNegotiatedProtocol() const OVERRIDE; + virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; + + // Socket implementation. + virtual int Read(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual int Write(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) OVERRIDE; + virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; + virtual bool SetSendBufferSize(int32 size) OVERRIDE; + virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE; + virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE; + + // SpdyStream::Delegate implementation. + virtual void OnRequestHeadersSent() OVERRIDE; + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE; + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE; + virtual void OnDataSent() OVERRIDE; + virtual void OnClose(int status) OVERRIDE; + + private: + enum State { + STATE_DISCONNECTED, + STATE_GENERATE_AUTH_TOKEN, + STATE_GENERATE_AUTH_TOKEN_COMPLETE, + STATE_SEND_REQUEST, + STATE_SEND_REQUEST_COMPLETE, + STATE_READ_REPLY_COMPLETE, + STATE_OPEN, + STATE_CLOSED + }; + + void LogBlockedTunnelResponse() const; + + void OnIOComplete(int result); + + int DoLoop(int last_io_result); + int DoGenerateAuthToken(); + int DoGenerateAuthTokenComplete(int result); + int DoSendRequest(); + int DoSendRequestComplete(int result); + int DoReadReplyComplete(int result); + + // Populates |user_buffer_| with as much read data as possible + // and returns the number of bytes read. + size_t PopulateUserReadBuffer(char* out, size_t len); + + State next_state_; + + // Pointer to the SPDY Stream that this sits on top of. + base::WeakPtr<SpdyStream> spdy_stream_; + + // Stores the callback to the layer above, called on completing Read() or + // Connect(). + CompletionCallback read_callback_; + // Stores the callback to the layer above, called on completing Write(). + CompletionCallback write_callback_; + + // CONNECT request and response. + HttpRequestInfo request_; + HttpResponseInfo response_; + + // The hostname and port of the endpoint. This is not necessarily the one + // specified by the URL, due to Alternate-Protocol or fixed testing ports. + const HostPortPair endpoint_; + scoped_refptr<HttpAuthController> auth_; + + // We buffer the response body as it arrives asynchronously from the stream. + SpdyReadQueue read_buffer_queue_; + + // User provided buffer for the Read() response. + scoped_refptr<IOBuffer> user_buffer_; + size_t user_buffer_len_; + + // User specified number of bytes to be written. + int write_buffer_len_; + + // True if the transport socket has ever sent data. + bool was_ever_used_; + + // Used only for redirects. + bool redirect_has_load_timing_info_; + LoadTimingInfo redirect_load_timing_info_; + + base::WeakPtrFactory<SpdyProxyClientSocket> weak_factory_; + + const BoundNetLog net_log_; + + DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocket); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_ diff --git a/chromium/net/spdy/spdy_proxy_client_socket_unittest.cc b/chromium/net/spdy/spdy_proxy_client_socket_unittest.cc new file mode 100644 index 00000000000..c128d9cdace --- /dev/null +++ b/chromium/net/spdy/spdy_proxy_client_socket_unittest.cc @@ -0,0 +1,1435 @@ +// 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 "net/spdy/spdy_proxy_client_socket.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/address_list.h" +#include "net/base/capturing_net_log.h" +#include "net/base/net_log.h" +#include "net/base/net_log_unittest.h" +#include "net/base/test_completion_callback.h" +#include "net/base/winsock_init.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/next_proto.h" +#include "net/socket/socket_test_util.h" +#include "net/socket/tcp_client_socket.h" +#include "net/spdy/buffered_spdy_framer.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_common.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +//----------------------------------------------------------------------------- + +namespace { + +static const char kRequestUrl[] = "https://www.google.com/"; +static const char kOriginHost[] = "www.google.com"; +static const int kOriginPort = 443; +static const char kOriginHostPort[] = "www.google.com:443"; +static const char kProxyUrl[] = "https://myproxy:6121/"; +static const char kProxyHost[] = "myproxy"; +static const int kProxyPort = 6121; +static const char kUserAgent[] = "Mozilla/1.0"; + +static const int kStreamId = 1; + +static const char kMsg1[] = "\0hello!\xff"; +static const int kLen1 = 8; +static const char kMsg2[] = "\00012345678\0"; +static const int kLen2 = 10; +static const char kMsg3[] = "bye!"; +static const int kLen3 = 4; +static const char kMsg33[] = "bye!bye!"; +static const int kLen33 = kLen3 + kLen3; +static const char kMsg333[] = "bye!bye!bye!"; +static const int kLen333 = kLen3 + kLen3 + kLen3; + +static const char kRedirectUrl[] = "https://example.com/"; + +} // anonymous namespace + +namespace net { + +class SpdyProxyClientSocketTest + : public PlatformTest, + public testing::WithParamInterface<NextProto> { + public: + SpdyProxyClientSocketTest(); + + virtual void TearDown(); + + protected: + void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes, + size_t writes_count); + SpdyFrame* ConstructConnectRequestFrame(); + SpdyFrame* ConstructConnectAuthRequestFrame(); + SpdyFrame* ConstructConnectReplyFrame(); + SpdyFrame* ConstructConnectAuthReplyFrame(); + SpdyFrame* ConstructConnectRedirectReplyFrame(); + SpdyFrame* ConstructConnectErrorReplyFrame(); + SpdyFrame* ConstructBodyFrame(const char* data, int length); + scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size); + void AssertConnectSucceeds(); + void AssertConnectFails(int result); + void AssertConnectionEstablished(); + void AssertSyncReadEquals(const char* data, int len); + void AssertAsyncReadEquals(const char* data, int len); + void AssertReadStarts(const char* data, int len); + void AssertReadReturns(const char* data, int len); + void AssertAsyncWriteSucceeds(const char* data, int len); + void AssertWriteReturns(const char* data, int len, int rv); + void AssertWriteLength(int len); + void AssertAsyncWriteWithReadsSucceeds(const char* data, int len, + int num_reads); + + void AddAuthToCache() { + const base::string16 kFoo(ASCIIToUTF16("foo")); + const base::string16 kBar(ASCIIToUTF16("bar")); + session_->http_auth_cache()->Add(GURL(kProxyUrl), + "MyRealm1", + HttpAuth::AUTH_SCHEME_BASIC, + "Basic realm=MyRealm1", + AuthCredentials(kFoo, kBar), + "/"); + } + + void Run(int steps) { + data_->StopAfter(steps); + data_->Run(); + } + + void CloseSpdySession(net::Error error, const std::string& description) { + spdy_session_->CloseSessionOnError(error, description); + } + + SpdyTestUtil spdy_util_; + scoped_ptr<SpdyProxyClientSocket> sock_; + TestCompletionCallback read_callback_; + TestCompletionCallback write_callback_; + scoped_ptr<DeterministicSocketData> data_; + CapturingBoundNetLog net_log_; + + private: + scoped_refptr<HttpNetworkSession> session_; + scoped_refptr<IOBuffer> read_buf_; + SpdySessionDependencies session_deps_; + MockConnect connect_data_; + base::WeakPtr<SpdySession> spdy_session_; + BufferedSpdyFramer framer_; + + std::string user_agent_; + GURL url_; + HostPortPair proxy_host_port_; + HostPortPair endpoint_host_port_pair_; + ProxyServer proxy_; + SpdySessionKey endpoint_spdy_session_key_; + + DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketTest); +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + SpdyProxyClientSocketTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +SpdyProxyClientSocketTest::SpdyProxyClientSocketTest() + : spdy_util_(GetParam()), + session_(NULL), + read_buf_(NULL), + session_deps_(GetParam()), + connect_data_(SYNCHRONOUS, OK), + framer_(spdy_util_.spdy_version(), false), + user_agent_(kUserAgent), + url_(kRequestUrl), + proxy_host_port_(kProxyHost, kProxyPort), + endpoint_host_port_pair_(kOriginHost, kOriginPort), + proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_), + endpoint_spdy_session_key_(endpoint_host_port_pair_, + proxy_, + kPrivacyModeDisabled) { + session_deps_.net_log = net_log_.bound().net_log(); +} + +void SpdyProxyClientSocketTest::TearDown() { + sock_.reset(NULL); + if (session_.get() != NULL) + session_->spdy_session_pool()->CloseAllSessions(); + + // Empty the current queue. + base::MessageLoop::current()->RunUntilIdle(); + PlatformTest::TearDown(); +} + +void SpdyProxyClientSocketTest::Initialize(MockRead* reads, + size_t reads_count, + MockWrite* writes, + size_t writes_count) { + data_.reset(new DeterministicSocketData(reads, reads_count, + writes, writes_count)); + data_->set_connect_data(connect_data_); + data_->SetStop(2); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider( + data_.get()); + session_deps_.host_resolver->set_synchronous_mode(true); + + session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + &session_deps_); + + // Creates the SPDY session and stream. + spdy_session_ = + CreateInsecureSpdySession( + session_, endpoint_spdy_session_key_, BoundNetLog()); + base::WeakPtr<SpdyStream> spdy_stream( + CreateStreamSynchronously( + SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url_, LOWEST, + net_log_.bound())); + ASSERT_TRUE(spdy_stream.get() != NULL); + + // Create the SpdyProxyClientSocket. + sock_.reset( + new SpdyProxyClientSocket(spdy_stream, user_agent_, + endpoint_host_port_pair_, url_, + proxy_host_port_, net_log_.bound(), + session_->http_auth_cache(), + session_->http_auth_handler_factory())); +} + +scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketTest::CreateBuffer( + const char* data, int size) { + scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size)); + memcpy(buf->data(), data, size); + return buf; +} + +void SpdyProxyClientSocketTest::AssertConnectSucceeds() { + ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback())); + data_->Run(); + ASSERT_EQ(OK, read_callback_.WaitForResult()); +} + +void SpdyProxyClientSocketTest::AssertConnectFails(int result) { + ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback())); + data_->Run(); + ASSERT_EQ(result, read_callback_.WaitForResult()); +} + +void SpdyProxyClientSocketTest::AssertConnectionEstablished() { + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(200, response->headers->response_code()); + ASSERT_EQ("Connection Established", response->headers->GetStatusText()); +} + +void SpdyProxyClientSocketTest::AssertSyncReadEquals(const char* data, + int len) { + scoped_refptr<IOBuffer> buf(new IOBuffer(len)); + ASSERT_EQ(len, sock_->Read(buf.get(), len, CompletionCallback())); + ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); + ASSERT_TRUE(sock_->IsConnected()); +} + +void SpdyProxyClientSocketTest::AssertAsyncReadEquals(const char* data, + int len) { + data_->StopAfter(1); + // Issue the read, which will be completed asynchronously + scoped_refptr<IOBuffer> buf(new IOBuffer(len)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(buf.get(), len, read_callback_.callback())); + EXPECT_TRUE(sock_->IsConnected()); + data_->Run(); + + EXPECT_TRUE(sock_->IsConnected()); + + // Now the read will return + EXPECT_EQ(len, read_callback_.WaitForResult()); + ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); +} + +void SpdyProxyClientSocketTest::AssertReadStarts(const char* data, + int len) { + data_->StopAfter(1); + // Issue the read, which will be completed asynchronously + read_buf_ = new IOBuffer(len); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(read_buf_.get(), len, read_callback_.callback())); + EXPECT_TRUE(sock_->IsConnected()); +} + +void SpdyProxyClientSocketTest::AssertReadReturns(const char* data, + int len) { + EXPECT_TRUE(sock_->IsConnected()); + + // Now the read will return + EXPECT_EQ(len, read_callback_.WaitForResult()); + ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len)); +} + +void SpdyProxyClientSocketTest::AssertAsyncWriteSucceeds(const char* data, + int len) { + AssertWriteReturns(data, len, ERR_IO_PENDING); + data_->RunFor(1); + AssertWriteLength(len); +} + +void SpdyProxyClientSocketTest::AssertWriteReturns(const char* data, + int len, + int rv) { + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); + EXPECT_EQ(rv, + sock_->Write(buf.get(), buf->size(), write_callback_.callback())); +} + +void SpdyProxyClientSocketTest::AssertWriteLength(int len) { + EXPECT_EQ(len, write_callback_.WaitForResult()); +} + +void SpdyProxyClientSocketTest::AssertAsyncWriteWithReadsSucceeds( + const char* data, int len, int num_reads) { + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); + + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(buf.get(), buf->size(), write_callback_.callback())); + + for (int i = 0; i < num_reads; i++) { + Run(1); + AssertSyncReadEquals(kMsg2, kLen2); + } + + write_callback_.WaitForResult(); +} + +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +SpdyFrame* +SpdyProxyClientSocketTest::ConstructConnectRequestFrame() { + const SpdyHeaderInfo kSynStartHeader = { + SYN_STREAM, + kStreamId, + 0, + net::ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + 0, + CONTROL_FLAG_NONE, + false, + RST_STREAM_INVALID, + NULL, + 0, + DATA_FLAG_NONE + }; + bool spdy2 = spdy_util_.is_spdy2(); + const char* const kConnectHeaders[] = { + spdy2 ? "method" : ":method", "CONNECT", + spdy2 ? "url" : ":path", kOriginHostPort, + spdy2 ? "host" : ":host", kOriginHost, + "user-agent", kUserAgent, + spdy2 ? "version" : ":version", "HTTP/1.1", + }; + return spdy_util_.ConstructSpdyFrame( + kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2); +} + +// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes +// Proxy-Authorization headers. +SpdyFrame* +SpdyProxyClientSocketTest::ConstructConnectAuthRequestFrame() { + const SpdyHeaderInfo kSynStartHeader = { + SYN_STREAM, + kStreamId, + 0, + net::ConvertRequestPriorityToSpdyPriority( + LOWEST, spdy_util_.spdy_version()), + 0, + CONTROL_FLAG_NONE, + false, + RST_STREAM_INVALID, + NULL, + 0, + DATA_FLAG_NONE + }; + bool spdy2 = spdy_util_.is_spdy2(); + const char* const kConnectHeaders[] = { + spdy2 ? "method" : ":method", "CONNECT", + spdy2 ? "url" : ":path", kOriginHostPort, + spdy2 ? "host" : ":host", kOriginHost, + "user-agent", kUserAgent, + spdy2 ? "version" : ":version", "HTTP/1.1", + "proxy-authorization", "Basic Zm9vOmJhcg==", + }; + return spdy_util_.ConstructSpdyFrame( + kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2); +} + +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectReplyFrame() { + bool spdy2 = spdy_util_.is_spdy2(); + const char* const kStandardReplyHeaders[] = { + spdy2 ? "status" : ":status", "200 Connection Established", + spdy2 ? "version" : ":version", "HTTP/1.1" + }; + return spdy_util_.ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders), + 0); +} + +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +SpdyFrame* +SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { + bool spdy2 = spdy_util_.is_spdy2(); + + const char* const kStandardReplyHeaders[] = { + spdy2 ? "status" : ":status", "407 Proxy Authentication Required", + spdy2 ? "version" : ":version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + + return spdy_util_.ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders), + 0); +} + +// Constructs a SPDY SYN_REPLY frame with an HTTP 302 redirect. +SpdyFrame* +SpdyProxyClientSocketTest::ConstructConnectRedirectReplyFrame() { + bool spdy2 = spdy_util_.is_spdy2(); + + const char* const kStandardReplyHeaders[] = { + spdy2 ? "status" : ":status", "302 Found", + spdy2 ? "version" : ":version", "HTTP/1.1", + "location", kRedirectUrl, + "set-cookie", "foo=bar" + }; + + return spdy_util_.ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders), + 0); +} + +// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error. +SpdyFrame* +SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() { + bool spdy2 = spdy_util_.is_spdy2(); + + const char* const kStandardReplyHeaders[] = { + spdy2 ? "status" : ":status", "500 Internal Server Error", + spdy2 ? "version" : ":version", "HTTP/1.1", + }; + + return spdy_util_.ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders), + 0); +} + +SpdyFrame* SpdyProxyClientSocketTest::ConstructBodyFrame( + const char* data, + int length) { + return framer_.CreateDataFrame(kStreamId, data, length, DATA_FLAG_NONE); +} + +// ----------- Connect + +TEST_P(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + ASSERT_FALSE(sock_->IsConnected()); + + AssertConnectSucceeds(); + + AssertConnectionEstablished(); +} + +TEST_P(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); + + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(407, response->headers->response_code()); + ASSERT_EQ("Proxy Authentication Required", + response->headers->GetStatusText()); +} + +TEST_P(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { + scoped_ptr<SpdyFrame> conn(ConstructConnectAuthRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + AddAuthToCache(); + + AssertConnectSucceeds(); + + AssertConnectionEstablished(); +} + +TEST_P(SpdyProxyClientSocketTest, ConnectRedirects) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectRedirectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_HTTPS_PROXY_TUNNEL_RESPONSE); + + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + + const HttpResponseHeaders* headers = response->headers.get(); + ASSERT_EQ(302, headers->response_code()); + ASSERT_FALSE(headers->HasHeader("set-cookie")); + ASSERT_TRUE(headers->HasHeaderValue("content-length", "0")); + + std::string location; + ASSERT_TRUE(headers->IsRedirect(&location)); + ASSERT_EQ(location, kRedirectUrl); +} + +TEST_P(SpdyProxyClientSocketTest, ConnectFails) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + MockRead(ASYNC, 0, 1), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + ASSERT_FALSE(sock_->IsConnected()); + + AssertConnectFails(ERR_CONNECTION_CLOSED); + + ASSERT_FALSE(sock_->IsConnected()); +} + +// ----------- WasEverUsed + +TEST_P(SpdyProxyClientSocketTest, WasEverUsedReturnsCorrectValues) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + EXPECT_FALSE(sock_->WasEverUsed()); + AssertConnectSucceeds(); + EXPECT_TRUE(sock_->WasEverUsed()); + sock_->Disconnect(); + EXPECT_TRUE(sock_->WasEverUsed()); +} + +// ----------- GetPeerAddress + +TEST_P(SpdyProxyClientSocketTest, GetPeerAddressReturnsCorrectValues) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + net::IPEndPoint addr; + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr)); + + AssertConnectSucceeds(); + EXPECT_TRUE(sock_->IsConnected()); + EXPECT_EQ(OK, sock_->GetPeerAddress(&addr)); + + Run(1); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr)); + + sock_->Disconnect(); + + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr)); +} + +// ----------- Write + +TEST_P(SpdyProxyClientSocketTest, WriteSendsDataInDataFrame) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*msg1, 2, SYNCHRONOUS), + CreateMockWrite(*msg2, 3, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertAsyncWriteSucceeds(kMsg1, kLen1); + AssertAsyncWriteSucceeds(kMsg2, kLen2); +} + +TEST_P(SpdyProxyClientSocketTest, WriteSplitsLargeDataIntoMultipleFrames) { + std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(), + chunk_data.length())); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*chunk, 2, SYNCHRONOUS), + CreateMockWrite(*chunk, 3, SYNCHRONOUS), + CreateMockWrite(*chunk, 4, SYNCHRONOUS) + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x'); + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(big_data.data(), + big_data.length())); + + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(buf.get(), buf->size(), write_callback_.callback())); + data_->RunFor(3); + + EXPECT_EQ(buf->size(), write_callback_.WaitForResult()); +} + +// ----------- Read + +TEST_P(SpdyProxyClientSocketTest, ReadReadsDataInDataFrame) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); +} + +TEST_P(SpdyProxyClientSocketTest, ReadDataFromBufferedFrames) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_P(SpdyProxyClientSocketTest, ReadDataMultipleBufferedFrames) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_P(SpdyProxyClientSocketTest, + LargeReadWillMergeDataFromDifferentFrames) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg3, 2, ASYNC), + CreateMockRead(*msg3, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + // The payload from two data frames, each with kMsg3 will be combined + // together into a single read(). + AssertSyncReadEquals(kMsg33, kLen33); +} + +TEST_P(SpdyProxyClientSocketTest, MultipleShortReadsThenMoreRead) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg3, 3, ASYNC), + CreateMockRead(*msg3, 4, ASYNC), + CreateMockRead(*msg2, 5, ASYNC), + MockRead(ASYNC, 0, 6), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(4); // SpdySession consumes the next four reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from two data frames, each with kMsg3 will be combined + // together into a single read(). + AssertSyncReadEquals(kMsg33, kLen33); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_P(SpdyProxyClientSocketTest, ReadWillSplitDataFromLargeFrame) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg33(ConstructBodyFrame(kMsg33, kLen33)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg33, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from the single large data frame will be read across + // two different reads. + AssertSyncReadEquals(kMsg3, kLen3); + AssertSyncReadEquals(kMsg3, kLen3); +} + +TEST_P(SpdyProxyClientSocketTest, MultipleReadsFromSameLargeFrame) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg333(ConstructBodyFrame(kMsg333, kLen333)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg333, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + // The payload from the single large data frame will be read across + // two different reads. + AssertSyncReadEquals(kMsg33, kLen33); + + // Now attempt to do a read of more data than remains buffered + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen33)); + ASSERT_EQ(kLen3, sock_->Read(buf.get(), kLen33, read_callback_.callback())); + ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3)); + ASSERT_TRUE(sock_->IsConnected()); +} + +TEST_P(SpdyProxyClientSocketTest, ReadAuthResponseBody) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_P(SpdyProxyClientSocketTest, ReadErrorResponseBody) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectErrorReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); +} + +// ----------- Reads and Writes + +TEST_P(SpdyProxyClientSocketTest, AsyncReadAroundWrite) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*msg2, 3, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), // sync read + CreateMockRead(*msg3, 4, ASYNC), // async read + MockRead(ASYNC, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); + AssertSyncReadEquals(kMsg1, kLen1); + + AssertReadStarts(kMsg3, kLen3); + // Read should block until after the write succeeds + + AssertAsyncWriteSucceeds(kMsg2, kLen2); // Runs 1 step + + ASSERT_FALSE(read_callback_.have_result()); + Run(1); + // Now the read will return + AssertReadReturns(kMsg3, kLen3); +} + +TEST_P(SpdyProxyClientSocketTest, AsyncWriteAroundReads) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*msg2, 4, ASYNC), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg3, 3, ASYNC), + MockRead(ASYNC, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); + AssertSyncReadEquals(kMsg1, kLen1); + // Write should block until the read completes + AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING); + + AssertAsyncReadEquals(kMsg3, kLen3); + + ASSERT_FALSE(write_callback_.have_result()); + + // Now the write will complete + Run(1); + AssertWriteLength(kLen2); +} + +// ----------- Reading/Writing on Closed socket + +// Reading from an already closed socket should return 0 +TEST_P(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); + + ASSERT_FALSE(sock_->IsConnected()); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_FALSE(sock_->IsConnectedAndIdle()); +} + +// Read pending when socket is closed should return 0 +TEST_P(SpdyProxyClientSocketTest, PendingReadOnCloseReturnsZero) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertReadStarts(kMsg1, kLen1); + + Run(1); + + ASSERT_EQ(0, read_callback_.WaitForResult()); +} + +// Reading from a disconnected socket is an error +TEST_P(SpdyProxyClientSocketTest, + ReadOnDisconnectSocketReturnsNotConnected) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + sock_->Disconnect(); + + ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Read(NULL, 1, CompletionCallback())); +} + +// Reading buffered data from an already closed socket should return +// buffered data, then 0. +TEST_P(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsBufferedData) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); + + ASSERT_FALSE(sock_->IsConnected()); + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1)); + ASSERT_EQ(kLen1, sock_->Read(buf.get(), kLen1, CompletionCallback())); + ASSERT_EQ(std::string(kMsg1, kLen1), std::string(buf->data(), kLen1)); + + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + sock_->Disconnect(); + ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Read(NULL, 1, CompletionCallback())); +} + +// Calling Write() on a closed socket is an error +TEST_P(SpdyProxyClientSocketTest, WriteOnClosedStream) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // Read EOF which will close the stream + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Write(buf.get(), buf->size(), CompletionCallback())); +} + +// Calling Write() on a disconnected socket is an error +TEST_P(SpdyProxyClientSocketTest, WriteOnDisconnectedSocket) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + sock_->Disconnect(); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Write(buf.get(), buf->size(), CompletionCallback())); +} + +// If the socket is closed with a pending Write(), the callback +// should be called with ERR_CONNECTION_CLOSED. +TEST_P(SpdyProxyClientSocketTest, WritePendingOnClose) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(ASYNC, ERR_ABORTED, 2), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(buf.get(), buf->size(), write_callback_.callback())); + + CloseSpdySession(ERR_ABORTED, std::string()); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult()); +} + +// If the socket is Disconnected with a pending Write(), the callback +// should not be called. +TEST_P(SpdyProxyClientSocketTest, DisconnectWithWritePending) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(SYNCHRONOUS, 0, 2), // EOF + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(buf.get(), buf->size(), write_callback_.callback())); + + sock_->Disconnect(); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_FALSE(write_callback_.have_result()); +} + +// If the socket is Disconnected with a pending Read(), the callback +// should not be called. +TEST_P(SpdyProxyClientSocketTest, DisconnectWithReadPending) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(buf.get(), kLen1, read_callback_.callback())); + + sock_->Disconnect(); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_FALSE(read_callback_.have_result()); +} + +// If the socket is Reset when both a read and write are pending, +// both should be called back. +TEST_P(SpdyProxyClientSocketTest, RstWithReadAndWritePending) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(ASYNC, ERR_ABORTED, 3), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*rst, 2, ASYNC), + MockRead(ASYNC, 0, 4) // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(read_buf.get(), kLen1, read_callback_.callback())); + + scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ( + ERR_IO_PENDING, + sock_->Write( + write_buf.get(), write_buf->size(), write_callback_.callback())); + + Run(2); + + EXPECT_TRUE(sock_.get()); + EXPECT_TRUE(read_callback_.have_result()); + EXPECT_TRUE(write_callback_.have_result()); +} + +// Makes sure the proxy client socket's source gets the expected NetLog events +// and only the expected NetLog events (No SpdySession events). +TEST_P(SpdyProxyClientSocketTest, NetLog) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + + NetLog::Source sock_source = sock_->NetLog().source(); + sock_.reset(); + + CapturingNetLog::CapturedEntryList entry_list; + net_log_.GetEntriesForSource(sock_source, &entry_list); + + ASSERT_EQ(entry_list.size(), 10u); + EXPECT_TRUE(LogContainsBeginEvent(entry_list, 0, NetLog::TYPE_SOCKET_ALIVE)); + EXPECT_TRUE(LogContainsEvent(entry_list, 1, + NetLog::TYPE_SPDY_PROXY_CLIENT_SESSION, + NetLog::PHASE_NONE)); + EXPECT_TRUE(LogContainsBeginEvent(entry_list, 2, + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST)); + EXPECT_TRUE(LogContainsEvent(entry_list, 3, + NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE)); + EXPECT_TRUE(LogContainsEndEvent(entry_list, 4, + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST)); + EXPECT_TRUE(LogContainsBeginEvent(entry_list, 5, + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS)); + EXPECT_TRUE(LogContainsEvent(entry_list, 6, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE)); + EXPECT_TRUE(LogContainsEndEvent(entry_list, 7, + NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS)); + EXPECT_TRUE(LogContainsEvent(entry_list, 8, + NetLog::TYPE_SOCKET_BYTES_RECEIVED, + NetLog::PHASE_NONE)); + EXPECT_TRUE(LogContainsEndEvent(entry_list, 9, NetLog::TYPE_SOCKET_ALIVE)); +} + +// CompletionCallback that causes the SpdyProxyClientSocket to be +// deleted when Run is invoked. +class DeleteSockCallback : public TestCompletionCallbackBase { + public: + explicit DeleteSockCallback(scoped_ptr<SpdyProxyClientSocket>* sock) + : sock_(sock), + callback_(base::Bind(&DeleteSockCallback::OnComplete, + base::Unretained(this))) { + } + + virtual ~DeleteSockCallback() { + } + + const CompletionCallback& callback() const { return callback_; } + + private: + void OnComplete(int result) { + sock_->reset(NULL); + SetResult(result); + } + + scoped_ptr<SpdyProxyClientSocket>* sock_; + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(DeleteSockCallback); +}; + +// If the socket is Reset when both a read and write are pending, and the +// read callback causes the socket to be deleted, the write callback should +// not be called. +TEST_P(SpdyProxyClientSocketTest, RstWithReadAndWritePendingDelete) { + scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(ASYNC, ERR_ABORTED, 3), + }; + + scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*rst, 2, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + DeleteSockCallback read_callback(&sock_); + + scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(read_buf.get(), kLen1, read_callback.callback())); + + scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ( + ERR_IO_PENDING, + sock_->Write( + write_buf.get(), write_buf->size(), write_callback_.callback())); + + Run(1); + + EXPECT_FALSE(sock_.get()); + EXPECT_TRUE(read_callback.have_result()); + EXPECT_FALSE(write_callback_.have_result()); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_read_queue.cc b/chromium/net/spdy/spdy_read_queue.cc new file mode 100644 index 00000000000..36f1e06c4cc --- /dev/null +++ b/chromium/net/spdy/spdy_read_queue.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_read_queue.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "net/spdy/spdy_buffer.h" + +namespace net { + +SpdyReadQueue::SpdyReadQueue() : total_size_(0) {} + +SpdyReadQueue::~SpdyReadQueue() { + Clear(); +} + +bool SpdyReadQueue::IsEmpty() const { + DCHECK_EQ(queue_.empty(), total_size_ == 0); + return queue_.empty(); +} + +size_t SpdyReadQueue::GetTotalSize() const { + return total_size_; +} + +void SpdyReadQueue::Enqueue(scoped_ptr<SpdyBuffer> buffer) { + DCHECK_GT(buffer->GetRemainingSize(), 0u); + total_size_ += buffer->GetRemainingSize(); + queue_.push_back(buffer.release()); +} + +size_t SpdyReadQueue::Dequeue(char* out, size_t len) { + DCHECK_GT(len, 0u); + size_t bytes_copied = 0; + while (!queue_.empty() && bytes_copied < len) { + SpdyBuffer* buffer = queue_.front(); + size_t bytes_to_copy = + std::min(len - bytes_copied, buffer->GetRemainingSize()); + memcpy(out + bytes_copied, buffer->GetRemainingData(), bytes_to_copy); + bytes_copied += bytes_to_copy; + if (bytes_to_copy == buffer->GetRemainingSize()) { + delete queue_.front(); + queue_.pop_front(); + } else { + buffer->Consume(bytes_to_copy); + } + } + total_size_ -= bytes_copied; + return bytes_copied; +} + +void SpdyReadQueue::Clear() { + STLDeleteElements(&queue_); + queue_.clear(); +} + +} // namespace diff --git a/chromium/net/spdy/spdy_read_queue.h b/chromium/net/spdy/spdy_read_queue.h new file mode 100644 index 00000000000..65f3dafc93f --- /dev/null +++ b/chromium/net/spdy/spdy_read_queue.h @@ -0,0 +1,51 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_BUFFER_QUEUE_H_ +#define NET_SPDY_SPDY_BUFFER_QUEUE_H_ + +#include <cstddef> +#include <deque> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" + +namespace net { + +class SpdyBuffer; + +// A FIFO queue of incoming data from a SPDY connection. Useful for +// SpdyStream delegates. +class NET_EXPORT_PRIVATE SpdyReadQueue { + public: + SpdyReadQueue(); + ~SpdyReadQueue(); + + // Returns whether there's anything in the queue. + bool IsEmpty() const; + + // Returns the total number of bytes in the queue. + size_t GetTotalSize() const; + + // Enqueues the bytes in |buffer|. + void Enqueue(scoped_ptr<SpdyBuffer> buffer); + + // Dequeues up to |len| (which must be positive) bytes into + // |out|. Returns the number of bytes dequeued. + size_t Dequeue(char* out, size_t len); + + // Removes all bytes from the queue. + void Clear(); + + private: + std::deque<SpdyBuffer*> queue_; + size_t total_size_; + + DISALLOW_COPY_AND_ASSIGN(SpdyReadQueue); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_BUFFER_QUEUE_H_ diff --git a/chromium/net/spdy/spdy_read_queue_unittest.cc b/chromium/net/spdy/spdy_read_queue_unittest.cc new file mode 100644 index 00000000000..7281f6857cf --- /dev/null +++ b/chromium/net/spdy/spdy_read_queue_unittest.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_read_queue.h" + +#include <algorithm> +#include <cstddef> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "net/spdy/spdy_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +const char kData[] = "SPDY read queue test data.\0Some more data."; +const size_t kDataSize = arraysize(kData); + +// Enqueues |data| onto |queue| in chunks of at most |max_buffer_size| +// bytes. +void EnqueueString(const std::string& data, + size_t max_buffer_size, + SpdyReadQueue* queue) { + ASSERT_GT(data.size(), 0u); + ASSERT_GT(max_buffer_size, 0u); + size_t old_total_size = queue->GetTotalSize(); + for (size_t i = 0; i < data.size();) { + size_t buffer_size = std::min(data.size() - i, max_buffer_size); + queue->Enqueue( + scoped_ptr<SpdyBuffer>(new SpdyBuffer(data.data() + i, buffer_size))); + i += buffer_size; + EXPECT_FALSE(queue->IsEmpty()); + EXPECT_EQ(old_total_size + i, queue->GetTotalSize()); + } +} + +// Dequeues all bytes in |queue| in chunks of at most +// |max_buffer_size| bytes and returns the data as a string. +std::string DrainToString(size_t max_buffer_size, SpdyReadQueue* queue) { + std::string data; + + // Pad the buffer so we can detect out-of-bound writes. + size_t padding = std::max(static_cast<size_t>(4096), queue->GetTotalSize()); + size_t buffer_size_with_padding = padding + max_buffer_size + padding; + scoped_ptr<char[]> buffer(new char[buffer_size_with_padding]); + std::memset(buffer.get(), 0, buffer_size_with_padding); + char* buffer_data = buffer.get() + padding; + + while (!queue->IsEmpty()) { + size_t old_total_size = queue->GetTotalSize(); + EXPECT_GT(old_total_size, 0u); + size_t dequeued_bytes = queue->Dequeue(buffer_data, max_buffer_size); + + // Make sure |queue| doesn't write past either end of its given + // boundaries. + for (int i = 1; i <= static_cast<int>(padding); ++i) { + EXPECT_EQ('\0', buffer_data[-i]) << -i; + } + for (size_t i = 0; i < padding; ++i) { + EXPECT_EQ('\0', buffer_data[max_buffer_size + i]) << i; + } + + data.append(buffer_data, dequeued_bytes); + EXPECT_EQ(dequeued_bytes, std::min(max_buffer_size, dequeued_bytes)); + EXPECT_EQ(queue->GetTotalSize(), old_total_size - dequeued_bytes); + } + EXPECT_TRUE(queue->IsEmpty()); + return data; +} + +// Enqueue a test string with the given enqueue/dequeue max buffer +// sizes. +void RunEnqueueDequeueTest(size_t enqueue_max_buffer_size, + size_t dequeue_max_buffer_size) { + std::string data(kData, kDataSize); + SpdyReadQueue read_queue; + EnqueueString(data, enqueue_max_buffer_size, &read_queue); + const std::string& drained_data = + DrainToString(dequeue_max_buffer_size, &read_queue); + EXPECT_EQ(data, drained_data); +} + +class SpdyReadQueueTest : public ::testing::Test {}; + +// Call RunEnqueueDequeueTest() with various buffer size combinatinos. + +TEST_F(SpdyReadQueueTest, LargeEnqueueAndDequeueBuffers) { + RunEnqueueDequeueTest(2 * kDataSize, 2 * kDataSize); +} + +TEST_F(SpdyReadQueueTest, OneByteEnqueueAndDequeueBuffers) { + RunEnqueueDequeueTest(1, 1); +} + +TEST_F(SpdyReadQueueTest, CoprimeBufferSizes) { + RunEnqueueDequeueTest(2, 3); + RunEnqueueDequeueTest(3, 2); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/spdy/spdy_session.cc b/chromium/net/spdy/spdy_session.cc new file mode 100644 index 00000000000..baef1952652 --- /dev/null +++ b/chromium/net/spdy/spdy_session.cc @@ -0,0 +1,2917 @@ +// 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 "net/spdy/spdy_session.h" + +#include <algorithm> +#include <map> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" +#include "base/metrics/stats_counters.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "net/base/connection_type_histograms.h" +#include "net/base/net_log.h" +#include "net/base/net_util.h" +#include "net/cert/asn1_util.h" +#include "net/http/http_network_session.h" +#include "net/http/http_server_properties.h" +#include "net/spdy/spdy_buffer_producer.h" +#include "net/spdy/spdy_credential_builder.h" +#include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_stream.h" +#include "net/ssl/server_bound_cert_service.h" + +namespace net { + +namespace { + +const int kReadBufferSize = 8 * 1024; +const int kDefaultConnectionAtRiskOfLossSeconds = 10; +const int kHungIntervalSeconds = 10; + +// Always start at 1 for the first stream id. +const SpdyStreamId kFirstStreamId = 1; + +// Minimum seconds that unclaimed pushed streams will be kept in memory. +const int kMinPushedStreamLifetimeSeconds = 300; + +base::Value* NetLogSpdySynCallback(const SpdyHeaderBlock* headers, + bool fin, + bool unidirectional, + SpdyStreamId stream_id, + SpdyStreamId associated_stream, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + base::ListValue* headers_list = new base::ListValue(); + for (SpdyHeaderBlock::const_iterator it = headers->begin(); + it != headers->end(); ++it) { + headers_list->Append(new base::StringValue(base::StringPrintf( + "%s: %s", it->first.c_str(), + (ShouldShowHttpHeaderValue( + it->first) ? it->second : "[elided]").c_str()))); + } + dict->SetBoolean("fin", fin); + dict->SetBoolean("unidirectional", unidirectional); + dict->Set("headers", headers_list); + dict->SetInteger("stream_id", stream_id); + if (associated_stream) + dict->SetInteger("associated_stream", associated_stream); + return dict; +} + +base::Value* NetLogSpdyCredentialCallback(size_t slot, + const std::string* origin, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("slot", slot); + dict->SetString("origin", *origin); + return dict; +} + +base::Value* NetLogSpdySessionCloseCallback(int net_error, + const std::string* description, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("net_error", net_error); + dict->SetString("description", *description); + return dict; +} + +base::Value* NetLogSpdySessionCallback(const HostPortProxyPair* host_pair, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("host", host_pair->first.ToString()); + dict->SetString("proxy", host_pair->second.ToPacString()); + return dict; +} + +base::Value* NetLogSpdySettingsCallback(const HostPortPair& host_port_pair, + bool clear_persisted, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("host", host_port_pair.ToString()); + dict->SetBoolean("clear_persisted", clear_persisted); + return dict; +} + +base::Value* NetLogSpdySettingCallback(SpdySettingsIds id, + SpdySettingsFlags flags, + uint32 value, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("id", id); + dict->SetInteger("flags", flags); + dict->SetInteger("value", value); + return dict; +} + +base::Value* NetLogSpdySendSettingsCallback(const SettingsMap* settings, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + base::ListValue* settings_list = new base::ListValue(); + for (SettingsMap::const_iterator it = settings->begin(); + it != settings->end(); ++it) { + const SpdySettingsIds id = it->first; + const SpdySettingsFlags flags = it->second.first; + const uint32 value = it->second.second; + settings_list->Append(new base::StringValue( + base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value))); + } + dict->Set("settings", settings_list); + return dict; +} + +base::Value* NetLogSpdyWindowUpdateFrameCallback( + SpdyStreamId stream_id, + uint32 delta, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("stream_id", static_cast<int>(stream_id)); + dict->SetInteger("delta", delta); + return dict; +} + +base::Value* NetLogSpdySessionWindowUpdateCallback( + int32 delta, + int32 window_size, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("delta", delta); + dict->SetInteger("window_size", window_size); + return dict; +} + +base::Value* NetLogSpdyDataCallback(SpdyStreamId stream_id, + int size, + bool fin, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("stream_id", static_cast<int>(stream_id)); + dict->SetInteger("size", size); + dict->SetBoolean("fin", fin); + return dict; +} + +base::Value* NetLogSpdyRstCallback(SpdyStreamId stream_id, + int status, + const std::string* description, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("stream_id", static_cast<int>(stream_id)); + dict->SetInteger("status", status); + dict->SetString("description", *description); + return dict; +} + +base::Value* NetLogSpdyPingCallback(uint32 unique_id, + const char* type, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("unique_id", unique_id); + dict->SetString("type", type); + return dict; +} + +base::Value* NetLogSpdyGoAwayCallback(SpdyStreamId last_stream_id, + int active_streams, + int unclaimed_streams, + SpdyGoAwayStatus status, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("last_accepted_stream_id", + static_cast<int>(last_stream_id)); + dict->SetInteger("active_streams", active_streams); + dict->SetInteger("unclaimed_streams", unclaimed_streams); + dict->SetInteger("status", static_cast<int>(status)); + return dict; +} + +// The maximum number of concurrent streams we will ever create. Even if +// the server permits more, we will never exceed this limit. +const size_t kMaxConcurrentStreamLimit = 256; + +} // namespace + +SpdyStreamRequest::SpdyStreamRequest() { + Reset(); +} + +SpdyStreamRequest::~SpdyStreamRequest() { + CancelRequest(); +} + +int SpdyStreamRequest::StartRequest( + SpdyStreamType type, + const base::WeakPtr<SpdySession>& session, + const GURL& url, + RequestPriority priority, + const BoundNetLog& net_log, + const CompletionCallback& callback) { + DCHECK(session.get()); + DCHECK(!session_.get()); + DCHECK(!stream_.get()); + DCHECK(callback_.is_null()); + + type_ = type; + session_ = session; + url_ = url; + priority_ = priority; + net_log_ = net_log; + callback_ = callback; + + base::WeakPtr<SpdyStream> stream; + int rv = session->TryCreateStream(this, &stream); + if (rv == OK) { + Reset(); + stream_ = stream; + } + return rv; +} + +void SpdyStreamRequest::CancelRequest() { + if (session_.get()) + session_->CancelStreamRequest(this); + Reset(); +} + +base::WeakPtr<SpdyStream> SpdyStreamRequest::ReleaseStream() { + DCHECK(!session_.get()); + base::WeakPtr<SpdyStream> stream = stream_; + DCHECK(stream.get()); + Reset(); + return stream; +} + +void SpdyStreamRequest::OnRequestCompleteSuccess( + base::WeakPtr<SpdyStream>* stream) { + DCHECK(session_.get()); + DCHECK(!stream_.get()); + DCHECK(!callback_.is_null()); + CompletionCallback callback = callback_; + Reset(); + DCHECK(*stream); + stream_ = *stream; + callback.Run(OK); +} + +void SpdyStreamRequest::OnRequestCompleteFailure(int rv) { + DCHECK(session_.get()); + DCHECK(!stream_.get()); + DCHECK(!callback_.is_null()); + CompletionCallback callback = callback_; + Reset(); + DCHECK_NE(rv, OK); + callback.Run(rv); +} + +void SpdyStreamRequest::Reset() { + type_ = SPDY_BIDIRECTIONAL_STREAM; + session_.reset(); + stream_.reset(); + url_ = GURL(); + priority_ = MINIMUM_PRIORITY; + net_log_ = BoundNetLog(); + callback_.Reset(); +} + +SpdySession::ActiveStreamInfo::ActiveStreamInfo() + : stream(NULL), + waiting_for_syn_reply(false) {} + +SpdySession::ActiveStreamInfo::ActiveStreamInfo(SpdyStream* stream) + : stream(stream), + waiting_for_syn_reply(stream->type() != SPDY_PUSH_STREAM) {} + +SpdySession::ActiveStreamInfo::~ActiveStreamInfo() {} + +SpdySession::PushedStreamInfo::PushedStreamInfo() : stream_id(0) {} + +SpdySession::PushedStreamInfo::PushedStreamInfo( + SpdyStreamId stream_id, + base::TimeTicks creation_time) + : stream_id(stream_id), + creation_time(creation_time) {} + +SpdySession::PushedStreamInfo::~PushedStreamInfo() {} + +SpdySession::SpdySession( + const SpdySessionKey& spdy_session_key, + const base::WeakPtr<HttpServerProperties>& http_server_properties, + bool verify_domain_authentication, + bool enable_sending_initial_data, + bool enable_credential_frames, + bool enable_compression, + bool enable_ping_based_connection_checking, + NextProto default_protocol, + size_t stream_initial_recv_window_size, + size_t initial_max_concurrent_streams, + size_t max_concurrent_streams_limit, + TimeFunc time_func, + const HostPortPair& trusted_spdy_proxy, + NetLog* net_log) + : weak_factory_(this), + in_io_loop_(false), + spdy_session_key_(spdy_session_key), + pool_(NULL), + http_server_properties_(http_server_properties), + read_buffer_(new IOBuffer(kReadBufferSize)), + stream_hi_water_mark_(kFirstStreamId), + in_flight_write_frame_type_(DATA), + in_flight_write_frame_size_(0), + is_secure_(false), + certificate_error_code_(OK), + availability_state_(STATE_AVAILABLE), + read_state_(READ_STATE_DO_READ), + write_state_(WRITE_STATE_IDLE), + error_on_close_(OK), + max_concurrent_streams_(initial_max_concurrent_streams == 0 ? + kInitialMaxConcurrentStreams : + initial_max_concurrent_streams), + max_concurrent_streams_limit_(max_concurrent_streams_limit == 0 ? + kMaxConcurrentStreamLimit : + max_concurrent_streams_limit), + streams_initiated_count_(0), + streams_pushed_count_(0), + streams_pushed_and_claimed_count_(0), + streams_abandoned_count_(0), + total_bytes_received_(0), + sent_settings_(false), + received_settings_(false), + stalled_streams_(0), + pings_in_flight_(0), + next_ping_id_(1), + last_activity_time_(time_func()), + check_ping_status_pending_(false), + send_connection_header_prefix_(false), + flow_control_state_(FLOW_CONTROL_NONE), + stream_initial_send_window_size_(kSpdyStreamInitialWindowSize), + stream_initial_recv_window_size_(stream_initial_recv_window_size == 0 ? + kDefaultInitialRecvWindowSize : + stream_initial_recv_window_size), + session_send_window_size_(0), + session_recv_window_size_(0), + session_unacked_recv_window_bytes_(0), + net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)), + verify_domain_authentication_(verify_domain_authentication), + enable_sending_initial_data_(enable_sending_initial_data), + enable_credential_frames_(enable_credential_frames), + enable_compression_(enable_compression), + enable_ping_based_connection_checking_( + enable_ping_based_connection_checking), + protocol_(default_protocol), + credential_state_(SpdyCredentialState::kDefaultNumSlots), + connection_at_risk_of_loss_time_( + base::TimeDelta::FromSeconds(kDefaultConnectionAtRiskOfLossSeconds)), + hung_interval_( + base::TimeDelta::FromSeconds(kHungIntervalSeconds)), + trusted_spdy_proxy_(trusted_spdy_proxy), + time_func_(time_func) { + // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we + // stop supporting SPDY/1. + DCHECK_GE(protocol_, kProtoSPDY2); + DCHECK_LE(protocol_, kProtoSPDYMaximumVersion); + DCHECK(HttpStreamFactory::spdy_enabled()); + net_log_.BeginEvent( + NetLog::TYPE_SPDY_SESSION, + base::Bind(&NetLogSpdySessionCallback, &host_port_proxy_pair())); + next_unclaimed_push_stream_sweep_time_ = time_func_() + + base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds); + // TODO(mbelshe): consider randomization of the stream_hi_water_mark. +} + +SpdySession::~SpdySession() { + CHECK(!in_io_loop_); + DCHECK(!pool_); + DcheckClosed(); + + // TODO(akalin): Check connection->is_initialized() instead. This + // requires re-working CreateFakeSpdySession(), though. + DCHECK(connection_->socket()); + // With SPDY we can't recycle sockets. + connection_->socket()->Disconnect(); + + RecordHistograms(); + + net_log_.EndEvent(NetLog::TYPE_SPDY_SESSION); +} + +Error SpdySession::InitializeWithSocket( + scoped_ptr<ClientSocketHandle> connection, + SpdySessionPool* pool, + bool is_secure, + int certificate_error_code) { + CHECK(!in_io_loop_); + DCHECK_EQ(availability_state_, STATE_AVAILABLE); + DCHECK_EQ(read_state_, READ_STATE_DO_READ); + DCHECK_EQ(write_state_, WRITE_STATE_IDLE); + DCHECK(!connection_); + + DCHECK(certificate_error_code == OK || + certificate_error_code < ERR_IO_PENDING); + // TODO(akalin): Check connection->is_initialized() instead. This + // requires re-working CreateFakeSpdySession(), though. + DCHECK(connection->socket()); + + base::StatsCounter spdy_sessions("spdy.sessions"); + spdy_sessions.Increment(); + + connection_ = connection.Pass(); + is_secure_ = is_secure; + certificate_error_code_ = certificate_error_code; + + NextProto protocol_negotiated = + connection_->socket()->GetNegotiatedProtocol(); + if (protocol_negotiated != kProtoUnknown) { + protocol_ = protocol_negotiated; + } + // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we + // stop supporting SPDY/1. + DCHECK_GE(protocol_, kProtoSPDY2); + DCHECK_LE(protocol_, kProtoSPDYMaximumVersion); + + SSLClientSocket* ssl_socket = GetSSLClientSocket(); + if (ssl_socket && ssl_socket->WasChannelIDSent()) { + // According to the SPDY spec, the credential associated with the TLS + // connection is stored in slot[1]. + credential_state_.SetHasCredential(GURL("https://" + + host_port_pair().ToString())); + } + + if (protocol_ == kProtoHTTP2Draft04) + send_connection_header_prefix_ = true; + + if (protocol_ >= kProtoSPDY31) { + flow_control_state_ = FLOW_CONTROL_STREAM_AND_SESSION; + session_send_window_size_ = kSpdySessionInitialWindowSize; + session_recv_window_size_ = kSpdySessionInitialWindowSize; + } else if (protocol_ >= kProtoSPDY3) { + flow_control_state_ = FLOW_CONTROL_STREAM; + } else { + flow_control_state_ = FLOW_CONTROL_NONE; + } + + buffered_spdy_framer_.reset( + new BufferedSpdyFramer(NextProtoToSpdyMajorVersion(protocol_), + enable_compression_)); + buffered_spdy_framer_->set_visitor(this); + buffered_spdy_framer_->set_debug_visitor(this); + UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol_, kProtoMaximumVersion); +#if defined(SPDY_PROXY_AUTH_ORIGIN) + UMA_HISTOGRAM_BOOLEAN("Net.SpdySessions_DataReductionProxy", + host_port_pair().Equals(HostPortPair::FromURL( + GURL(SPDY_PROXY_AUTH_ORIGIN)))); +#endif + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_INITIALIZED, + connection_->socket()->NetLog().source().ToEventParametersCallback()); + + int error = DoReadLoop(READ_STATE_DO_READ, OK); + if (error == ERR_IO_PENDING) + error = OK; + if (error == OK) { + DCHECK_NE(availability_state_, STATE_CLOSED); + connection_->AddLayeredPool(this); + if (enable_sending_initial_data_) + SendInitialData(); + pool_ = pool; + } else { + DcheckClosed(); + } + return static_cast<Error>(error); +} + +bool SpdySession::VerifyDomainAuthentication(const std::string& domain) { + if (!verify_domain_authentication_) + return true; + + if (availability_state_ == STATE_CLOSED) + return false; + + SSLInfo ssl_info; + bool was_npn_negotiated; + NextProto protocol_negotiated = kProtoUnknown; + if (!GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated)) + return true; // This is not a secure session, so all domains are okay. + + return !ssl_info.client_cert_sent && + (enable_credential_frames_ || !ssl_info.channel_id_sent || + ServerBoundCertService::GetDomainForHost(domain) == + ServerBoundCertService::GetDomainForHost(host_port_pair().host())) && + ssl_info.cert->VerifyNameMatch(domain); +} + +int SpdySession::GetPushStream( + const GURL& url, + base::WeakPtr<SpdyStream>* stream, + const BoundNetLog& stream_net_log) { + CHECK(!in_io_loop_); + + stream->reset(); + + // TODO(akalin): Add unit test exercising this code path. + if (availability_state_ == STATE_CLOSED) + return ERR_CONNECTION_CLOSED; + + Error err = TryAccessStream(url); + if (err != OK) + return err; + + *stream = GetActivePushStream(url); + if (*stream) { + DCHECK_LT(streams_pushed_and_claimed_count_, streams_pushed_count_); + streams_pushed_and_claimed_count_++; + } + return OK; +} + +// {,Try}CreateStream() and TryAccessStream() can be called with +// |in_io_loop_| set if a stream is being created in response to +// another being closed due to received data. + +Error SpdySession::TryAccessStream(const GURL& url) { + DCHECK_NE(availability_state_, STATE_CLOSED); + + if (is_secure_ && certificate_error_code_ != OK && + (url.SchemeIs("https") || url.SchemeIs("wss"))) { + RecordProtocolErrorHistogram( + PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION); + CloseSessionResult result = DoCloseSession( + static_cast<Error>(certificate_error_code_), + "Tried to get SPDY stream for secure content over an unauthenticated " + "session."); + DCHECK_EQ(result, SESSION_CLOSED_AND_REMOVED); + return ERR_SPDY_PROTOCOL_ERROR; + } + return OK; +} + +int SpdySession::TryCreateStream(SpdyStreamRequest* request, + base::WeakPtr<SpdyStream>* stream) { + CHECK(request); + + if (availability_state_ == STATE_GOING_AWAY) + return ERR_FAILED; + + // TODO(akalin): Add unit test exercising this code path. + if (availability_state_ == STATE_CLOSED) + return ERR_CONNECTION_CLOSED; + + Error err = TryAccessStream(request->url()); + if (err != OK) + return err; + + if (!max_concurrent_streams_ || + (active_streams_.size() + created_streams_.size() < + max_concurrent_streams_)) { + return CreateStream(*request, stream); + } + + stalled_streams_++; + net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_STALLED_MAX_STREAMS); + pending_create_stream_queues_[request->priority()].push_back(request); + return ERR_IO_PENDING; +} + +int SpdySession::CreateStream(const SpdyStreamRequest& request, + base::WeakPtr<SpdyStream>* stream) { + DCHECK_GE(request.priority(), MINIMUM_PRIORITY); + DCHECK_LT(request.priority(), NUM_PRIORITIES); + + if (availability_state_ == STATE_GOING_AWAY) + return ERR_FAILED; + + // TODO(akalin): Add unit test exercising this code path. + if (availability_state_ == STATE_CLOSED) + return ERR_CONNECTION_CLOSED; + + Error err = TryAccessStream(request.url()); + if (err != OK) { + // This should have been caught in TryCreateStream(). + NOTREACHED(); + return err; + } + + DCHECK(connection_->socket()); + DCHECK(connection_->socket()->IsConnected()); + if (connection_->socket()) { + UMA_HISTOGRAM_BOOLEAN("Net.SpdySession.CreateStreamWithSocketConnected", + connection_->socket()->IsConnected()); + if (!connection_->socket()->IsConnected()) { + CloseSessionResult result = DoCloseSession( + ERR_CONNECTION_CLOSED, + "Tried to create SPDY stream for a closed socket connection."); + DCHECK_EQ(result, SESSION_CLOSED_AND_REMOVED); + return ERR_CONNECTION_CLOSED; + } + } + + scoped_ptr<SpdyStream> new_stream( + new SpdyStream(request.type(), GetWeakPtr(), request.url(), + request.priority(), + stream_initial_send_window_size_, + stream_initial_recv_window_size_, + request.net_log())); + *stream = new_stream->GetWeakPtr(); + InsertCreatedStream(new_stream.Pass()); + + UMA_HISTOGRAM_CUSTOM_COUNTS( + "Net.SpdyPriorityCount", + static_cast<int>(request.priority()), 0, 10, 11); + + return OK; +} + +void SpdySession::CancelStreamRequest(SpdyStreamRequest* request) { + CHECK(request); + + if (DCHECK_IS_ON()) { + // |request| should not be in a queue not matching its priority. + for (int i = 0; i < NUM_PRIORITIES; ++i) { + if (request->priority() == i) + continue; + PendingStreamRequestQueue* queue = &pending_create_stream_queues_[i]; + DCHECK(std::find(queue->begin(), queue->end(), request) == queue->end()); + } + } + + PendingStreamRequestQueue* queue = + &pending_create_stream_queues_[request->priority()]; + // Remove |request| from |queue| while preserving the order of the + // other elements. + PendingStreamRequestQueue::iterator it = + std::find(queue->begin(), queue->end(), request); + if (it != queue->end()) { + it = queue->erase(it); + // |request| should be in the queue at most once, and if it is + // present, should not be pending completion. + DCHECK(std::find(it, queue->end(), request) == queue->end()); + DCHECK(!ContainsKey(pending_stream_request_completions_, + request)); + return; + } + + pending_stream_request_completions_.erase(request); +} + +void SpdySession::ProcessPendingStreamRequests() { + // Like |max_concurrent_streams_|, 0 means infinite for + // |max_requests_to_process|. + size_t max_requests_to_process = 0; + if (max_concurrent_streams_ != 0) { + max_requests_to_process = + max_concurrent_streams_ - + (active_streams_.size() + created_streams_.size()); + } + for (size_t i = 0; + max_requests_to_process == 0 || i < max_requests_to_process; ++i) { + bool processed_request = false; + for (int j = NUM_PRIORITIES - 1; j >= MINIMUM_PRIORITY; --j) { + if (pending_create_stream_queues_[j].empty()) + continue; + + SpdyStreamRequest* pending_request = + pending_create_stream_queues_[j].front(); + CHECK(pending_request); + pending_create_stream_queues_[j].pop_front(); + processed_request = true; + DCHECK(!ContainsKey(pending_stream_request_completions_, + pending_request)); + pending_stream_request_completions_.insert(pending_request); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&SpdySession::CompleteStreamRequest, + weak_factory_.GetWeakPtr(), pending_request)); + break; + } + if (!processed_request) + break; + } +} + +bool SpdySession::NeedsCredentials() const { + if (!is_secure_) + return false; + SSLClientSocket* ssl_socket = GetSSLClientSocket(); + if (ssl_socket->GetNegotiatedProtocol() < kProtoSPDY3) + return false; + return ssl_socket->WasChannelIDSent(); +} + +void SpdySession::AddPooledAlias(const SpdySessionKey& alias_key) { + pooled_aliases_.insert(alias_key); +} + +int SpdySession::GetProtocolVersion() const { + DCHECK(buffered_spdy_framer_.get()); + return buffered_spdy_framer_->protocol_version(); +} + +base::WeakPtr<SpdySession> SpdySession::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + +bool SpdySession::CloseOneIdleConnection() { + CHECK(!in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK(pool_); + if (!active_streams_.empty()) + return false; + CloseSessionResult result = + DoCloseSession(ERR_CONNECTION_CLOSED, "Closing one idle connection."); + if (result != SESSION_CLOSED_AND_REMOVED) { + NOTREACHED(); + return false; + } + return true; +} + +void SpdySession::EnqueueStreamWrite( + const base::WeakPtr<SpdyStream>& stream, + SpdyFrameType frame_type, + scoped_ptr<SpdyBufferProducer> producer) { + DCHECK(frame_type == HEADERS || + frame_type == DATA || + frame_type == CREDENTIAL || + frame_type == SYN_STREAM); + EnqueueWrite(stream->priority(), frame_type, producer.Pass(), stream); +} + +scoped_ptr<SpdyFrame> SpdySession::CreateSynStream( + SpdyStreamId stream_id, + RequestPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + const SpdyHeaderBlock& headers) { + ActiveStreamMap::const_iterator it = active_streams_.find(stream_id); + CHECK(it != active_streams_.end()); + CHECK_EQ(it->second.stream->stream_id(), stream_id); + + SendPrefacePingIfNoneInFlight(); + + DCHECK(buffered_spdy_framer_.get()); + scoped_ptr<SpdyFrame> syn_frame( + buffered_spdy_framer_->CreateSynStream( + stream_id, 0, + ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion()), + credential_slot, flags, enable_compression_, &headers)); + + base::StatsCounter spdy_requests("spdy.requests"); + spdy_requests.Increment(); + streams_initiated_count_++; + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_SYN_STREAM, + base::Bind(&NetLogSpdySynCallback, &headers, + (flags & CONTROL_FLAG_FIN) != 0, + (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0, + stream_id, 0)); + } + + return syn_frame.Pass(); +} + +int SpdySession::CreateCredentialFrame( + const std::string& origin, + const std::string& key, + const std::string& cert, + RequestPriority priority, + scoped_ptr<SpdyFrame>* credential_frame) { + DCHECK(is_secure_); + SSLClientSocket* ssl_socket = GetSSLClientSocket(); + DCHECK(ssl_socket); + DCHECK(ssl_socket->WasChannelIDSent()); + + SpdyCredential credential; + std::string tls_unique; + ssl_socket->GetTLSUniqueChannelBinding(&tls_unique); + size_t slot = credential_state_.SetHasCredential(GURL(origin)); + int rv = SpdyCredentialBuilder::Build(tls_unique, key, cert, slot, + &credential); + DCHECK_NE(rv, ERR_IO_PENDING); + if (rv != OK) + return rv; + + DCHECK(buffered_spdy_framer_.get()); + credential_frame->reset( + buffered_spdy_framer_->CreateCredentialFrame(credential)); + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_SEND_CREDENTIAL, + base::Bind(&NetLogSpdyCredentialCallback, credential.slot, &origin)); + } + return OK; +} + +scoped_ptr<SpdyBuffer> SpdySession::CreateDataBuffer(SpdyStreamId stream_id, + IOBuffer* data, + int len, + SpdyDataFlags flags) { + if (availability_state_ == STATE_CLOSED) { + NOTREACHED(); + return scoped_ptr<SpdyBuffer>(); + } + + ActiveStreamMap::const_iterator it = active_streams_.find(stream_id); + CHECK(it != active_streams_.end()); + SpdyStream* stream = it->second.stream; + CHECK_EQ(stream->stream_id(), stream_id); + + if (len < 0) { + NOTREACHED(); + return scoped_ptr<SpdyBuffer>(); + } + + int effective_len = std::min(len, kMaxSpdyFrameChunkSize); + + bool send_stalled_by_stream = + (flow_control_state_ >= FLOW_CONTROL_STREAM) && + (stream->send_window_size() <= 0); + bool send_stalled_by_session = IsSendStalled(); + + // NOTE: There's an enum of the same name in histograms.xml. + enum SpdyFrameFlowControlState { + SEND_NOT_STALLED, + SEND_STALLED_BY_STREAM, + SEND_STALLED_BY_SESSION, + SEND_STALLED_BY_STREAM_AND_SESSION, + }; + + SpdyFrameFlowControlState frame_flow_control_state = SEND_NOT_STALLED; + if (send_stalled_by_stream) { + if (send_stalled_by_session) { + frame_flow_control_state = SEND_STALLED_BY_STREAM_AND_SESSION; + } else { + frame_flow_control_state = SEND_STALLED_BY_STREAM; + } + } else if (send_stalled_by_session) { + frame_flow_control_state = SEND_STALLED_BY_SESSION; + } + + if (flow_control_state_ == FLOW_CONTROL_STREAM) { + UMA_HISTOGRAM_ENUMERATION( + "Net.SpdyFrameStreamFlowControlState", + frame_flow_control_state, + SEND_STALLED_BY_STREAM + 1); + } else if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) { + UMA_HISTOGRAM_ENUMERATION( + "Net.SpdyFrameStreamAndSessionFlowControlState", + frame_flow_control_state, + SEND_STALLED_BY_STREAM_AND_SESSION + 1); + } + + // Obey send window size of the stream if stream flow control is + // enabled. + if (flow_control_state_ >= FLOW_CONTROL_STREAM) { + if (send_stalled_by_stream) { + stream->set_send_stalled_by_flow_control(true); + // Even though we're currently stalled only by the stream, we + // might end up being stalled by the session also. + QueueSendStalledStream(*stream); + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW, + NetLog::IntegerCallback("stream_id", stream_id)); + return scoped_ptr<SpdyBuffer>(); + } + + effective_len = std::min(effective_len, stream->send_window_size()); + } + + // Obey send window size of the session if session flow control is + // enabled. + if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) { + if (send_stalled_by_session) { + stream->set_send_stalled_by_flow_control(true); + QueueSendStalledStream(*stream); + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW, + NetLog::IntegerCallback("stream_id", stream_id)); + return scoped_ptr<SpdyBuffer>(); + } + + effective_len = std::min(effective_len, session_send_window_size_); + } + + DCHECK_GE(effective_len, 0); + + // Clear FIN flag if only some of the data will be in the data + // frame. + if (effective_len < len) + flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN); + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_SEND_DATA, + base::Bind(&NetLogSpdyDataCallback, stream_id, effective_len, + (flags & DATA_FLAG_FIN) != 0)); + } + + // Send PrefacePing for DATA_FRAMEs with nonzero payload size. + if (effective_len > 0) + SendPrefacePingIfNoneInFlight(); + + // TODO(mbelshe): reduce memory copies here. + DCHECK(buffered_spdy_framer_.get()); + scoped_ptr<SpdyFrame> frame( + buffered_spdy_framer_->CreateDataFrame( + stream_id, data->data(), + static_cast<uint32>(effective_len), flags)); + + scoped_ptr<SpdyBuffer> data_buffer(new SpdyBuffer(frame.Pass())); + + if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) { + DecreaseSendWindowSize(static_cast<int32>(effective_len)); + data_buffer->AddConsumeCallback( + base::Bind(&SpdySession::OnWriteBufferConsumed, + weak_factory_.GetWeakPtr(), + static_cast<size_t>(effective_len))); + } + + return data_buffer.Pass(); +} + +void SpdySession::CloseActiveStream(SpdyStreamId stream_id, int status) { + DCHECK_NE(stream_id, 0u); + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + NOTREACHED(); + return; + } + + CloseActiveStreamIterator(it, status); +} + +void SpdySession::CloseCreatedStream( + const base::WeakPtr<SpdyStream>& stream, int status) { + DCHECK_EQ(stream->stream_id(), 0u); + + CreatedStreamSet::iterator it = created_streams_.find(stream.get()); + if (it == created_streams_.end()) { + NOTREACHED(); + return; + } + + CloseCreatedStreamIterator(it, status); +} + +void SpdySession::ResetStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status, + const std::string& description) { + DCHECK_NE(stream_id, 0u); + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + NOTREACHED(); + return; + } + + ResetStreamIterator(it, status, description); +} + +bool SpdySession::IsStreamActive(SpdyStreamId stream_id) const { + return ContainsKey(active_streams_, stream_id); +} + +LoadState SpdySession::GetLoadState() const { + // Just report that we're idle since the session could be doing + // many things concurrently. + return LOAD_STATE_IDLE; +} + +void SpdySession::CloseActiveStreamIterator(ActiveStreamMap::iterator it, + int status) { + // TODO(mbelshe): We should send a RST_STREAM control frame here + // so that the server can cancel a large send. + + scoped_ptr<SpdyStream> owned_stream(it->second.stream); + active_streams_.erase(it); + + // TODO(akalin): When SpdyStream was ref-counted (and + // |unclaimed_pushed_streams_| held scoped_refptr<SpdyStream>), this + // was only done when status was not OK. This meant that pushed + // streams can still be claimed after they're closed. This is + // probably something that we still want to support, although server + // push is hardly used. Write tests for this and fix this. (See + // http://crbug.com/261712 .) + if (owned_stream->type() == SPDY_PUSH_STREAM) + unclaimed_pushed_streams_.erase(owned_stream->url()); + + DeleteStream(owned_stream.Pass(), status); +} + +void SpdySession::CloseCreatedStreamIterator(CreatedStreamSet::iterator it, + int status) { + scoped_ptr<SpdyStream> owned_stream(*it); + created_streams_.erase(it); + DeleteStream(owned_stream.Pass(), status); +} + +void SpdySession::ResetStreamIterator(ActiveStreamMap::iterator it, + SpdyRstStreamStatus status, + const std::string& description) { + // Send the RST_STREAM frame first as CloseActiveStreamIterator() + // may close us. + SpdyStreamId stream_id = it->first; + RequestPriority priority = it->second.stream->priority(); + EnqueueResetStreamFrame(stream_id, priority, status, description); + + // Removes any pending writes for the stream except for possibly an + // in-flight one. + CloseActiveStreamIterator(it, ERR_SPDY_PROTOCOL_ERROR); +} + +void SpdySession::EnqueueResetStreamFrame(SpdyStreamId stream_id, + RequestPriority priority, + SpdyRstStreamStatus status, + const std::string& description) { + DCHECK_NE(stream_id, 0u); + + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_SEND_RST_STREAM, + base::Bind(&NetLogSpdyRstCallback, stream_id, status, &description)); + + DCHECK(buffered_spdy_framer_.get()); + scoped_ptr<SpdyFrame> rst_frame( + buffered_spdy_framer_->CreateRstStream(stream_id, status)); + + EnqueueSessionWrite(priority, RST_STREAM, rst_frame.Pass()); + RecordProtocolErrorHistogram( + static_cast<SpdyProtocolErrorDetails>(status + STATUS_CODE_INVALID)); +} + +void SpdySession::PumpReadLoop(ReadState expected_read_state, int result) { + CHECK(!in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_EQ(read_state_, expected_read_state); + + result = DoReadLoop(expected_read_state, result); + + if (availability_state_ == STATE_CLOSED) { + DCHECK_EQ(result, error_on_close_); + DCHECK_LT(error_on_close_, ERR_IO_PENDING); + RemoveFromPool(); + return; + } + + DCHECK(result == OK || result == ERR_IO_PENDING); +} + +int SpdySession::DoReadLoop(ReadState expected_read_state, int result) { + CHECK(!in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_EQ(read_state_, expected_read_state); + + in_io_loop_ = true; + + int bytes_read_without_yielding = 0; + + // Loop until the session is closed, the read becomes blocked, or + // the read limit is exceeded. + while (true) { + switch (read_state_) { + case READ_STATE_DO_READ: + DCHECK_EQ(result, OK); + result = DoRead(); + break; + case READ_STATE_DO_READ_COMPLETE: + if (result > 0) + bytes_read_without_yielding += result; + result = DoReadComplete(result); + break; + default: + NOTREACHED() << "read_state_: " << read_state_; + break; + } + + if (availability_state_ == STATE_CLOSED) { + DCHECK_EQ(result, error_on_close_); + DCHECK_LT(result, ERR_IO_PENDING); + break; + } + + if (result == ERR_IO_PENDING) + break; + + if (bytes_read_without_yielding > kMaxReadBytesWithoutYielding) { + read_state_ = READ_STATE_DO_READ; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&SpdySession::PumpReadLoop, + weak_factory_.GetWeakPtr(), READ_STATE_DO_READ, OK)); + result = ERR_IO_PENDING; + break; + } + } + + CHECK(in_io_loop_); + in_io_loop_ = false; + + return result; +} + +int SpdySession::DoRead() { + CHECK(in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + + CHECK(connection_); + CHECK(connection_->socket()); + read_state_ = READ_STATE_DO_READ_COMPLETE; + return connection_->socket()->Read( + read_buffer_.get(), + kReadBufferSize, + base::Bind(&SpdySession::PumpReadLoop, + weak_factory_.GetWeakPtr(), READ_STATE_DO_READ_COMPLETE)); +} + +int SpdySession::DoReadComplete(int result) { + CHECK(in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + + // Parse a frame. For now this code requires that the frame fit into our + // buffer (kReadBufferSize). + // TODO(mbelshe): support arbitrarily large frames! + + if (result == 0) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySession.BytesRead.EOF", + total_bytes_received_, 1, 100000000, 50); + CloseSessionResult close_session_result = + DoCloseSession(ERR_CONNECTION_CLOSED, "Connection closed"); + DCHECK_EQ(close_session_result, SESSION_CLOSED_BUT_NOT_REMOVED); + DCHECK_EQ(availability_state_, STATE_CLOSED); + DCHECK_EQ(error_on_close_, ERR_CONNECTION_CLOSED); + return ERR_CONNECTION_CLOSED; + } + + if (result < 0) { + CloseSessionResult close_session_result = + DoCloseSession(static_cast<Error>(result), "result is < 0."); + DCHECK_EQ(close_session_result, SESSION_CLOSED_BUT_NOT_REMOVED); + DCHECK_EQ(availability_state_, STATE_CLOSED); + DCHECK_EQ(error_on_close_, result); + return result; + } + + total_bytes_received_ += result; + + last_activity_time_ = time_func_(); + + DCHECK(buffered_spdy_framer_.get()); + char* data = read_buffer_->data(); + while (result > 0) { + uint32 bytes_processed = buffered_spdy_framer_->ProcessInput(data, result); + result -= bytes_processed; + data += bytes_processed; + + if (availability_state_ == STATE_CLOSED) { + DCHECK_LT(error_on_close_, ERR_IO_PENDING); + return error_on_close_; + } + + DCHECK_EQ(buffered_spdy_framer_->error_code(), SpdyFramer::SPDY_NO_ERROR); + } + + read_state_ = READ_STATE_DO_READ; + return OK; +} + +void SpdySession::PumpWriteLoop(WriteState expected_write_state, int result) { + CHECK(!in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_EQ(write_state_, expected_write_state); + + result = DoWriteLoop(expected_write_state, result); + + if (availability_state_ == STATE_CLOSED) { + DCHECK_EQ(result, error_on_close_); + DCHECK_LT(error_on_close_, ERR_IO_PENDING); + RemoveFromPool(); + return; + } + + DCHECK(result == OK || result == ERR_IO_PENDING); +} + +int SpdySession::DoWriteLoop(WriteState expected_write_state, int result) { + CHECK(!in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_NE(write_state_, WRITE_STATE_IDLE); + DCHECK_EQ(write_state_, expected_write_state); + + in_io_loop_ = true; + + // Loop until the session is closed or the write becomes blocked. + while (true) { + switch (write_state_) { + case WRITE_STATE_DO_WRITE: + DCHECK_EQ(result, OK); + result = DoWrite(); + break; + case WRITE_STATE_DO_WRITE_COMPLETE: + result = DoWriteComplete(result); + break; + case WRITE_STATE_IDLE: + default: + NOTREACHED() << "write_state_: " << write_state_; + break; + } + + if (availability_state_ == STATE_CLOSED) { + DCHECK_EQ(result, error_on_close_); + DCHECK_LT(result, ERR_IO_PENDING); + break; + } + + if (write_state_ == WRITE_STATE_IDLE) { + DCHECK_EQ(result, ERR_IO_PENDING); + break; + } + + if (result == ERR_IO_PENDING) + break; + } + + CHECK(in_io_loop_); + in_io_loop_ = false; + + return result; +} + +int SpdySession::DoWrite() { + CHECK(in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + + DCHECK(buffered_spdy_framer_); + if (in_flight_write_) { + DCHECK_GT(in_flight_write_->GetRemainingSize(), 0u); + } else { + // Grab the next frame to send. + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> producer; + base::WeakPtr<SpdyStream> stream; + if (!write_queue_.Dequeue(&frame_type, &producer, &stream)) { + write_state_ = WRITE_STATE_IDLE; + return ERR_IO_PENDING; + } + + if (stream.get()) + DCHECK(!stream->IsClosed()); + + // Activate the stream only when sending the SYN_STREAM frame to + // guarantee monotonically-increasing stream IDs. + if (frame_type == SYN_STREAM) { + if (stream.get() && stream->stream_id() == 0) { + scoped_ptr<SpdyStream> owned_stream = + ActivateCreatedStream(stream.get()); + InsertActivatedStream(owned_stream.Pass()); + } else { + NOTREACHED(); + return ERR_UNEXPECTED; + } + } + + in_flight_write_ = producer->ProduceBuffer(); + if (!in_flight_write_) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + in_flight_write_frame_type_ = frame_type; + in_flight_write_frame_size_ = in_flight_write_->GetRemainingSize(); + DCHECK_GE(in_flight_write_frame_size_, + buffered_spdy_framer_->GetFrameMinimumSize()); + in_flight_write_stream_ = stream; + } + + write_state_ = WRITE_STATE_DO_WRITE_COMPLETE; + + // Explicitly store in a scoped_refptr<IOBuffer> to avoid problems + // with Socket implementations that don't store their IOBuffer + // argument in a scoped_refptr<IOBuffer> (see crbug.com/232345). + scoped_refptr<IOBuffer> write_io_buffer = + in_flight_write_->GetIOBufferForRemainingData(); + return connection_->socket()->Write( + write_io_buffer.get(), + in_flight_write_->GetRemainingSize(), + base::Bind(&SpdySession::PumpWriteLoop, + weak_factory_.GetWeakPtr(), WRITE_STATE_DO_WRITE_COMPLETE)); +} + +int SpdySession::DoWriteComplete(int result) { + CHECK(in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_NE(result, ERR_IO_PENDING); + DCHECK_GT(in_flight_write_->GetRemainingSize(), 0u); + + last_activity_time_ = time_func_(); + + if (result < 0) { + DCHECK_NE(result, ERR_IO_PENDING); + in_flight_write_.reset(); + in_flight_write_frame_type_ = DATA; + in_flight_write_frame_size_ = 0; + in_flight_write_stream_.reset(); + CloseSessionResult close_session_result = + DoCloseSession(static_cast<Error>(result), "Write error"); + DCHECK_EQ(close_session_result, SESSION_CLOSED_BUT_NOT_REMOVED); + DCHECK_EQ(availability_state_, STATE_CLOSED); + DCHECK_EQ(error_on_close_, result); + return result; + } + + // It should not be possible to have written more bytes than our + // in_flight_write_. + DCHECK_LE(static_cast<size_t>(result), + in_flight_write_->GetRemainingSize()); + + if (result > 0) { + in_flight_write_->Consume(static_cast<size_t>(result)); + + // We only notify the stream when we've fully written the pending frame. + if (in_flight_write_->GetRemainingSize() == 0) { + // It is possible that the stream was cancelled while we were + // writing to the socket. + if (in_flight_write_stream_.get()) { + DCHECK_GT(in_flight_write_frame_size_, 0u); + in_flight_write_stream_->OnFrameWriteComplete( + in_flight_write_frame_type_, + in_flight_write_frame_size_); + } + + // Cleanup the write which just completed. + in_flight_write_.reset(); + in_flight_write_frame_type_ = DATA; + in_flight_write_frame_size_ = 0; + in_flight_write_stream_.reset(); + } + } + + write_state_ = WRITE_STATE_DO_WRITE; + return OK; +} + +void SpdySession::DcheckGoingAway() const { + DCHECK_GE(availability_state_, STATE_GOING_AWAY); + if (DCHECK_IS_ON()) { + for (int i = 0; i < NUM_PRIORITIES; ++i) { + DCHECK(pending_create_stream_queues_[i].empty()); + } + } + DCHECK(pending_stream_request_completions_.empty()); + DCHECK(created_streams_.empty()); +} + +void SpdySession::DcheckClosed() const { + DcheckGoingAway(); + DCHECK_EQ(availability_state_, STATE_CLOSED); + DCHECK_LT(error_on_close_, ERR_IO_PENDING); + DCHECK(active_streams_.empty()); + DCHECK(unclaimed_pushed_streams_.empty()); + DCHECK(write_queue_.IsEmpty()); +} + +void SpdySession::StartGoingAway(SpdyStreamId last_good_stream_id, + Error status) { + DCHECK_GE(availability_state_, STATE_GOING_AWAY); + + // The loops below are carefully written to avoid reentrancy problems. + // + // TODO(akalin): Any of the functions below can cause |this| to be + // deleted, so handle that below (and add tests for it). + + for (int i = 0; i < NUM_PRIORITIES; ++i) { + PendingStreamRequestQueue queue; + queue.swap(pending_create_stream_queues_[i]); + for (PendingStreamRequestQueue::const_iterator it = queue.begin(); + it != queue.end(); ++it) { + CHECK(*it); + (*it)->OnRequestCompleteFailure(ERR_ABORTED); + } + } + + PendingStreamRequestCompletionSet pending_completions; + pending_completions.swap(pending_stream_request_completions_); + for (PendingStreamRequestCompletionSet::const_iterator it = + pending_completions.begin(); + it != pending_completions.end(); ++it) { + (*it)->OnRequestCompleteFailure(ERR_ABORTED); + } + + while (true) { + ActiveStreamMap::iterator it = + active_streams_.lower_bound(last_good_stream_id + 1); + if (it == active_streams_.end()) + break; + LogAbandonedActiveStream(it, status); + CloseActiveStreamIterator(it, status); + } + + while (!created_streams_.empty()) { + CreatedStreamSet::iterator it = created_streams_.begin(); + LogAbandonedStream(*it, status); + CloseCreatedStreamIterator(it, status); + } + + write_queue_.RemovePendingWritesForStreamsAfter(last_good_stream_id); + + DcheckGoingAway(); +} + +void SpdySession::MaybeFinishGoingAway() { + DcheckGoingAway(); + if (active_streams_.empty() && availability_state_ != STATE_CLOSED) { + CloseSessionResult result = + DoCloseSession(ERR_CONNECTION_CLOSED, "Finished going away"); + DCHECK_NE(result, SESSION_ALREADY_CLOSED); + } +} + +SpdySession::CloseSessionResult SpdySession::DoCloseSession( + Error err, + const std::string& description) { + DCHECK_LT(err, ERR_IO_PENDING); + + if (availability_state_ == STATE_CLOSED) + return SESSION_ALREADY_CLOSED; + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_CLOSE, + base::Bind(&NetLogSpdySessionCloseCallback, err, &description)); + + UMA_HISTOGRAM_SPARSE_SLOWLY("Net.SpdySession.ClosedOnError", -err); + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySession.BytesRead.OtherErrors", + total_bytes_received_, 1, 100000000, 50); + + // |pool_| will be NULL when |InitializeWithSocket()| is in the + // call stack. + if (pool_ && availability_state_ != STATE_GOING_AWAY) + pool_->MakeSessionUnavailable(GetWeakPtr()); + + availability_state_ = STATE_CLOSED; + error_on_close_ = err; + + StartGoingAway(0, err); + write_queue_.Clear(); + + DcheckClosed(); + + if (in_io_loop_) + return SESSION_CLOSED_BUT_NOT_REMOVED; + + RemoveFromPool(); + return SESSION_CLOSED_AND_REMOVED; +} + +void SpdySession::RemoveFromPool() { + DcheckClosed(); + CHECK(pool_); + + SpdySessionPool* pool = pool_; + pool_ = NULL; + pool->RemoveUnavailableSession(GetWeakPtr()); +} + +void SpdySession::LogAbandonedStream(SpdyStream* stream, Error status) { + DCHECK(stream); + std::string description = base::StringPrintf( + "ABANDONED (stream_id=%d): ", stream->stream_id()) + + stream->url().spec(); + stream->LogStreamError(status, description); + // We don't increment the streams abandoned counter here. If the + // stream isn't active (i.e., it hasn't written anything to the wire + // yet) then it's as if it never existed. If it is active, then + // LogAbandonedActiveStream() will increment the counters. +} + +void SpdySession::LogAbandonedActiveStream(ActiveStreamMap::const_iterator it, + Error status) { + DCHECK_GT(it->first, 0u); + LogAbandonedStream(it->second.stream, status); + ++streams_abandoned_count_; + base::StatsCounter abandoned_streams("spdy.abandoned_streams"); + abandoned_streams.Increment(); + if (it->second.stream->type() == SPDY_PUSH_STREAM && + unclaimed_pushed_streams_.find(it->second.stream->url()) != + unclaimed_pushed_streams_.end()) { + base::StatsCounter abandoned_push_streams("spdy.abandoned_push_streams"); + abandoned_push_streams.Increment(); + } +} + +int SpdySession::GetNewStreamId() { + int id = stream_hi_water_mark_; + stream_hi_water_mark_ += 2; + if (stream_hi_water_mark_ > 0x7fff) + stream_hi_water_mark_ = 1; + return id; +} + +void SpdySession::CloseSessionOnError(Error err, + const std::string& description) { + // We may be called from anywhere, so we can't expect a particular + // return value. + ignore_result(DoCloseSession(err, description)); +} + +base::Value* SpdySession::GetInfoAsValue() const { + base::DictionaryValue* dict = new base::DictionaryValue(); + + dict->SetInteger("source_id", net_log_.source().id); + + dict->SetString("host_port_pair", host_port_pair().ToString()); + if (!pooled_aliases_.empty()) { + base::ListValue* alias_list = new base::ListValue(); + for (std::set<SpdySessionKey>::const_iterator it = + pooled_aliases_.begin(); + it != pooled_aliases_.end(); it++) { + alias_list->Append(new base::StringValue( + it->host_port_pair().ToString())); + } + dict->Set("aliases", alias_list); + } + dict->SetString("proxy", host_port_proxy_pair().second.ToURI()); + + dict->SetInteger("active_streams", active_streams_.size()); + + dict->SetInteger("unclaimed_pushed_streams", + unclaimed_pushed_streams_.size()); + + dict->SetBoolean("is_secure", is_secure_); + + dict->SetString("protocol_negotiated", + SSLClientSocket::NextProtoToString( + connection_->socket()->GetNegotiatedProtocol())); + + dict->SetInteger("error", error_on_close_); + dict->SetInteger("max_concurrent_streams", max_concurrent_streams_); + + dict->SetInteger("streams_initiated_count", streams_initiated_count_); + dict->SetInteger("streams_pushed_count", streams_pushed_count_); + dict->SetInteger("streams_pushed_and_claimed_count", + streams_pushed_and_claimed_count_); + dict->SetInteger("streams_abandoned_count", streams_abandoned_count_); + DCHECK(buffered_spdy_framer_.get()); + dict->SetInteger("frames_received", buffered_spdy_framer_->frames_received()); + + dict->SetBoolean("sent_settings", sent_settings_); + dict->SetBoolean("received_settings", received_settings_); + + dict->SetInteger("send_window_size", session_send_window_size_); + dict->SetInteger("recv_window_size", session_recv_window_size_); + dict->SetInteger("unacked_recv_window_bytes", + session_unacked_recv_window_bytes_); + return dict; +} + +bool SpdySession::IsReused() const { + return buffered_spdy_framer_->frames_received() > 0; +} + +bool SpdySession::GetLoadTimingInfo(SpdyStreamId stream_id, + LoadTimingInfo* load_timing_info) const { + return connection_->GetLoadTimingInfo(stream_id != kFirstStreamId, + load_timing_info); +} + +int SpdySession::GetPeerAddress(IPEndPoint* address) const { + int rv = ERR_SOCKET_NOT_CONNECTED; + if (connection_->socket()) { + rv = connection_->socket()->GetPeerAddress(address); + } + + UMA_HISTOGRAM_BOOLEAN("Net.SpdySessionSocketNotConnectedGetPeerAddress", + rv == ERR_SOCKET_NOT_CONNECTED); + + return rv; +} + +int SpdySession::GetLocalAddress(IPEndPoint* address) const { + int rv = ERR_SOCKET_NOT_CONNECTED; + if (connection_->socket()) { + rv = connection_->socket()->GetLocalAddress(address); + } + + UMA_HISTOGRAM_BOOLEAN("Net.SpdySessionSocketNotConnectedGetLocalAddress", + rv == ERR_SOCKET_NOT_CONNECTED); + + return rv; +} + +void SpdySession::EnqueueSessionWrite(RequestPriority priority, + SpdyFrameType frame_type, + scoped_ptr<SpdyFrame> frame) { + DCHECK(frame_type == RST_STREAM || + frame_type == SETTINGS || + frame_type == WINDOW_UPDATE || + frame_type == PING); + EnqueueWrite( + priority, frame_type, + scoped_ptr<SpdyBufferProducer>( + new SimpleBufferProducer( + scoped_ptr<SpdyBuffer>(new SpdyBuffer(frame.Pass())))), + base::WeakPtr<SpdyStream>()); +} + +void SpdySession::EnqueueWrite(RequestPriority priority, + SpdyFrameType frame_type, + scoped_ptr<SpdyBufferProducer> producer, + const base::WeakPtr<SpdyStream>& stream) { + if (availability_state_ == STATE_CLOSED) + return; + + bool was_idle = write_queue_.IsEmpty(); + write_queue_.Enqueue(priority, frame_type, producer.Pass(), stream); + if (write_state_ == WRITE_STATE_IDLE) { + DCHECK(was_idle); + DCHECK(!in_flight_write_); + write_state_ = WRITE_STATE_DO_WRITE; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&SpdySession::PumpWriteLoop, + weak_factory_.GetWeakPtr(), WRITE_STATE_DO_WRITE, OK)); + } +} + +void SpdySession::InsertCreatedStream(scoped_ptr<SpdyStream> stream) { + DCHECK_EQ(stream->stream_id(), 0u); + DCHECK(created_streams_.find(stream.get()) == created_streams_.end()); + created_streams_.insert(stream.release()); +} + +scoped_ptr<SpdyStream> SpdySession::ActivateCreatedStream(SpdyStream* stream) { + DCHECK_EQ(stream->stream_id(), 0u); + DCHECK(created_streams_.find(stream) != created_streams_.end()); + stream->set_stream_id(GetNewStreamId()); + scoped_ptr<SpdyStream> owned_stream(stream); + created_streams_.erase(stream); + return owned_stream.Pass(); +} + +void SpdySession::InsertActivatedStream(scoped_ptr<SpdyStream> stream) { + SpdyStreamId stream_id = stream->stream_id(); + DCHECK_NE(stream_id, 0u); + std::pair<ActiveStreamMap::iterator, bool> result = + active_streams_.insert( + std::make_pair(stream_id, ActiveStreamInfo(stream.get()))); + if (result.second) { + ignore_result(stream.release()); + } else { + NOTREACHED(); + } +} + +void SpdySession::DeleteStream(scoped_ptr<SpdyStream> stream, int status) { + if (in_flight_write_stream_.get() == stream.get()) { + // If we're deleting the stream for the in-flight write, we still + // need to let the write complete, so we clear + // |in_flight_write_stream_| and let the write finish on its own + // without notifying |in_flight_write_stream_|. + in_flight_write_stream_.reset(); + } + + write_queue_.RemovePendingWritesForStream(stream->GetWeakPtr()); + + // |stream->OnClose()| may end up closing |this|, so detect that. + base::WeakPtr<SpdySession> weak_this = GetWeakPtr(); + + stream->OnClose(status); + + if (!weak_this) + return; + + switch (availability_state_) { + case STATE_AVAILABLE: + ProcessPendingStreamRequests(); + break; + case STATE_GOING_AWAY: + DcheckGoingAway(); + MaybeFinishGoingAway(); + break; + case STATE_CLOSED: + // Do nothing. + break; + } +} + +base::WeakPtr<SpdyStream> SpdySession::GetActivePushStream(const GURL& url) { + base::StatsCounter used_push_streams("spdy.claimed_push_streams"); + + PushedStreamMap::iterator unclaimed_it = unclaimed_pushed_streams_.find(url); + if (unclaimed_it == unclaimed_pushed_streams_.end()) + return base::WeakPtr<SpdyStream>(); + + SpdyStreamId stream_id = unclaimed_it->second.stream_id; + unclaimed_pushed_streams_.erase(unclaimed_it); + + ActiveStreamMap::iterator active_it = active_streams_.find(stream_id); + if (active_it == active_streams_.end()) { + NOTREACHED(); + return base::WeakPtr<SpdyStream>(); + } + + net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM); + used_push_streams.Increment(); + return active_it->second.stream->GetWeakPtr(); +} + +bool SpdySession::GetSSLInfo(SSLInfo* ssl_info, + bool* was_npn_negotiated, + NextProto* protocol_negotiated) { + *was_npn_negotiated = connection_->socket()->WasNpnNegotiated(); + *protocol_negotiated = connection_->socket()->GetNegotiatedProtocol(); + return connection_->socket()->GetSSLInfo(ssl_info); +} + +bool SpdySession::GetSSLCertRequestInfo( + SSLCertRequestInfo* cert_request_info) { + if (!is_secure_) + return false; + GetSSLClientSocket()->GetSSLCertRequestInfo(cert_request_info); + return true; +} + +ServerBoundCertService* SpdySession::GetServerBoundCertService() const { + if (!is_secure_) + return NULL; + return GetSSLClientSocket()->GetServerBoundCertService(); +} + +void SpdySession::OnError(SpdyFramer::SpdyError error_code) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + RecordProtocolErrorHistogram( + static_cast<SpdyProtocolErrorDetails>(error_code)); + std::string description = base::StringPrintf( + "SPDY_ERROR error_code: %d.", error_code); + CloseSessionResult result = + DoCloseSession(ERR_SPDY_PROTOCOL_ERROR, description); + DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED); +} + +void SpdySession::OnStreamError(SpdyStreamId stream_id, + const std::string& description) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + // We still want to send a frame to reset the stream even if we + // don't know anything about it. + EnqueueResetStreamFrame( + stream_id, IDLE, RST_STREAM_PROTOCOL_ERROR, description); + return; + } + + ResetStreamIterator(it, RST_STREAM_PROTOCOL_ERROR, description); +} + +void SpdySession::OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + DCHECK_LT(len, 1u << 24); + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_RECV_DATA, + base::Bind(&NetLogSpdyDataCallback, stream_id, len, fin)); + } + + // Build the buffer as early as possible so that we go through the + // session flow control checks and update + // |unacked_recv_window_bytes_| properly even when the stream is + // inactive (since the other side has still reduced its session send + // window). + scoped_ptr<SpdyBuffer> buffer; + if (data) { + DCHECK_GT(len, 0u); + buffer.reset(new SpdyBuffer(data, len)); + + if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) { + DecreaseRecvWindowSize(static_cast<int32>(len)); + buffer->AddConsumeCallback( + base::Bind(&SpdySession::OnReadBufferConsumed, + weak_factory_.GetWeakPtr())); + } + } else { + DCHECK_EQ(len, 0u); + } + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + + // By the time data comes in, the stream may already be inactive. + if (it == active_streams_.end()) + return; + + SpdyStream* stream = it->second.stream; + CHECK_EQ(stream->stream_id(), stream_id); + + if (it->second.waiting_for_syn_reply) { + const std::string& error = "Data received before SYN_REPLY."; + stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR, error); + ResetStreamIterator(it, RST_STREAM_PROTOCOL_ERROR, error); + return; + } + + stream->OnDataReceived(buffer.Pass()); +} + +void SpdySession::OnSettings(bool clear_persisted) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + if (clear_persisted) + http_server_properties_->ClearSpdySettings(host_port_pair()); + + if (net_log_.IsLoggingAllEvents()) { + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_RECV_SETTINGS, + base::Bind(&NetLogSpdySettingsCallback, host_port_pair(), + clear_persisted)); + } +} + +void SpdySession::OnSetting(SpdySettingsIds id, + uint8 flags, + uint32 value) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + HandleSetting(id, value); + http_server_properties_->SetSpdySetting( + host_port_pair(), + id, + static_cast<SpdySettingsFlags>(flags), + value); + received_settings_ = true; + + // Log the setting. + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_RECV_SETTING, + base::Bind(&NetLogSpdySettingCallback, + id, static_cast<SpdySettingsFlags>(flags), value)); +} + +void SpdySession::OnSendCompressedFrame( + SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) { + if (type != SYN_STREAM) + return; + + DCHECK(buffered_spdy_framer_.get()); + size_t compressed_len = + frame_len - buffered_spdy_framer_->GetSynStreamMinimumSize(); + + if (payload_len) { + // Make sure we avoid early decimal truncation. + int compression_pct = 100 - (100 * compressed_len) / payload_len; + UMA_HISTOGRAM_PERCENTAGE("Net.SpdySynStreamCompressionPercentage", + compression_pct); + } +} + +int SpdySession::OnInitialResponseHeadersReceived( + const SpdyHeaderBlock& response_headers, + base::Time response_time, + base::TimeTicks recv_first_byte_time, + SpdyStream* stream) { + CHECK(in_io_loop_); + SpdyStreamId stream_id = stream->stream_id(); + // May invalidate |stream|. + int rv = stream->OnInitialResponseHeadersReceived( + response_headers, response_time, recv_first_byte_time); + if (rv < 0) { + DCHECK_NE(rv, ERR_IO_PENDING); + DCHECK(active_streams_.find(stream_id) == active_streams_.end()); + } + return rv; +} + +void SpdySession::OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional, + const SpdyHeaderBlock& headers) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + base::Time response_time = base::Time::Now(); + base::TimeTicks recv_first_byte_time = time_func_(); + + if (net_log_.IsLoggingAllEvents()) { + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM, + base::Bind(&NetLogSpdySynCallback, + &headers, fin, unidirectional, + stream_id, associated_stream_id)); + } + + // Server-initiated streams should have even sequence numbers. + if ((stream_id & 0x1) != 0) { + LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id; + return; + } + + if (IsStreamActive(stream_id)) { + LOG(WARNING) << "Received OnSyn for active stream " << stream_id; + return; + } + + RequestPriority request_priority = + ConvertSpdyPriorityToRequestPriority(priority, GetProtocolVersion()); + + if (availability_state_ == STATE_GOING_AWAY) { + // TODO(akalin): This behavior isn't in the SPDY spec, although it + // probably should be. + EnqueueResetStreamFrame(stream_id, request_priority, + RST_STREAM_REFUSED_STREAM, + "OnSyn received when going away"); + return; + } + + if (associated_stream_id == 0) { + std::string description = base::StringPrintf( + "Received invalid OnSyn associated stream id %d for stream %d", + associated_stream_id, stream_id); + EnqueueResetStreamFrame(stream_id, request_priority, + RST_STREAM_REFUSED_STREAM, description); + return; + } + + streams_pushed_count_++; + + // TODO(mbelshe): DCHECK that this is a GET method? + + // Verify that the response had a URL for us. + GURL gurl = GetUrlFromHeaderBlock(headers, GetProtocolVersion(), true); + if (!gurl.is_valid()) { + EnqueueResetStreamFrame( + stream_id, request_priority, RST_STREAM_PROTOCOL_ERROR, + "Pushed stream url was invalid: " + gurl.spec()); + return; + } + + // Verify we have a valid stream association. + ActiveStreamMap::iterator associated_it = + active_streams_.find(associated_stream_id); + if (associated_it == active_streams_.end()) { + EnqueueResetStreamFrame( + stream_id, request_priority, RST_STREAM_INVALID_STREAM, + base::StringPrintf( + "Received OnSyn with inactive associated stream %d", + associated_stream_id)); + return; + } + + // Check that the SYN advertises the same origin as its associated stream. + // Bypass this check if and only if this session is with a SPDY proxy that + // is trusted explicitly via the --trusted-spdy-proxy switch. + if (trusted_spdy_proxy_.Equals(host_port_pair())) { + // Disallow pushing of HTTPS content. + if (gurl.SchemeIs("https")) { + EnqueueResetStreamFrame( + stream_id, request_priority, RST_STREAM_REFUSED_STREAM, + base::StringPrintf( + "Rejected push of Cross Origin HTTPS content %d", + associated_stream_id)); + } + } else { + GURL associated_url(associated_it->second.stream->GetUrlFromHeaders()); + if (associated_url.GetOrigin() != gurl.GetOrigin()) { + EnqueueResetStreamFrame( + stream_id, request_priority, RST_STREAM_REFUSED_STREAM, + base::StringPrintf( + "Rejected Cross Origin Push Stream %d", + associated_stream_id)); + return; + } + } + + // There should not be an existing pushed stream with the same path. + PushedStreamMap::iterator pushed_it = + unclaimed_pushed_streams_.lower_bound(gurl); + if (pushed_it != unclaimed_pushed_streams_.end() && + pushed_it->first == gurl) { + EnqueueResetStreamFrame( + stream_id, request_priority, RST_STREAM_PROTOCOL_ERROR, + "Received duplicate pushed stream with url: " + + gurl.spec()); + return; + } + + scoped_ptr<SpdyStream> stream( + new SpdyStream(SPDY_PUSH_STREAM, GetWeakPtr(), gurl, + request_priority, + stream_initial_send_window_size_, + stream_initial_recv_window_size_, + net_log_)); + stream->set_stream_id(stream_id); + + DeleteExpiredPushedStreams(); + PushedStreamMap::iterator inserted_pushed_it = + unclaimed_pushed_streams_.insert( + pushed_it, + std::make_pair(gurl, PushedStreamInfo(stream_id, time_func_()))); + DCHECK(inserted_pushed_it != pushed_it); + + InsertActivatedStream(stream.Pass()); + + ActiveStreamMap::iterator active_it = active_streams_.find(stream_id); + if (active_it == active_streams_.end()) { + NOTREACHED(); + return; + } + + // Parse the headers. + if (OnInitialResponseHeadersReceived( + headers, response_time, + recv_first_byte_time, active_it->second.stream) != OK) + return; + + base::StatsCounter push_requests("spdy.pushed_streams"); + push_requests.Increment(); +} + +void SpdySession::DeleteExpiredPushedStreams() { + if (unclaimed_pushed_streams_.empty()) + return; + + // Check that adequate time has elapsed since the last sweep. + if (time_func_() < next_unclaimed_push_stream_sweep_time_) + return; + + // Gather old streams to delete. + base::TimeTicks minimum_freshness = time_func_() - + base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds); + std::vector<SpdyStreamId> streams_to_close; + for (PushedStreamMap::iterator it = unclaimed_pushed_streams_.begin(); + it != unclaimed_pushed_streams_.end(); ++it) { + if (minimum_freshness > it->second.creation_time) + streams_to_close.push_back(it->second.stream_id); + } + + for (std::vector<SpdyStreamId>::const_iterator to_close_it = + streams_to_close.begin(); + to_close_it != streams_to_close.end(); ++to_close_it) { + ActiveStreamMap::iterator active_it = active_streams_.find(*to_close_it); + if (active_it == active_streams_.end()) + continue; + + LogAbandonedActiveStream(active_it, ERR_INVALID_SPDY_STREAM); + // CloseActiveStreamIterator() will remove the stream from + // |unclaimed_pushed_streams_|. + CloseActiveStreamIterator(active_it, ERR_INVALID_SPDY_STREAM); + } + + next_unclaimed_push_stream_sweep_time_ = time_func_() + + base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds); +} + +void SpdySession::OnSynReply(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + base::Time response_time = base::Time::Now(); + base::TimeTicks recv_first_byte_time = time_func_(); + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_SYN_REPLY, + base::Bind(&NetLogSpdySynCallback, + &headers, fin, false, // not unidirectional + stream_id, 0)); + } + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + // NOTE: it may just be that the stream was cancelled. + return; + } + + SpdyStream* stream = it->second.stream; + CHECK_EQ(stream->stream_id(), stream_id); + + if (!it->second.waiting_for_syn_reply) { + const std::string& error = + "Received duplicate SYN_REPLY for stream."; + stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR, error); + ResetStreamIterator(it, RST_STREAM_STREAM_IN_USE, error); + return; + } + it->second.waiting_for_syn_reply = false; + + ignore_result(OnInitialResponseHeadersReceived( + headers, response_time, recv_first_byte_time, stream)); +} + +void SpdySession::OnHeaders(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_RECV_HEADERS, + base::Bind(&NetLogSpdySynCallback, + &headers, fin, /*unidirectional=*/false, + stream_id, 0)); + } + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + // NOTE: it may just be that the stream was cancelled. + LOG(WARNING) << "Received HEADERS for invalid stream " << stream_id; + return; + } + + SpdyStream* stream = it->second.stream; + CHECK_EQ(stream->stream_id(), stream_id); + + int rv = stream->OnAdditionalResponseHeadersReceived(headers); + if (rv < 0) { + DCHECK_NE(rv, ERR_IO_PENDING); + DCHECK(active_streams_.find(stream_id) == active_streams_.end()); + } +} + +void SpdySession::OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + std::string description; + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_RST_STREAM, + base::Bind(&NetLogSpdyRstCallback, + stream_id, status, &description)); + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + // NOTE: it may just be that the stream was cancelled. + LOG(WARNING) << "Received RST for invalid stream" << stream_id; + return; + } + + CHECK_EQ(it->second.stream->stream_id(), stream_id); + + if (status == 0) { + it->second.stream->OnDataReceived(scoped_ptr<SpdyBuffer>()); + } else if (status == RST_STREAM_REFUSED_STREAM) { + CloseActiveStreamIterator(it, ERR_SPDY_SERVER_REFUSED_STREAM); + } else { + RecordProtocolErrorHistogram( + PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM); + it->second.stream->LogStreamError( + ERR_SPDY_PROTOCOL_ERROR, + base::StringPrintf("SPDY stream closed with status: %d", status)); + // TODO(mbelshe): Map from Spdy-protocol errors to something sensical. + // For now, it doesn't matter much - it is a protocol error. + CloseActiveStreamIterator(it, ERR_SPDY_PROTOCOL_ERROR); + } +} + +void SpdySession::OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_GOAWAY, + base::Bind(&NetLogSpdyGoAwayCallback, + last_accepted_stream_id, + active_streams_.size(), + unclaimed_pushed_streams_.size(), + status)); + if (availability_state_ < STATE_GOING_AWAY) { + availability_state_ = STATE_GOING_AWAY; + // |pool_| will be NULL when |InitializeWithSocket()| is in the + // call stack. + if (pool_) + pool_->MakeSessionUnavailable(GetWeakPtr()); + } + StartGoingAway(last_accepted_stream_id, ERR_ABORTED); + // This is to handle the case when we already don't have any active + // streams (i.e., StartGoingAway() did nothing). Otherwise, we have + // active streams and so the last one being closed will finish the + // going away process (see DeleteStream()). + MaybeFinishGoingAway(); +} + +void SpdySession::OnPing(uint32 unique_id) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_PING, + base::Bind(&NetLogSpdyPingCallback, unique_id, "received")); + + // Send response to a PING from server. + if (unique_id % 2 == 0) { + WritePingFrame(unique_id); + return; + } + + --pings_in_flight_; + if (pings_in_flight_ < 0) { + RecordProtocolErrorHistogram(PROTOCOL_ERROR_UNEXPECTED_PING); + CloseSessionResult result = + DoCloseSession(ERR_SPDY_PROTOCOL_ERROR, "pings_in_flight_ is < 0."); + DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED); + pings_in_flight_ = 0; + return; + } + + if (pings_in_flight_ > 0) + return; + + // We will record RTT in histogram when there are no more client sent + // pings_in_flight_. + RecordPingRTTHistogram(time_func_() - last_ping_sent_time_); +} + +void SpdySession::OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) { + CHECK(in_io_loop_); + + if (availability_state_ == STATE_CLOSED) + return; + + DCHECK_LE(delta_window_size, static_cast<uint32>(kint32max)); + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_RECEIVED_WINDOW_UPDATE_FRAME, + base::Bind(&NetLogSpdyWindowUpdateFrameCallback, + stream_id, delta_window_size)); + + if (stream_id == kSessionFlowControlStreamId) { + // WINDOW_UPDATE for the session. + if (flow_control_state_ < FLOW_CONTROL_STREAM_AND_SESSION) { + LOG(WARNING) << "Received WINDOW_UPDATE for session when " + << "session flow control is not turned on"; + // TODO(akalin): Record an error and close the session. + return; + } + + if (delta_window_size < 1u) { + RecordProtocolErrorHistogram(PROTOCOL_ERROR_INVALID_WINDOW_UPDATE_SIZE); + CloseSessionResult result = DoCloseSession( + ERR_SPDY_PROTOCOL_ERROR, + "Received WINDOW_UPDATE with an invalid delta_window_size " + + base::UintToString(delta_window_size)); + DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED); + return; + } + + IncreaseSendWindowSize(static_cast<int32>(delta_window_size)); + } else { + // WINDOW_UPDATE for a stream. + if (flow_control_state_ < FLOW_CONTROL_STREAM) { + // TODO(akalin): Record an error and close the session. + LOG(WARNING) << "Received WINDOW_UPDATE for stream " << stream_id + << " when flow control is not turned on"; + return; + } + + ActiveStreamMap::iterator it = active_streams_.find(stream_id); + + if (it == active_streams_.end()) { + // NOTE: it may just be that the stream was cancelled. + LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id; + return; + } + + SpdyStream* stream = it->second.stream; + CHECK_EQ(stream->stream_id(), stream_id); + + if (delta_window_size < 1u) { + ResetStreamIterator(it, + RST_STREAM_FLOW_CONTROL_ERROR, + base::StringPrintf( + "Received WINDOW_UPDATE with an invalid " + "delta_window_size %ud", delta_window_size)); + return; + } + + CHECK_EQ(it->second.stream->stream_id(), stream_id); + it->second.stream->IncreaseSendWindowSize( + static_cast<int32>(delta_window_size)); + } +} + +void SpdySession::OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) { + // TODO(akalin): Handle PUSH_PROMISE frames. +} + +void SpdySession::SendStreamWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) { + CHECK_GE(flow_control_state_, FLOW_CONTROL_STREAM); + ActiveStreamMap::const_iterator it = active_streams_.find(stream_id); + CHECK(it != active_streams_.end()); + CHECK_EQ(it->second.stream->stream_id(), stream_id); + SendWindowUpdateFrame( + stream_id, delta_window_size, it->second.stream->priority()); +} + +void SpdySession::SendInitialData() { + DCHECK(enable_sending_initial_data_); + DCHECK_NE(availability_state_, STATE_CLOSED); + + if (send_connection_header_prefix_) { + DCHECK_EQ(protocol_, kProtoHTTP2Draft04); + scoped_ptr<SpdyFrame> connection_header_prefix_frame( + new SpdyFrame(const_cast<char*>(kHttp2ConnectionHeaderPrefix), + kHttp2ConnectionHeaderPrefixSize, + false /* take_ownership */)); + // Count the prefix as part of the subsequent SETTINGS frame. + EnqueueSessionWrite(HIGHEST, SETTINGS, + connection_header_prefix_frame.Pass()); + } + + // First, notify the server about the settings they should use when + // communicating with us. + SettingsMap settings_map; + // Create a new settings frame notifying the server of our + // max concurrent streams and initial window size. + settings_map[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams); + if (flow_control_state_ >= FLOW_CONTROL_STREAM && + stream_initial_recv_window_size_ != kSpdyStreamInitialWindowSize) { + settings_map[SETTINGS_INITIAL_WINDOW_SIZE] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, + stream_initial_recv_window_size_); + } + SendSettings(settings_map); + + // Next, notify the server about our initial recv window size. + if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) { + // Bump up the receive window size to the real initial value. This + // has to go here since the WINDOW_UPDATE frame sent by + // IncreaseRecvWindowSize() call uses |buffered_spdy_framer_|. + DCHECK_GT(kDefaultInitialRecvWindowSize, session_recv_window_size_); + // This condition implies that |kDefaultInitialRecvWindowSize| - + // |session_recv_window_size_| doesn't overflow. + DCHECK_GT(session_recv_window_size_, 0); + IncreaseRecvWindowSize( + kDefaultInitialRecvWindowSize - session_recv_window_size_); + } + + // Finally, notify the server about the settings they have + // previously told us to use when communicating with them (after + // applying them). + const SettingsMap& server_settings_map = + http_server_properties_->GetSpdySettings(host_port_pair()); + if (server_settings_map.empty()) + return; + + SettingsMap::const_iterator it = + server_settings_map.find(SETTINGS_CURRENT_CWND); + uint32 cwnd = (it != server_settings_map.end()) ? it->second.second : 0; + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100); + + for (SettingsMap::const_iterator it = server_settings_map.begin(); + it != server_settings_map.end(); ++it) { + const SpdySettingsIds new_id = it->first; + const uint32 new_val = it->second.second; + HandleSetting(new_id, new_val); + } + + SendSettings(server_settings_map); +} + + +void SpdySession::SendSettings(const SettingsMap& settings) { + DCHECK_NE(availability_state_, STATE_CLOSED); + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS, + base::Bind(&NetLogSpdySendSettingsCallback, &settings)); + + // Create the SETTINGS frame and send it. + DCHECK(buffered_spdy_framer_.get()); + scoped_ptr<SpdyFrame> settings_frame( + buffered_spdy_framer_->CreateSettings(settings)); + sent_settings_ = true; + EnqueueSessionWrite(HIGHEST, SETTINGS, settings_frame.Pass()); +} + +void SpdySession::HandleSetting(uint32 id, uint32 value) { + switch (id) { + case SETTINGS_MAX_CONCURRENT_STREAMS: + max_concurrent_streams_ = std::min(static_cast<size_t>(value), + kMaxConcurrentStreamLimit); + ProcessPendingStreamRequests(); + break; + case SETTINGS_INITIAL_WINDOW_SIZE: { + if (flow_control_state_ < FLOW_CONTROL_STREAM) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_INITIAL_WINDOW_SIZE_NO_FLOW_CONTROL); + return; + } + + if (value > static_cast<uint32>(kint32max)) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_INITIAL_WINDOW_SIZE_OUT_OF_RANGE, + NetLog::IntegerCallback("initial_window_size", value)); + return; + } + + // SETTINGS_INITIAL_WINDOW_SIZE updates initial_send_window_size_ only. + int32 delta_window_size = + static_cast<int32>(value) - stream_initial_send_window_size_; + stream_initial_send_window_size_ = static_cast<int32>(value); + UpdateStreamsSendWindowSize(delta_window_size); + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE, + NetLog::IntegerCallback("delta_window_size", delta_window_size)); + break; + } + } +} + +void SpdySession::UpdateStreamsSendWindowSize(int32 delta_window_size) { + DCHECK_GE(flow_control_state_, FLOW_CONTROL_STREAM); + for (ActiveStreamMap::iterator it = active_streams_.begin(); + it != active_streams_.end(); ++it) { + it->second.stream->AdjustSendWindowSize(delta_window_size); + } + + for (CreatedStreamSet::const_iterator it = created_streams_.begin(); + it != created_streams_.end(); it++) { + (*it)->AdjustSendWindowSize(delta_window_size); + } +} + +void SpdySession::SendPrefacePingIfNoneInFlight() { + if (pings_in_flight_ || !enable_ping_based_connection_checking_) + return; + + base::TimeTicks now = time_func_(); + // If there is no activity in the session, then send a preface-PING. + if ((now - last_activity_time_) > connection_at_risk_of_loss_time_) + SendPrefacePing(); +} + +void SpdySession::SendPrefacePing() { + WritePingFrame(next_ping_id_); +} + +void SpdySession::SendWindowUpdateFrame(SpdyStreamId stream_id, + uint32 delta_window_size, + RequestPriority priority) { + CHECK_GE(flow_control_state_, FLOW_CONTROL_STREAM); + ActiveStreamMap::const_iterator it = active_streams_.find(stream_id); + if (it != active_streams_.end()) { + CHECK_EQ(it->second.stream->stream_id(), stream_id); + } else { + CHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + CHECK_EQ(stream_id, kSessionFlowControlStreamId); + } + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_SENT_WINDOW_UPDATE_FRAME, + base::Bind(&NetLogSpdyWindowUpdateFrameCallback, + stream_id, delta_window_size)); + + DCHECK(buffered_spdy_framer_.get()); + scoped_ptr<SpdyFrame> window_update_frame( + buffered_spdy_framer_->CreateWindowUpdate(stream_id, delta_window_size)); + EnqueueSessionWrite(priority, WINDOW_UPDATE, window_update_frame.Pass()); +} + +void SpdySession::WritePingFrame(uint32 unique_id) { + DCHECK(buffered_spdy_framer_.get()); + scoped_ptr<SpdyFrame> ping_frame( + buffered_spdy_framer_->CreatePingFrame(unique_id)); + EnqueueSessionWrite(HIGHEST, PING, ping_frame.Pass()); + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_PING, + base::Bind(&NetLogSpdyPingCallback, unique_id, "sent")); + } + if (unique_id % 2 != 0) { + next_ping_id_ += 2; + ++pings_in_flight_; + PlanToCheckPingStatus(); + last_ping_sent_time_ = time_func_(); + } +} + +void SpdySession::PlanToCheckPingStatus() { + if (check_ping_status_pending_) + return; + + check_ping_status_pending_ = true; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(), + time_func_()), hung_interval_); +} + +void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) { + CHECK(!in_io_loop_); + DCHECK_NE(availability_state_, STATE_CLOSED); + + // Check if we got a response back for all PINGs we had sent. + if (pings_in_flight_ == 0) { + check_ping_status_pending_ = false; + return; + } + + DCHECK(check_ping_status_pending_); + + base::TimeTicks now = time_func_(); + base::TimeDelta delay = hung_interval_ - (now - last_activity_time_); + + if (delay.InMilliseconds() < 0 || last_activity_time_ < last_check_time) { + // Track all failed PING messages in a separate bucket. + const base::TimeDelta kFailedPing = + base::TimeDelta::FromInternalValue(INT_MAX); + RecordPingRTTHistogram(kFailedPing); + CloseSessionResult result = + DoCloseSession(ERR_SPDY_PING_FAILED, "Failed ping."); + DCHECK_EQ(result, SESSION_CLOSED_AND_REMOVED); + return; + } + + // Check the status of connection after a delay. + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(), + now), + delay); +} + +void SpdySession::RecordPingRTTHistogram(base::TimeDelta duration) { + UMA_HISTOGRAM_TIMES("Net.SpdyPing.RTT", duration); +} + +void SpdySession::RecordProtocolErrorHistogram( + SpdyProtocolErrorDetails details) { + UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails2", details, + NUM_SPDY_PROTOCOL_ERROR_DETAILS); + if (EndsWith(host_port_pair().host(), "google.com", false)) { + UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails_Google2", details, + NUM_SPDY_PROTOCOL_ERROR_DETAILS); + } +} + +void SpdySession::RecordHistograms() { + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession", + streams_initiated_count_, + 0, 300, 50); + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedPerSession", + streams_pushed_count_, + 0, 300, 50); + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedAndClaimedPerSession", + streams_pushed_and_claimed_count_, + 0, 300, 50); + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsAbandonedPerSession", + streams_abandoned_count_, + 0, 300, 50); + UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsSent", + sent_settings_ ? 1 : 0, 2); + UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsReceived", + received_settings_ ? 1 : 0, 2); + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamStallsPerSession", + stalled_streams_, + 0, 300, 50); + UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionsWithStalls", + stalled_streams_ > 0 ? 1 : 0, 2); + + if (received_settings_) { + // Enumerate the saved settings, and set histograms for it. + const SettingsMap& settings_map = + http_server_properties_->GetSpdySettings(host_port_pair()); + + SettingsMap::const_iterator it; + for (it = settings_map.begin(); it != settings_map.end(); ++it) { + const SpdySettingsIds id = it->first; + const uint32 val = it->second.second; + switch (id) { + case SETTINGS_CURRENT_CWND: + // Record several different histograms to see if cwnd converges + // for larger volumes of data being sent. + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd", + val, 1, 200, 100); + if (total_bytes_received_ > 10 * 1024) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd10K", + val, 1, 200, 100); + if (total_bytes_received_ > 25 * 1024) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd25K", + val, 1, 200, 100); + if (total_bytes_received_ > 50 * 1024) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd50K", + val, 1, 200, 100); + if (total_bytes_received_ > 100 * 1024) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd100K", + val, 1, 200, 100); + } + } + } + } + break; + case SETTINGS_ROUND_TRIP_TIME: + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRTT", + val, 1, 1200, 100); + break; + case SETTINGS_DOWNLOAD_RETRANS_RATE: + UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRetransRate", + val, 1, 100, 50); + break; + default: + break; + } + } + } +} + +void SpdySession::CompleteStreamRequest(SpdyStreamRequest* pending_request) { + CHECK(pending_request); + + PendingStreamRequestCompletionSet::iterator it = + pending_stream_request_completions_.find(pending_request); + + // Abort if the request has already been cancelled. + if (it == pending_stream_request_completions_.end()) + return; + + base::WeakPtr<SpdyStream> stream; + int rv = CreateStream(*pending_request, &stream); + pending_stream_request_completions_.erase(it); + + if (rv == OK) { + DCHECK(stream.get()); + pending_request->OnRequestCompleteSuccess(&stream); + } else { + DCHECK(!stream.get()); + pending_request->OnRequestCompleteFailure(rv); + } +} + +SSLClientSocket* SpdySession::GetSSLClientSocket() const { + if (!is_secure_) + return NULL; + SSLClientSocket* ssl_socket = + reinterpret_cast<SSLClientSocket*>(connection_->socket()); + DCHECK(ssl_socket); + return ssl_socket; +} + +void SpdySession::OnWriteBufferConsumed( + size_t frame_payload_size, + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source) { + // We can be called with |in_io_loop_| set if a write SpdyBuffer is + // deleted (e.g., a stream is closed due to incoming data). + + if (availability_state_ == STATE_CLOSED) + return; + + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + + if (consume_source == SpdyBuffer::DISCARD) { + // If we're discarding a frame or part of it, increase the send + // window by the number of discarded bytes. (Although if we're + // discarding part of a frame, it's probably because of a write + // error and we'll be tearing down the session soon.) + size_t remaining_payload_bytes = std::min(consume_size, frame_payload_size); + DCHECK_GT(remaining_payload_bytes, 0u); + IncreaseSendWindowSize(static_cast<int32>(remaining_payload_bytes)); + } + // For consumed bytes, the send window is increased when we receive + // a WINDOW_UPDATE frame. +} + +void SpdySession::IncreaseSendWindowSize(int32 delta_window_size) { + // We can be called with |in_io_loop_| set if a SpdyBuffer is + // deleted (e.g., a stream is closed due to incoming data). + + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + DCHECK_GE(delta_window_size, 1); + + // Check for overflow. + int32 max_delta_window_size = kint32max - session_send_window_size_; + if (delta_window_size > max_delta_window_size) { + RecordProtocolErrorHistogram(PROTOCOL_ERROR_INVALID_WINDOW_UPDATE_SIZE); + CloseSessionResult result = DoCloseSession( + ERR_SPDY_PROTOCOL_ERROR, + "Received WINDOW_UPDATE [delta: " + + base::IntToString(delta_window_size) + + "] for session overflows session_send_window_size_ [current: " + + base::IntToString(session_send_window_size_) + "]"); + DCHECK_NE(result, SESSION_ALREADY_CLOSED); + return; + } + + session_send_window_size_ += delta_window_size; + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_UPDATE_SEND_WINDOW, + base::Bind(&NetLogSpdySessionWindowUpdateCallback, + delta_window_size, session_send_window_size_)); + + DCHECK(!IsSendStalled()); + ResumeSendStalledStreams(); +} + +void SpdySession::DecreaseSendWindowSize(int32 delta_window_size) { + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + + // We only call this method when sending a frame. Therefore, + // |delta_window_size| should be within the valid frame size range. + DCHECK_GE(delta_window_size, 1); + DCHECK_LE(delta_window_size, kMaxSpdyFrameChunkSize); + + // |send_window_size_| should have been at least |delta_window_size| for + // this call to happen. + DCHECK_GE(session_send_window_size_, delta_window_size); + + session_send_window_size_ -= delta_window_size; + + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_UPDATE_SEND_WINDOW, + base::Bind(&NetLogSpdySessionWindowUpdateCallback, + -delta_window_size, session_send_window_size_)); +} + +void SpdySession::OnReadBufferConsumed( + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source) { + // We can be called with |in_io_loop_| set if a read SpdyBuffer is + // deleted (e.g., discarded by a SpdyReadQueue). + + if (availability_state_ == STATE_CLOSED) + return; + + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + DCHECK_GE(consume_size, 1u); + DCHECK_LE(consume_size, static_cast<size_t>(kint32max)); + + IncreaseRecvWindowSize(static_cast<int32>(consume_size)); +} + +void SpdySession::IncreaseRecvWindowSize(int32 delta_window_size) { + DCHECK_NE(availability_state_, STATE_CLOSED); + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + DCHECK_GE(session_unacked_recv_window_bytes_, 0); + DCHECK_GE(session_recv_window_size_, session_unacked_recv_window_bytes_); + DCHECK_GE(delta_window_size, 1); + // Check for overflow. + DCHECK_LE(delta_window_size, kint32max - session_recv_window_size_); + + session_recv_window_size_ += delta_window_size; + net_log_.AddEvent( + NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW, + base::Bind(&NetLogSpdySessionWindowUpdateCallback, + delta_window_size, session_recv_window_size_)); + + session_unacked_recv_window_bytes_ += delta_window_size; + if (session_unacked_recv_window_bytes_ > kSpdySessionInitialWindowSize / 2) { + SendWindowUpdateFrame(kSessionFlowControlStreamId, + session_unacked_recv_window_bytes_, + HIGHEST); + session_unacked_recv_window_bytes_ = 0; + } +} + +void SpdySession::DecreaseRecvWindowSize(int32 delta_window_size) { + CHECK(in_io_loop_); + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + DCHECK_GE(delta_window_size, 1); + + // Since we never decrease the initial receive window size, + // |delta_window_size| should never cause |recv_window_size_| to go + // negative. If we do, the receive window isn't being respected. + if (delta_window_size > session_recv_window_size_) { + RecordProtocolErrorHistogram(PROTOCOL_ERROR_RECEIVE_WINDOW_VIOLATION); + CloseSessionResult result = DoCloseSession( + ERR_SPDY_PROTOCOL_ERROR, + "delta_window_size is " + base::IntToString(delta_window_size) + + " in DecreaseRecvWindowSize, which is larger than the receive " + + "window size of " + base::IntToString(session_recv_window_size_)); + DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED); + return; + } + + session_recv_window_size_ -= delta_window_size; + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_UPDATE_RECV_WINDOW, + base::Bind(&NetLogSpdySessionWindowUpdateCallback, + -delta_window_size, session_recv_window_size_)); +} + +void SpdySession::QueueSendStalledStream(const SpdyStream& stream) { + DCHECK(stream.send_stalled_by_flow_control()); + stream_send_unstall_queue_[stream.priority()].push_back(stream.stream_id()); +} + +namespace { + +// Helper function to return the total size of an array of objects +// with .size() member functions. +template <typename T, size_t N> size_t GetTotalSize(const T (&arr)[N]) { + size_t total_size = 0; + for (size_t i = 0; i < N; ++i) { + total_size += arr[i].size(); + } + return total_size; +} + +} // namespace + +void SpdySession::ResumeSendStalledStreams() { + DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION); + + // We don't have to worry about new streams being queued, since + // doing so would cause IsSendStalled() to return true. But we do + // have to worry about streams being closed, as well as ourselves + // being closed. + + while (availability_state_ != STATE_CLOSED && !IsSendStalled()) { + size_t old_size = 0; + if (DCHECK_IS_ON()) + old_size = GetTotalSize(stream_send_unstall_queue_); + + SpdyStreamId stream_id = PopStreamToPossiblyResume(); + if (stream_id == 0) + break; + ActiveStreamMap::const_iterator it = active_streams_.find(stream_id); + // The stream may actually still be send-stalled after this (due + // to its own send window) but that's okay -- it'll then be + // resumed once its send window increases. + if (it != active_streams_.end()) + it->second.stream->PossiblyResumeIfSendStalled(); + + // The size should decrease unless we got send-stalled again. + if (!IsSendStalled()) + DCHECK_LT(GetTotalSize(stream_send_unstall_queue_), old_size); + } +} + +SpdyStreamId SpdySession::PopStreamToPossiblyResume() { + for (int i = NUM_PRIORITIES - 1; i >= 0; --i) { + std::deque<SpdyStreamId>* queue = &stream_send_unstall_queue_[i]; + if (!queue->empty()) { + SpdyStreamId stream_id = queue->front(); + queue->pop_front(); + return stream_id; + } + } + return 0; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_session.h b/chromium/net/spdy/spdy_session.h new file mode 100644 index 00000000000..819db111b26 --- /dev/null +++ b/chromium/net/spdy/spdy_session.h @@ -0,0 +1,1143 @@ +// 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. + +#ifndef NET_SPDY_SPDY_SESSION_H_ +#define NET_SPDY_SPDY_SESSION_H_ + +#include <deque> +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "net/base/io_buffer.h" +#include "net/base/load_states.h" +#include "net/base/net_errors.h" +#include "net/base/net_export.h" +#include "net/base/request_priority.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/client_socket_pool.h" +#include "net/socket/next_proto.h" +#include "net/socket/ssl_client_socket.h" +#include "net/socket/stream_socket.h" +#include "net/spdy/buffered_spdy_framer.h" +#include "net/spdy/spdy_buffer.h" +#include "net/spdy/spdy_credential_state.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_stream.h" +#include "net/spdy/spdy_write_queue.h" +#include "net/ssl/ssl_config_service.h" +#include "url/gurl.h" + +namespace net { + +// This is somewhat arbitrary and not really fixed, but it will always work +// reasonably with ethernet. Chop the world into 2-packet chunks. This is +// somewhat arbitrary, but is reasonably small and ensures that we elicit +// ACKs quickly from TCP (because TCP tries to only ACK every other packet). +const int kMss = 1430; +// The 8 is the size of the SPDY frame header. +const int kMaxSpdyFrameChunkSize = (2 * kMss) - 8; + +// Maximum number of concurrent streams we will create, unless the server +// sends a SETTINGS frame with a different value. +const size_t kInitialMaxConcurrentStreams = 100; + +// Specifies the maxiumum concurrent streams server could send (via push). +const int kMaxConcurrentPushedStreams = 1000; + +// Specifies the maximum number of bytes to read synchronously before +// yielding. +const int kMaxReadBytesWithoutYielding = 32 * 1024; + +// The initial receive window size for both streams and sessions. +const int32 kDefaultInitialRecvWindowSize = 10 * 1024 * 1024; // 10MB + +class BoundNetLog; +struct LoadTimingInfo; +class SpdyStream; +class SSLInfo; + +// NOTE: There's an enum of the same name (also with numeric suffixes) +// in histograms.xml. +// +// WARNING: DO NOT INSERT ENUMS INTO THIS LIST! Add only to the end. +enum SpdyProtocolErrorDetails { + // SpdyFramer::SpdyErrors + SPDY_ERROR_NO_ERROR, + SPDY_ERROR_INVALID_CONTROL_FRAME, + SPDY_ERROR_CONTROL_PAYLOAD_TOO_LARGE, + SPDY_ERROR_ZLIB_INIT_FAILURE, + SPDY_ERROR_UNSUPPORTED_VERSION, + SPDY_ERROR_DECOMPRESS_FAILURE, + SPDY_ERROR_COMPRESS_FAILURE, + SPDY_ERROR_CREDENTIAL_FRAME_CORRUPT, + SPDY_ERROR_INVALID_DATA_FRAME_FLAGS, + SPDY_ERROR_INVALID_CONTROL_FRAME_FLAGS, + // SpdyRstStreamStatus + STATUS_CODE_INVALID, + STATUS_CODE_PROTOCOL_ERROR, + STATUS_CODE_INVALID_STREAM, + STATUS_CODE_REFUSED_STREAM, + STATUS_CODE_UNSUPPORTED_VERSION, + STATUS_CODE_CANCEL, + STATUS_CODE_INTERNAL_ERROR, + STATUS_CODE_FLOW_CONTROL_ERROR, + STATUS_CODE_STREAM_IN_USE, + STATUS_CODE_STREAM_ALREADY_CLOSED, + STATUS_CODE_INVALID_CREDENTIALS, + STATUS_CODE_FRAME_TOO_LARGE, + // SpdySession errors + PROTOCOL_ERROR_UNEXPECTED_PING, + PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM, + PROTOCOL_ERROR_SPDY_COMPRESSION_FAILURE, + PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION, + PROTOCOL_ERROR_SYN_REPLY_NOT_RECEIVED, + PROTOCOL_ERROR_INVALID_WINDOW_UPDATE_SIZE, + PROTOCOL_ERROR_RECEIVE_WINDOW_VIOLATION, + NUM_SPDY_PROTOCOL_ERROR_DETAILS +}; + +COMPILE_ASSERT(STATUS_CODE_INVALID == + static_cast<SpdyProtocolErrorDetails>(SpdyFramer::LAST_ERROR), + SpdyProtocolErrorDetails_SpdyErrors_mismatch); + +COMPILE_ASSERT(PROTOCOL_ERROR_UNEXPECTED_PING == + static_cast<SpdyProtocolErrorDetails>( + RST_STREAM_NUM_STATUS_CODES + STATUS_CODE_INVALID), + SpdyProtocolErrorDetails_SpdyErrors_mismatch); + +// A helper class used to manage a request to create a stream. +class NET_EXPORT_PRIVATE SpdyStreamRequest { + public: + SpdyStreamRequest(); + // Calls CancelRequest(). + ~SpdyStreamRequest(); + + // Starts the request to create a stream. If OK is returned, then + // ReleaseStream() may be called. If ERR_IO_PENDING is returned, + // then when the stream is created, |callback| will be called, at + // which point ReleaseStream() may be called. Otherwise, the stream + // is not created, an error is returned, and ReleaseStream() may not + // be called. + // + // If OK is returned, must not be called again without + // ReleaseStream() being called first. If ERR_IO_PENDING is + // returned, must not be called again without CancelRequest() or + // ReleaseStream() being called first. Otherwise, in case of an + // immediate error, this may be called again. + int StartRequest(SpdyStreamType type, + const base::WeakPtr<SpdySession>& session, + const GURL& url, + RequestPriority priority, + const BoundNetLog& net_log, + const CompletionCallback& callback); + + // Cancels any pending stream creation request. May be called + // repeatedly. + void CancelRequest(); + + // Transfers the created stream (guaranteed to not be NULL) to the + // caller. Must be called at most once after StartRequest() returns + // OK or |callback| is called with OK. The caller must immediately + // set a delegate for the returned stream (except for test code). + base::WeakPtr<SpdyStream> ReleaseStream(); + + private: + friend class SpdySession; + + // Called by |session_| when the stream attempt has finished + // successfully. + void OnRequestCompleteSuccess(base::WeakPtr<SpdyStream>* stream); + + // Called by |session_| when the stream attempt has finished with an + // error. Also called with ERR_ABORTED if |session_| is destroyed + // while the stream attempt is still pending. + void OnRequestCompleteFailure(int rv); + + // Accessors called by |session_|. + SpdyStreamType type() const { return type_; } + const GURL& url() const { return url_; } + RequestPriority priority() const { return priority_; } + const BoundNetLog& net_log() const { return net_log_; } + + void Reset(); + + SpdyStreamType type_; + base::WeakPtr<SpdySession> session_; + base::WeakPtr<SpdyStream> stream_; + GURL url_; + RequestPriority priority_; + BoundNetLog net_log_; + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(SpdyStreamRequest); +}; + +class NET_EXPORT SpdySession : public BufferedSpdyFramerVisitorInterface, + public SpdyFramerDebugVisitorInterface, + public LayeredPool { + public: + // TODO(akalin): Use base::TickClock when it becomes available. + typedef base::TimeTicks (*TimeFunc)(void); + + // How we handle flow control (version-dependent). + enum FlowControlState { + FLOW_CONTROL_NONE, + FLOW_CONTROL_STREAM, + FLOW_CONTROL_STREAM_AND_SESSION + }; + + // Create a new SpdySession. + // |spdy_session_key| is the host/port that this session connects to, privacy + // and proxy configuration settings that it's using. + // |session| is the HttpNetworkSession. |net_log| is the NetLog that we log + // network events to. + SpdySession(const SpdySessionKey& spdy_session_key, + const base::WeakPtr<HttpServerProperties>& http_server_properties, + bool verify_domain_authentication, + bool enable_sending_initial_data, + bool enable_credential_frames, + bool enable_compression, + bool enable_ping_based_connection_checking, + NextProto default_protocol, + size_t stream_initial_recv_window_size, + size_t initial_max_concurrent_streams, + size_t max_concurrent_streams_limit, + TimeFunc time_func, + const HostPortPair& trusted_spdy_proxy, + NetLog* net_log); + + virtual ~SpdySession(); + + const HostPortPair& host_port_pair() const { + return spdy_session_key_.host_port_proxy_pair().first; + } + const HostPortProxyPair& host_port_proxy_pair() const { + return spdy_session_key_.host_port_proxy_pair(); + } + const SpdySessionKey& spdy_session_key() const { + return spdy_session_key_; + } + // Get a pushed stream for a given |url|. If the server initiates a + // stream, it might already exist for a given path. The server + // might also not have initiated the stream yet, but indicated it + // will via X-Associated-Content. Returns OK if a stream was found + // and put into |spdy_stream|, or if one was not found but it is + // okay to create a new stream (in which case |spdy_stream| is + // reset). Returns an error (not ERR_IO_PENDING) otherwise, and + // resets |spdy_stream|. + int GetPushStream( + const GURL& url, + base::WeakPtr<SpdyStream>* spdy_stream, + const BoundNetLog& stream_net_log); + + // Initialize the session with the given connection. |is_secure| + // must indicate whether |connection| uses an SSL socket or not; it + // is usually true, but it can be false for testing or when SPDY is + // configured to work with non-secure sockets. + // + // |pool| is the SpdySessionPool that owns us. Its lifetime must + // strictly be greater than |this|. + // + // |certificate_error_code| must either be OK or less than + // ERR_IO_PENDING. + // + // Returns OK on success, or an error on failure. Never returns + // ERR_IO_PENDING. If an error is returned, the session must be + // destroyed immediately. + Error InitializeWithSocket(scoped_ptr<ClientSocketHandle> connection, + SpdySessionPool* pool, + bool is_secure, + int certificate_error_code); + + // Returns the protocol used by this session. Always between + // kProtoSPDY2 and kProtoSPDYMaximumVersion. + // + // TODO(akalin): Change the lower bound to kProtoSPDYMinimumVersion + // once we stop supporting SPDY/1. + NextProto protocol() const { return protocol_; } + + // Check to see if this SPDY session can support an additional domain. + // If the session is un-authenticated, then this call always returns true. + // For SSL-based sessions, verifies that the server certificate in use by + // this session provides authentication for the domain and no client + // certificate or channel ID was sent to the original server during the SSL + // handshake. NOTE: This function can have false negatives on some + // platforms. + // TODO(wtc): rename this function and the Net.SpdyIPPoolDomainMatch + // histogram because this function does more than verifying domain + // authentication now. + bool VerifyDomainAuthentication(const std::string& domain); + + // Pushes the given producer into the write queue for + // |stream|. |stream| is guaranteed to be activated before the + // producer is used to produce its frame. + void EnqueueStreamWrite(const base::WeakPtr<SpdyStream>& stream, + SpdyFrameType frame_type, + scoped_ptr<SpdyBufferProducer> producer); + + // Creates and returns a SYN frame for |stream_id|. + scoped_ptr<SpdyFrame> CreateSynStream( + SpdyStreamId stream_id, + RequestPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + const SpdyHeaderBlock& headers); + + // Tries to create a CREDENTIAL frame. If successful, fills in + // |credential_frame| and returns OK. Returns the error (guaranteed + // to not be ERR_IO_PENDING) otherwise. + int CreateCredentialFrame(const std::string& origin, + const std::string& key, + const std::string& cert, + RequestPriority priority, + scoped_ptr<SpdyFrame>* credential_frame); + + // Creates and returns a SpdyBuffer holding a data frame with the + // given data. May return NULL if stalled by flow control. + scoped_ptr<SpdyBuffer> CreateDataBuffer(SpdyStreamId stream_id, + IOBuffer* data, + int len, + SpdyDataFlags flags); + + // Close the stream with the given ID, which must exist and be + // active. Note that that stream may hold the last reference to the + // session. + void CloseActiveStream(SpdyStreamId stream_id, int status); + + // Close the given created stream, which must exist but not yet be + // active. Note that |stream| may hold the last reference to the + // session. + void CloseCreatedStream(const base::WeakPtr<SpdyStream>& stream, int status); + + // Send a RST_STREAM frame with the given status code and close the + // stream with the given ID, which must exist and be active. Note + // that that stream may hold the last reference to the session. + void ResetStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status, + const std::string& description); + + // Check if a stream is active. + bool IsStreamActive(SpdyStreamId stream_id) const; + + // The LoadState is used for informing the user of the current network + // status, such as "resolving host", "connecting", etc. + LoadState GetLoadState() const; + + // Fills SSL info in |ssl_info| and returns true when SSL is in use. + bool GetSSLInfo(SSLInfo* ssl_info, + bool* was_npn_negotiated, + NextProto* protocol_negotiated); + + // Fills SSL Certificate Request info |cert_request_info| and returns + // true when SSL is in use. + bool GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info); + + // Returns the ServerBoundCertService used by this Socket, or NULL + // if server bound certs are not supported in this session. + ServerBoundCertService* GetServerBoundCertService() const; + + // Send a WINDOW_UPDATE frame for a stream. Called by a stream + // whenever receive window size is increased. + void SendStreamWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size); + + // Whether the stream is closed, i.e. it has stopped processing data + // and is about to be destroyed. + // + // TODO(akalin): This is only used in tests. Remove this function + // and have tests test the WeakPtr instead. + bool IsClosed() const { return availability_state_ == STATE_CLOSED; } + + // Closes this session. This will close all active streams and mark + // the session as permanently closed. Callers must assume that the + // session is destroyed after this is called. (However, it may not + // be destroyed right away, e.g. when a SpdySession function is + // present in the call stack.) + // + // |err| should be < ERR_IO_PENDING; this function is intended to be + // called on error. + // |description| indicates the reason for the error. + void CloseSessionOnError(Error err, const std::string& description); + + // Retrieves information on the current state of the SPDY session as a + // Value. Caller takes possession of the returned value. + base::Value* GetInfoAsValue() const; + + // Indicates whether the session is being reused after having successfully + // used to send/receive data in the past. + bool IsReused() const; + + // Returns true if the underlying transport socket ever had any reads or + // writes. + bool WasEverUsed() const { + return connection_->socket()->WasEverUsed(); + } + + // Returns the load timing information from the perspective of the given + // stream. If it's not the first stream, the connection is considered reused + // for that stream. + // + // This uses a different notion of reuse than IsReused(). This function + // sets |socket_reused| to false only if |stream_id| is the ID of the first + // stream using the session. IsReused(), on the other hand, indicates if the + // session has been used to send/receive data at all. + bool GetLoadTimingInfo(SpdyStreamId stream_id, + LoadTimingInfo* load_timing_info) const; + + // Returns true if session is not currently active + bool is_active() const { + return !active_streams_.empty() || !created_streams_.empty(); + } + + // Access to the number of active and pending streams. These are primarily + // available for testing and diagnostics. + size_t num_active_streams() const { return active_streams_.size(); } + size_t num_unclaimed_pushed_streams() const { + return unclaimed_pushed_streams_.size(); + } + size_t num_created_streams() const { return created_streams_.size(); } + + size_t pending_create_stream_queue_size(RequestPriority priority) const { + DCHECK_LT(priority, NUM_PRIORITIES); + return pending_create_stream_queues_[priority].size(); + } + + // Returns the (version-dependent) flow control state. + FlowControlState flow_control_state() const { + return flow_control_state_; + } + + // Returns the current |stream_initial_send_window_size_|. + int32 stream_initial_send_window_size() const { + return stream_initial_send_window_size_; + } + + // Returns the current |stream_initial_recv_window_size_|. + int32 stream_initial_recv_window_size() const { + return stream_initial_recv_window_size_; + } + + // Returns true if no stream in the session can send data due to + // session flow control. + bool IsSendStalled() const { + return + flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION && + session_send_window_size_ == 0; + } + + const BoundNetLog& net_log() const { return net_log_; } + + int GetPeerAddress(IPEndPoint* address) const; + int GetLocalAddress(IPEndPoint* address) const; + + // Returns true if requests on this session require credentials. + bool NeedsCredentials() const; + + SpdyCredentialState* credential_state() { return &credential_state_; } + + // Adds |alias| to set of aliases associated with this session. + void AddPooledAlias(const SpdySessionKey& alias_key); + + // Returns the set of aliases associated with this session. + const std::set<SpdySessionKey>& pooled_aliases() const { + return pooled_aliases_; + } + + int GetProtocolVersion() const; + + size_t GetDataFrameMinimumSize() const { + return buffered_spdy_framer_->GetDataFrameMinimumSize(); + } + + size_t GetControlFrameHeaderSize() const { + return buffered_spdy_framer_->GetControlFrameHeaderSize(); + } + + size_t GetFrameMinimumSize() const { + return buffered_spdy_framer_->GetFrameMinimumSize(); + } + + size_t GetFrameMaximumSize() const { + return buffered_spdy_framer_->GetFrameMaximumSize(); + } + + size_t GetDataFrameMaximumPayload() const { + return buffered_spdy_framer_->GetDataFrameMaximumPayload(); + } + + // Must be used only by |pool_|. + base::WeakPtr<SpdySession> GetWeakPtr(); + + // LayeredPool implementation: + virtual bool CloseOneIdleConnection() OVERRIDE; + + private: + friend class base::RefCounted<SpdySession>; + friend class SpdyStreamRequest; + friend class SpdySessionTest; + + // Allow tests to access our innards for testing purposes. + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, ClientPing); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, FailedPing); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GetActivePushStream); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, DeleteExpiredPushStreams); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, ProtocolNegotiation); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, ClearSettings); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, AdjustRecvWindowSize); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, AdjustSendWindowSize); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlInactiveStream); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlNoReceiveLeaks); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlNoSendLeaks); + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlEndToEnd); + + typedef std::deque<SpdyStreamRequest*> PendingStreamRequestQueue; + typedef std::set<SpdyStreamRequest*> PendingStreamRequestCompletionSet; + + struct ActiveStreamInfo { + ActiveStreamInfo(); + explicit ActiveStreamInfo(SpdyStream* stream); + ~ActiveStreamInfo(); + + SpdyStream* stream; + bool waiting_for_syn_reply; + }; + typedef std::map<SpdyStreamId, ActiveStreamInfo> ActiveStreamMap; + + struct PushedStreamInfo { + PushedStreamInfo(); + PushedStreamInfo(SpdyStreamId stream_id, base::TimeTicks creation_time); + ~PushedStreamInfo(); + + SpdyStreamId stream_id; + base::TimeTicks creation_time; + }; + typedef std::map<GURL, PushedStreamInfo> PushedStreamMap; + + typedef std::set<SpdyStream*> CreatedStreamSet; + + enum AvailabilityState { + // The session is available in its socket pool and can be used + // freely. + STATE_AVAILABLE, + // The session can process data on existing streams but will + // refuse to create new ones. + STATE_GOING_AWAY, + // The session has been closed, is waiting to be deleted, and will + // refuse to process any more data. + STATE_CLOSED + }; + + enum ReadState { + READ_STATE_DO_READ, + READ_STATE_DO_READ_COMPLETE, + }; + + enum WriteState { + // There is no in-flight write and the write queue is empty. + WRITE_STATE_IDLE, + WRITE_STATE_DO_WRITE, + WRITE_STATE_DO_WRITE_COMPLETE, + }; + + // The return value of DoCloseSession() describing what was done. + enum CloseSessionResult { + // The session was already closed so nothing was done. + SESSION_ALREADY_CLOSED, + // The session was moved into the closed state but was not removed + // from |pool_| (because we're in an IO loop). + SESSION_CLOSED_BUT_NOT_REMOVED, + // The session was moved into the closed state and removed from + // |pool_|. + SESSION_CLOSED_AND_REMOVED, + }; + + // Checks whether a stream for the given |url| can be created or + // retrieved from the set of unclaimed push streams. Returns OK if + // so. Otherwise, the session is closed and an error < + // ERR_IO_PENDING is returned. + Error TryAccessStream(const GURL& url); + + // Called by SpdyStreamRequest to start a request to create a + // stream. If OK is returned, then |stream| will be filled in with a + // valid stream. If ERR_IO_PENDING is returned, then + // |request->OnRequestComplete{Success,Failure}()| will be called + // when the stream is created (unless it is cancelled). Otherwise, + // no stream is created and the error is returned. + int TryCreateStream(SpdyStreamRequest* request, + base::WeakPtr<SpdyStream>* stream); + + // Actually create a stream into |stream|. Returns OK if successful; + // otherwise, returns an error and |stream| is not filled. + int CreateStream(const SpdyStreamRequest& request, + base::WeakPtr<SpdyStream>* stream); + + // Called by SpdyStreamRequest to remove |request| from the stream + // creation queue. + void CancelStreamRequest(SpdyStreamRequest* request); + + // Called when there is room to create more streams (e.g., a stream + // was closed). Processes as many pending stream requests as + // possible. + void ProcessPendingStreamRequests(); + + // Close the stream pointed to by the given iterator. Note that that + // stream may hold the last reference to the session. + void CloseActiveStreamIterator(ActiveStreamMap::iterator it, int status); + + // Close the stream pointed to by the given iterator. Note that that + // stream may hold the last reference to the session. + void CloseCreatedStreamIterator(CreatedStreamSet::iterator it, int status); + + // Calls EnqueueResetStreamFrame() and then + // CloseActiveStreamIterator(). + void ResetStreamIterator(ActiveStreamMap::iterator it, + SpdyRstStreamStatus status, + const std::string& description); + + // Send a RST_STREAM frame with the given parameters. There should + // either be no active stream with the given ID, or that active + // stream should be closed shortly after this function is called. + // + // TODO(akalin): Rename this to EnqueueResetStreamFrame(). + void EnqueueResetStreamFrame(SpdyStreamId stream_id, + RequestPriority priority, + SpdyRstStreamStatus status, + const std::string& description); + + // Calls DoReadLoop and then if |availability_state_| is + // STATE_CLOSED, calls RemoveFromPool(). + // + // Use this function instead of DoReadLoop when posting a task to + // pump the read loop. + void PumpReadLoop(ReadState expected_read_state, int result); + + // Advance the ReadState state machine. |expected_read_state| is the + // expected starting read state. + // + // This function must always be called via PumpReadLoop() except for + // from InitializeWithSocket(). + int DoReadLoop(ReadState expected_read_state, int result); + // The implementations of the states of the ReadState state machine. + int DoRead(); + int DoReadComplete(int result); + + // Calls DoWriteLoop and then if |availability_state_| is + // STATE_CLOSED, calls RemoveFromPool(). + // + // Use this function instead of DoWriteLoop when posting a task to + // pump the write loop. + void PumpWriteLoop(WriteState expected_write_state, int result); + + // Advance the WriteState state machine. |expected_write_state| is + // the expected starting write state. + // + // This function must always be called via PumpWriteLoop(). + int DoWriteLoop(WriteState expected_write_state, int result); + // The implementations of the states of the WriteState state machine. + int DoWrite(); + int DoWriteComplete(int result); + + // TODO(akalin): Rename the Send* and Write* functions below to + // Enqueue*. + + // Send initial data. Called when a connection is successfully + // established in InitializeWithSocket() and + // |enable_sending_initial_data_| is true. + void SendInitialData(); + + // Helper method to send a SETTINGS frame. + void SendSettings(const SettingsMap& settings); + + // Handle SETTING. Either when we send settings, or when we receive a + // SETTINGS control frame, update our SpdySession accordingly. + void HandleSetting(uint32 id, uint32 value); + + // Adjust the send window size of all ActiveStreams and PendingStreamRequests. + void UpdateStreamsSendWindowSize(int32 delta_window_size); + + // Send the PING (preface-PING) frame. + void SendPrefacePingIfNoneInFlight(); + + // Send PING if there are no PINGs in flight and we haven't heard from server. + void SendPrefacePing(); + + // Send a single WINDOW_UPDATE frame. + void SendWindowUpdateFrame(SpdyStreamId stream_id, uint32 delta_window_size, + RequestPriority priority); + + // Send the PING frame. + void WritePingFrame(uint32 unique_id); + + // Post a CheckPingStatus call after delay. Don't post if there is already + // CheckPingStatus running. + void PlanToCheckPingStatus(); + + // Check the status of the connection. It calls |CloseSessionOnError| if we + // haven't received any data in |kHungInterval| time period. + void CheckPingStatus(base::TimeTicks last_check_time); + + // Get a new stream id. + int GetNewStreamId(); + + // Pushes the given frame with the given priority into the write + // queue for the session. + void EnqueueSessionWrite(RequestPriority priority, + SpdyFrameType frame_type, + scoped_ptr<SpdyFrame> frame); + + // Puts |producer| associated with |stream| onto the write queue + // with the given priority. + void EnqueueWrite(RequestPriority priority, + SpdyFrameType frame_type, + scoped_ptr<SpdyBufferProducer> producer, + const base::WeakPtr<SpdyStream>& stream); + + // Inserts a newly-created stream into |created_streams_|. + void InsertCreatedStream(scoped_ptr<SpdyStream> stream); + + // Activates |stream| (which must be in |created_streams_|) by + // assigning it an ID and returns it. + scoped_ptr<SpdyStream> ActivateCreatedStream(SpdyStream* stream); + + // Inserts a newly-activated stream into |active_streams_|. + void InsertActivatedStream(scoped_ptr<SpdyStream> stream); + + // Remove all internal references to |stream|, call OnClose() on it, + // and process any pending stream requests before deleting it. Note + // that |stream| may hold the last reference to the session. + void DeleteStream(scoped_ptr<SpdyStream> stream, int status); + + // Check if we have a pending pushed-stream for this url + // Returns the stream if found (and returns it from the pending + // list). Returns NULL otherwise. + base::WeakPtr<SpdyStream> GetActivePushStream(const GURL& url); + + // Delegates to |stream->OnInitialResponseHeadersReceived()|. If an + // error is returned, the last reference to |this| may have been + // released. + int OnInitialResponseHeadersReceived(const SpdyHeaderBlock& response_headers, + base::Time response_time, + base::TimeTicks recv_first_byte_time, + SpdyStream* stream); + + void RecordPingRTTHistogram(base::TimeDelta duration); + void RecordHistograms(); + void RecordProtocolErrorHistogram(SpdyProtocolErrorDetails details); + + // DCHECKs that |availability_state_| >= STATE_GOING_AWAY, that + // there are no pending stream creation requests, and that there are + // no created streams. + void DcheckGoingAway() const; + + // Calls DcheckGoingAway(), then DCHECKs that |availability_state_| + // == STATE_CLOSED, |error_on_close_| has a valid value, that there + // are no active streams or unclaimed pushed streams, and that the + // write queue is empty. + void DcheckClosed() const; + + // Closes all active streams with stream id's greater than + // |last_good_stream_id|, as well as any created or pending + // streams. Must be called only when |availability_state_| >= + // STATE_GOING_AWAY. After this function, DcheckGoingAway() will + // pass. May be called multiple times. + void StartGoingAway(SpdyStreamId last_good_stream_id, Error status); + + // Must be called only when going away (i.e., DcheckGoingAway() + // passes). If there are no more active streams and the session + // isn't closed yet, close it. + void MaybeFinishGoingAway(); + + // If the stream is already closed, does nothing. Otherwise, moves + // the session to a closed state. Then, if we're in an IO loop, + // returns (as the IO loop will do the pool removal itself when its + // done). Otherwise, also removes |this| from |pool_|. The returned + // result describes what was done. + CloseSessionResult DoCloseSession(Error err, const std::string& description); + + // Remove this session from its pool, which must exist. Must be + // called only when the session is closed. + // + // Must be called only via Pump{Read,Write}Loop() or + // DoCloseSession(). + void RemoveFromPool(); + + // Called right before closing a (possibly-inactive) stream for a + // reason other than being requested to by the stream. + void LogAbandonedStream(SpdyStream* stream, Error status); + + // Called right before closing an active stream for a reason other + // than being requested to by the stream. + void LogAbandonedActiveStream(ActiveStreamMap::const_iterator it, + Error status); + + // Invokes a user callback for stream creation. We provide this method so it + // can be deferred to the MessageLoop, so we avoid re-entrancy problems. + void CompleteStreamRequest(SpdyStreamRequest* pending_request); + + // Remove old unclaimed pushed streams. + void DeleteExpiredPushedStreams(); + + // BufferedSpdyFramerVisitorInterface: + virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE; + virtual void OnStreamError(SpdyStreamId stream_id, + const std::string& description) OVERRIDE; + virtual void OnPing(uint32 unique_id) OVERRIDE; + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) OVERRIDE; + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) OVERRIDE; + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) OVERRIDE; + virtual void OnSettings(bool clear_persisted) OVERRIDE; + virtual void OnSetting( + SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE; + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) OVERRIDE; + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) OVERRIDE; + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional, + const SpdyHeaderBlock& headers) OVERRIDE; + virtual void OnSynReply( + SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) OVERRIDE; + virtual void OnHeaders( + SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) OVERRIDE; + + // SpdyFramerDebugVisitorInterface + virtual void OnSendCompressedFrame( + SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) OVERRIDE; + virtual void OnReceiveCompressedFrame( + SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len) OVERRIDE {} + + // Called when bytes are consumed from a SpdyBuffer for a DATA frame + // that is to be written or is being written. Increases the send + // window size accordingly if some or all of the SpdyBuffer is being + // discarded. + // + // If session flow control is turned off, this must not be called. + void OnWriteBufferConsumed(size_t frame_payload_size, + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source); + + // Called by OnWindowUpdate() (which is in turn called by the + // framer) to increase this session's send window size by + // |delta_window_size| from a WINDOW_UPDATE frome, which must be at + // least 1. If |delta_window_size| would cause this session's send + // window size to overflow, does nothing. + // + // If session flow control is turned off, this must not be called. + void IncreaseSendWindowSize(int32 delta_window_size); + + // If session flow control is turned on, called by CreateDataFrame() + // (which is in turn called by a stream) to decrease this session's + // send window size by |delta_window_size|, which must be at least 1 + // and at most kMaxSpdyFrameChunkSize. |delta_window_size| must not + // cause this session's send window size to go negative. + // + // If session flow control is turned off, this must not be called. + void DecreaseSendWindowSize(int32 delta_window_size); + + // Called when bytes are consumed by the delegate from a SpdyBuffer + // containing received data. Increases the receive window size + // accordingly. + // + // If session flow control is turned off, this must not be called. + void OnReadBufferConsumed(size_t consume_size, + SpdyBuffer::ConsumeSource consume_source); + + // Called by OnReadBufferConsume to increase this session's receive + // window size by |delta_window_size|, which must be at least 1 and + // must not cause this session's receive window size to overflow, + // possibly also sending a WINDOW_UPDATE frame. Also called during + // initialization to set the initial receive window size. + // + // If session flow control is turned off, this must not be called. + void IncreaseRecvWindowSize(int32 delta_window_size); + + // Called by OnStreamFrameData (which is in turn called by the + // framer) to decrease this session's receive window size by + // |delta_window_size|, which must be at least 1 and must not cause + // this session's receive window size to go negative. + // + // If session flow control is turned off, this must not be called. + void DecreaseRecvWindowSize(int32 delta_window_size); + + // Queue a send-stalled stream for possibly resuming once we're not + // send-stalled anymore. + void QueueSendStalledStream(const SpdyStream& stream); + + // Go through the queue of send-stalled streams and try to resume as + // many as possible. + void ResumeSendStalledStreams(); + + // Returns the next stream to possibly resume, or 0 if the queue is + // empty. + SpdyStreamId PopStreamToPossiblyResume(); + + // -------------------------- + // Helper methods for testing + // -------------------------- + + void set_connection_at_risk_of_loss_time(base::TimeDelta duration) { + connection_at_risk_of_loss_time_ = duration; + } + + void set_hung_interval(base::TimeDelta duration) { + hung_interval_ = duration; + } + + int64 pings_in_flight() const { return pings_in_flight_; } + + uint32 next_ping_id() const { return next_ping_id_; } + + base::TimeTicks last_activity_time() const { return last_activity_time_; } + + bool check_ping_status_pending() const { return check_ping_status_pending_; } + + size_t max_concurrent_streams() const { return max_concurrent_streams_; } + + // Returns the SSLClientSocket that this SPDY session sits on top of, + // or NULL, if the transport is not SSL. + SSLClientSocket* GetSSLClientSocket() const; + + // Used for posting asynchronous IO tasks. We use this even though + // SpdySession is refcounted because we don't need to keep the SpdySession + // alive if the last reference is within a RunnableMethod. Just revoke the + // method. + base::WeakPtrFactory<SpdySession> weak_factory_; + + // Whether Do{Read,Write}Loop() is in the call stack. Useful for + // making sure we don't destroy ourselves prematurely in that case. + bool in_io_loop_; + + // The key used to identify this session. + const SpdySessionKey spdy_session_key_; + + // Set set of SpdySessionKeys for which this session has serviced + // requests. + std::set<SpdySessionKey> pooled_aliases_; + + // |pool_| owns us, therefore its lifetime must exceed ours. We set + // this to NULL after we are removed from the pool. + SpdySessionPool* pool_; + const base::WeakPtr<HttpServerProperties> http_server_properties_; + + // The socket handle for this session. + scoped_ptr<ClientSocketHandle> connection_; + + // The read buffer used to read data from the socket. + scoped_refptr<IOBuffer> read_buffer_; + + int stream_hi_water_mark_; // The next stream id to use. + + // Queue, for each priority, of pending stream requests that have + // not yet been satisfied. + PendingStreamRequestQueue pending_create_stream_queues_[NUM_PRIORITIES]; + + // A set of requests that are waiting to be completed (i.e., for the + // stream to actually be created). This is necessary since we kick + // off the stream creation asynchronously, and so the request may be + // cancelled before the asynchronous task to create the stream runs. + PendingStreamRequestCompletionSet pending_stream_request_completions_; + + // Map from stream id to all active streams. Streams are active in the sense + // that they have a consumer (typically SpdyNetworkTransaction and regardless + // of whether or not there is currently any ongoing IO [might be waiting for + // the server to start pushing the stream]) or there are still network events + // incoming even though the consumer has already gone away (cancellation). + // + // |active_streams_| owns all its SpdyStream objects. + // + // TODO(willchan): Perhaps we should separate out cancelled streams and move + // them into a separate ActiveStreamMap, and not deliver network events to + // them? + ActiveStreamMap active_streams_; + + // (Bijective) map from the URL to the ID of the streams that have + // already started to be pushed by the server, but do not have + // consumers yet. Contains a subset of |active_streams_|. + PushedStreamMap unclaimed_pushed_streams_; + + // Set of all created streams but that have not yet sent any frames. + // + // |created_streams_| owns all its SpdyStream objects. + CreatedStreamSet created_streams_; + + // The write queue. + SpdyWriteQueue write_queue_; + + // Data for the frame we are currently sending. + + // The buffer we're currently writing. + scoped_ptr<SpdyBuffer> in_flight_write_; + // The type of the frame in |in_flight_write_|. + SpdyFrameType in_flight_write_frame_type_; + // The size of the frame in |in_flight_write_|. + size_t in_flight_write_frame_size_; + // The stream to notify when |in_flight_write_| has been written to + // the socket completely. + base::WeakPtr<SpdyStream> in_flight_write_stream_; + + // Flag if we're using an SSL connection for this SpdySession. + bool is_secure_; + + // Certificate error code when using a secure connection. + int certificate_error_code_; + + // Spdy Frame state. + scoped_ptr<BufferedSpdyFramer> buffered_spdy_framer_; + + // The state variables. + AvailabilityState availability_state_; + ReadState read_state_; + WriteState write_state_; + + // If the session was closed (i.e., |availability_state_| is + // STATE_CLOSED), then |error_on_close_| holds the error with which + // it was closed, which is < ERR_IO_PENDING. Otherwise, it is set to + // OK. + Error error_on_close_; + + // Limits + size_t max_concurrent_streams_; // 0 if no limit + size_t max_concurrent_streams_limit_; + + // Some statistics counters for the session. + int streams_initiated_count_; + int streams_pushed_count_; + int streams_pushed_and_claimed_count_; + int streams_abandoned_count_; + + // |total_bytes_received_| keeps track of all the bytes read by the + // SpdySession. It is used by the |Net.SpdySettingsCwnd...| histograms. + int total_bytes_received_; + + bool sent_settings_; // Did this session send settings when it started. + bool received_settings_; // Did this session receive at least one settings + // frame. + int stalled_streams_; // Count of streams that were ever stalled. + + // Count of all pings on the wire, for which we have not gotten a response. + int64 pings_in_flight_; + + // This is the next ping_id (unique_id) to be sent in PING frame. + uint32 next_ping_id_; + + // This is the last time we have sent a PING. + base::TimeTicks last_ping_sent_time_; + + // This is the last time we had activity in the session. + base::TimeTicks last_activity_time_; + + // This is the next time that unclaimed push streams should be checked for + // expirations. + base::TimeTicks next_unclaimed_push_stream_sweep_time_; + + // Indicate if we have already scheduled a delayed task to check the ping + // status. + bool check_ping_status_pending_; + + // Whether to send the (HTTP/2) connection header prefix. + bool send_connection_header_prefix_; + + // The (version-dependent) flow control state. + FlowControlState flow_control_state_; + + // Initial send window size for this session's streams. Can be + // changed by an arriving SETTINGS frame. Newly created streams use + // this value for the initial send window size. + int32 stream_initial_send_window_size_; + + // Initial receive window size for this session's streams. There are + // plans to add a command line switch that would cause a SETTINGS + // frame with window size announcement to be sent on startup. Newly + // created streams will use this value for the initial receive + // window size. + int32 stream_initial_recv_window_size_; + + // Session flow control variables. All zero unless session flow + // control is turned on. + int32 session_send_window_size_; + int32 session_recv_window_size_; + int32 session_unacked_recv_window_bytes_; + + // A queue of stream IDs that have been send-stalled at some point + // in the past. + std::deque<SpdyStreamId> stream_send_unstall_queue_[NUM_PRIORITIES]; + + BoundNetLog net_log_; + + // Outside of tests, these should always be true. + bool verify_domain_authentication_; + bool enable_sending_initial_data_; + bool enable_credential_frames_; + bool enable_compression_; + bool enable_ping_based_connection_checking_; + + // The SPDY protocol used. Always between kProtoSPDY2 and + // kProtoSPDYMaximumVersion. + // + // TODO(akalin): Change the lower bound to kProtoSPDYMinimumVersion + // once we stop supporting SPDY/1. + NextProto protocol_; + + SpdyCredentialState credential_state_; + + // |connection_at_risk_of_loss_time_| is an optimization to avoid sending + // wasteful preface pings (when we just got some data). + // + // If it is zero (the most conservative figure), then we always send the + // preface ping (when none are in flight). + // + // It is common for TCP/IP sessions to time out in about 3-5 minutes. + // Certainly if it has been more than 3 minutes, we do want to send a preface + // ping. + // + // We don't think any connection will time out in under about 10 seconds. So + // this might as well be set to something conservative like 10 seconds. Later, + // we could adjust it to send fewer pings perhaps. + base::TimeDelta connection_at_risk_of_loss_time_; + + // The amount of time that we are willing to tolerate with no activity (of any + // form), while there is a ping in flight, before we declare the connection to + // be hung. TODO(rtenneti): When hung, instead of resetting connection, race + // to build a new connection, and see if that completes before we (finally) + // get a PING response (http://crbug.com/127812). + base::TimeDelta hung_interval_; + + // This SPDY proxy is allowed to push resources from origins that are + // different from those of their associated streams. + HostPortPair trusted_spdy_proxy_; + + TimeFunc time_func_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_SESSION_H_ diff --git a/chromium/net/spdy/spdy_session_key.cc b/chromium/net/spdy/spdy_session_key.cc new file mode 100644 index 00000000000..3d6cdeafad1 --- /dev/null +++ b/chromium/net/spdy/spdy_session_key.cc @@ -0,0 +1,50 @@ +// 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 "net/spdy/spdy_session_key.h" + +#include "base/logging.h" + +namespace net { + +SpdySessionKey::SpdySessionKey() : privacy_mode_(kPrivacyModeDisabled) { +} + +SpdySessionKey::SpdySessionKey(const HostPortPair& host_port_pair, + const ProxyServer& proxy_server, + PrivacyMode privacy_mode) + : host_port_proxy_pair_(host_port_pair, proxy_server), + privacy_mode_(privacy_mode) { + DVLOG(1) << "SpdySessionKey(host=" << host_port_pair.ToString() + << ", proxy=" << proxy_server.ToURI() + << ", privacy=" << privacy_mode; +} + +SpdySessionKey::SpdySessionKey(const HostPortProxyPair& host_port_proxy_pair, + PrivacyMode privacy_mode) + : host_port_proxy_pair_(host_port_proxy_pair), + privacy_mode_(privacy_mode) { + DVLOG(1) << "SpdySessionKey(hppp=" << host_port_proxy_pair.first.ToString() + << "," << host_port_proxy_pair.second.ToURI() + << ", privacy=" << privacy_mode; +} + +SpdySessionKey::~SpdySessionKey() {} + +bool SpdySessionKey::operator<(const SpdySessionKey& other) const { + if (privacy_mode_ != other.privacy_mode_) + return privacy_mode_ < other.privacy_mode_; + if (!host_port_proxy_pair_.first.Equals(other.host_port_proxy_pair_.first)) + return host_port_proxy_pair_.first < other.host_port_proxy_pair_.first; + return host_port_proxy_pair_.second < other.host_port_proxy_pair_.second; +} + +bool SpdySessionKey::Equals(const SpdySessionKey& other) const { + return privacy_mode_ == other.privacy_mode_ && + host_port_proxy_pair_.first.Equals(other.host_port_proxy_pair_.first) && + host_port_proxy_pair_.second == other.host_port_proxy_pair_.second; +} + +} // namespace net + diff --git a/chromium/net/spdy/spdy_session_key.h b/chromium/net/spdy/spdy_session_key.h new file mode 100644 index 00000000000..59c832bb52b --- /dev/null +++ b/chromium/net/spdy/spdy_session_key.h @@ -0,0 +1,58 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_SESSION_KEY_H_ +#define NET_SPDY_SPDY_SESSION_KEY_H_ + +#include "net/base/privacy_mode.h" +#include "net/proxy/proxy_server.h" + +namespace net { + +// SpdySessionKey is used as unique index for SpdySessionPool. +class NET_EXPORT_PRIVATE SpdySessionKey { + public: + SpdySessionKey(); + SpdySessionKey(const HostPortPair& host_port_pair, + const ProxyServer& proxy_server, + PrivacyMode privacy_mode); + + // Temporary hack for implicit copy constructor + SpdySessionKey(const HostPortProxyPair& host_port_proxy_pair, + PrivacyMode privacy_mode); + + ~SpdySessionKey(); + + // Comparator function so this can be placed in a std::map. + bool operator<(const SpdySessionKey& other) const; + + // Equality test of contents. (Probably another violation of style guide). + bool Equals(const SpdySessionKey& other) const; + + const HostPortProxyPair& host_port_proxy_pair() const { + return host_port_proxy_pair_; + } + + const HostPortPair& host_port_pair() const { + return host_port_proxy_pair_.first; + } + + const ProxyServer& proxy_server() const { + return host_port_proxy_pair_.second; + } + + PrivacyMode privacy_mode() const { + return privacy_mode_; + } + + private: + HostPortProxyPair host_port_proxy_pair_; + // If enabled, then session cannot be tracked by the server. + PrivacyMode privacy_mode_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_SESSION_KEY_H_ + diff --git a/chromium/net/spdy/spdy_session_pool.cc b/chromium/net/spdy/spdy_session_pool.cc new file mode 100644 index 00000000000..9e47f3f4e89 --- /dev/null +++ b/chromium/net/spdy/spdy_session_pool.cc @@ -0,0 +1,399 @@ +// 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 "net/spdy/spdy_session_pool.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/values.h" +#include "net/base/address_list.h" +#include "net/http/http_network_session.h" +#include "net/http/http_server_properties.h" +#include "net/spdy/spdy_session.h" + + +namespace net { + +namespace { + +enum SpdySessionGetTypes { + CREATED_NEW = 0, + FOUND_EXISTING = 1, + FOUND_EXISTING_FROM_IP_POOL = 2, + IMPORTED_FROM_SOCKET = 3, + SPDY_SESSION_GET_MAX = 4 +}; + +} // namespace + +SpdySessionPool::SpdySessionPool( + HostResolver* resolver, + SSLConfigService* ssl_config_service, + const base::WeakPtr<HttpServerProperties>& http_server_properties, + bool force_single_domain, + bool enable_ip_pooling, + bool enable_credential_frames, + bool enable_compression, + bool enable_ping_based_connection_checking, + NextProto default_protocol, + size_t stream_initial_recv_window_size, + size_t initial_max_concurrent_streams, + size_t max_concurrent_streams_limit, + SpdySessionPool::TimeFunc time_func, + const std::string& trusted_spdy_proxy) + : http_server_properties_(http_server_properties), + ssl_config_service_(ssl_config_service), + resolver_(resolver), + verify_domain_authentication_(true), + enable_sending_initial_data_(true), + force_single_domain_(force_single_domain), + enable_ip_pooling_(enable_ip_pooling), + enable_credential_frames_(enable_credential_frames), + enable_compression_(enable_compression), + enable_ping_based_connection_checking_( + enable_ping_based_connection_checking), + // TODO(akalin): Force callers to have a valid value of + // |default_protocol_|. Or at least make the default be + // kProtoSPDY3. + default_protocol_( + (default_protocol == kProtoUnknown) ? + kProtoSPDY2 : default_protocol), + stream_initial_recv_window_size_(stream_initial_recv_window_size), + initial_max_concurrent_streams_(initial_max_concurrent_streams), + max_concurrent_streams_limit_(max_concurrent_streams_limit), + time_func_(time_func), + trusted_spdy_proxy_( + HostPortPair::FromString(trusted_spdy_proxy)) { + // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we + // stop supporting SPDY/1. + DCHECK(default_protocol_ >= kProtoSPDY2 && + default_protocol_ <= kProtoSPDYMaximumVersion); + NetworkChangeNotifier::AddIPAddressObserver(this); + if (ssl_config_service_.get()) + ssl_config_service_->AddObserver(this); + CertDatabase::GetInstance()->AddObserver(this); +} + +SpdySessionPool::~SpdySessionPool() { + CloseAllSessions(); + + if (ssl_config_service_.get()) + ssl_config_service_->RemoveObserver(this); + NetworkChangeNotifier::RemoveIPAddressObserver(this); + CertDatabase::GetInstance()->RemoveObserver(this); +} + +net::Error SpdySessionPool::CreateAvailableSessionFromSocket( + const SpdySessionKey& key, + scoped_ptr<ClientSocketHandle> connection, + const BoundNetLog& net_log, + int certificate_error_code, + base::WeakPtr<SpdySession>* available_session, + bool is_secure) { + // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we + // stop supporting SPDY/1. + DCHECK_GE(default_protocol_, kProtoSPDY2); + DCHECK_LE(default_protocol_, kProtoSPDYMaximumVersion); + + UMA_HISTOGRAM_ENUMERATION( + "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX); + + scoped_ptr<SpdySession> new_session( + new SpdySession(key, + http_server_properties_, + verify_domain_authentication_, + enable_sending_initial_data_, + enable_credential_frames_, + enable_compression_, + enable_ping_based_connection_checking_, + default_protocol_, + stream_initial_recv_window_size_, + initial_max_concurrent_streams_, + max_concurrent_streams_limit_, + time_func_, + trusted_spdy_proxy_, + net_log.net_log())); + + Error error = new_session->InitializeWithSocket( + connection.Pass(), this, is_secure, certificate_error_code); + DCHECK_NE(error, ERR_IO_PENDING); + + if (error != OK) { + available_session->reset(); + return error; + } + + *available_session = new_session->GetWeakPtr(); + sessions_.insert(new_session.release()); + MapKeyToAvailableSession(key, *available_session); + + net_log.AddEvent( + NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET, + (*available_session)->net_log().source().ToEventParametersCallback()); + + // Look up the IP address for this session so that we can match + // future sessions (potentially to different domains) which can + // potentially be pooled with this one. Because GetPeerAddress() + // reports the proxy's address instead of the origin server, check + // to see if this is a direct connection. + if (enable_ip_pooling_ && key.proxy_server().is_direct()) { + IPEndPoint address; + if ((*available_session)->GetPeerAddress(&address) == OK) + aliases_[address] = key; + } + + return error; +} + +base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession( + const SpdySessionKey& key, + const BoundNetLog& net_log) { + AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); + if (it != available_sessions_.end()) { + UMA_HISTOGRAM_ENUMERATION( + "Net.SpdySessionGet", FOUND_EXISTING, SPDY_SESSION_GET_MAX); + net_log.AddEvent( + NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION, + it->second->net_log().source().ToEventParametersCallback()); + return it->second; + } + + if (!enable_ip_pooling_) + return base::WeakPtr<SpdySession>(); + + // Look up the key's from the resolver's cache. + net::HostResolver::RequestInfo resolve_info(key.host_port_pair()); + AddressList addresses; + int rv = resolver_->ResolveFromCache(resolve_info, &addresses, net_log); + DCHECK_NE(rv, ERR_IO_PENDING); + if (rv != OK) + return base::WeakPtr<SpdySession>(); + + // Check if we have a session through a domain alias. + for (AddressList::const_iterator address_it = addresses.begin(); + address_it != addresses.end(); + ++address_it) { + AliasMap::const_iterator alias_it = aliases_.find(*address_it); + if (alias_it == aliases_.end()) + continue; + + // We found an alias. + const SpdySessionKey& alias_key = alias_it->second; + + // We can reuse this session only if the proxy and privacy + // settings match. + if (!(alias_key.proxy_server() == key.proxy_server()) || + !(alias_key.privacy_mode() == key.privacy_mode())) + continue; + + AvailableSessionMap::iterator available_session_it = + LookupAvailableSessionByKey(alias_key); + if (available_session_it == available_sessions_.end()) { + NOTREACHED(); // It shouldn't be in the aliases table if we can't get it! + continue; + } + + const base::WeakPtr<SpdySession>& available_session = + available_session_it->second; + DCHECK(ContainsKey(sessions_, available_session.get())); + // If the session is a secure one, we need to verify that the + // server is authenticated to serve traffic for |host_port_proxy_pair| too. + if (!available_session->VerifyDomainAuthentication( + key.host_port_pair().host())) { + UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2); + continue; + } + + UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2); + UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", + FOUND_EXISTING_FROM_IP_POOL, + SPDY_SESSION_GET_MAX); + net_log.AddEvent( + NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL, + available_session->net_log().source().ToEventParametersCallback()); + // Add this session to the map so that we can find it next time. + MapKeyToAvailableSession(key, available_session); + available_session->AddPooledAlias(key); + return available_session; + } + + return base::WeakPtr<SpdySession>(); +} + +void SpdySessionPool::MakeSessionUnavailable( + const base::WeakPtr<SpdySession>& available_session) { + UnmapKey(available_session->spdy_session_key()); + RemoveAliases(available_session->spdy_session_key()); + const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases(); + for (std::set<SpdySessionKey>::const_iterator it = aliases.begin(); + it != aliases.end(); ++it) { + UnmapKey(*it); + RemoveAliases(*it); + } + DCHECK(!IsSessionAvailable(available_session)); +} + +void SpdySessionPool::RemoveUnavailableSession( + const base::WeakPtr<SpdySession>& unavailable_session) { + DCHECK(!IsSessionAvailable(unavailable_session)); + + unavailable_session->net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION, + unavailable_session->net_log().source().ToEventParametersCallback()); + + SessionSet::iterator it = sessions_.find(unavailable_session.get()); + CHECK(it != sessions_.end()); + scoped_ptr<SpdySession> owned_session(*it); + sessions_.erase(it); +} + +// Make a copy of |sessions_| in the Close* functions below to avoid +// reentrancy problems. Since arbitrary functions get called by close +// handlers, it doesn't suffice to simply increment the iterator +// before closing. + +void SpdySessionPool::CloseCurrentSessions(net::Error error) { + CloseCurrentSessionsHelper(error, "Closing current sessions.", + false /* idle_only */); +} + +void SpdySessionPool::CloseCurrentIdleSessions() { + CloseCurrentSessionsHelper(ERR_ABORTED, "Closing idle sessions.", + true /* idle_only */); +} + +void SpdySessionPool::CloseAllSessions() { + while (!sessions_.empty()) { + CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.", + false /* idle_only */); + } +} + +base::Value* SpdySessionPool::SpdySessionPoolInfoToValue() const { + base::ListValue* list = new base::ListValue(); + + for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); + it != available_sessions_.end(); ++it) { + // Only add the session if the key in the map matches the main + // host_port_proxy_pair (not an alias). + const SpdySessionKey& key = it->first; + const SpdySessionKey& session_key = it->second->spdy_session_key(); + if (key.Equals(session_key)) + list->Append(it->second->GetInfoAsValue()); + } + return list; +} + +void SpdySessionPool::OnIPAddressChanged() { + CloseCurrentSessions(ERR_NETWORK_CHANGED); + http_server_properties_->ClearAllSpdySettings(); +} + +void SpdySessionPool::OnSSLConfigChanged() { + CloseCurrentSessions(ERR_NETWORK_CHANGED); +} + +void SpdySessionPool::OnCertAdded(const X509Certificate* cert) { + CloseCurrentSessions(ERR_NETWORK_CHANGED); +} + +void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) { + // Per wtc, we actually only need to CloseCurrentSessions when trust is + // reduced. CloseCurrentSessions now because OnCertTrustChanged does not + // tell us this. + // See comments in ClientSocketPoolManager::OnCertTrustChanged. + CloseCurrentSessions(ERR_NETWORK_CHANGED); +} + +bool SpdySessionPool::IsSessionAvailable( + const base::WeakPtr<SpdySession>& session) const { + for (AvailableSessionMap::const_iterator it = available_sessions_.begin(); + it != available_sessions_.end(); ++it) { + if (it->second.get() == session.get()) + return true; + } + return false; +} + +const SpdySessionKey& SpdySessionPool::NormalizeListKey( + const SpdySessionKey& key) const { + if (!force_single_domain_) + return key; + + static SpdySessionKey* single_domain_key = NULL; + if (!single_domain_key) { + HostPortPair single_domain = HostPortPair("singledomain.com", 80); + single_domain_key = new SpdySessionKey(single_domain, + ProxyServer::Direct(), + kPrivacyModeDisabled); + } + return *single_domain_key; +} + +void SpdySessionPool::MapKeyToAvailableSession( + const SpdySessionKey& key, + const base::WeakPtr<SpdySession>& session) { + DCHECK(ContainsKey(sessions_, session.get())); + const SpdySessionKey& normalized_key = NormalizeListKey(key); + std::pair<AvailableSessionMap::iterator, bool> result = + available_sessions_.insert(std::make_pair(normalized_key, session)); + CHECK(result.second); +} + +SpdySessionPool::AvailableSessionMap::iterator +SpdySessionPool::LookupAvailableSessionByKey( + const SpdySessionKey& key) { + const SpdySessionKey& normalized_key = NormalizeListKey(key); + return available_sessions_.find(normalized_key); +} + +void SpdySessionPool::UnmapKey(const SpdySessionKey& key) { + AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key); + CHECK(it != available_sessions_.end()); + available_sessions_.erase(it); +} + +void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) { + // Walk the aliases map, find references to this pair. + // TODO(mbelshe): Figure out if this is too expensive. + for (AliasMap::iterator it = aliases_.begin(); it != aliases_.end(); ) { + if (it->second.Equals(key)) { + AliasMap::iterator old_it = it; + ++it; + aliases_.erase(old_it); + } else { + ++it; + } + } +} + +SpdySessionPool::WeakSessionList SpdySessionPool::GetCurrentSessions() const { + WeakSessionList current_sessions; + for (SessionSet::const_iterator it = sessions_.begin(); + it != sessions_.end(); ++it) { + current_sessions.push_back((*it)->GetWeakPtr()); + } + return current_sessions; +} + +void SpdySessionPool::CloseCurrentSessionsHelper( + Error error, + const std::string& description, + bool idle_only) { + WeakSessionList current_sessions = GetCurrentSessions(); + for (WeakSessionList::const_iterator it = current_sessions.begin(); + it != current_sessions.end(); ++it) { + if (!*it) + continue; + + if (idle_only && (*it)->is_active()) + continue; + + (*it)->CloseSessionOnError(error, description); + DCHECK(!IsSessionAvailable(*it)); + DCHECK(!*it); + } +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_session_pool.h b/chromium/net/spdy/spdy_session_pool.h new file mode 100644 index 00000000000..812b7bd8890 --- /dev/null +++ b/chromium/net/spdy/spdy_session_pool.h @@ -0,0 +1,235 @@ +// 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. + +#ifndef NET_SPDY_SPDY_SESSION_POOL_H_ +#define NET_SPDY_SPDY_SESSION_POOL_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "net/base/host_port_pair.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/net_export.h" +#include "net/base/network_change_notifier.h" +#include "net/cert/cert_database.h" +#include "net/proxy/proxy_config.h" +#include "net/proxy/proxy_server.h" +#include "net/socket/next_proto.h" +#include "net/spdy/spdy_session_key.h" +#include "net/ssl/ssl_config_service.h" + +namespace net { + +class AddressList; +class BoundNetLog; +class ClientSocketHandle; +class HostResolver; +class HttpServerProperties; +class SpdySession; + +// This is a very simple pool for open SpdySessions. +class NET_EXPORT SpdySessionPool + : public NetworkChangeNotifier::IPAddressObserver, + public SSLConfigService::Observer, + public CertDatabase::Observer { + public: + typedef base::TimeTicks (*TimeFunc)(void); + + // |default_protocol| may be kProtoUnknown (e.g., if SPDY is + // disabled), in which case it's set to a default value. Otherwise, + // it must be a SPDY protocol. + SpdySessionPool( + HostResolver* host_resolver, + SSLConfigService* ssl_config_service, + const base::WeakPtr<HttpServerProperties>& http_server_properties, + bool force_single_domain, + bool enable_ip_pooling, + bool enable_credential_frames, + bool enable_compression, + bool enable_ping_based_connection_checking, + NextProto default_protocol, + size_t stream_initial_recv_window_size, + size_t initial_max_concurrent_streams, + size_t max_concurrent_streams_limit, + SpdySessionPool::TimeFunc time_func, + const std::string& trusted_spdy_proxy); + virtual ~SpdySessionPool(); + + // In the functions below, a session is "available" if this pool has + // a reference to it and there is some SpdySessionKey for which + // FindAvailableSession() will return it. A session is "unavailable" + // if this pool has a reference to it but it won't be returned by + // FindAvailableSession() for any SpdySessionKey; for example, this + // can happen when a session receives a GOAWAY frame and is still + // processing existing streams. + + // Create a new SPDY session from an existing socket. There must + // not already be a session for the given key. This pool must have + // been constructed with a valid |default_protocol| value. + // + // |is_secure| can be false for testing or when SPDY is configured + // to work with non-secure sockets. If |is_secure| is true, + // |certificate_error_code| indicates that the certificate error + // encountered when connecting the SSL socket, with OK meaning there + // was no error. + // + // If successful, OK is returned and |available_session| will be + // non-NULL and available. Otherwise, an error is returned and + // |available_session| will be NULL. + net::Error CreateAvailableSessionFromSocket( + const SpdySessionKey& key, + scoped_ptr<ClientSocketHandle> connection, + const BoundNetLog& net_log, + int certificate_error_code, + base::WeakPtr<SpdySession>* available_session, + bool is_secure); + + // Find an available session for the given key, or NULL if there isn't one. + base::WeakPtr<SpdySession> FindAvailableSession(const SpdySessionKey& key, + const BoundNetLog& net_log); + + // Remove all mappings and aliases for the given session, which must + // still be available. Except for in tests, this must be called by + // the given session itself. + void MakeSessionUnavailable( + const base::WeakPtr<SpdySession>& available_session); + + // Removes an unavailable session from the pool. Except for in + // tests, this must be called by the given session itself. + void RemoveUnavailableSession( + const base::WeakPtr<SpdySession>& unavailable_session); + + // Close only the currently existing SpdySessions with |error|. + // Let any new ones created while this method is running continue to + // live. + void CloseCurrentSessions(net::Error error); + + // Close only the currently existing SpdySessions that are idle. + // Let any new ones created while this method is running continue to + // live. + void CloseCurrentIdleSessions(); + + // Close all SpdySessions, including any new ones created in the process of + // closing the current ones. + void CloseAllSessions(); + + // Creates a Value summary of the state of the spdy session pool. The caller + // responsible for deleting the returned value. + base::Value* SpdySessionPoolInfoToValue() const; + + base::WeakPtr<HttpServerProperties> http_server_properties() { + return http_server_properties_; + } + + // NetworkChangeNotifier::IPAddressObserver methods: + + // We flush all idle sessions and release references to the active ones so + // they won't get re-used. The active ones will either complete successfully + // or error out due to the IP address change. + virtual void OnIPAddressChanged() OVERRIDE; + + // SSLConfigService::Observer methods: + + // We perform the same flushing as described above when SSL settings change. + virtual void OnSSLConfigChanged() OVERRIDE; + + // CertDatabase::Observer methods: + virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE; + virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE; + + private: + friend class SpdySessionPoolPeer; // For testing. + + typedef std::set<SpdySession*> SessionSet; + typedef std::vector<base::WeakPtr<SpdySession> > WeakSessionList; + typedef std::map<SpdySessionKey, base::WeakPtr<SpdySession> > + AvailableSessionMap; + typedef std::map<IPEndPoint, SpdySessionKey> AliasMap; + + // Returns true iff |session| is in |available_sessions_|. + bool IsSessionAvailable(const base::WeakPtr<SpdySession>& session) const; + + // Returns a normalized version of the given key suitable for lookup + // into |available_sessions_|. + const SpdySessionKey& NormalizeListKey(const SpdySessionKey& key) const; + + // Map the given key to the given session. There must not already be + // a mapping for |key|. + void MapKeyToAvailableSession(const SpdySessionKey& key, + const base::WeakPtr<SpdySession>& session); + + // Returns an iterator into |available_sessions_| for the given key, + // which may be equal to |available_sessions_.end()|. + AvailableSessionMap::iterator LookupAvailableSessionByKey( + const SpdySessionKey& key); + + // Remove the mapping of the given key, which must exist. + void UnmapKey(const SpdySessionKey& key); + + // Remove all aliases for |key| from the aliases table. + void RemoveAliases(const SpdySessionKey& key); + + // Get a copy of the current sessions as a list of WeakPtrs. Used by + // CloseCurrentSessionsHelper() below. + WeakSessionList GetCurrentSessions() const; + + // Close only the currently existing SpdySessions with |error|. Let + // any new ones created while this method is running continue to + // live. If |idle_only| is true only idle sessions are closed. + void CloseCurrentSessionsHelper( + Error error, + const std::string& description, + bool idle_only); + + const base::WeakPtr<HttpServerProperties> http_server_properties_; + + // The set of all sessions. This is a superset of the sessions in + // |available_sessions_|. + // + // |sessions_| owns all its SpdySession objects. + SessionSet sessions_; + + // This is a map of available sessions by key. A session may appear + // more than once in this map if it has aliases. + AvailableSessionMap available_sessions_; + + // A map of IPEndPoint aliases for sessions. + AliasMap aliases_; + + static bool g_force_single_domain; + + const scoped_refptr<SSLConfigService> ssl_config_service_; + HostResolver* const resolver_; + + // Defaults to true. May be controlled via SpdySessionPoolPeer for tests. + bool verify_domain_authentication_; + bool enable_sending_initial_data_; + bool force_single_domain_; + bool enable_ip_pooling_; + bool enable_credential_frames_; + bool enable_compression_; + bool enable_ping_based_connection_checking_; + const NextProto default_protocol_; + size_t stream_initial_recv_window_size_; + size_t initial_max_concurrent_streams_; + size_t max_concurrent_streams_limit_; + TimeFunc time_func_; + + // This SPDY proxy is allowed to push resources from origins that are + // different from those of their associated streams. + HostPortPair trusted_spdy_proxy_; + + DISALLOW_COPY_AND_ASSIGN(SpdySessionPool); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_SESSION_POOL_H_ diff --git a/chromium/net/spdy/spdy_session_pool_unittest.cc b/chromium/net/spdy/spdy_session_pool_unittest.cc new file mode 100644 index 00000000000..9d0679d4d4b --- /dev/null +++ b/chromium/net/spdy/spdy_session_pool_unittest.cc @@ -0,0 +1,494 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_session_pool.h" + +#include <cstddef> +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/dns/host_cache.h" +#include "net/http/http_network_session.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/transport_client_socket_pool.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_test_util_common.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class SpdySessionPoolTest : public ::testing::Test, + public ::testing::WithParamInterface<NextProto> { + protected: + // Used by RunIPPoolingTest(). + enum SpdyPoolCloseSessionsType { + SPDY_POOL_CLOSE_SESSIONS_MANUALLY, + SPDY_POOL_CLOSE_CURRENT_SESSIONS, + SPDY_POOL_CLOSE_IDLE_SESSIONS, + }; + + SpdySessionPoolTest() + : session_deps_(GetParam()), + spdy_session_pool_(NULL) {} + + void CreateNetworkSession() { + http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + spdy_session_pool_ = http_session_->spdy_session_pool(); + } + + void RunIPPoolingTest(SpdyPoolCloseSessionsType close_sessions_type); + + SpdySessionDependencies session_deps_; + scoped_refptr<HttpNetworkSession> http_session_; + SpdySessionPool* spdy_session_pool_; +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + SpdySessionPoolTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +// A delegate that opens a new session when it is closed. +class SessionOpeningDelegate : public SpdyStream::Delegate { + public: + SessionOpeningDelegate(SpdySessionPool* spdy_session_pool, + const SpdySessionKey& key) + : spdy_session_pool_(spdy_session_pool), + key_(key) {} + + virtual ~SessionOpeningDelegate() {} + + virtual void OnRequestHeadersSent() OVERRIDE {} + + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE { + return RESPONSE_HEADERS_ARE_COMPLETE; + } + + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {} + + virtual void OnDataSent() OVERRIDE {} + + virtual void OnClose(int status) OVERRIDE { + ignore_result(CreateFakeSpdySession(spdy_session_pool_, key_)); + } + + private: + SpdySessionPool* const spdy_session_pool_; + const SpdySessionKey key_; +}; + +// Set up a SpdyStream to create a new session when it is closed. +// CloseCurrentSessions should not close the newly-created session. +TEST_P(SpdySessionPoolTest, CloseCurrentSessions) { + const char kTestHost[] = "www.foo.com"; + const int kTestPort = 80; + + session_deps_.host_resolver->set_synchronous_mode(true); + + HostPortPair test_host_port_pair(kTestHost, kTestPort); + SpdySessionKey test_key = + SpdySessionKey( + test_host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + // Setup the first session to the first host. + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, test_key, BoundNetLog()); + + // Flush the SpdySession::OnReadComplete() task. + base::MessageLoop::current()->RunUntilIdle(); + + // Verify that we have sessions for everything. + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key)); + + // Set the stream to create a new session when it is closed. + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, GURL("http://www.foo.com"), + MEDIUM, BoundNetLog()); + SessionOpeningDelegate delegate(spdy_session_pool_, test_key); + spdy_stream->SetDelegate(&delegate); + + // Close the current session. + spdy_session_pool_->CloseCurrentSessions(net::ERR_ABORTED); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key)); +} + +TEST_P(SpdySessionPoolTest, CloseCurrentIdleSessions) { + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + + session_deps_.host_resolver->set_synchronous_mode(true); + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + // Set up session 1 + const std::string kTestHost1("http://www.a.com"); + HostPortPair test_host_port_pair1(kTestHost1, 80); + SpdySessionKey key1(test_host_port_pair1, ProxyServer::Direct(), + kPrivacyModeDisabled); + base::WeakPtr<SpdySession> session1 = + CreateInsecureSpdySession(http_session_, key1, BoundNetLog()); + GURL url1(kTestHost1); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session1, url1, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + + // Set up session 2 + session_deps_.socket_factory->AddSocketDataProvider(&data); + const std::string kTestHost2("http://www.b.com"); + HostPortPair test_host_port_pair2(kTestHost2, 80); + SpdySessionKey key2(test_host_port_pair2, ProxyServer::Direct(), + kPrivacyModeDisabled); + base::WeakPtr<SpdySession> session2 = + CreateInsecureSpdySession(http_session_, key2, BoundNetLog()); + GURL url2(kTestHost2); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session2, url2, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + + // Set up session 3 + session_deps_.socket_factory->AddSocketDataProvider(&data); + const std::string kTestHost3("http://www.c.com"); + HostPortPair test_host_port_pair3(kTestHost3, 80); + SpdySessionKey key3(test_host_port_pair3, ProxyServer::Direct(), + kPrivacyModeDisabled); + base::WeakPtr<SpdySession> session3 = + CreateInsecureSpdySession(http_session_, key3, BoundNetLog()); + GURL url3(kTestHost3); + base::WeakPtr<SpdyStream> spdy_stream3 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session3, url3, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream3.get() != NULL); + + // All sessions are active and not closed + EXPECT_TRUE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_TRUE(session3->is_active()); + EXPECT_FALSE(session3->IsClosed()); + + // Should not do anything, all are active + spdy_session_pool_->CloseCurrentIdleSessions(); + EXPECT_TRUE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_TRUE(session3->is_active()); + EXPECT_FALSE(session3->IsClosed()); + + // Make sessions 1 and 3 inactive, but keep them open. + // Session 2 still open and active + session1->CloseCreatedStream(spdy_stream1, OK); + EXPECT_EQ(NULL, spdy_stream1.get()); + session3->CloseCreatedStream(spdy_stream3, OK); + EXPECT_EQ(NULL, spdy_stream3.get()); + EXPECT_FALSE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_FALSE(session3->is_active()); + EXPECT_FALSE(session3->IsClosed()); + + // Should close session 1 and 3, 2 should be left open + spdy_session_pool_->CloseCurrentIdleSessions(); + EXPECT_TRUE(session1 == NULL); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_TRUE(session3 == NULL); + + // Should not do anything + spdy_session_pool_->CloseCurrentIdleSessions(); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + + // Make 2 not active + session2->CloseCreatedStream(spdy_stream2, OK); + EXPECT_EQ(NULL, spdy_stream2.get()); + EXPECT_FALSE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + + // This should close session 2 + spdy_session_pool_->CloseCurrentIdleSessions(); + EXPECT_TRUE(session2 == NULL); +} + +// Set up a SpdyStream to create a new session when it is closed. +// CloseAllSessions should close the newly-created session. +TEST_P(SpdySessionPoolTest, CloseAllSessions) { + const char kTestHost[] = "www.foo.com"; + const int kTestPort = 80; + + session_deps_.host_resolver->set_synchronous_mode(true); + + HostPortPair test_host_port_pair(kTestHost, kTestPort); + SpdySessionKey test_key = + SpdySessionKey( + test_host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + // Setup the first session to the first host. + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, test_key, BoundNetLog()); + + // Flush the SpdySession::OnReadComplete() task. + base::MessageLoop::current()->RunUntilIdle(); + + // Verify that we have sessions for everything. + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key)); + + // Set the stream to create a new session when it is closed. + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, GURL("http://www.foo.com"), + MEDIUM, BoundNetLog()); + SessionOpeningDelegate delegate(spdy_session_pool_, test_key); + spdy_stream->SetDelegate(&delegate); + + // Close the current session. + spdy_session_pool_->CloseAllSessions(); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_key)); +} + +// This test has three variants, one for each style of closing the connection. +// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_SESSIONS_MANUALLY, +// the sessions are closed manually, calling SpdySessionPool::Remove() directly. +// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_CURRENT_SESSIONS, +// sessions are closed with SpdySessionPool::CloseCurrentSessions(). +// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_IDLE_SESSIONS, +// sessions are closed with SpdySessionPool::CloseIdleSessions(). +void SpdySessionPoolTest::RunIPPoolingTest( + SpdyPoolCloseSessionsType close_sessions_type) { + const int kTestPort = 80; + struct TestHosts { + std::string url; + std::string name; + std::string iplist; + SpdySessionKey key; + AddressList addresses; + } test_hosts[] = { + { "http:://www.foo.com", + "www.foo.com", + "192.0.2.33,192.168.0.1,192.168.0.5" + }, + { "http://js.foo.com", + "js.foo.com", + "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" + }, + { "http://images.foo.com", + "images.foo.com", + "192.168.0.4,192.168.0.3" + }, + }; + + session_deps_.host_resolver->set_synchronous_mode(true); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) { + session_deps_.host_resolver->rules()->AddIPLiteralRule( + test_hosts[i].name, test_hosts[i].iplist, std::string()); + + // This test requires that the HostResolver cache be populated. Normal + // code would have done this already, but we do it manually. + HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort)); + session_deps_.host_resolver->Resolve( + info, &test_hosts[i].addresses, CompletionCallback(), NULL, + BoundNetLog()); + + // Setup a SpdySessionKey + test_hosts[i].key = SpdySessionKey( + HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(), + kPrivacyModeDisabled); + } + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + // Setup the first session to the first host. + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession( + http_session_, test_hosts[0].key, BoundNetLog()); + + // Flush the SpdySession::OnReadComplete() task. + base::MessageLoop::current()->RunUntilIdle(); + + // The third host has no overlap with the first, so it can't pool IPs. + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key)); + + // The second host overlaps with the first, and should IP pool. + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key)); + + // Verify that the second host, through a proxy, won't share the IP. + SpdySessionKey proxy_key(test_hosts[1].key.host_port_pair(), + ProxyServer::FromPacString("HTTP http://proxy.foo.com/"), + kPrivacyModeDisabled); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, proxy_key)); + + // Overlap between 2 and 3 does is not transitive to 1. + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key)); + + // Create a new session to host 2. + session_deps_.socket_factory->AddSocketDataProvider(&data); + base::WeakPtr<SpdySession> session2 = + CreateInsecureSpdySession( + http_session_, test_hosts[2].key, BoundNetLog()); + + // Verify that we have sessions for everything. + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[0].key)); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key)); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[2].key)); + + // Grab the session to host 1 and verify that it is the same session + // we got with host 0, and that is a different from host 2's session. + base::WeakPtr<SpdySession> session1 = + spdy_session_pool_->FindAvailableSession( + test_hosts[1].key, BoundNetLog()); + EXPECT_EQ(session.get(), session1.get()); + EXPECT_NE(session2.get(), session1.get()); + + // Remove the aliases and observe that we still have a session for host1. + SpdySessionPoolPeer pool_peer(spdy_session_pool_); + pool_peer.RemoveAliases(test_hosts[0].key); + pool_peer.RemoveAliases(test_hosts[1].key); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key)); + + // Expire the host cache + session_deps_.host_resolver->GetHostCache()->clear(); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key)); + + // Cleanup the sessions. + switch (close_sessions_type) { + case SPDY_POOL_CLOSE_SESSIONS_MANUALLY: + session->CloseSessionOnError(ERR_ABORTED, std::string()); + EXPECT_TRUE(session == NULL); + session2->CloseSessionOnError(ERR_ABORTED, std::string()); + EXPECT_TRUE(session2 == NULL); + break; + case SPDY_POOL_CLOSE_CURRENT_SESSIONS: + spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED); + break; + case SPDY_POOL_CLOSE_IDLE_SESSIONS: + GURL url(test_hosts[0].url); + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url, MEDIUM, BoundNetLog()); + GURL url1(test_hosts[1].url); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session1, url1, MEDIUM, BoundNetLog()); + GURL url2(test_hosts[2].url); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session2, url2, MEDIUM, BoundNetLog()); + + // Close streams to make spdy_session and spdy_session1 inactive. + session->CloseCreatedStream(spdy_stream, OK); + EXPECT_EQ(NULL, spdy_stream.get()); + session1->CloseCreatedStream(spdy_stream1, OK); + EXPECT_EQ(NULL, spdy_stream1.get()); + + // Check spdy_session and spdy_session1 are not closed. + EXPECT_FALSE(session->is_active()); + EXPECT_FALSE(session->IsClosed()); + EXPECT_FALSE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + + // Test that calling CloseIdleSessions, does not cause a crash. + // http://crbug.com/181400 + spdy_session_pool_->CloseCurrentIdleSessions(); + + // Verify spdy_session and spdy_session1 are closed. + EXPECT_TRUE(session == NULL); + EXPECT_TRUE(session1 == NULL); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + + spdy_stream2->Cancel(); + EXPECT_EQ(NULL, spdy_stream.get()); + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_EQ(NULL, spdy_stream2.get()); + session2->CloseSessionOnError(ERR_ABORTED, std::string()); + EXPECT_TRUE(session2 == NULL); + break; + } + + // Verify that the map is all cleaned up. + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[0].key)); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[1].key)); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key)); +} + +TEST_P(SpdySessionPoolTest, IPPooling) { + RunIPPoolingTest(SPDY_POOL_CLOSE_SESSIONS_MANUALLY); +} + +TEST_P(SpdySessionPoolTest, IPPoolingCloseCurrentSessions) { + RunIPPoolingTest(SPDY_POOL_CLOSE_CURRENT_SESSIONS); +} + +TEST_P(SpdySessionPoolTest, IPPoolingCloseIdleSessions) { + RunIPPoolingTest(SPDY_POOL_CLOSE_IDLE_SESSIONS); +} + +} // namespace + +} // namespace net diff --git a/chromium/net/spdy/spdy_session_test_util.cc b/chromium/net/spdy/spdy_session_test_util.cc new file mode 100644 index 00000000000..d901cf64c41 --- /dev/null +++ b/chromium/net/spdy/spdy_session_test_util.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_session_test_util.h" + +#include "base/location.h" +#include "base/strings/string_util.h" + +namespace net { + +SpdySessionTestTaskObserver::SpdySessionTestTaskObserver( + const std::string& file_name, + const std::string& function_name) + : executed_count_(0), + file_name_(file_name), + function_name_(function_name) { + base::MessageLoop::current()->AddTaskObserver(this); +} + +SpdySessionTestTaskObserver::~SpdySessionTestTaskObserver() { + base::MessageLoop::current()->RemoveTaskObserver(this); +} + +void SpdySessionTestTaskObserver::WillProcessTask( + const base::PendingTask& pending_task) { +} + +void SpdySessionTestTaskObserver::DidProcessTask( + const base::PendingTask& pending_task) { + if (EndsWith(pending_task.posted_from.file_name(), file_name_, true) && + EndsWith(pending_task.posted_from.function_name(), function_name_, + true)) { + ++executed_count_; + } +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_session_test_util.h b/chromium/net/spdy/spdy_session_test_util.h new file mode 100644 index 00000000000..08a82ee69dd --- /dev/null +++ b/chromium/net/spdy/spdy_session_test_util.h @@ -0,0 +1,46 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_SESSION_TEST_UTIL_H_ +#define NET_SPDY_SPDY_SESSION_TEST_UTIL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/message_loop/message_loop.h" +#include "base/pending_task.h" + +namespace net { + +// SpdySessionTestTaskObserver is a MessageLoop::TaskObserver that monitors the +// completion of all tasks executed by the current MessageLoop, recording the +// number of tasks that refer to a specific function and filename. +class SpdySessionTestTaskObserver : public base::MessageLoop::TaskObserver { + public: + // Creates a SpdySessionTaskObserver that will record all tasks that are + // executed that were posted by the function named by |function_name|, located + // in the file |file_name|. + // Example: + // file_name = "foo.cc" + // function = "DoFoo" + SpdySessionTestTaskObserver(const std::string& file_name, + const std::string& function_name); + virtual ~SpdySessionTestTaskObserver(); + + // Implements MessageLoop::TaskObserver. + virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE; + virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE; + + // Returns the number of tasks posted by the given function and file. + uint16 executed_count() const { return executed_count_; } + + private: + uint16 executed_count_; + std::string file_name_; + std::string function_name_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_SESSION_TEST_UTIL_H_ diff --git a/chromium/net/spdy/spdy_session_unittest.cc b/chromium/net/spdy/spdy_session_unittest.cc new file mode 100644 index 00000000000..f0d448cd700 --- /dev/null +++ b/chromium/net/spdy/spdy_session_unittest.cc @@ -0,0 +1,4116 @@ +// 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 "net/spdy/spdy_session.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_log_unittest.h" +#include "net/base/request_priority.h" +#include "net/base/test_data_directory.h" +#include "net/base/test_data_stream.h" +#include "net/socket/client_socket_pool_manager.h" +#include "net/socket/next_proto.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_session_test_util.h" +#include "net/spdy/spdy_stream.h" +#include "net/spdy/spdy_stream_test_util.h" +#include "net/spdy/spdy_test_util_common.h" +#include "net/spdy/spdy_test_utils.h" +#include "net/test/cert_test_util.h" +#include "testing/platform_test.h" + +namespace net { + +namespace { + +static const char kTestUrl[] = "http://www.example.org/"; +static const char kTestHost[] = "www.example.org"; +static const int kTestPort = 80; + +const char kBodyData[] = "Body data"; +const size_t kBodyDataSize = arraysize(kBodyData); +const base::StringPiece kBodyDataStringPiece(kBodyData, kBodyDataSize); + +static base::TimeDelta g_time_delta; +base::TimeTicks TheNearFuture() { + return base::TimeTicks::Now() + g_time_delta; +} + +} // namespace + +class SpdySessionTest : public PlatformTest, + public ::testing::WithParamInterface<NextProto> { + public: + // Functions used with RunResumeAfterUnstallTest(). + + void StallSessionOnly(SpdySession* session, SpdyStream* stream) { + StallSessionSend(session); + } + + void StallStreamOnly(SpdySession* session, SpdyStream* stream) { + StallStreamSend(stream); + } + + void StallSessionStream(SpdySession* session, SpdyStream* stream) { + StallSessionSend(session); + StallStreamSend(stream); + } + + void StallStreamSession(SpdySession* session, SpdyStream* stream) { + StallStreamSend(stream); + StallSessionSend(session); + } + + void UnstallSessionOnly(SpdySession* session, + SpdyStream* stream, + int32 delta_window_size) { + UnstallSessionSend(session, delta_window_size); + } + + void UnstallStreamOnly(SpdySession* session, + SpdyStream* stream, + int32 delta_window_size) { + UnstallStreamSend(stream, delta_window_size); + } + + void UnstallSessionStream(SpdySession* session, + SpdyStream* stream, + int32 delta_window_size) { + UnstallSessionSend(session, delta_window_size); + UnstallStreamSend(stream, delta_window_size); + } + + void UnstallStreamSession(SpdySession* session, + SpdyStream* stream, + int32 delta_window_size) { + UnstallStreamSend(stream, delta_window_size); + UnstallSessionSend(session, delta_window_size); + } + + protected: + SpdySessionTest() + : old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL)), + spdy_util_(GetParam()), + session_deps_(GetParam()), + spdy_session_pool_(NULL), + test_url_(kTestUrl), + test_host_port_pair_(kTestHost, kTestPort), + key_(test_host_port_pair_, ProxyServer::Direct(), + kPrivacyModeDisabled) { + } + + virtual ~SpdySessionTest() { + // Important to restore the per-pool limit first, since the pool limit must + // always be greater than group limit, and the tests reduce both limits. + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_); + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_); + } + + virtual void SetUp() OVERRIDE { + g_time_delta = base::TimeDelta(); + } + + void CreateDeterministicNetworkSession() { + http_session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + spdy_session_pool_ = http_session_->spdy_session_pool(); + } + + void CreateNetworkSession() { + http_session_ = + SpdySessionDependencies::SpdyCreateSession(&session_deps_); + spdy_session_pool_ = http_session_->spdy_session_pool(); + } + + void StallSessionSend(SpdySession* session) { + // Reduce the send window size to 0 to stall. + while (session->session_send_window_size_ > 0) { + session->DecreaseSendWindowSize( + std::min(kMaxSpdyFrameChunkSize, session->session_send_window_size_)); + } + } + + void UnstallSessionSend(SpdySession* session, int32 delta_window_size) { + session->IncreaseSendWindowSize(delta_window_size); + } + + void StallStreamSend(SpdyStream* stream) { + // Reduce the send window size to 0 to stall. + while (stream->send_window_size() > 0) { + stream->DecreaseSendWindowSize( + std::min(kMaxSpdyFrameChunkSize, stream->send_window_size())); + } + } + + void UnstallStreamSend(SpdyStream* stream, int32 delta_window_size) { + stream->IncreaseSendWindowSize(delta_window_size); + } + + void RunResumeAfterUnstallTest( + const base::Callback<void(SpdySession*, SpdyStream*)>& stall_function, + const base::Callback<void(SpdySession*, SpdyStream*, int32)>& + unstall_function); + + // Original socket limits. Some tests set these. Safest to always restore + // them once each test has been run. + int old_max_group_sockets_; + int old_max_pool_sockets_; + + SpdyTestUtil spdy_util_; + SpdySessionDependencies session_deps_; + scoped_refptr<HttpNetworkSession> http_session_; + SpdySessionPool* spdy_session_pool_; + GURL test_url_; + HostPortPair test_host_port_pair_; + SpdySessionKey key_; +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + SpdySessionTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +// Try to create a SPDY session that will fail during +// initialization. Nothing should blow up. +TEST_P(SpdySessionTest, InitialReadError) { + CreateDeterministicNetworkSession(); + + TryCreateFakeSpdySessionExpectingFailure( + spdy_session_pool_, key_, ERR_FAILED); +} + +// A session receiving a GOAWAY frame with no active streams should +// immediately close. +TEST_P(SpdySessionTest, GoAwayWithNoActiveStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1)); + MockRead reads[] = { + CreateMockRead(*goaway, 0), + }; + DeterministicSocketData data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Read and process the GOAWAY frame. + data.RunFor(1); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + EXPECT_TRUE(session == NULL); +} + +// A session receiving a GOAWAY frame immediately with no active +// streams should then close. +TEST_P(SpdySessionTest, GoAwayImmediatelyWithNoActiveStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1)); + MockRead reads[] = { + CreateMockRead(*goaway, 0, SYNCHRONOUS), + }; + DeterministicSocketData data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + data.StopAfter(1); + + TryCreateInsecureSpdySessionExpectingFailure( + http_session_, key_, ERR_CONNECTION_CLOSED, BoundNetLog()); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); +} + +// A session receiving a GOAWAY frame with active streams should close +// when the last active stream is closed. +TEST_P(SpdySessionTest, GoAwayWithActiveStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1)); + MockRead reads[] = { + CreateMockRead(*goaway, 2), + MockRead(ASYNC, 0, 3) // EOF + }; + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion()); + + GURL url("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate2(spdy_stream2); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock(*headers)); + + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + data.RunFor(2); + + EXPECT_EQ(1u, spdy_stream1->stream_id()); + EXPECT_EQ(3u, spdy_stream2->stream_id()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Read and process the GOAWAY frame. + data.RunFor(1); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + EXPECT_FALSE(session->IsStreamActive(3)); + EXPECT_EQ(NULL, spdy_stream2.get()); + EXPECT_TRUE(session->IsStreamActive(1)); + + EXPECT_FALSE(session->IsClosed()); + + // Should close the session. + spdy_stream1->Close(); + EXPECT_EQ(NULL, spdy_stream1.get()); + + EXPECT_TRUE(session == NULL); +} + +// Have a session receive two GOAWAY frames, with the last one causing +// the last active stream to be closed. The session should then be +// closed after the second GOAWAY frame. +TEST_P(SpdySessionTest, GoAwayTwice) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway1(spdy_util_.ConstructSpdyGoAway(1)); + scoped_ptr<SpdyFrame> goaway2(spdy_util_.ConstructSpdyGoAway(0)); + MockRead reads[] = { + CreateMockRead(*goaway1, 2), + CreateMockRead(*goaway2, 3), + MockRead(ASYNC, 0, 4) // EOF + }; + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion()); + + GURL url("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate2(spdy_stream2); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock(*headers)); + + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + data.RunFor(2); + + EXPECT_EQ(1u, spdy_stream1->stream_id()); + EXPECT_EQ(3u, spdy_stream2->stream_id()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Read and process the first GOAWAY frame. + data.RunFor(1); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + EXPECT_FALSE(session->IsStreamActive(3)); + EXPECT_EQ(NULL, spdy_stream2.get()); + EXPECT_TRUE(session->IsStreamActive(1)); + + EXPECT_FALSE(session->IsClosed()); + + // Read and process the second GOAWAY frame, which should close the + // session. + data.RunFor(1); + + EXPECT_TRUE(session == NULL); +} + +// Have a session with active streams receive a GOAWAY frame and then +// close it. It should handle the close properly (i.e., not try to +// make itself unavailable in its pool twice). +TEST_P(SpdySessionTest, GoAwayWithActiveStreamsThenClose) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1)); + MockRead reads[] = { + CreateMockRead(*goaway, 2), + MockRead(ASYNC, 0, 3) // EOF + }; + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion()); + + GURL url("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate2(spdy_stream2); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock(*headers)); + + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + data.RunFor(2); + + EXPECT_EQ(1u, spdy_stream1->stream_id()); + EXPECT_EQ(3u, spdy_stream2->stream_id()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Read and process the GOAWAY frame. + data.RunFor(1); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + EXPECT_FALSE(session->IsStreamActive(3)); + EXPECT_EQ(NULL, spdy_stream2.get()); + EXPECT_TRUE(session->IsStreamActive(1)); + + EXPECT_FALSE(session->IsClosed()); + + session->CloseSessionOnError(ERR_ABORTED, "Aborting session"); + + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_TRUE(session == NULL); +} + +// Try to create a stream after receiving a GOAWAY frame. It should +// fail. +TEST_P(SpdySessionTest, CreateStreamAfterGoAway) { + const char kStreamUrl[] = "http://www.google.com"; + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1)); + MockRead reads[] = { + CreateMockRead(*goaway, 1), + MockRead(ASYNC, 0, 2) // EOF + }; + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion()); + + GURL url(kStreamUrl); + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate(spdy_stream); + spdy_stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream->HasUrlFromHeaders()); + + data.RunFor(1); + + EXPECT_EQ(1u, spdy_stream->stream_id()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Read and process the GOAWAY frame. + data.RunFor(1); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + EXPECT_TRUE(session->IsStreamActive(1)); + + SpdyStreamRequest stream_request; + int rv = stream_request.StartRequest( + SPDY_REQUEST_RESPONSE_STREAM, session, url, MEDIUM, BoundNetLog(), + CompletionCallback()); + EXPECT_EQ(ERR_FAILED, rv); + + // Read and process EOF. + data.RunFor(1); + + EXPECT_TRUE(session == NULL); +} + +// Receiving a SYN_STREAM frame after a GOAWAY frame should result in +// the stream being refused. +TEST_P(SpdySessionTest, SynStreamAfterGoAway) { + const char kStreamUrl[] = "http://www.google.com"; + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1)); + scoped_ptr<SpdyFrame> + push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl)); + MockRead reads[] = { + CreateMockRead(*goaway, 1), + CreateMockRead(*push, 2), + MockRead(ASYNC, 0, 4) // EOF + }; + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*rst, 3) + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion()); + + GURL url(kStreamUrl); + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate(spdy_stream); + spdy_stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream->HasUrlFromHeaders()); + + data.RunFor(1); + + EXPECT_EQ(1u, spdy_stream->stream_id()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Read and process the GOAWAY frame. + data.RunFor(1); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + EXPECT_TRUE(session->IsStreamActive(1)); + + // Read and process the SYN_STREAM frame, the subsequent RST_STREAM, + // and EOF. + data.RunFor(3); + + EXPECT_TRUE(session == NULL); +} + +TEST_P(SpdySessionTest, ClientPing) { + session_deps_.enable_ping = true; + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> read_ping(spdy_util_.ConstructSpdyPing(1)); + MockRead reads[] = { + CreateMockRead(*read_ping, 1), + MockRead(ASYNC, 0, 0, 2) // EOF + }; + scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(1)); + MockWrite writes[] = { + CreateMockWrite(*write_ping, 0), + }; + DeterministicSocketData data( + reads, arraysize(reads), writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + test::StreamDelegateSendImmediate delegate(spdy_stream1, NULL); + spdy_stream1->SetDelegate(&delegate); + + base::TimeTicks before_ping_time = base::TimeTicks::Now(); + + session->set_connection_at_risk_of_loss_time( + base::TimeDelta::FromSeconds(-1)); + session->set_hung_interval(base::TimeDelta::FromMilliseconds(50)); + + session->SendPrefacePingIfNoneInFlight(); + + data.RunFor(2); + + session->CheckPingStatus(before_ping_time); + + EXPECT_EQ(0, session->pings_in_flight()); + EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1)); + EXPECT_FALSE(session->check_ping_status_pending()); + EXPECT_GE(session->last_activity_time(), before_ping_time); + + data.RunFor(1); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + EXPECT_TRUE(session == NULL); +} + +TEST_P(SpdySessionTest, ServerPing) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> read_ping(spdy_util_.ConstructSpdyPing(2)); + MockRead reads[] = { + CreateMockRead(*read_ping), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(2)); + MockWrite writes[] = { + CreateMockWrite(*write_ping), + }; + StaticSocketDataProvider data( + reads, arraysize(reads), writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + test::StreamDelegateSendImmediate delegate(spdy_stream1, NULL); + spdy_stream1->SetDelegate(&delegate); + + // Flush the read completion task. + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + EXPECT_TRUE(session == NULL); + EXPECT_EQ(NULL, spdy_stream1.get()); +} + +// Cause a ping to be sent out while producing a write. The write loop +// should handle this properly, i.e. another DoWriteLoop task should +// not be posted. This is a regression test for +// http://crbug.com/261043 . +TEST_P(SpdySessionTest, PingAndWriteLoop) { + session_deps_.enable_ping = true; + session_deps_.time_func = TheNearFuture; + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(1)); + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*write_ping, 1), + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 2) // EOF + }; + + session_deps_.host_resolver->set_synchronous_mode(true); + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + test::StreamDelegateDoNothing delegate(spdy_stream); + spdy_stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + + // Shift time so that a ping will be sent out. + g_time_delta = base::TimeDelta::FromSeconds(11); + + data.RunFor(2); + + session->CloseSessionOnError(ERR_ABORTED, "Aborting"); +} + +TEST_P(SpdySessionTest, DeleteExpiredPushStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + session_deps_.time_func = TheNearFuture; + + CreateNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateFakeSpdySession(spdy_session_pool_, key_); + + session->buffered_spdy_framer_.reset( + new BufferedSpdyFramer(spdy_util_.spdy_version(), false)); + + // Create the associated stream and add to active streams. + scoped_ptr<SpdyHeaderBlock> request_headers( + spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); + + scoped_ptr<SpdyStream> stream(new SpdyStream(SPDY_REQUEST_RESPONSE_STREAM, + session, + GURL(), + DEFAULT_PRIORITY, + kSpdyStreamInitialWindowSize, + kSpdyStreamInitialWindowSize, + session->net_log_)); + stream->SendRequestHeaders(request_headers.Pass(), NO_MORE_DATA_TO_SEND); + SpdyStream* stream_ptr = stream.get(); + session->InsertCreatedStream(stream.Pass()); + stream = session->ActivateCreatedStream(stream_ptr); + session->InsertActivatedStream(stream.Pass()); + + SpdyHeaderBlock headers; + spdy_util_.AddUrlToHeaderBlock("http://www.google.com/a.dat", &headers); + + // OnSynStream() expects |in_io_loop_| to be true. + session->in_io_loop_ = true; + session->OnSynStream(2, 1, 0, 0, true, false, headers); + session->in_io_loop_ = false; + + // Verify that there is one unclaimed push stream. + EXPECT_EQ(1u, session->num_unclaimed_pushed_streams()); + SpdySession::PushedStreamMap::iterator iter = + session->unclaimed_pushed_streams_.find( + GURL("http://www.google.com/a.dat")); + EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter); + + // Shift time to expire the push stream. + g_time_delta = base::TimeDelta::FromSeconds(301); + + spdy_util_.AddUrlToHeaderBlock("http://www.google.com/b.dat", &headers); + session->in_io_loop_ = true; + session->OnSynStream(4, 1, 0, 0, true, false, headers); + session->in_io_loop_ = false; + + // Verify that the second pushed stream evicted the first pushed stream. + EXPECT_EQ(1u, session->num_unclaimed_pushed_streams()); + iter = session->unclaimed_pushed_streams_.find( + GURL("http://www.google.com/b.dat")); + EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter); +} + +TEST_P(SpdySessionTest, FailedPing) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(ASYNC, 0, 0, 0) // EOF + }; + scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(1)); + DeterministicSocketData data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + test::StreamDelegateSendImmediate delegate(spdy_stream1, NULL); + spdy_stream1->SetDelegate(&delegate); + + session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0)); + session->set_hung_interval(base::TimeDelta::FromSeconds(0)); + + // Send a PING frame. + session->WritePingFrame(1); + EXPECT_LT(0, session->pings_in_flight()); + EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1)); + EXPECT_TRUE(session->check_ping_status_pending()); + + // Assert session is not closed. + EXPECT_FALSE(session->IsClosed()); + EXPECT_LT(0u, session->num_active_streams() + session->num_created_streams()); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // We set last time we have received any data in 1 sec less than now. + // CheckPingStatus will trigger timeout because hung interval is zero. + base::TimeTicks now = base::TimeTicks::Now(); + session->last_activity_time_ = now - base::TimeDelta::FromSeconds(1); + session->CheckPingStatus(now); + + EXPECT_TRUE(session == NULL); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + data.RunFor(1); + EXPECT_EQ(NULL, spdy_stream1.get()); +} + +// Request kInitialMaxConcurrentStreams + 1 streams. Receive a +// settings frame increasing the max concurrent streams by 1. Make +// sure nothing blows up. This is a regression test for +// http://crbug.com/57331 . +TEST_P(SpdySessionTest, OnSettings) { + session_deps_.host_resolver->set_synchronous_mode(true); + + const SpdySettingsIds kSpdySettingsIds = SETTINGS_MAX_CONCURRENT_STREAMS; + + SettingsMap new_settings; + const uint32 max_concurrent_streams = kInitialMaxConcurrentStreams + 1; + new_settings[kSpdySettingsIds] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(new_settings)); + MockRead reads[] = { + CreateMockRead(*settings_frame, 0), + MockRead(ASYNC, 0, 1), + }; + + DeterministicSocketData data(reads, arraysize(reads), NULL, 0); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + // Create the maximum number of concurrent streams. + for (size_t i = 0; i < kInitialMaxConcurrentStreams; ++i) { + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream != NULL); + } + + StreamReleaserCallback stream_releaser; + SpdyStreamRequest request; + ASSERT_EQ(ERR_IO_PENDING, + request.StartRequest( + SPDY_BIDIRECTIONAL_STREAM, session, test_url_, MEDIUM, + BoundNetLog(), + stream_releaser.MakeCallback(&request))); + + data.RunFor(1); + + EXPECT_EQ(OK, stream_releaser.WaitForResult()); + + data.RunFor(1); + EXPECT_TRUE(session == NULL); +} + +// Start with a persisted value for max concurrent streams. Receive a +// settings frame increasing the max concurrent streams by 1 and which +// also clears the persisted data. Verify that persisted data is +// correct. +TEST_P(SpdySessionTest, ClearSettings) { + session_deps_.host_resolver->set_synchronous_mode(true); + + SettingsMap new_settings; + const uint32 max_concurrent_streams = kInitialMaxConcurrentStreams + 1; + new_settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(new_settings)); + uint8 flags = SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS; + test::SetFrameFlags(settings_frame.get(), flags, spdy_util_.spdy_version()); + MockRead reads[] = { + CreateMockRead(*settings_frame, 0), + MockRead(ASYNC, 0, 1), + }; + + DeterministicSocketData data(reads, arraysize(reads), NULL, 0); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + // Initialize the SpdySetting with the default. + spdy_session_pool_->http_server_properties()->SetSpdySetting( + test_host_port_pair_, + SETTINGS_MAX_CONCURRENT_STREAMS, + SETTINGS_FLAG_PLEASE_PERSIST, + kInitialMaxConcurrentStreams); + + EXPECT_FALSE( + spdy_session_pool_->http_server_properties()->GetSpdySettings( + test_host_port_pair_).empty()); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + // Create the maximum number of concurrent streams. + for (size_t i = 0; i < kInitialMaxConcurrentStreams; ++i) { + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream != NULL); + } + + StreamReleaserCallback stream_releaser; + + SpdyStreamRequest request; + ASSERT_EQ(ERR_IO_PENDING, + request.StartRequest( + SPDY_BIDIRECTIONAL_STREAM, session, test_url_, MEDIUM, + BoundNetLog(), + stream_releaser.MakeCallback(&request))); + + data.RunFor(1); + + EXPECT_EQ(OK, stream_releaser.WaitForResult()); + + // Make sure that persisted data is cleared. + EXPECT_TRUE( + spdy_session_pool_->http_server_properties()->GetSpdySettings( + test_host_port_pair_).empty()); + + // Make sure session's max_concurrent_streams is correct. + EXPECT_EQ(kInitialMaxConcurrentStreams + 1, + session->max_concurrent_streams()); + + data.RunFor(1); + EXPECT_TRUE(session == NULL); +} + +// Start with max concurrent streams set to 1. Request two streams. +// When the first completes, have the callback close its stream, which +// should trigger the second stream creation. Then cancel that one +// immediately. Don't crash. This is a regression test for +// http://crbug.com/63532 . +TEST_P(SpdySessionTest, CancelPendingCreateStream) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + MockConnect connect_data(SYNCHRONOUS, OK); + + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + // Initialize the SpdySetting with 1 max concurrent streams. + spdy_session_pool_->http_server_properties()->SetSpdySetting( + test_host_port_pair_, + SETTINGS_MAX_CONCURRENT_STREAMS, + SETTINGS_FLAG_PLEASE_PERSIST, + 1); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + // Leave room for only one more stream to be created. + for (size_t i = 0; i < kInitialMaxConcurrentStreams - 1; ++i) { + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream != NULL); + } + + // Create 2 more streams. First will succeed. Second will be pending. + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + + // Use scoped_ptr to let us invalidate the memory when we want to, to trigger + // a valgrind error if the callback is invoked when it's not supposed to be. + scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback); + + SpdyStreamRequest request; + ASSERT_EQ(ERR_IO_PENDING, + request.StartRequest( + SPDY_BIDIRECTIONAL_STREAM, session, test_url_, MEDIUM, + BoundNetLog(), + callback->callback())); + + // Release the first one, this will allow the second to be created. + spdy_stream1->Cancel(); + EXPECT_EQ(NULL, spdy_stream1.get()); + + request.CancelRequest(); + callback.reset(); + + // Should not crash when running the pending callback. + base::MessageLoop::current()->RunUntilIdle(); +} + +TEST_P(SpdySessionTest, SendInitialDataOnNewSession) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + SettingsMap settings; + const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS; + const SpdySettingsIds kSpdySettingsIds2 = SETTINGS_INITIAL_WINDOW_SIZE; + const uint32 kInitialRecvWindowSize = 10 * 1024 * 1024; + settings[kSpdySettingsIds1] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams); + if (spdy_util_.spdy_version() >= SPDY3) { + settings[kSpdySettingsIds2] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kInitialRecvWindowSize); + } + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + scoped_ptr<SpdyFrame> initial_window_update( + spdy_util_.ConstructSpdyWindowUpdate( + kSessionFlowControlStreamId, + kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize)); + std::vector<MockWrite> writes; + if (GetParam() == kProtoHTTP2Draft04) { + writes.push_back( + MockWrite(ASYNC, + kHttp2ConnectionHeaderPrefix, + kHttp2ConnectionHeaderPrefixSize)); + } + writes.push_back(CreateMockWrite(*settings_frame)); + if (GetParam() >= kProtoSPDY31) { + writes.push_back(CreateMockWrite(*initial_window_update)); + }; + + SettingsMap server_settings; + const uint32 initial_max_concurrent_streams = 1; + server_settings[SETTINGS_MAX_CONCURRENT_STREAMS] = + SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED, + initial_max_concurrent_streams); + scoped_ptr<SpdyFrame> server_settings_frame( + spdy_util_.ConstructSpdySettings(server_settings)); + writes.push_back(CreateMockWrite(*server_settings_frame)); + + session_deps_.stream_initial_recv_window_size = kInitialRecvWindowSize; + + StaticSocketDataProvider data(reads, arraysize(reads), + vector_as_array(&writes), writes.size()); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + spdy_session_pool_->http_server_properties()->SetSpdySetting( + test_host_port_pair_, + SETTINGS_MAX_CONCURRENT_STREAMS, + SETTINGS_FLAG_PLEASE_PERSIST, + initial_max_concurrent_streams); + + SpdySessionPoolPeer pool_peer(spdy_session_pool_); + pool_peer.SetEnableSendingInitialData(true); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdySessionTest, ClearSettingsStorageOnIPAddressChanged) { + CreateNetworkSession(); + + base::WeakPtr<HttpServerProperties> test_http_server_properties = + spdy_session_pool_->http_server_properties(); + SettingsFlagsAndValue flags_and_value1(SETTINGS_FLAG_PLEASE_PERSIST, 2); + test_http_server_properties->SetSpdySetting( + test_host_port_pair_, + SETTINGS_MAX_CONCURRENT_STREAMS, + SETTINGS_FLAG_PLEASE_PERSIST, + 2); + EXPECT_NE(0u, test_http_server_properties->GetSpdySettings( + test_host_port_pair_).size()); + spdy_session_pool_->OnIPAddressChanged(); + EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings( + test_host_port_pair_).size()); +} + +TEST_P(SpdySessionTest, Initialize) { + CapturingBoundNetLog log; + session_deps_.net_log = log.bound().net_log(); + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, log.bound()); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Flush the read completion task. + base::MessageLoop::current()->RunUntilIdle(); + + net::CapturingNetLog::CapturedEntryList entries; + log.GetEntries(&entries); + EXPECT_LT(0u, entries.size()); + + // Check that we logged TYPE_SPDY_SESSION_INITIALIZED correctly. + int pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_SESSION_INITIALIZED, + net::NetLog::PHASE_NONE); + EXPECT_LT(0, pos); + + CapturingNetLog::CapturedEntry entry = entries[pos]; + NetLog::Source socket_source; + EXPECT_TRUE(NetLog::Source::FromEventParameters(entry.params.get(), + &socket_source)); + EXPECT_TRUE(socket_source.IsValid()); + EXPECT_NE(log.bound().source().id, socket_source.id); +} + +TEST_P(SpdySessionTest, CloseSessionOnError) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway()); + MockRead reads[] = { + CreateMockRead(*goaway), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + CapturingBoundNetLog log; + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, log.bound()); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Flush the read completion task. + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + EXPECT_TRUE(session == NULL); + + // Check that the NetLog was filled reasonably. + net::CapturingNetLog::CapturedEntryList entries; + log.GetEntries(&entries); + EXPECT_LT(0u, entries.size()); + + // Check that we logged SPDY_SESSION_CLOSE correctly. + int pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_SESSION_CLOSE, + net::NetLog::PHASE_NONE); + + if (pos < static_cast<int>(entries.size())) { + CapturingNetLog::CapturedEntry entry = entries[pos]; + int error_code = 0; + ASSERT_TRUE(entry.GetNetErrorCode(&error_code)); + EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code); + } else { + ADD_FAILURE(); + } +} + +// Queue up a low-priority SYN_STREAM followed by a high-priority +// one. The high priority one should still send first and receive +// first. +TEST_P(SpdySessionTest, OutOfOrderSynStreams) { + // Construct the request. + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> req_highest( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, HIGHEST, true)); + scoped_ptr<SpdyFrame> req_lowest( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req_highest, 0), + CreateMockWrite(*req_lowest, 1), + }; + + scoped_ptr<SpdyFrame> resp_highest( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body_highest( + spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> resp_lowest( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body_lowest( + spdy_util_.ConstructSpdyBodyFrame(3, true)); + MockRead reads[] = { + CreateMockRead(*resp_highest, 2), + CreateMockRead(*body_highest, 3), + CreateMockRead(*resp_lowest, 4), + CreateMockRead(*body_lowest, 5), + MockRead(ASYNC, 0, 6) // EOF + }; + + session_deps_.host_resolver->set_synchronous_mode(true); + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url("http://www.google.com"); + + base::WeakPtr<SpdyStream> spdy_stream_lowest = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream_lowest); + EXPECT_EQ(0u, spdy_stream_lowest->stream_id()); + test::StreamDelegateDoNothing delegate_lowest(spdy_stream_lowest); + spdy_stream_lowest->SetDelegate(&delegate_lowest); + + base::WeakPtr<SpdyStream> spdy_stream_highest = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, HIGHEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream_highest); + EXPECT_EQ(0u, spdy_stream_highest->stream_id()); + test::StreamDelegateDoNothing delegate_highest(spdy_stream_highest); + spdy_stream_highest->SetDelegate(&delegate_highest); + + // Queue the lower priority one first. + + scoped_ptr<SpdyHeaderBlock> headers_lowest( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream_lowest->SendRequestHeaders( + headers_lowest.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream_lowest->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers_highest( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream_highest->SendRequestHeaders( + headers_highest.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream_highest->HasUrlFromHeaders()); + + data.RunFor(7); + + EXPECT_FALSE(spdy_stream_lowest); + EXPECT_FALSE(spdy_stream_highest); + EXPECT_EQ(3u, delegate_lowest.stream_id()); + EXPECT_EQ(1u, delegate_highest.stream_id()); +} + +TEST_P(SpdySessionTest, CancelStream) { + MockConnect connect_data(SYNCHRONOUS, OK); + // Request 1, at HIGHEST priority, will be cancelled before it writes data. + // Request 2, at LOWEST priority, will be a full request and will be id 1. + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req2, 0), + }; + + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp2, 1), + CreateMockRead(*body2, 2), + MockRead(ASYNC, 0, 3) // EOF + }; + + session_deps_.host_resolver->set_synchronous_mode(true); + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, HIGHEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + GURL url2("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url2, LOWEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + test::StreamDelegateDoNothing delegate2(spdy_stream2); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock(url2.spec())); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + spdy_stream1->Cancel(); + EXPECT_EQ(NULL, spdy_stream1.get()); + + EXPECT_EQ(0u, delegate1.stream_id()); + + data.RunFor(1); + + EXPECT_EQ(0u, delegate1.stream_id()); + EXPECT_EQ(1u, delegate2.stream_id()); + + spdy_stream2->Cancel(); + EXPECT_EQ(NULL, spdy_stream2.get()); +} + +// Create two streams that are set to re-close themselves on close, +// and then close the session. Nothing should blow up. Also a +// regression test for http://crbug.com/139518 . +TEST_P(SpdySessionTest, CloseSessionWithTwoCreatedSelfClosingStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + // No actual data will be sent. + MockWrite writes[] = { + MockWrite(ASYNC, 0, 1) // EOF + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url1, HIGHEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + GURL url2("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url2, LOWEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + test::ClosingDelegate delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + test::ClosingDelegate delegate2(spdy_stream2); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock(url2.spec())); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + // Ensure that the streams have not yet been activated and assigned an id. + EXPECT_EQ(0u, spdy_stream1->stream_id()); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + // Ensure we don't crash while closing the session. + session->CloseSessionOnError(ERR_ABORTED, std::string()); + + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_EQ(NULL, spdy_stream2.get()); + + EXPECT_TRUE(delegate1.StreamIsClosed()); + EXPECT_TRUE(delegate2.StreamIsClosed()); + + EXPECT_TRUE(session == NULL); +} + +// Create two streams that are set to close each other on close, and +// then close the session. Nothing should blow up. +TEST_P(SpdySessionTest, CloseSessionWithTwoCreatedMutuallyClosingStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + // No actual data will be sent. + MockWrite writes[] = { + MockWrite(ASYNC, 0, 1) // EOF + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url1, HIGHEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + GURL url2("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url2, LOWEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + // Make |spdy_stream1| close |spdy_stream2|. + test::ClosingDelegate delegate1(spdy_stream2); + spdy_stream1->SetDelegate(&delegate1); + + // Make |spdy_stream2| close |spdy_stream1|. + test::ClosingDelegate delegate2(spdy_stream1); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock(url2.spec())); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + // Ensure that the streams have not yet been activated and assigned an id. + EXPECT_EQ(0u, spdy_stream1->stream_id()); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + // Ensure we don't crash while closing the session. + session->CloseSessionOnError(ERR_ABORTED, std::string()); + + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_EQ(NULL, spdy_stream2.get()); + + EXPECT_TRUE(delegate1.StreamIsClosed()); + EXPECT_TRUE(delegate2.StreamIsClosed()); + + EXPECT_TRUE(session == NULL); +} + +// Create two streams that are set to re-close themselves on close, +// activate them, and then close the session. Nothing should blow up. +TEST_P(SpdySessionTest, CloseSessionWithTwoActivatedSelfClosingStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 2) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + GURL url2("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url2, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + test::ClosingDelegate delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + test::ClosingDelegate delegate2(spdy_stream2); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock(url2.spec())); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + // Ensure that the streams have not yet been activated and assigned an id. + EXPECT_EQ(0u, spdy_stream1->stream_id()); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + data.RunFor(2); + + EXPECT_EQ(1u, spdy_stream1->stream_id()); + EXPECT_EQ(3u, spdy_stream2->stream_id()); + + // Ensure we don't crash while closing the session. + session->CloseSessionOnError(ERR_ABORTED, std::string()); + + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_EQ(NULL, spdy_stream2.get()); + + EXPECT_TRUE(delegate1.StreamIsClosed()); + EXPECT_TRUE(delegate2.StreamIsClosed()); + + EXPECT_TRUE(session == NULL); +} + +// Create two streams that are set to close each other on close, +// activate them, and then close the session. Nothing should blow up. +TEST_P(SpdySessionTest, CloseSessionWithTwoActivatedMutuallyClosingStreams) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 2) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + GURL url2("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url2, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + // Make |spdy_stream1| close |spdy_stream2|. + test::ClosingDelegate delegate1(spdy_stream2); + spdy_stream1->SetDelegate(&delegate1); + + // Make |spdy_stream2| close |spdy_stream1|. + test::ClosingDelegate delegate2(spdy_stream1); + spdy_stream2->SetDelegate(&delegate2); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock(url2.spec())); + spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders()); + + // Ensure that the streams have not yet been activated and assigned an id. + EXPECT_EQ(0u, spdy_stream1->stream_id()); + EXPECT_EQ(0u, spdy_stream2->stream_id()); + + data.RunFor(2); + + EXPECT_EQ(1u, spdy_stream1->stream_id()); + EXPECT_EQ(3u, spdy_stream2->stream_id()); + + // Ensure we don't crash while closing the session. + session->CloseSessionOnError(ERR_ABORTED, std::string()); + + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_EQ(NULL, spdy_stream2.get()); + + EXPECT_TRUE(delegate1.StreamIsClosed()); + EXPECT_TRUE(delegate2.StreamIsClosed()); + + EXPECT_TRUE(session == NULL); +} + +// Delegate that closes a given session when the stream is closed. +class SessionClosingDelegate : public test::StreamDelegateDoNothing { + public: + SessionClosingDelegate(const base::WeakPtr<SpdyStream>& stream, + const base::WeakPtr<SpdySession>& session_to_close) + : StreamDelegateDoNothing(stream), + session_to_close_(session_to_close) {} + + virtual ~SessionClosingDelegate() {} + + virtual void OnClose(int status) OVERRIDE { + session_to_close_->CloseSessionOnError(ERR_ABORTED, "Aborted"); + } + + private: + base::WeakPtr<SpdySession> session_to_close_; +}; + +// Close an activated stream that closes its session. Nothing should +// blow up. This is a regression test for http://crbug.com/263691 . +TEST_P(SpdySessionTest, CloseActivatedStreamThatClosesSession) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 1) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream.get() != NULL); + EXPECT_EQ(0u, spdy_stream->stream_id()); + + SessionClosingDelegate delegate(spdy_stream, session); + spdy_stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream->HasUrlFromHeaders()); + + EXPECT_EQ(0u, spdy_stream->stream_id()); + + data.RunFor(1); + + EXPECT_EQ(1u, spdy_stream->stream_id()); + + // Ensure we don't crash while closing the stream (which closes the + // session). + spdy_stream->Cancel(); + + EXPECT_EQ(NULL, spdy_stream.get()); + EXPECT_TRUE(delegate.StreamIsClosed()); + EXPECT_TRUE(session == NULL); +} + +TEST_P(SpdySessionTest, VerifyDomainAuthentication) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + // No actual data will be sent. + MockWrite writes[] = { + MockWrite(ASYNC, 0, 1) // EOF + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + // Load a cert that is valid for: + // www.example.org + // mail.example.org + // www.example.com + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(certs_dir, "spdy_pooling.pem")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.cert = test_cert; + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateSecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org")); + EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org")); + EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.com")); + EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com")); +} + +TEST_P(SpdySessionTest, ConnectionPooledWithTlsChannelId) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + // No actual data will be sent. + MockWrite writes[] = { + MockWrite(ASYNC, 0, 1) // EOF + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 0) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + // Load a cert that is valid for: + // www.example.org + // mail.example.org + // www.example.com + base::FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> test_cert( + ImportCertFromFile(certs_dir, "spdy_pooling.pem")); + ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.channel_id_sent = true; + ssl.cert = test_cert; + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateSecureSpdySession(http_session_, key_, BoundNetLog()); + + EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org")); + EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org")); + EXPECT_FALSE(session->VerifyDomainAuthentication("mail.example.com")); + EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com")); +} + +TEST_P(SpdySessionTest, CloseTwoStalledCreateStream) { + // TODO(rtenneti): Define a helper class/methods and move the common code in + // this file. + MockConnect connect_data(SYNCHRONOUS, OK); + + SettingsMap new_settings; + const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS; + const uint32 max_concurrent_streams = 1; + new_settings[kSpdySettingsIds1] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); + scoped_ptr<SpdyFrame> req3( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, LOWEST, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 1), + CreateMockWrite(*req2, 4), + CreateMockWrite(*req3, 7), + }; + + // Set up the socket so we read a SETTINGS frame that sets max concurrent + // streams to 1. + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(new_settings)); + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, true)); + + MockRead reads[] = { + CreateMockRead(*settings_frame), + CreateMockRead(*resp1, 2), + CreateMockRead(*body1, 3), + CreateMockRead(*resp2, 5), + CreateMockRead(*body2, 6), + CreateMockRead(*resp3, 8), + CreateMockRead(*body3, 9), + MockRead(ASYNC, 0, 10) // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + // Read the settings frame. + data.RunFor(1); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, LOWEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + TestCompletionCallback callback2; + GURL url2("http://www.google.com"); + SpdyStreamRequest request2; + ASSERT_EQ(ERR_IO_PENDING, + request2.StartRequest( + SPDY_REQUEST_RESPONSE_STREAM, + session, url2, LOWEST, BoundNetLog(), callback2.callback())); + + TestCompletionCallback callback3; + GURL url3("http://www.google.com"); + SpdyStreamRequest request3; + ASSERT_EQ(ERR_IO_PENDING, + request3.StartRequest( + SPDY_REQUEST_RESPONSE_STREAM, + session, url3, LOWEST, BoundNetLog(), callback3.callback())); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(1u, session->num_created_streams()); + EXPECT_EQ(2u, session->pending_create_stream_queue_size(LOWEST)); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + // Run until 1st stream is activated and then closed. + EXPECT_EQ(0u, delegate1.stream_id()); + data.RunFor(3); + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_EQ(1u, delegate1.stream_id()); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(0u, session->num_created_streams()); + EXPECT_EQ(1u, session->pending_create_stream_queue_size(LOWEST)); + + // Pump loop for SpdySession::ProcessPendingStreamRequests() to + // create the 2nd stream. + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(1u, session->num_created_streams()); + EXPECT_EQ(1u, session->pending_create_stream_queue_size(LOWEST)); + + base::WeakPtr<SpdyStream> stream2 = request2.ReleaseStream(); + test::StreamDelegateDoNothing delegate2(stream2); + stream2->SetDelegate(&delegate2); + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructGetHeaderBlock(url2.spec())); + stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(stream2->HasUrlFromHeaders()); + + // Run until 2nd stream is activated and then closed. + EXPECT_EQ(0u, delegate2.stream_id()); + data.RunFor(3); + EXPECT_EQ(NULL, stream2.get()); + EXPECT_EQ(3u, delegate2.stream_id()); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(0u, session->num_created_streams()); + EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST)); + + // Pump loop for SpdySession::ProcessPendingStreamRequests() to + // create the 3rd stream. + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(1u, session->num_created_streams()); + EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST)); + + base::WeakPtr<SpdyStream> stream3 = request3.ReleaseStream(); + test::StreamDelegateDoNothing delegate3(stream3); + stream3->SetDelegate(&delegate3); + scoped_ptr<SpdyHeaderBlock> headers3( + spdy_util_.ConstructGetHeaderBlock(url3.spec())); + stream3->SendRequestHeaders(headers3.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(stream3->HasUrlFromHeaders()); + + // Run until 2nd stream is activated and then closed. + EXPECT_EQ(0u, delegate3.stream_id()); + data.RunFor(3); + EXPECT_EQ(NULL, stream3.get()); + EXPECT_EQ(5u, delegate3.stream_id()); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(0u, session->num_created_streams()); + EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST)); + + data.RunFor(1); +} + +TEST_P(SpdySessionTest, CancelTwoStalledCreateStream) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + MockConnect connect_data(SYNCHRONOUS, OK); + + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + // Leave room for only one more stream to be created. + for (size_t i = 0; i < kInitialMaxConcurrentStreams - 1; ++i) { + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream != NULL); + } + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url1, LOWEST, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + TestCompletionCallback callback2; + GURL url2("http://www.google.com"); + SpdyStreamRequest request2; + ASSERT_EQ(ERR_IO_PENDING, + request2.StartRequest( + SPDY_BIDIRECTIONAL_STREAM, session, url2, LOWEST, BoundNetLog(), + callback2.callback())); + + TestCompletionCallback callback3; + GURL url3("http://www.google.com"); + SpdyStreamRequest request3; + ASSERT_EQ(ERR_IO_PENDING, + request3.StartRequest( + SPDY_BIDIRECTIONAL_STREAM, session, url3, LOWEST, BoundNetLog(), + callback3.callback())); + + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(kInitialMaxConcurrentStreams, session->num_created_streams()); + EXPECT_EQ(2u, session->pending_create_stream_queue_size(LOWEST)); + + // Cancel the first stream; this will allow the second stream to be created. + EXPECT_TRUE(spdy_stream1.get() != NULL); + spdy_stream1->Cancel(); + EXPECT_EQ(NULL, spdy_stream1.get()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(kInitialMaxConcurrentStreams, session->num_created_streams()); + EXPECT_EQ(1u, session->pending_create_stream_queue_size(LOWEST)); + + // Cancel the second stream; this will allow the third stream to be created. + base::WeakPtr<SpdyStream> spdy_stream2 = request2.ReleaseStream(); + spdy_stream2->Cancel(); + EXPECT_EQ(NULL, spdy_stream2.get()); + + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(kInitialMaxConcurrentStreams, session->num_created_streams()); + EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST)); + + // Cancel the third stream. + base::WeakPtr<SpdyStream> spdy_stream3 = request3.ReleaseStream(); + spdy_stream3->Cancel(); + EXPECT_EQ(NULL, spdy_stream3.get()); + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(kInitialMaxConcurrentStreams - 1, session->num_created_streams()); + EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST)); +} + +TEST_P(SpdySessionTest, NeedsCredentials) { + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.channel_id_sent = true; + ssl.protocol_negotiated = GetParam(); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + const GURL url("https://www.foo.com"); + HostPortPair test_host_port_pair(url.host(), 443); + SpdySessionKey key(test_host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + base::WeakPtr<SpdySession> session = + CreateSecureSpdySession(http_session_, key, BoundNetLog()); + + EXPECT_EQ(spdy_util_.spdy_version() >= SPDY3, session->NeedsCredentials()); + + // Flush the read completion task. + base::MessageLoop::current()->RunUntilIdle(); + + session->CloseSessionOnError(ERR_ABORTED, std::string()); +} + +// Test that SpdySession::DoReadLoop reads data from the socket +// without yielding. This test makes 32k - 1 bytes of data available +// on the socket for reading. It then verifies that it has read all +// the available data without yielding. +TEST_P(SpdySessionTest, ReadDataWithoutYielding) { + MockConnect connect_data(SYNCHRONOUS, OK); + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + }; + + // Build buffer of size kMaxReadBytesWithoutYielding / 4 + // (-spdy_data_frame_size). + ASSERT_EQ(32 * 1024, kMaxReadBytesWithoutYielding); + const int kPayloadSize = + kMaxReadBytesWithoutYielding / 4 - framer.GetControlFrameHeaderSize(); + TestDataStream test_stream; + scoped_refptr<net::IOBuffer> payload(new net::IOBuffer(kPayloadSize)); + char* payload_data = payload->data(); + test_stream.GetBytes(payload_data, kPayloadSize); + + scoped_ptr<SpdyFrame> partial_data_frame( + framer.CreateDataFrame(1, payload_data, kPayloadSize, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> finish_data_frame( + framer.CreateDataFrame(1, payload_data, kPayloadSize - 1, DATA_FLAG_FIN)); + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + + // Write 1 byte less than kMaxReadBytes to check that DoRead reads up to 32k + // bytes. + MockRead reads[] = { + CreateMockRead(*resp1, 1), + CreateMockRead(*partial_data_frame, 2), + CreateMockRead(*partial_data_frame, 3, SYNCHRONOUS), + CreateMockRead(*partial_data_frame, 4, SYNCHRONOUS), + CreateMockRead(*finish_data_frame, 5, SYNCHRONOUS), + MockRead(ASYNC, 0, 6) // EOF + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + // Set up the TaskObserver to verify SpdySession::DoReadLoop doesn't + // post a task. + SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); + + // Run until 1st read. + EXPECT_EQ(0u, delegate1.stream_id()); + data.RunFor(2); + EXPECT_EQ(1u, delegate1.stream_id()); + EXPECT_EQ(0u, observer.executed_count()); + + // Read all the data and verify SpdySession::DoReadLoop has not + // posted a task. + data.RunFor(4); + EXPECT_EQ(NULL, spdy_stream1.get()); + + // Verify task observer's executed_count is zero, which indicates DoRead read + // all the available data. + EXPECT_EQ(0u, observer.executed_count()); + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); +} + +// Test that SpdySession::DoReadLoop yields while reading the +// data. This test makes 32k + 1 bytes of data available on the socket +// for reading. It then verifies that DoRead has yielded even though +// there is data available for it to read (i.e, socket()->Read didn't +// return ERR_IO_PENDING during socket reads). +TEST_P(SpdySessionTest, TestYieldingDuringReadData) { + MockConnect connect_data(SYNCHRONOUS, OK); + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + }; + + // Build buffer of size kMaxReadBytesWithoutYielding / 4 + // (-spdy_data_frame_size). + ASSERT_EQ(32 * 1024, kMaxReadBytesWithoutYielding); + const int kPayloadSize = + kMaxReadBytesWithoutYielding / 4 - framer.GetControlFrameHeaderSize(); + TestDataStream test_stream; + scoped_refptr<net::IOBuffer> payload(new net::IOBuffer(kPayloadSize)); + char* payload_data = payload->data(); + test_stream.GetBytes(payload_data, kPayloadSize); + + scoped_ptr<SpdyFrame> partial_data_frame( + framer.CreateDataFrame(1, payload_data, kPayloadSize, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> finish_data_frame( + framer.CreateDataFrame(1, "h", 1, DATA_FLAG_FIN)); + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + + // Write 1 byte more than kMaxReadBytes to check that DoRead yields. + MockRead reads[] = { + CreateMockRead(*resp1, 1), + CreateMockRead(*partial_data_frame, 2), + CreateMockRead(*partial_data_frame, 3, SYNCHRONOUS), + CreateMockRead(*partial_data_frame, 4, SYNCHRONOUS), + CreateMockRead(*partial_data_frame, 5, SYNCHRONOUS), + CreateMockRead(*finish_data_frame, 6, SYNCHRONOUS), + MockRead(ASYNC, 0, 7) // EOF + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + // Set up the TaskObserver to verify SpdySession::DoReadLoop posts a + // task. + SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); + + // Run until 1st read. + EXPECT_EQ(0u, delegate1.stream_id()); + data.RunFor(2); + EXPECT_EQ(1u, delegate1.stream_id()); + EXPECT_EQ(0u, observer.executed_count()); + + // Read all the data and verify SpdySession::DoReadLoop has posted a + // task. + data.RunFor(6); + EXPECT_EQ(NULL, spdy_stream1.get()); + + // Verify task observer's executed_count is 1, which indicates DoRead has + // posted only one task and thus yielded though there is data available for it + // to read. + EXPECT_EQ(1u, observer.executed_count()); + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); +} + +// Test that SpdySession::DoReadLoop() tests interactions of yielding +// + async, by doing the following MockReads. +// +// MockRead of SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K +// ASYNC 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K. +// +// The above reads 26K synchronously. Since that is less that 32K, we +// will attempt to read again. However, that DoRead() will return +// ERR_IO_PENDING (because of async read), so DoReadLoop() will +// yield. When we come back, DoRead() will read the results from the +// async read, and rest of the data synchronously. +TEST_P(SpdySessionTest, TestYieldingDuringAsyncReadData) { + MockConnect connect_data(SYNCHRONOUS, OK); + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + }; + + // Build buffer of size kMaxReadBytesWithoutYielding / 4 + // (-spdy_data_frame_size). + ASSERT_EQ(32 * 1024, kMaxReadBytesWithoutYielding); + TestDataStream test_stream; + const int kEightKPayloadSize = + kMaxReadBytesWithoutYielding / 4 - framer.GetControlFrameHeaderSize(); + scoped_refptr<net::IOBuffer> eightk_payload( + new net::IOBuffer(kEightKPayloadSize)); + char* eightk_payload_data = eightk_payload->data(); + test_stream.GetBytes(eightk_payload_data, kEightKPayloadSize); + + // Build buffer of 2k size. + TestDataStream test_stream2; + const int kTwoKPayloadSize = kEightKPayloadSize - 6 * 1024; + scoped_refptr<net::IOBuffer> twok_payload( + new net::IOBuffer(kTwoKPayloadSize)); + char* twok_payload_data = twok_payload->data(); + test_stream2.GetBytes(twok_payload_data, kTwoKPayloadSize); + + scoped_ptr<SpdyFrame> eightk_data_frame(framer.CreateDataFrame( + 1, eightk_payload_data, kEightKPayloadSize, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> twok_data_frame(framer.CreateDataFrame( + 1, twok_payload_data, kTwoKPayloadSize, DATA_FLAG_NONE)); + scoped_ptr<SpdyFrame> finish_data_frame(framer.CreateDataFrame( + 1, "h", 1, DATA_FLAG_FIN)); + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + + MockRead reads[] = { + CreateMockRead(*resp1, 1), + CreateMockRead(*eightk_data_frame, 2), + CreateMockRead(*eightk_data_frame, 3, SYNCHRONOUS), + CreateMockRead(*eightk_data_frame, 4, SYNCHRONOUS), + CreateMockRead(*twok_data_frame, 5, SYNCHRONOUS), + CreateMockRead(*eightk_data_frame, 6, ASYNC), + CreateMockRead(*eightk_data_frame, 7, SYNCHRONOUS), + CreateMockRead(*eightk_data_frame, 8, SYNCHRONOUS), + CreateMockRead(*eightk_data_frame, 9, SYNCHRONOUS), + CreateMockRead(*twok_data_frame, 10, SYNCHRONOUS), + CreateMockRead(*finish_data_frame, 11, SYNCHRONOUS), + MockRead(ASYNC, 0, 12) // EOF + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + // Set up the TaskObserver to monitor SpdySession::DoReadLoop + // posting of tasks. + SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); + + // Run until 1st read. + EXPECT_EQ(0u, delegate1.stream_id()); + data.RunFor(2); + EXPECT_EQ(1u, delegate1.stream_id()); + EXPECT_EQ(0u, observer.executed_count()); + + // Read all the data and verify SpdySession::DoReadLoop has posted a + // task. + data.RunFor(12); + EXPECT_EQ(NULL, spdy_stream1.get()); + + // Verify task observer's executed_count is 1, which indicates DoRead has + // posted only one task and thus yielded though there is data available for + // it to read. + EXPECT_EQ(1u, observer.executed_count()); + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); +} + +// Send a GoAway frame when SpdySession is in DoReadLoop. Make sure +// nothing blows up. +TEST_P(SpdySessionTest, GoAwayWhileInDoReadLoop) { + MockConnect connect_data(SYNCHRONOUS, OK); + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + }; + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true)); + scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway()); + + MockRead reads[] = { + CreateMockRead(*resp1, 1), + CreateMockRead(*body1, 2), + CreateMockRead(*goaway, 3), + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url1("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url1, MEDIUM, BoundNetLog()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + ASSERT_TRUE(spdy_stream1.get() != NULL); + EXPECT_EQ(0u, spdy_stream1->stream_id()); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + // Run until 1st read. + EXPECT_EQ(0u, spdy_stream1->stream_id()); + data.RunFor(1); + EXPECT_EQ(1u, spdy_stream1->stream_id()); + + // Run until GoAway. + data.RunFor(3); + EXPECT_EQ(NULL, spdy_stream1.get()); + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(session == NULL); +} + +// Within this framework, a SpdySession should be initialized with +// flow control disabled for protocol version 2, with flow control +// enabled only for streams for protocol version 3, and with flow +// control enabled for streams and sessions for higher versions. +TEST_P(SpdySessionTest, ProtocolNegotiation) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateFakeSpdySession(spdy_session_pool_, key_); + + EXPECT_EQ(spdy_util_.spdy_version(), + session->buffered_spdy_framer_->protocol_version()); + if (GetParam() == kProtoSPDY2) { + EXPECT_EQ(SpdySession::FLOW_CONTROL_NONE, session->flow_control_state()); + EXPECT_EQ(0, session->session_send_window_size_); + EXPECT_EQ(0, session->session_recv_window_size_); + } else if (GetParam() == kProtoSPDY3) { + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM, session->flow_control_state()); + EXPECT_EQ(0, session->session_send_window_size_); + EXPECT_EQ(0, session->session_recv_window_size_); + } else { + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + EXPECT_EQ(kSpdySessionInitialWindowSize, + session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, + session->session_recv_window_size_); + } + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); +} + +// Tests the case of a non-SPDY request closing an idle SPDY session when no +// pointers to the idle session are currently held. +TEST_P(SpdySessionTest, CloseOneIdleConnection) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create an idle SPDY session. + SpdySessionKey key1(HostPortPair("1.com", 80), ProxyServer::Direct(), + kPrivacyModeDisabled); + base::WeakPtr<SpdySession> session1 = + CreateInsecureSpdySession(http_session_, key1, BoundNetLog()); + EXPECT_FALSE(pool->IsStalled()); + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // The socket pool should close the connection asynchronously and establish a + // new connection. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_FALSE(pool->IsStalled()); + EXPECT_TRUE(session1 == NULL); +} + +// Tests the case of a non-SPDY request closing an idle SPDY session when no +// pointers to the idle session are currently held, in the case the SPDY session +// has an alias. +TEST_P(SpdySessionTest, CloseOneIdleConnectionWithAlias) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.host_resolver->rules()->AddIPLiteralRule( + "1.com", "192.168.0.2", std::string()); + session_deps_.host_resolver->rules()->AddIPLiteralRule( + "2.com", "192.168.0.2", std::string()); + // Not strictly needed. + session_deps_.host_resolver->rules()->AddIPLiteralRule( + "3.com", "192.168.0.3", std::string()); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create an idle SPDY session. + SpdySessionKey key1(HostPortPair("1.com", 80), ProxyServer::Direct(), + kPrivacyModeDisabled); + base::WeakPtr<SpdySession> session1 = + CreateInsecureSpdySession(http_session_, key1, BoundNetLog()); + EXPECT_FALSE(pool->IsStalled()); + + // Set up an alias for the idle SPDY session, increasing its ref count to 2. + SpdySessionKey key2(HostPortPair("2.com", 80), ProxyServer::Direct(), + kPrivacyModeDisabled); + HostResolver::RequestInfo info(key2.host_port_pair()); + AddressList addresses; + // Pre-populate the DNS cache, since a synchronous resolution is required in + // order to create the alias. + session_deps_.host_resolver->Resolve( + info, &addresses, CompletionCallback(), NULL, BoundNetLog()); + // Get a session for |key2|, which should return the session created earlier. + base::WeakPtr<SpdySession> session2 = + spdy_session_pool_->FindAvailableSession(key2, BoundNetLog()); + ASSERT_EQ(session1.get(), session2.get()); + EXPECT_FALSE(pool->IsStalled()); + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback3; + HostPortPair host_port3("3.com", 80); + scoped_refptr<TransportSocketParams> params3( + new TransportSocketParams(host_port3, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection3(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection3->Init(host_port3.ToString(), params3, DEFAULT_PRIORITY, + callback3.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // The socket pool should close the connection asynchronously and establish a + // new connection. + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_FALSE(pool->IsStalled()); + EXPECT_TRUE(session1 == NULL); + EXPECT_TRUE(session2 == NULL); +} + +// Tests that a non-SPDY request can't close a SPDY session that's currently in +// use. +TEST_P(SpdySessionTest, CloseOneIdleConnectionFailsWhenSessionInUse) { + ClientSocketPoolManager::set_max_sockets_per_group( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + ClientSocketPoolManager::set_max_sockets_per_pool( + HttpNetworkSession::NORMAL_SOCKET_POOL, 1); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + scoped_ptr<SpdyFrame> cancel1( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req1, 1), + CreateMockWrite(*cancel1, 1), + }; + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + + TransportClientSocketPool* pool = + http_session_->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL); + + // Create a SPDY session. + GURL url1("http://www.google.com"); + SpdySessionKey key1(HostPortPair(url1.host(), 80), + ProxyServer::Direct(), kPrivacyModeDisabled); + base::WeakPtr<SpdySession> session1 = + CreateInsecureSpdySession(http_session_, key1, BoundNetLog()); + EXPECT_FALSE(pool->IsStalled()); + + // Create a stream using the session, and send a request. + + TestCompletionCallback callback1; + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session1, url1, DEFAULT_PRIORITY, + BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get()); + test::StreamDelegateDoNothing delegate1(spdy_stream1); + spdy_stream1->SetDelegate(&delegate1); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructGetHeaderBlock(url1.spec())); + EXPECT_EQ(ERR_IO_PENDING, + spdy_stream1->SendRequestHeaders( + headers1.Pass(), NO_MORE_DATA_TO_SEND)); + EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders()); + + base::MessageLoop::current()->RunUntilIdle(); + + // Trying to create a new connection should cause the pool to be stalled, and + // post a task asynchronously to try and close the session. + TestCompletionCallback callback2; + HostPortPair host_port2("2.com", 80); + scoped_refptr<TransportSocketParams> params2( + new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false, + OnHostResolutionCallback())); + scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, + callback2.callback(), pool, BoundNetLog())); + EXPECT_TRUE(pool->IsStalled()); + + // Running the message loop should cause the socket pool to ask the SPDY + // session to close an idle socket, but since the socket is in use, nothing + // happens. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); + + // Cancelling the request should still not release the session's socket, + // since the session is still kept alive by the SpdySessionPool. + ASSERT_TRUE(spdy_stream1.get()); + spdy_stream1->Cancel(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(pool->IsStalled()); + EXPECT_FALSE(callback2.have_result()); + EXPECT_TRUE(session1 != NULL); +} + +// Verify that SpdySessionKey and therefore SpdySession is different when +// privacy mode is enabled or disabled. +TEST_P(SpdySessionTest, SpdySessionKeyPrivacyMode) { + CreateDeterministicNetworkSession(); + + HostPortPair host_port_pair("www.google.com", 443); + SpdySessionKey key_privacy_enabled(host_port_pair, ProxyServer::Direct(), + kPrivacyModeEnabled); + SpdySessionKey key_privacy_disabled(host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); + + // Add SpdySession with PrivacyMode Enabled to the pool. + base::WeakPtr<SpdySession> session_privacy_enabled = + CreateFakeSpdySession(spdy_session_pool_, key_privacy_enabled); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); + + // Add SpdySession with PrivacyMode Disabled to the pool. + base::WeakPtr<SpdySession> session_privacy_disabled = + CreateFakeSpdySession(spdy_session_pool_, key_privacy_disabled); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); + + session_privacy_enabled->CloseSessionOnError(ERR_ABORTED, std::string()); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); + + session_privacy_disabled->CloseSessionOnError(ERR_ABORTED, std::string()); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); +} + +// Delegate that creates another stream when its stream is closed. +class StreamCreatingDelegate : public test::StreamDelegateDoNothing { + public: + StreamCreatingDelegate(const base::WeakPtr<SpdyStream>& stream, + const base::WeakPtr<SpdySession>& session) + : StreamDelegateDoNothing(stream), + session_(session) {} + + virtual ~StreamCreatingDelegate() {} + + virtual void OnClose(int status) OVERRIDE { + GURL url("http://www.google.com"); + ignore_result( + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session_, url, MEDIUM, BoundNetLog())); + } + + private: + const base::WeakPtr<SpdySession> session_; +}; + +// Create another stream in response to a stream being reset. Nothing +// should blow up. This is a regression test for +// http://crbug.com/263690 . +TEST_P(SpdySessionTest, CreateStreamOnStreamReset) { + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + }; + + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_REFUSED_STREAM)); + MockRead reads[] = { + CreateMockRead(*rst, 1), + MockRead(ASYNC, 0, 2) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url("http://www.google.com"); + base::WeakPtr<SpdyStream> spdy_stream = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream.get() != NULL); + EXPECT_EQ(0u, spdy_stream->stream_id()); + + StreamCreatingDelegate delegate(spdy_stream, session); + spdy_stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(url.spec())); + spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND); + EXPECT_TRUE(spdy_stream->HasUrlFromHeaders()); + + EXPECT_EQ(0u, spdy_stream->stream_id()); + + data.RunFor(1); + + EXPECT_EQ(1u, spdy_stream->stream_id()); + + // Cause the stream to be reset, which should cause another stream + // to be created. + data.RunFor(1); + + EXPECT_EQ(NULL, spdy_stream.get()); + EXPECT_TRUE(delegate.StreamIsClosed()); + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(1u, session->num_created_streams()); +} + +// The tests below are only for SPDY/3 and above. + +TEST_P(SpdySessionTest, SendCredentials) { + if (GetParam() < kProtoSPDY3) + return; + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + SettingsMap settings; + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(settings)); + MockWrite writes[] = { + CreateMockWrite(*settings_frame), + }; + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.channel_id_sent = true; + ssl.protocol_negotiated = GetParam(); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateNetworkSession(); + + const GURL kTestUrl("https://www.foo.com"); + HostPortPair test_host_port_pair(kTestUrl.host(), 443); + SpdySessionKey key(test_host_port_pair, ProxyServer::Direct(), + kPrivacyModeDisabled); + + base::WeakPtr<SpdySession> session = + CreateSecureSpdySession(http_session_, key, BoundNetLog()); + + EXPECT_TRUE(session->NeedsCredentials()); + + // Flush the read completion task. + base::MessageLoop::current()->RunUntilIdle(); + + session->CloseSessionOnError(ERR_ABORTED, std::string()); + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key)); +} + +TEST_P(SpdySessionTest, UpdateStreamsSendWindowSize) { + if (GetParam() < kProtoSPDY3) + return; + + // Set SETTINGS_INITIAL_WINDOW_SIZE to a small number so that WINDOW_UPDATE + // gets sent. + SettingsMap new_settings; + int32 window_size = 1; + new_settings[SETTINGS_INITIAL_WINDOW_SIZE] = + SettingsFlagsAndValue(SETTINGS_FLAG_NONE, window_size); + + // Set up the socket so we read a SETTINGS frame that sets + // INITIAL_WINDOW_SIZE. + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(new_settings)); + MockRead reads[] = { + CreateMockRead(*settings_frame, 0), + MockRead(ASYNC, 0, 1) // EOF + }; + + session_deps_.host_resolver->set_synchronous_mode(true); + + scoped_ptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), NULL, 0)); + data->set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(data.get()); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + base::WeakPtr<SpdyStream> spdy_stream1 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream1.get() != NULL); + TestCompletionCallback callback1; + EXPECT_NE(spdy_stream1->send_window_size(), window_size); + + data->RunFor(1); // Process the SETTINGS frame, but not the EOF + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(session->stream_initial_send_window_size(), window_size); + EXPECT_EQ(spdy_stream1->send_window_size(), window_size); + + // Release the first one, this will allow the second to be created. + spdy_stream1->Cancel(); + EXPECT_EQ(NULL, spdy_stream1.get()); + + base::WeakPtr<SpdyStream> spdy_stream2 = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, test_url_, MEDIUM, BoundNetLog()); + ASSERT_TRUE(spdy_stream2.get() != NULL); + EXPECT_EQ(spdy_stream2->send_window_size(), window_size); + spdy_stream2->Cancel(); + EXPECT_EQ(NULL, spdy_stream2.get()); +} + +// The tests below are only for SPDY/3.1 and above. + +// SpdySession::{Increase,Decrease}RecvWindowSize should properly +// adjust the session receive window size for SPDY 3.1 and higher. In +// addition, SpdySession::IncreaseRecvWindowSize should trigger +// sending a WINDOW_UPDATE frame for a large enough delta. +TEST_P(SpdySessionTest, AdjustRecvWindowSize) { + if (GetParam() < kProtoSPDY31) + return; + + session_deps_.host_resolver->set_synchronous_mode(true); + + const int32 delta_window_size = 100; + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(ASYNC, 0, 1) // EOF + }; + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate( + kSessionFlowControlStreamId, + kSpdySessionInitialWindowSize + delta_window_size)); + MockWrite writes[] = { + CreateMockWrite(*window_update, 0), + }; + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + session->IncreaseRecvWindowSize(delta_window_size); + EXPECT_EQ(kSpdySessionInitialWindowSize + delta_window_size, + session->session_recv_window_size_); + EXPECT_EQ(delta_window_size, session->session_unacked_recv_window_bytes_); + + // Should trigger sending a WINDOW_UPDATE frame. + session->IncreaseRecvWindowSize(kSpdySessionInitialWindowSize); + EXPECT_EQ(kSpdySessionInitialWindowSize + delta_window_size + + kSpdySessionInitialWindowSize, + session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + // DecreaseRecvWindowSize() expects |in_io_loop_| to be true. + session->in_io_loop_ = true; + session->DecreaseRecvWindowSize( + kSpdySessionInitialWindowSize + delta_window_size + + kSpdySessionInitialWindowSize); + session->in_io_loop_ = false; + EXPECT_EQ(0, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); +} + +// SpdySession::{Increase,Decrease}SendWindowSize should properly +// adjust the session send window size when the "enable_spdy_31" flag +// is set. +TEST_P(SpdySessionTest, AdjustSendWindowSize) { + if (GetParam() < kProtoSPDY31) + return; + + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + CreateNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateFakeSpdySession(spdy_session_pool_, key_); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + const int32 delta_window_size = 100; + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + + session->IncreaseSendWindowSize(delta_window_size); + EXPECT_EQ(kSpdySessionInitialWindowSize + delta_window_size, + session->session_send_window_size_); + + session->DecreaseSendWindowSize(delta_window_size); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); +} + +// Incoming data for an inactive stream should not cause the session +// receive window size to decrease, but it should cause the unacked +// bytes to increase. +TEST_P(SpdySessionTest, SessionFlowControlInactiveStream) { + if (GetParam() < kProtoSPDY31) + return; + + session_deps_.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyBodyFrame(1, false)); + MockRead reads[] = { + CreateMockRead(*resp, 0), + MockRead(ASYNC, 0, 1) // EOF + }; + DeterministicSocketData data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(kUploadDataSize, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); +} + +// A delegate that drops any received data. +class DropReceivedDataDelegate : public test::StreamDelegateSendImmediate { + public: + DropReceivedDataDelegate(const base::WeakPtr<SpdyStream>& stream, + base::StringPiece data) + : StreamDelegateSendImmediate(stream, data) {} + + virtual ~DropReceivedDataDelegate() {} + + // Drop any received data. + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {} +}; + +// Send data back and forth but use a delegate that drops its received +// data. The receive window should still increase to its original +// value, i.e. we shouldn't "leak" receive window bytes. +TEST_P(SpdySessionTest, SessionFlowControlNoReceiveLeaks) { + if (GetParam() < kProtoSPDY31) + return; + + const char kStreamUrl[] = "http://www.google.com/"; + + const int32 msg_data_size = 100; + const std::string msg_data(msg_data_size, 'a'); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, msg_data_size, MEDIUM, NULL, 0)); + scoped_ptr<SpdyFrame> msg( + spdy_util_.ConstructSpdyBodyFrame( + 1, msg_data.data(), msg_data_size, false)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*msg, 2), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> echo( + spdy_util_.ConstructSpdyBodyFrame( + 1, msg_data.data(), msg_data_size, false)); + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate( + kSessionFlowControlStreamId, msg_data_size)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*echo, 3), + MockRead(ASYNC, 0, 4) // EOF + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url(kStreamUrl); + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url, MEDIUM, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + EXPECT_EQ(0u, stream->stream_id()); + + DropReceivedDataDelegate delegate(stream, msg_data); + stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(url.spec(), msg_data_size)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(4); + + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_); + + stream->Close(); + EXPECT_EQ(NULL, stream.get()); + + EXPECT_EQ(OK, delegate.WaitForClose()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_); +} + +// Send data back and forth but close the stream before its data frame +// can be written to the socket. The send window should then increase +// to its original value, i.e. we shouldn't "leak" send window bytes. +TEST_P(SpdySessionTest, SessionFlowControlNoSendLeaks) { + if (GetParam() < kProtoSPDY31) + return; + + const char kStreamUrl[] = "http://www.google.com/"; + + const int32 msg_data_size = 100; + const std::string msg_data(msg_data_size, 'a'); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, msg_data_size, MEDIUM, NULL, 0)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + MockRead(ASYNC, 0, 2) // EOF + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url(kStreamUrl); + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url, MEDIUM, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + EXPECT_EQ(0u, stream->stream_id()); + + test::StreamDelegateSendImmediate delegate(stream, msg_data); + stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(url.spec(), msg_data_size)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + + data.RunFor(1); + + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); + + EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size, + session->session_send_window_size_); + + // Closing the stream should increase the session's send window. + stream->Close(); + EXPECT_EQ(NULL, stream.get()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + + EXPECT_EQ(OK, delegate.WaitForClose()); +} + +// Send data back and forth; the send and receive windows should +// change appropriately. +TEST_P(SpdySessionTest, SessionFlowControlEndToEnd) { + if (GetParam() < kProtoSPDY31) + return; + + const char kStreamUrl[] = "http://www.google.com/"; + + const int32 msg_data_size = 100; + const std::string msg_data(msg_data_size, 'a'); + + MockConnect connect_data(SYNCHRONOUS, OK); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, msg_data_size, MEDIUM, NULL, 0)); + scoped_ptr<SpdyFrame> msg( + spdy_util_.ConstructSpdyBodyFrame( + 1, msg_data.data(), msg_data_size, false)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*msg, 2), + }; + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> echo( + spdy_util_.ConstructSpdyBodyFrame( + 1, msg_data.data(), msg_data_size, false)); + scoped_ptr<SpdyFrame> window_update( + spdy_util_.ConstructSpdyWindowUpdate( + kSessionFlowControlStreamId, msg_data_size)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*echo, 3), + CreateMockRead(*window_update, 4), + MockRead(ASYNC, 0, 5) // EOF + }; + + // Create SpdySession and SpdyStream and send the request. + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps_.host_resolver->set_synchronous_mode(true); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl); + + CreateDeterministicNetworkSession(); + + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + + GURL url(kStreamUrl); + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, + session, url, MEDIUM, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + EXPECT_EQ(0u, stream->stream_id()); + + test::StreamDelegateSendImmediate delegate(stream, msg_data); + stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(url.spec(), msg_data_size)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size, + session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size, + session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size, + session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size, + session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + data.RunFor(1); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size, + session->session_recv_window_size_); + EXPECT_EQ(0, session->session_unacked_recv_window_bytes_); + + EXPECT_TRUE(data.at_write_eof()); + EXPECT_TRUE(data.at_read_eof()); + + EXPECT_EQ(msg_data, delegate.TakeReceivedData()); + + // Draining the delegate's read queue should increase the session's + // receive window. + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_); + + stream->Close(); + EXPECT_EQ(NULL, stream.get()); + + EXPECT_EQ(OK, delegate.WaitForClose()); + + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_); + EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_); + EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_); +} + +// Given a stall function and an unstall function, runs a test to make +// sure that a stream resumes after unstall. +void SpdySessionTest::RunResumeAfterUnstallTest( + const base::Callback<void(SpdySession*, SpdyStream*)>& stall_function, + const base::Callback<void(SpdySession*, SpdyStream*, int32)>& + unstall_function) { + const char kStreamUrl[] = "http://www.google.com/"; + GURL url(kStreamUrl); + + session_deps_.host_resolver->set_synchronous_mode(true); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body( + spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, true)); + MockWrite writes[] = { + CreateMockWrite(*req, 0), + CreateMockWrite(*body, 1), + }; + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> echo( + spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, false)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3), // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + CreateDeterministicNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + test::StreamDelegateWithBody delegate(stream, kBodyDataStringPiece); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + EXPECT_FALSE(stream->send_stalled_by_flow_control()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + stall_function.Run(session.get(), stream.get()); + + data.RunFor(1); + + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + + unstall_function.Run(session.get(), stream.get(), kBodyDataSize); + + EXPECT_FALSE(stream->send_stalled_by_flow_control()); + + data.RunFor(3); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status")); + EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version")); + EXPECT_EQ(std::string(), delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); +} + +// Run the resume-after-unstall test with all possible stall and +// unstall sequences. + +TEST_P(SpdySessionTest, ResumeAfterUnstallSession) { + if (GetParam() < kProtoSPDY31) + return; + + RunResumeAfterUnstallTest( + base::Bind(&SpdySessionTest::StallSessionOnly, + base::Unretained(this)), + base::Bind(&SpdySessionTest::UnstallSessionOnly, + base::Unretained(this))); +} + +// Equivalent to +// SpdyStreamTest.ResumeAfterSendWindowSizeIncrease. +TEST_P(SpdySessionTest, ResumeAfterUnstallStream) { + if (GetParam() < kProtoSPDY31) + return; + + RunResumeAfterUnstallTest( + base::Bind(&SpdySessionTest::StallStreamOnly, + base::Unretained(this)), + base::Bind(&SpdySessionTest::UnstallStreamOnly, + base::Unretained(this))); +} + +TEST_P(SpdySessionTest, StallSessionStreamResumeAfterUnstallSessionStream) { + if (GetParam() < kProtoSPDY31) + return; + + RunResumeAfterUnstallTest( + base::Bind(&SpdySessionTest::StallSessionStream, + base::Unretained(this)), + base::Bind(&SpdySessionTest::UnstallSessionStream, + base::Unretained(this))); +} + +TEST_P(SpdySessionTest, StallStreamSessionResumeAfterUnstallSessionStream) { + if (GetParam() < kProtoSPDY31) + return; + + RunResumeAfterUnstallTest( + base::Bind(&SpdySessionTest::StallStreamSession, + base::Unretained(this)), + base::Bind(&SpdySessionTest::UnstallSessionStream, + base::Unretained(this))); +} + +TEST_P(SpdySessionTest, StallStreamSessionResumeAfterUnstallStreamSession) { + if (GetParam() < kProtoSPDY31) + return; + + RunResumeAfterUnstallTest( + base::Bind(&SpdySessionTest::StallStreamSession, + base::Unretained(this)), + base::Bind(&SpdySessionTest::UnstallStreamSession, + base::Unretained(this))); +} + +TEST_P(SpdySessionTest, StallSessionStreamResumeAfterUnstallStreamSession) { + if (GetParam() < kProtoSPDY31) + return; + + RunResumeAfterUnstallTest( + base::Bind(&SpdySessionTest::StallSessionStream, + base::Unretained(this)), + base::Bind(&SpdySessionTest::UnstallStreamSession, + base::Unretained(this))); +} + +// Cause a stall by reducing the flow control send window to 0. The +// streams should resume in priority order when that window is then +// increased. +TEST_P(SpdySessionTest, ResumeByPriorityAfterSendWindowSizeIncrease) { + if (GetParam() < kProtoSPDY31) + return; + + const char kStreamUrl[] = "http://www.google.com/"; + GURL url(kStreamUrl); + + session_deps_.host_resolver->set_synchronous_mode(true); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 3, kBodyDataSize, MEDIUM, NULL, 0)); + scoped_ptr<SpdyFrame> body1( + spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, true)); + scoped_ptr<SpdyFrame> body2( + spdy_util_.ConstructSpdyBodyFrame(3, kBodyData, kBodyDataSize, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + CreateMockWrite(*body2, 2), + CreateMockWrite(*body1, 3), + }; + + scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + MockRead reads[] = { + CreateMockRead(*resp1, 4), + CreateMockRead(*resp2, 5), + MockRead(ASYNC, 0, 0, 6), // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + CreateDeterministicNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + base::WeakPtr<SpdyStream> stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream1.get() != NULL); + + test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); + stream1->SetDelegate(&delegate1); + + EXPECT_FALSE(stream1->HasUrlFromHeaders()); + + base::WeakPtr<SpdyStream> stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, MEDIUM, BoundNetLog()); + ASSERT_TRUE(stream2.get() != NULL); + + test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece); + stream2->SetDelegate(&delegate2); + + EXPECT_FALSE(stream2->HasUrlFromHeaders()); + + EXPECT_FALSE(stream1->send_stalled_by_flow_control()); + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + + StallSessionSend(session.get()); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream1->SendRequestHeaders(headers1.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream1->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream1->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(1u, stream1->stream_id()); + EXPECT_TRUE(stream1->send_stalled_by_flow_control()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream2->SendRequestHeaders(headers2.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream2->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream2->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(3u, stream2->stream_id()); + EXPECT_TRUE(stream2->send_stalled_by_flow_control()); + + // This should unstall only stream2. + UnstallSessionSend(session.get(), kBodyDataSize); + + EXPECT_TRUE(stream1->send_stalled_by_flow_control()); + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + + data.RunFor(1); + + EXPECT_TRUE(stream1->send_stalled_by_flow_control()); + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + + // This should then unstall stream1. + UnstallSessionSend(session.get(), kBodyDataSize); + + EXPECT_FALSE(stream1->send_stalled_by_flow_control()); + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + + data.RunFor(4); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate1.WaitForClose()); + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate2.WaitForClose()); + + EXPECT_TRUE(delegate1.send_headers_completed()); + EXPECT_EQ("200", delegate1.GetResponseHeaderValue(":status")); + EXPECT_EQ("HTTP/1.1", delegate1.GetResponseHeaderValue(":version")); + EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); + + EXPECT_TRUE(delegate2.send_headers_completed()); + EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status")); + EXPECT_EQ("HTTP/1.1", delegate2.GetResponseHeaderValue(":version")); + EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); + + EXPECT_TRUE(data.at_write_eof()); +} + +// Delegate that closes a given stream after sending its body. +class StreamClosingDelegate : public test::StreamDelegateWithBody { + public: + StreamClosingDelegate(const base::WeakPtr<SpdyStream>& stream, + base::StringPiece data) + : StreamDelegateWithBody(stream, data) {} + + virtual ~StreamClosingDelegate() {} + + void set_stream_to_close(const base::WeakPtr<SpdyStream>& stream_to_close) { + stream_to_close_ = stream_to_close; + } + + virtual void OnDataSent() OVERRIDE { + test::StreamDelegateWithBody::OnDataSent(); + if (stream_to_close_.get()) { + stream_to_close_->Close(); + EXPECT_EQ(NULL, stream_to_close_.get()); + } + } + + private: + base::WeakPtr<SpdyStream> stream_to_close_; +}; + +// Cause a stall by reducing the flow control send window to +// 0. Unstalling the session should properly handle deleted streams. +TEST_P(SpdySessionTest, SendWindowSizeIncreaseWithDeletedStreams) { + if (GetParam() < kProtoSPDY31) + return; + + const char kStreamUrl[] = "http://www.google.com/"; + GURL url(kStreamUrl); + + session_deps_.host_resolver->set_synchronous_mode(true); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 3, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> req3( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 5, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body2( + spdy_util_.ConstructSpdyBodyFrame(3, kBodyData, kBodyDataSize, true)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + CreateMockWrite(*req3, 2), + CreateMockWrite(*body2, 3), + }; + + scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); + MockRead reads[] = { + CreateMockRead(*resp2, 4), + MockRead(ASYNC, 0, 0, 5), // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + CreateDeterministicNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + base::WeakPtr<SpdyStream> stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream1.get() != NULL); + + test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); + stream1->SetDelegate(&delegate1); + + EXPECT_FALSE(stream1->HasUrlFromHeaders()); + + base::WeakPtr<SpdyStream> stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream2.get() != NULL); + + StreamClosingDelegate delegate2(stream2, kBodyDataStringPiece); + stream2->SetDelegate(&delegate2); + + EXPECT_FALSE(stream2->HasUrlFromHeaders()); + + base::WeakPtr<SpdyStream> stream3 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream3.get() != NULL); + + test::StreamDelegateWithBody delegate3(stream3, kBodyDataStringPiece); + stream3->SetDelegate(&delegate3); + + EXPECT_FALSE(stream3->HasUrlFromHeaders()); + + EXPECT_FALSE(stream1->send_stalled_by_flow_control()); + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + EXPECT_FALSE(stream3->send_stalled_by_flow_control()); + + StallSessionSend(session.get()); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream1->SendRequestHeaders(headers1.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream1->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream1->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(1u, stream1->stream_id()); + EXPECT_TRUE(stream1->send_stalled_by_flow_control()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream2->SendRequestHeaders(headers2.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream2->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream2->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(3u, stream2->stream_id()); + EXPECT_TRUE(stream2->send_stalled_by_flow_control()); + + scoped_ptr<SpdyHeaderBlock> headers3( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream3->SendRequestHeaders(headers3.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream3->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream3->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(5u, stream3->stream_id()); + EXPECT_TRUE(stream3->send_stalled_by_flow_control()); + + SpdyStreamId stream_id1 = stream1->stream_id(); + SpdyStreamId stream_id2 = stream2->stream_id(); + SpdyStreamId stream_id3 = stream3->stream_id(); + + // Close stream1 preemptively. + session->CloseActiveStream(stream_id1, ERR_CONNECTION_CLOSED); + EXPECT_EQ(NULL, stream1.get()); + + EXPECT_FALSE(session->IsStreamActive(stream_id1)); + EXPECT_TRUE(session->IsStreamActive(stream_id2)); + EXPECT_TRUE(session->IsStreamActive(stream_id3)); + + // Unstall stream2, which should then close stream3. + delegate2.set_stream_to_close(stream3); + UnstallSessionSend(session.get(), kBodyDataSize); + + data.RunFor(1); + EXPECT_EQ(NULL, stream3.get()); + + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + EXPECT_FALSE(session->IsStreamActive(stream_id1)); + EXPECT_TRUE(session->IsStreamActive(stream_id2)); + EXPECT_FALSE(session->IsStreamActive(stream_id3)); + + data.RunFor(2); + EXPECT_EQ(NULL, stream2.get()); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate1.WaitForClose()); + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate2.WaitForClose()); + EXPECT_EQ(OK, delegate3.WaitForClose()); + + EXPECT_TRUE(delegate1.send_headers_completed()); + EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); + + EXPECT_TRUE(delegate2.send_headers_completed()); + EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status")); + EXPECT_EQ("HTTP/1.1", delegate2.GetResponseHeaderValue(":version")); + EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); + + EXPECT_TRUE(delegate3.send_headers_completed()); + EXPECT_EQ(std::string(), delegate3.TakeReceivedData()); + + EXPECT_TRUE(data.at_write_eof()); +} + +// Cause a stall by reducing the flow control send window to +// 0. Unstalling the session should properly handle the session itself +// being closed. +TEST_P(SpdySessionTest, SendWindowSizeIncreaseWithDeletedSession) { + if (GetParam() < kProtoSPDY31) + return; + + const char kStreamUrl[] = "http://www.google.com/"; + GURL url(kStreamUrl); + + session_deps_.host_resolver->set_synchronous_mode(true); + + scoped_ptr<SpdyFrame> req1( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> req2( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 3, kBodyDataSize, LOWEST, NULL, 0)); + scoped_ptr<SpdyFrame> body1( + spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, false)); + MockWrite writes[] = { + CreateMockWrite(*req1, 0), + CreateMockWrite(*req2, 1), + }; + + MockRead reads[] = { + MockRead(ASYNC, 0, 0, 2), // EOF + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + CreateDeterministicNetworkSession(); + base::WeakPtr<SpdySession> session = + CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); + EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, + session->flow_control_state()); + + base::WeakPtr<SpdyStream> stream1 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream1.get() != NULL); + + test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); + stream1->SetDelegate(&delegate1); + + EXPECT_FALSE(stream1->HasUrlFromHeaders()); + + base::WeakPtr<SpdyStream> stream2 = + CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, + session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream2.get() != NULL); + + test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece); + stream2->SetDelegate(&delegate2); + + EXPECT_FALSE(stream2->HasUrlFromHeaders()); + + EXPECT_FALSE(stream1->send_stalled_by_flow_control()); + EXPECT_FALSE(stream2->send_stalled_by_flow_control()); + + StallSessionSend(session.get()); + + scoped_ptr<SpdyHeaderBlock> headers1( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream1->SendRequestHeaders(headers1.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream1->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream1->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(1u, stream1->stream_id()); + EXPECT_TRUE(stream1->send_stalled_by_flow_control()); + + scoped_ptr<SpdyHeaderBlock> headers2( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize)); + EXPECT_EQ(ERR_IO_PENDING, + stream2->SendRequestHeaders(headers2.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream2->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream2->GetUrlFromHeaders().spec()); + + data.RunFor(1); + EXPECT_EQ(3u, stream2->stream_id()); + EXPECT_TRUE(stream2->send_stalled_by_flow_control()); + + EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); + + // Unstall stream1. + UnstallSessionSend(session.get(), kBodyDataSize); + + // Close the session (since we can't do it from within the delegate + // method, since it's in the stream's loop). + session->CloseSessionOnError(ERR_CONNECTION_CLOSED, "Closing session"); + EXPECT_TRUE(session == NULL); + + EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate1.WaitForClose()); + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate2.WaitForClose()); + + EXPECT_TRUE(delegate1.send_headers_completed()); + EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); + + EXPECT_TRUE(delegate2.send_headers_completed()); + EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); + + EXPECT_TRUE(data.at_write_eof()); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_stream.cc b/chromium/net/spdy/spdy_stream.cc new file mode 100644 index 00000000000..a603a6c93e0 --- /dev/null +++ b/chromium/net/spdy/spdy_stream.cc @@ -0,0 +1,1035 @@ +// 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 "net/spdy/spdy_stream.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "net/spdy/spdy_buffer_producer.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" + +namespace net { + +namespace { + +base::Value* NetLogSpdyStreamErrorCallback(SpdyStreamId stream_id, + int status, + const std::string* description, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("stream_id", static_cast<int>(stream_id)); + dict->SetInteger("status", status); + dict->SetString("description", *description); + return dict; +} + +base::Value* NetLogSpdyStreamWindowUpdateCallback( + SpdyStreamId stream_id, + int32 delta, + int32 window_size, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("stream_id", stream_id); + dict->SetInteger("delta", delta); + dict->SetInteger("window_size", window_size); + return dict; +} + +bool ContainsUppercaseAscii(const std::string& str) { + for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) { + if (*i >= 'A' && *i <= 'Z') { + return true; + } + } + return false; +} + +} // namespace + +// A wrapper around a stream that calls into ProduceSynStreamFrame(). +class SpdyStream::SynStreamBufferProducer : public SpdyBufferProducer { + public: + SynStreamBufferProducer(const base::WeakPtr<SpdyStream>& stream) + : stream_(stream) { + DCHECK(stream_.get()); + } + + virtual ~SynStreamBufferProducer() {} + + virtual scoped_ptr<SpdyBuffer> ProduceBuffer() OVERRIDE { + if (!stream_.get()) { + NOTREACHED(); + return scoped_ptr<SpdyBuffer>(); + } + DCHECK_GT(stream_->stream_id(), 0u); + return scoped_ptr<SpdyBuffer>( + new SpdyBuffer(stream_->ProduceSynStreamFrame())); + } + + private: + const base::WeakPtr<SpdyStream> stream_; +}; + +SpdyStream::SpdyStream(SpdyStreamType type, + const base::WeakPtr<SpdySession>& session, + const GURL& url, + RequestPriority priority, + int32 initial_send_window_size, + int32 initial_recv_window_size, + const BoundNetLog& net_log) + : type_(type), + weak_ptr_factory_(this), + in_do_loop_(false), + continue_buffering_data_(type_ == SPDY_PUSH_STREAM), + stream_id_(0), + url_(url), + priority_(priority), + slot_(0), + send_stalled_by_flow_control_(false), + send_window_size_(initial_send_window_size), + recv_window_size_(initial_recv_window_size), + unacked_recv_window_bytes_(0), + session_(session), + delegate_(NULL), + send_status_( + (type_ == SPDY_PUSH_STREAM) ? + NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND), + request_time_(base::Time::Now()), + response_headers_status_(RESPONSE_HEADERS_ARE_INCOMPLETE), + io_state_((type_ == SPDY_PUSH_STREAM) ? STATE_IDLE : STATE_NONE), + response_status_(OK), + net_log_(net_log), + send_bytes_(0), + recv_bytes_(0), + just_completed_frame_type_(DATA), + just_completed_frame_size_(0) { + CHECK(type_ == SPDY_BIDIRECTIONAL_STREAM || + type_ == SPDY_REQUEST_RESPONSE_STREAM || + type_ == SPDY_PUSH_STREAM); +} + +SpdyStream::~SpdyStream() { + CHECK(!in_do_loop_); + UpdateHistograms(); +} + +void SpdyStream::SetDelegate(Delegate* delegate) { + CHECK(!delegate_); + CHECK(delegate); + delegate_ = delegate; + + if (type_ == SPDY_PUSH_STREAM) { + DCHECK(continue_buffering_data_); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&SpdyStream::PushedStreamReplayData, GetWeakPtr())); + } +} + +void SpdyStream::PushedStreamReplayData() { + DCHECK_EQ(type_, SPDY_PUSH_STREAM); + DCHECK_NE(stream_id_, 0u); + DCHECK(continue_buffering_data_); + + continue_buffering_data_ = false; + + // The delegate methods called below may delete |this|, so use + // |weak_this| to detect that. + base::WeakPtr<SpdyStream> weak_this = GetWeakPtr(); + + CHECK(delegate_); + SpdyResponseHeadersStatus status = + delegate_->OnResponseHeadersUpdated(response_headers_); + if (status == RESPONSE_HEADERS_ARE_INCOMPLETE) { + // Since RESPONSE_HEADERS_ARE_INCOMPLETE was returned, we must not + // have been closed. Since we don't have complete headers, assume + // we're waiting for another HEADERS frame, and we had better not + // have any pending data frames. + CHECK(weak_this); + if (!pending_buffers_.empty()) { + LogStreamError(ERR_SPDY_PROTOCOL_ERROR, + "Data received with incomplete headers."); + session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR); + } + return; + } + + // OnResponseHeadersUpdated() may have closed |this|. + if (!weak_this) + return; + + response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE; + + while (!pending_buffers_.empty()) { + // Take ownership of the first element of |pending_buffers_|. + scoped_ptr<SpdyBuffer> buffer(pending_buffers_.front()); + pending_buffers_.weak_erase(pending_buffers_.begin()); + + bool eof = (buffer == NULL); + + CHECK(delegate_); + delegate_->OnDataReceived(buffer.Pass()); + + // OnDataReceived() may have closed |this|. + if (!weak_this) + return; + + if (eof) { + DCHECK(pending_buffers_.empty()); + session_->CloseActiveStream(stream_id_, OK); + DCHECK(!weak_this); + // |pending_buffers_| is invalid at this point. + break; + } + } +} + +scoped_ptr<SpdyFrame> SpdyStream::ProduceSynStreamFrame() { + CHECK_EQ(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE); + CHECK(request_headers_); + CHECK_GT(stream_id_, 0u); + + SpdyControlFlags flags = + (send_status_ == NO_MORE_DATA_TO_SEND) ? + CONTROL_FLAG_FIN : CONTROL_FLAG_NONE; + scoped_ptr<SpdyFrame> frame(session_->CreateSynStream( + stream_id_, priority_, slot_, flags, *request_headers_)); + send_time_ = base::TimeTicks::Now(); + return frame.Pass(); +} + +void SpdyStream::DetachDelegate() { + CHECK(!in_do_loop_); + DCHECK(!IsClosed()); + delegate_ = NULL; + Cancel(); +} + +void SpdyStream::AdjustSendWindowSize(int32 delta_window_size) { + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + + if (IsClosed()) + return; + + // Check for wraparound. + if (send_window_size_ > 0) { + DCHECK_LE(delta_window_size, kint32max - send_window_size_); + } + if (send_window_size_ < 0) { + DCHECK_GE(delta_window_size, kint32min - send_window_size_); + } + send_window_size_ += delta_window_size; + PossiblyResumeIfSendStalled(); +} + +void SpdyStream::OnWriteBufferConsumed( + size_t frame_payload_size, + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source) { + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + if (consume_source == SpdyBuffer::DISCARD) { + // If we're discarding a frame or part of it, increase the send + // window by the number of discarded bytes. (Although if we're + // discarding part of a frame, it's probably because of a write + // error and we'll be tearing down the stream soon.) + size_t remaining_payload_bytes = std::min(consume_size, frame_payload_size); + DCHECK_GT(remaining_payload_bytes, 0u); + IncreaseSendWindowSize(static_cast<int32>(remaining_payload_bytes)); + } + // For consumed bytes, the send window is increased when we receive + // a WINDOW_UPDATE frame. +} + +void SpdyStream::IncreaseSendWindowSize(int32 delta_window_size) { + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + DCHECK_GE(delta_window_size, 1); + + // Ignore late WINDOW_UPDATEs. + if (IsClosed()) + return; + + if (send_window_size_ > 0) { + // Check for overflow. + int32 max_delta_window_size = kint32max - send_window_size_; + if (delta_window_size > max_delta_window_size) { + std::string desc = base::StringPrintf( + "Received WINDOW_UPDATE [delta: %d] for stream %d overflows " + "send_window_size_ [current: %d]", delta_window_size, stream_id_, + send_window_size_); + session_->ResetStream(stream_id_, RST_STREAM_FLOW_CONTROL_ERROR, desc); + return; + } + } + + send_window_size_ += delta_window_size; + + net_log_.AddEvent( + NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW, + base::Bind(&NetLogSpdyStreamWindowUpdateCallback, + stream_id_, delta_window_size, send_window_size_)); + + PossiblyResumeIfSendStalled(); +} + +void SpdyStream::DecreaseSendWindowSize(int32 delta_window_size) { + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + + if (IsClosed()) + return; + + // We only call this method when sending a frame. Therefore, + // |delta_window_size| should be within the valid frame size range. + DCHECK_GE(delta_window_size, 1); + DCHECK_LE(delta_window_size, kMaxSpdyFrameChunkSize); + + // |send_window_size_| should have been at least |delta_window_size| for + // this call to happen. + DCHECK_GE(send_window_size_, delta_window_size); + + send_window_size_ -= delta_window_size; + + net_log_.AddEvent( + NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW, + base::Bind(&NetLogSpdyStreamWindowUpdateCallback, + stream_id_, -delta_window_size, send_window_size_)); +} + +void SpdyStream::OnReadBufferConsumed( + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source) { + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + DCHECK_GE(consume_size, 1u); + DCHECK_LE(consume_size, static_cast<size_t>(kint32max)); + IncreaseRecvWindowSize(static_cast<int32>(consume_size)); +} + +void SpdyStream::IncreaseRecvWindowSize(int32 delta_window_size) { + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + + // By the time a read is processed by the delegate, this stream may + // already be inactive. + if (!session_->IsStreamActive(stream_id_)) + return; + + DCHECK_GE(unacked_recv_window_bytes_, 0); + DCHECK_GE(recv_window_size_, unacked_recv_window_bytes_); + DCHECK_GE(delta_window_size, 1); + // Check for overflow. + DCHECK_LE(delta_window_size, kint32max - recv_window_size_); + + recv_window_size_ += delta_window_size; + net_log_.AddEvent( + NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW, + base::Bind(&NetLogSpdyStreamWindowUpdateCallback, + stream_id_, delta_window_size, recv_window_size_)); + + unacked_recv_window_bytes_ += delta_window_size; + if (unacked_recv_window_bytes_ > + session_->stream_initial_recv_window_size() / 2) { + session_->SendStreamWindowUpdate( + stream_id_, static_cast<uint32>(unacked_recv_window_bytes_)); + unacked_recv_window_bytes_ = 0; + } +} + +void SpdyStream::DecreaseRecvWindowSize(int32 delta_window_size) { + DCHECK(session_->IsStreamActive(stream_id_)); + DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM); + DCHECK_GE(delta_window_size, 1); + + // Since we never decrease the initial receive window size, + // |delta_window_size| should never cause |recv_window_size_| to go + // negative. If we do, the receive window isn't being respected. + if (delta_window_size > recv_window_size_) { + session_->ResetStream( + stream_id_, RST_STREAM_PROTOCOL_ERROR, + "delta_window_size is " + base::IntToString(delta_window_size) + + " in DecreaseRecvWindowSize, which is larger than the receive " + + "window size of " + base::IntToString(recv_window_size_)); + return; + } + + recv_window_size_ -= delta_window_size; + net_log_.AddEvent( + NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW, + base::Bind(&NetLogSpdyStreamWindowUpdateCallback, + stream_id_, -delta_window_size, recv_window_size_)); +} + +int SpdyStream::GetPeerAddress(IPEndPoint* address) const { + return session_->GetPeerAddress(address); +} + +int SpdyStream::GetLocalAddress(IPEndPoint* address) const { + return session_->GetLocalAddress(address); +} + +bool SpdyStream::WasEverUsed() const { + return session_->WasEverUsed(); +} + +base::Time SpdyStream::GetRequestTime() const { + return request_time_; +} + +void SpdyStream::SetRequestTime(base::Time t) { + request_time_ = t; +} + +int SpdyStream::OnInitialResponseHeadersReceived( + const SpdyHeaderBlock& initial_response_headers, + base::Time response_time, + base::TimeTicks recv_first_byte_time) { + // SpdySession guarantees that this is called at most once. + CHECK(response_headers_.empty()); + + // Check to make sure that we don't receive the response headers + // before we're ready for it. + switch (type_) { + case SPDY_BIDIRECTIONAL_STREAM: + // For a bidirectional stream, we're ready for the response + // headers once we've finished sending the request headers. + if (io_state_ < STATE_IDLE) { + session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Response received before request sent"); + return ERR_SPDY_PROTOCOL_ERROR; + } + break; + + case SPDY_REQUEST_RESPONSE_STREAM: + // For a request/response stream, we're ready for the response + // headers once we've finished sending the request headers and + // the request body (if we have one). + if ((io_state_ < STATE_IDLE) || (send_status_ == MORE_DATA_TO_SEND) || + pending_send_data_.get()) { + session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Response received before request sent"); + return ERR_SPDY_PROTOCOL_ERROR; + } + break; + + case SPDY_PUSH_STREAM: + // For a push stream, we're ready immediately. + DCHECK_EQ(send_status_, NO_MORE_DATA_TO_SEND); + DCHECK_EQ(io_state_, STATE_IDLE); + break; + } + + metrics_.StartStream(); + + DCHECK_EQ(io_state_, STATE_IDLE); + + response_time_ = response_time; + recv_first_byte_time_ = recv_first_byte_time; + return MergeWithResponseHeaders(initial_response_headers); +} + +int SpdyStream::OnAdditionalResponseHeadersReceived( + const SpdyHeaderBlock& additional_response_headers) { + if (type_ == SPDY_REQUEST_RESPONSE_STREAM) { + session_->ResetStream( + stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Additional headers received for request/response stream"); + return ERR_SPDY_PROTOCOL_ERROR; + } else if (type_ == SPDY_PUSH_STREAM && + response_headers_status_ == RESPONSE_HEADERS_ARE_COMPLETE) { + session_->ResetStream( + stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Additional headers received for push stream"); + return ERR_SPDY_PROTOCOL_ERROR; + } + return MergeWithResponseHeaders(additional_response_headers); +} + +void SpdyStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) { + DCHECK(session_->IsStreamActive(stream_id_)); + + // If we're still buffering data for a push stream, we will do the + // check for data received with incomplete headers in + // PushedStreamReplayData(). + if (!delegate_ || continue_buffering_data_) { + DCHECK_EQ(type_, SPDY_PUSH_STREAM); + // It should be valid for this to happen in the server push case. + // We'll return received data when delegate gets attached to the stream. + if (buffer) { + pending_buffers_.push_back(buffer.release()); + } else { + pending_buffers_.push_back(NULL); + metrics_.StopStream(); + // Note: we leave the stream open in the session until the stream + // is claimed. + } + return; + } + + // If we have response headers but the delegate has indicated that + // it's still incomplete, then that's a protocol error. + if (response_headers_status_ == RESPONSE_HEADERS_ARE_INCOMPLETE) { + LogStreamError(ERR_SPDY_PROTOCOL_ERROR, + "Data received with incomplete headers."); + session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR); + return; + } + + CHECK(!IsClosed()); + + if (!buffer) { + metrics_.StopStream(); + // Deletes |this|. + session_->CloseActiveStream(stream_id_, OK); + return; + } + + size_t length = buffer->GetRemainingSize(); + DCHECK_LE(length, session_->GetDataFrameMaximumPayload()); + if (session_->flow_control_state() >= SpdySession::FLOW_CONTROL_STREAM) { + DecreaseRecvWindowSize(static_cast<int32>(length)); + buffer->AddConsumeCallback( + base::Bind(&SpdyStream::OnReadBufferConsumed, GetWeakPtr())); + } + + // Track our bandwidth. + metrics_.RecordBytes(length); + recv_bytes_ += length; + recv_last_byte_time_ = base::TimeTicks::Now(); + + // May close |this|. + delegate_->OnDataReceived(buffer.Pass()); +} + +void SpdyStream::OnFrameWriteComplete(SpdyFrameType frame_type, + size_t frame_size) { + if (frame_size < session_->GetFrameMinimumSize() || + frame_size > session_->GetFrameMaximumSize()) { + NOTREACHED(); + return; + } + if (IsClosed()) + return; + just_completed_frame_type_ = frame_type; + just_completed_frame_size_ = frame_size; + DoLoop(OK); +} + +int SpdyStream::GetProtocolVersion() const { + return session_->GetProtocolVersion(); +} + +void SpdyStream::LogStreamError(int status, const std::string& description) { + net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ERROR, + base::Bind(&NetLogSpdyStreamErrorCallback, + stream_id_, status, &description)); +} + +void SpdyStream::OnClose(int status) { + CHECK(!in_do_loop_); + io_state_ = STATE_CLOSED; + response_status_ = status; + Delegate* delegate = delegate_; + delegate_ = NULL; + if (delegate) + delegate->OnClose(status); + // Unset |stream_id_| last so that the delegate can look it up. + stream_id_ = 0; +} + +void SpdyStream::Cancel() { + CHECK(!in_do_loop_); + // We may be called again from a delegate's OnClose(). + if (io_state_ == STATE_CLOSED) + return; + + if (stream_id_ != 0) { + session_->ResetStream(stream_id_, RST_STREAM_CANCEL, std::string()); + } else { + session_->CloseCreatedStream(GetWeakPtr(), RST_STREAM_CANCEL); + } + // |this| is invalid at this point. +} + +void SpdyStream::Close() { + CHECK(!in_do_loop_); + // We may be called again from a delegate's OnClose(). + if (io_state_ == STATE_CLOSED) + return; + + if (stream_id_ != 0) { + session_->CloseActiveStream(stream_id_, OK); + } else { + session_->CloseCreatedStream(GetWeakPtr(), OK); + } + // |this| is invalid at this point. +} + +base::WeakPtr<SpdyStream> SpdyStream::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + +int SpdyStream::SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> request_headers, + SpdySendStatus send_status) { + CHECK_NE(type_, SPDY_PUSH_STREAM); + CHECK_EQ(send_status_, MORE_DATA_TO_SEND); + CHECK(!request_headers_); + CHECK(!pending_send_data_.get()); + CHECK_EQ(io_state_, STATE_NONE); + request_headers_ = request_headers.Pass(); + send_status_ = send_status; + io_state_ = STATE_GET_DOMAIN_BOUND_CERT; + return DoLoop(OK); +} + +void SpdyStream::SendData(IOBuffer* data, + int length, + SpdySendStatus send_status) { + CHECK_NE(type_, SPDY_PUSH_STREAM); + CHECK_EQ(send_status_, MORE_DATA_TO_SEND); + CHECK_GE(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE); + CHECK(!pending_send_data_.get()); + pending_send_data_ = new DrainableIOBuffer(data, length); + send_status_ = send_status; + QueueNextDataFrame(); +} + +bool SpdyStream::GetSSLInfo(SSLInfo* ssl_info, + bool* was_npn_negotiated, + NextProto* protocol_negotiated) { + return session_->GetSSLInfo( + ssl_info, was_npn_negotiated, protocol_negotiated); +} + +bool SpdyStream::GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) { + return session_->GetSSLCertRequestInfo(cert_request_info); +} + +void SpdyStream::PossiblyResumeIfSendStalled() { + DCHECK(!IsClosed()); + + if (send_stalled_by_flow_control_ && !session_->IsSendStalled() && + send_window_size_ > 0) { + net_log_.AddEvent( + NetLog::TYPE_SPDY_STREAM_FLOW_CONTROL_UNSTALLED, + NetLog::IntegerCallback("stream_id", stream_id_)); + send_stalled_by_flow_control_ = false; + QueueNextDataFrame(); + } +} + +bool SpdyStream::IsClosed() const { + return io_state_ == STATE_CLOSED; +} + +bool SpdyStream::IsIdle() const { + return io_state_ == STATE_IDLE; +} + +NextProto SpdyStream::GetProtocol() const { + return session_->protocol(); +} + +bool SpdyStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const { + if (stream_id_ == 0) + return false; + + return session_->GetLoadTimingInfo(stream_id_, load_timing_info); +} + +GURL SpdyStream::GetUrlFromHeaders() const { + if (type_ != SPDY_PUSH_STREAM && !request_headers_) + return GURL(); + + const SpdyHeaderBlock& headers = + (type_ == SPDY_PUSH_STREAM) ? response_headers_ : *request_headers_; + return GetUrlFromHeaderBlock(headers, GetProtocolVersion(), + type_ == SPDY_PUSH_STREAM); +} + +bool SpdyStream::HasUrlFromHeaders() const { + return !GetUrlFromHeaders().is_empty(); +} + +void SpdyStream::OnGetDomainBoundCertComplete(int result) { + DCHECK_EQ(io_state_, STATE_GET_DOMAIN_BOUND_CERT_COMPLETE); + DoLoop(result); +} + +int SpdyStream::DoLoop(int result) { + CHECK(!in_do_loop_); + in_do_loop_ = true; + + do { + State state = io_state_; + io_state_ = STATE_NONE; + switch (state) { + case STATE_GET_DOMAIN_BOUND_CERT: + CHECK_EQ(result, OK); + result = DoGetDomainBoundCert(); + break; + case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE: + result = DoGetDomainBoundCertComplete(result); + break; + case STATE_SEND_DOMAIN_BOUND_CERT: + CHECK_EQ(result, OK); + result = DoSendDomainBoundCert(); + break; + case STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE: + result = DoSendDomainBoundCertComplete(result); + break; + case STATE_SEND_REQUEST_HEADERS: + CHECK_EQ(result, OK); + result = DoSendRequestHeaders(); + break; + case STATE_SEND_REQUEST_HEADERS_COMPLETE: + CHECK_EQ(result, OK); + result = DoSendRequestHeadersComplete(); + break; + + // For request/response streams, no data is sent from the client + // while in the OPEN state, so OnFrameWriteComplete is never + // called here. The HTTP body is handled in the OnDataReceived + // callback, which does not call into DoLoop. + // + // For bidirectional streams, we'll send and receive data once + // the connection is established. Received data is handled in + // OnDataReceived. Sent data is handled in + // OnFrameWriteComplete, which calls DoOpen(). + case STATE_IDLE: + CHECK_EQ(result, OK); + result = DoOpen(); + break; + + case STATE_CLOSED: + DCHECK_NE(result, ERR_IO_PENDING); + break; + default: + NOTREACHED() << io_state_; + break; + } + } while (result != ERR_IO_PENDING && io_state_ != STATE_NONE && + io_state_ != STATE_IDLE); + + CHECK(in_do_loop_); + in_do_loop_ = false; + + return result; +} + +int SpdyStream::DoGetDomainBoundCert() { + CHECK(request_headers_); + DCHECK_NE(type_, SPDY_PUSH_STREAM); + GURL url = GetUrlFromHeaders(); + if (!session_->NeedsCredentials() || !url.SchemeIs("https")) { + // Proceed directly to sending the request headers + io_state_ = STATE_SEND_REQUEST_HEADERS; + return OK; + } + + slot_ = session_->credential_state()->FindCredentialSlot(GetUrlFromHeaders()); + if (slot_ != SpdyCredentialState::kNoEntry) { + // Proceed directly to sending the request headers + io_state_ = STATE_SEND_REQUEST_HEADERS; + return OK; + } + + io_state_ = STATE_GET_DOMAIN_BOUND_CERT_COMPLETE; + ServerBoundCertService* sbc_service = session_->GetServerBoundCertService(); + DCHECK(sbc_service != NULL); + int rv = sbc_service->GetOrCreateDomainBoundCert( + url.GetOrigin().host(), + &domain_bound_private_key_, + &domain_bound_cert_, + base::Bind(&SpdyStream::OnGetDomainBoundCertComplete, GetWeakPtr()), + &domain_bound_cert_request_handle_); + return rv; +} + +int SpdyStream::DoGetDomainBoundCertComplete(int result) { + DCHECK_NE(type_, SPDY_PUSH_STREAM); + if (result != OK) + return result; + + io_state_ = STATE_SEND_DOMAIN_BOUND_CERT; + slot_ = session_->credential_state()->SetHasCredential(GetUrlFromHeaders()); + return OK; +} + +int SpdyStream::DoSendDomainBoundCert() { + CHECK(request_headers_); + DCHECK_NE(type_, SPDY_PUSH_STREAM); + io_state_ = STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE; + + std::string origin = GetUrlFromHeaders().GetOrigin().spec(); + DCHECK(origin[origin.length() - 1] == '/'); + origin.erase(origin.length() - 1); // Trim trailing slash. + scoped_ptr<SpdyFrame> frame; + int rv = session_->CreateCredentialFrame( + origin, + domain_bound_private_key_, + domain_bound_cert_, + priority_, + &frame); + if (rv != OK) { + DCHECK_NE(rv, ERR_IO_PENDING); + return rv; + } + + DCHECK(frame); + // TODO(akalin): Fix the following race condition: + // + // Since this is decoupled from sending the SYN_STREAM frame, it is + // possible that other domain-bound cert frames will clobber ours + // before our SYN_STREAM frame gets sent. This can be solved by + // immediately enqueueing the SYN_STREAM frame here and adjusting + // the state machine appropriately. + session_->EnqueueStreamWrite( + GetWeakPtr(), CREDENTIAL, + scoped_ptr<SpdyBufferProducer>( + new SimpleBufferProducer( + scoped_ptr<SpdyBuffer>(new SpdyBuffer(frame.Pass()))))); + return ERR_IO_PENDING; +} + +int SpdyStream::DoSendDomainBoundCertComplete(int result) { + DCHECK_NE(type_, SPDY_PUSH_STREAM); + if (result != OK) + return result; + + DCHECK_EQ(just_completed_frame_type_, CREDENTIAL); + io_state_ = STATE_SEND_REQUEST_HEADERS; + return OK; +} + +int SpdyStream::DoSendRequestHeaders() { + DCHECK_NE(type_, SPDY_PUSH_STREAM); + io_state_ = STATE_SEND_REQUEST_HEADERS_COMPLETE; + + session_->EnqueueStreamWrite( + GetWeakPtr(), SYN_STREAM, + scoped_ptr<SpdyBufferProducer>( + new SynStreamBufferProducer(GetWeakPtr()))); + return ERR_IO_PENDING; +} + +namespace { + +// Assuming we're in STATE_IDLE, maps the given type (which must not +// be SPDY_PUSH_STREAM) and send status to a result to return from +// DoSendRequestHeadersComplete() or DoOpen(). +int GetOpenStateResult(SpdyStreamType type, SpdySendStatus send_status) { + switch (type) { + case SPDY_BIDIRECTIONAL_STREAM: + // For bidirectional streams, there's nothing else to do. + DCHECK_EQ(send_status, MORE_DATA_TO_SEND); + return OK; + + case SPDY_REQUEST_RESPONSE_STREAM: + // For request/response streams, wait for the delegate to send + // data if there's request data to send; we'll get called back + // when the send finishes. + if (send_status == MORE_DATA_TO_SEND) + return ERR_IO_PENDING; + + return OK; + + case SPDY_PUSH_STREAM: + // This should never be called for push streams. + break; + } + + CHECK(false); + return ERR_UNEXPECTED; +} + +} // namespace + +int SpdyStream::DoSendRequestHeadersComplete() { + DCHECK_NE(type_, SPDY_PUSH_STREAM); + DCHECK_EQ(just_completed_frame_type_, SYN_STREAM); + DCHECK_NE(stream_id_, 0u); + + io_state_ = STATE_IDLE; + + CHECK(delegate_); + // Must not close |this|; if it does, it will trigger the |in_do_loop_| + // check in the destructor. + delegate_->OnRequestHeadersSent(); + + return GetOpenStateResult(type_, send_status_); +} + +int SpdyStream::DoOpen() { + DCHECK_NE(type_, SPDY_PUSH_STREAM); + + if (just_completed_frame_type_ != DATA) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + if (just_completed_frame_size_ < session_->GetDataFrameMinimumSize()) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + size_t frame_payload_size = + just_completed_frame_size_ - session_->GetDataFrameMinimumSize(); + if (frame_payload_size > session_->GetDataFrameMaximumPayload()) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + // Set |io_state_| first as |delegate_| may check it. + io_state_ = STATE_IDLE; + + send_bytes_ += frame_payload_size; + + pending_send_data_->DidConsume(frame_payload_size); + if (pending_send_data_->BytesRemaining() > 0) { + QueueNextDataFrame(); + return ERR_IO_PENDING; + } + + pending_send_data_ = NULL; + + CHECK(delegate_); + // Must not close |this|; if it does, it will trigger the + // |in_do_loop_| check in the destructor. + delegate_->OnDataSent(); + + return GetOpenStateResult(type_, send_status_); +} + +void SpdyStream::UpdateHistograms() { + // We need at least the receive timers to be filled in, as otherwise + // metrics can be bogus. + if (recv_first_byte_time_.is_null() || recv_last_byte_time_.is_null()) + return; + + base::TimeTicks effective_send_time; + if (type_ == SPDY_PUSH_STREAM) { + // Push streams shouldn't have |send_time_| filled in. + DCHECK(send_time_.is_null()); + effective_send_time = recv_first_byte_time_; + } else { + // For non-push streams, we also need |send_time_| to be filled + // in. + if (send_time_.is_null()) + return; + effective_send_time = send_time_; + } + + UMA_HISTOGRAM_TIMES("Net.SpdyStreamTimeToFirstByte", + recv_first_byte_time_ - effective_send_time); + UMA_HISTOGRAM_TIMES("Net.SpdyStreamDownloadTime", + recv_last_byte_time_ - recv_first_byte_time_); + UMA_HISTOGRAM_TIMES("Net.SpdyStreamTime", + recv_last_byte_time_ - effective_send_time); + + UMA_HISTOGRAM_COUNTS("Net.SpdySendBytes", send_bytes_); + UMA_HISTOGRAM_COUNTS("Net.SpdyRecvBytes", recv_bytes_); +} + +void SpdyStream::QueueNextDataFrame() { + // Until the request has been completely sent, we cannot be sure + // that our stream_id is correct. + DCHECK_GT(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE); + CHECK_GT(stream_id_, 0u); + CHECK(pending_send_data_.get()); + CHECK_GT(pending_send_data_->BytesRemaining(), 0); + + SpdyDataFlags flags = + (send_status_ == NO_MORE_DATA_TO_SEND) ? + DATA_FLAG_FIN : DATA_FLAG_NONE; + scoped_ptr<SpdyBuffer> data_buffer( + session_->CreateDataBuffer(stream_id_, + pending_send_data_.get(), + pending_send_data_->BytesRemaining(), + flags)); + // We'll get called again by PossiblyResumeIfSendStalled(). + if (!data_buffer) + return; + + if (session_->flow_control_state() >= SpdySession::FLOW_CONTROL_STREAM) { + DCHECK_GE(data_buffer->GetRemainingSize(), + session_->GetDataFrameMinimumSize()); + size_t payload_size = + data_buffer->GetRemainingSize() - session_->GetDataFrameMinimumSize(); + DCHECK_LE(payload_size, session_->GetDataFrameMaximumPayload()); + DecreaseSendWindowSize(static_cast<int32>(payload_size)); + // This currently isn't strictly needed, since write frames are + // discarded only if the stream is about to be closed. But have it + // here anyway just in case this changes. + data_buffer->AddConsumeCallback( + base::Bind(&SpdyStream::OnWriteBufferConsumed, + GetWeakPtr(), payload_size)); + } + + session_->EnqueueStreamWrite( + GetWeakPtr(), DATA, + scoped_ptr<SpdyBufferProducer>( + new SimpleBufferProducer(data_buffer.Pass()))); +} + +int SpdyStream::MergeWithResponseHeaders( + const SpdyHeaderBlock& new_response_headers) { + if (new_response_headers.find("transfer-encoding") != + new_response_headers.end()) { + session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Received transfer-encoding header"); + return ERR_SPDY_PROTOCOL_ERROR; + } + + for (SpdyHeaderBlock::const_iterator it = new_response_headers.begin(); + it != new_response_headers.end(); ++it) { + // Disallow uppercase headers. + if (ContainsUppercaseAscii(it->first)) { + session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Upper case characters in header: " + it->first); + return ERR_SPDY_PROTOCOL_ERROR; + } + + SpdyHeaderBlock::iterator it2 = response_headers_.lower_bound(it->first); + // Disallow duplicate headers. This is just to be conservative. + if (it2 != response_headers_.end() && it2->first == it->first) { + session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Duplicate header: " + it->first); + return ERR_SPDY_PROTOCOL_ERROR; + } + + response_headers_.insert(it2, *it); + } + + // If delegate_ is not yet attached, we'll call + // OnResponseHeadersUpdated() after the delegate gets attached to + // the stream. + if (delegate_) { + // The call to OnResponseHeadersUpdated() below may delete |this|, + // so use |weak_this| to detect that. + base::WeakPtr<SpdyStream> weak_this = GetWeakPtr(); + + SpdyResponseHeadersStatus status = + delegate_->OnResponseHeadersUpdated(response_headers_); + if (status == RESPONSE_HEADERS_ARE_INCOMPLETE) { + // Since RESPONSE_HEADERS_ARE_INCOMPLETE was returned, we must not + // have been closed. + CHECK(weak_this); + // Incomplete headers are OK only for push streams. + if (type_ != SPDY_PUSH_STREAM) { + session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR, + "Incomplete headers"); + return ERR_INCOMPLETE_SPDY_HEADERS; + } + } else if (weak_this) { + response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE; + } + } + + return OK; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_stream.h b/chromium/net/spdy/spdy_stream.h new file mode 100644 index 00000000000..4d18e3eff38 --- /dev/null +++ b/chromium/net/spdy/spdy_stream.h @@ -0,0 +1,557 @@ +// 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. + +#ifndef NET_SPDY_SPDY_STREAM_H_ +#define NET_SPDY_SPDY_STREAM_H_ + +#include <deque> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "net/base/bandwidth_metrics.h" +#include "net/base/io_buffer.h" +#include "net/base/net_export.h" +#include "net/base/net_log.h" +#include "net/base/request_priority.h" +#include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_buffer.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" +#include "net/ssl/server_bound_cert_service.h" +#include "net/ssl/ssl_client_cert_type.h" +#include "url/gurl.h" + +namespace net { + +class AddressList; +class IPEndPoint; +struct LoadTimingInfo; +class SSLCertRequestInfo; +class SSLInfo; +class SpdySession; + +enum SpdyStreamType { + // The most general type of stream; there are no restrictions on + // when data can be sent and received. + SPDY_BIDIRECTIONAL_STREAM, + // A stream where the client sends a request with possibly a body, + // and the server then sends a response with a body. + SPDY_REQUEST_RESPONSE_STREAM, + // A server-initiated stream where the server just sends a response + // with a body and the client does not send anything. + SPDY_PUSH_STREAM +}; + +// Passed to some SpdyStream functions to indicate whether there's +// more data to send. +enum SpdySendStatus { + MORE_DATA_TO_SEND, + NO_MORE_DATA_TO_SEND +}; + +// Returned by SpdyStream::OnResponseHeadersUpdated() to indicate +// whether the current response headers are complete or not. +enum SpdyResponseHeadersStatus { + RESPONSE_HEADERS_ARE_INCOMPLETE, + RESPONSE_HEADERS_ARE_COMPLETE +}; + +// The SpdyStream is used by the SpdySession to represent each stream known +// on the SpdySession. This class provides interfaces for SpdySession to use. +// Streams can be created either by the client or by the server. When they +// are initiated by the client, both the SpdySession and client object (such as +// a SpdyNetworkTransaction) will maintain a reference to the stream. When +// initiated by the server, only the SpdySession will maintain any reference, +// until such a time as a client object requests a stream for the path. +class NET_EXPORT_PRIVATE SpdyStream { + public: + // Delegate handles protocol specific behavior of spdy stream. + class NET_EXPORT_PRIVATE Delegate { + public: + Delegate() {} + + // Called when the request headers have been sent. Never called + // for push streams. Must not cause the stream to be closed. + virtual void OnRequestHeadersSent() = 0; + + // WARNING: This function is complicated! Be sure to read the + // whole comment below if you're working with code that implements + // or calls this function. + // + // Called when the response headers are updated from the + // server. |response_headers| contains the set of all headers + // received up to this point; delegates can assume that any + // headers previously received remain unchanged. + // + // This is called at least once before any data is received. If + // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this will be + // called again when more headers are received until + // RESPONSE_HEADERS_ARE_COMPLETE is returned, and any data + // received before then will be treated as a protocol error. + // + // If RESPONSE_HEADERS_ARE_INCOMPLETE is returned, the delegate + // must not have closed the stream. Otherwise, if + // RESPONSE_HEADERS_ARE_COMPLETE is returned, the delegate has + // processed the headers successfully. However, it still may have + // closed the stream, e.g. if the headers indicated an error + // condition. + // + // Some type-specific behavior: + // + // - For bidirectional streams, this may be called even after + // data is received, but it is expected that + // RESPONSE_HEADERS_ARE_COMPLETE is always returned. If + // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this is + // treated as a protocol error. + // + // - For request/response streams, this function is called + // exactly once before data is received, and it is expected + // that RESPONSE_HEADERS_ARE_COMPLETE is returned. If + // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this is + // treated as a protocol error. + // + // - For push streams, it is expected that this function will be + // called until RESPONSE_HEADERS_ARE_COMPLETE is returned + // before any data is received; any deviation from this is + // treated as a protocol error. + // + // TODO(akalin): Treat headers received after data has been + // received as a protocol error for non-bidirectional streams. + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) = 0; + + // Called when data is received after all required response + // headers have been received. |buffer| may be NULL, which signals + // EOF. Must return OK if the data was received successfully, or + // a network error code otherwise. + // + // May cause the stream to be closed. + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) = 0; + + // Called when data is sent. Must not cause the stream to be + // closed. + virtual void OnDataSent() = 0; + + // Called when SpdyStream is closed. No other delegate functions + // will be called after this is called, and the delegate must not + // access the stream after this is called. Must not cause the + // stream to be be (re-)closed. + // + // TODO(akalin): Allow this function to re-close the stream and + // handle it gracefully. + virtual void OnClose(int status) = 0; + + protected: + virtual ~Delegate() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + // SpdyStream constructor + SpdyStream(SpdyStreamType type, + const base::WeakPtr<SpdySession>& session, + const GURL& url, + RequestPriority priority, + int32 initial_send_window_size, + int32 initial_recv_window_size, + const BoundNetLog& net_log); + + ~SpdyStream(); + + // Set the delegate, which must not be NULL. Must not be called more + // than once. For push streams, calling this may cause buffered data + // to be sent to the delegate (from a posted task). + void SetDelegate(Delegate* delegate); + + // Detach the delegate from the stream, which must not yet be + // closed, and cancel it. + void DetachDelegate(); + + // The time at which the first bytes of the response were received + // from the server, or null if the response hasn't been received + // yet. + base::Time response_time() const { return response_time_; } + + SpdyStreamType type() const { return type_; } + + SpdyStreamId stream_id() const { return stream_id_; } + void set_stream_id(SpdyStreamId stream_id) { stream_id_ = stream_id; } + + const GURL& url() const { return url_; } + + RequestPriority priority() const { return priority_; } + + int32 send_window_size() const { return send_window_size_; } + + int32 recv_window_size() const { return recv_window_size_; } + + bool send_stalled_by_flow_control() const { + return send_stalled_by_flow_control_; + } + + void set_send_stalled_by_flow_control(bool stalled) { + send_stalled_by_flow_control_ = stalled; + } + + // Called by the session to adjust this stream's send window size by + // |delta_window_size|, which is the difference between the + // SETTINGS_INITIAL_WINDOW_SIZE in the most recent SETTINGS frame + // and the previous initial send window size, possibly unstalling + // this stream. Although |delta_window_size| may cause this stream's + // send window size to go negative, it must not cause it to wrap + // around in either direction. Does nothing if the stream is already + // closed. + // + // If stream flow control is turned off, this must not be called. + void AdjustSendWindowSize(int32 delta_window_size); + + // Called when bytes are consumed from a SpdyBuffer for a DATA frame + // that is to be written or is being written. Increases the send + // window size accordingly if some or all of the SpdyBuffer is being + // discarded. + // + // If stream flow control is turned off, this must not be called. + void OnWriteBufferConsumed(size_t frame_payload_size, + size_t consume_size, + SpdyBuffer::ConsumeSource consume_source); + + // Called by the session to increase this stream's send window size + // by |delta_window_size| (which must be at least 1) from a received + // WINDOW_UPDATE frame or from a dropped DATA frame that was + // intended to be sent, possibly unstalling this stream. If + // |delta_window_size| would cause this stream's send window size to + // overflow, calls into the session to reset this stream. Does + // nothing if the stream is already closed. + // + // If stream flow control is turned off, this must not be called. + void IncreaseSendWindowSize(int32 delta_window_size); + + // If stream flow control is turned on, called by the session to + // decrease this stream's send window size by |delta_window_size|, + // which must be at least 0 and at most kMaxSpdyFrameChunkSize. + // |delta_window_size| must not cause this stream's send window size + // to go negative. Does nothing if the stream is already closed. + // + // If stream flow control is turned off, this must not be called. + void DecreaseSendWindowSize(int32 delta_window_size); + + // Called when bytes are consumed by the delegate from a SpdyBuffer + // containing received data. Increases the receive window size + // accordingly. + // + // If stream flow control is turned off, this must not be called. + void OnReadBufferConsumed(size_t consume_size, + SpdyBuffer::ConsumeSource consume_source); + + // Called by OnReadBufferConsume to increase this stream's receive + // window size by |delta_window_size|, which must be at least 1 and + // must not cause this stream's receive window size to overflow, + // possibly also sending a WINDOW_UPDATE frame. Does nothing if the + // stream is not active. + // + // If stream flow control is turned off, this must not be called. + void IncreaseRecvWindowSize(int32 delta_window_size); + + // Called by OnDataReceived (which is in turn called by the session) + // to decrease this stream's receive window size by + // |delta_window_size|, which must be at least 1 and must not cause + // this stream's receive window size to go negative. + // + // If stream flow control is turned off or the stream is not active, + // this must not be called. + void DecreaseRecvWindowSize(int32 delta_window_size); + + int GetPeerAddress(IPEndPoint* address) const; + int GetLocalAddress(IPEndPoint* address) const; + + // Returns true if the underlying transport socket ever had any reads or + // writes. + bool WasEverUsed() const; + + const BoundNetLog& net_log() const { return net_log_; } + + base::Time GetRequestTime() const; + void SetRequestTime(base::Time t); + + // Called at most once by the SpdySession when the initial response + // headers have been received for this stream, i.e., a SYN_REPLY (or + // SYN_STREAM for push streams) frame has been received. This is the + // entry point for a push stream. Returns a status code; if it is + // an error, the stream was closed by this function. + int OnInitialResponseHeadersReceived(const SpdyHeaderBlock& response_headers, + base::Time response_time, + base::TimeTicks recv_first_byte_time); + + // Called by the SpdySession (only after + // OnInitialResponseHeadersReceived() has been called) when + // late-bound headers are received for a stream. Returns a status + // code; if it is an error, the stream was closed by this function. + int OnAdditionalResponseHeadersReceived( + const SpdyHeaderBlock& additional_response_headers); + + // Called by the SpdySession when response data has been received + // for this stream. This callback may be called multiple times as + // data arrives from the network, and will never be called prior to + // OnResponseHeadersReceived. + // + // |buffer| contains the data received, or NULL if the stream is + // being closed. The stream must copy any data from this + // buffer before returning from this callback. + // + // |length| is the number of bytes received (at most 2^24 - 1) or 0 if + // the stream is being closed. + void OnDataReceived(scoped_ptr<SpdyBuffer> buffer); + + // Called by the SpdySession when a frame has been successfully and + // completely written. |frame_size| is the total size of the frame + // in bytes, including framing overhead. + void OnFrameWriteComplete(SpdyFrameType frame_type, size_t frame_size); + + // Called by the SpdySession when the request is finished. This callback + // will always be called at the end of the request and signals to the + // stream that the stream has no more network events. No further callbacks + // to the stream will be made after this call. + // |status| is an error code or OK. + void OnClose(int status); + + // Called by the SpdySession to log stream related errors. + void LogStreamError(int status, const std::string& description); + + // If this stream is active, reset it, and close it otherwise. In + // either case the stream is deleted. + void Cancel(); + + // Close this stream without sending a RST_STREAM and delete + // it. + void Close(); + + // Must be used only by |session_|. + base::WeakPtr<SpdyStream> GetWeakPtr(); + + // Interface for the delegate to use. + + // Only one send can be in flight at a time, except for push + // streams, which must not send anything. + + // Sends the request headers. The delegate is called back via + // OnRequestHeadersSent() when the request headers have completed + // sending. |send_status| must be MORE_DATA_TO_SEND for + // bidirectional streams; for request/response streams, it must be + // MORE_DATA_TO_SEND if the request has data to upload, or + // NO_MORE_DATA_TO_SEND if not. + int SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> request_headers, + SpdySendStatus send_status); + + // Sends a DATA frame. The delegate will be notified via + // OnDataSent() when the send is complete. |send_status| must be + // MORE_DATA_TO_SEND for bidirectional streams; for request/response + // streams, it must be MORE_DATA_TO_SEND if there is more data to + // upload, or NO_MORE_DATA_TO_SEND if not. + void SendData(IOBuffer* data, int length, SpdySendStatus send_status); + + // Fills SSL info in |ssl_info| and returns true when SSL is in use. + bool GetSSLInfo(SSLInfo* ssl_info, + bool* was_npn_negotiated, + NextProto* protocol_negotiated); + + // Fills SSL Certificate Request info |cert_request_info| and returns + // true when SSL is in use. + bool GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info); + + // If the stream is stalled on sending data, but the session is not + // stalled on sending data and |send_window_size_| is positive, then + // set |send_stalled_by_flow_control_| to false and unstall the data + // sending. Called by the session or by the stream itself. Must be + // called only when the stream is still open. + void PossiblyResumeIfSendStalled(); + + // Returns whether or not this stream is closed. Note that the only + // time a stream is closed and not deleted is in its delegate's + // OnClose() method. + bool IsClosed() const; + + // Returns whether or not this stream has finished sending its + // request headers and is ready to send/receive more data. + bool IsIdle() const; + + // Returns the protocol used by this stream. Always between + // kProtoSPDY2 and kProtoSPDYMaximumVersion. + // + // TODO(akalin): Change the lower bound to kProtoSPDYMinimumVersion + // once we stop supporting SPDY/1. + NextProto GetProtocol() const; + + int response_status() const { return response_status_; } + + bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const; + + // Get the URL from the appropriate stream headers, or the empty + // GURL() if it is unknown. + // + // TODO(akalin): Figure out if we really need this function, + // i.e. can we just use the URL this stream was created with and/or + // one we receive headers validate that the URL from them is the + // same. + GURL GetUrlFromHeaders() const; + + // Returns whether the URL for this stream is known. + // + // TODO(akalin): Remove this, as it's only used in tests. + bool HasUrlFromHeaders() const; + + int GetProtocolVersion() const; + + private: + class SynStreamBufferProducer; + class HeaderBufferProducer; + + enum State { + STATE_NONE, + STATE_GET_DOMAIN_BOUND_CERT, + STATE_GET_DOMAIN_BOUND_CERT_COMPLETE, + STATE_SEND_DOMAIN_BOUND_CERT, + STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE, + STATE_SEND_REQUEST_HEADERS, + STATE_SEND_REQUEST_HEADERS_COMPLETE, + STATE_IDLE, + STATE_CLOSED + }; + + void OnGetDomainBoundCertComplete(int result); + + // Try to make progress sending/receiving the request/response. + int DoLoop(int result); + + // The implementations of each state of the state machine. + int DoGetDomainBoundCert(); + int DoGetDomainBoundCertComplete(int result); + int DoSendDomainBoundCert(); + int DoSendDomainBoundCertComplete(int result); + int DoSendRequestHeaders(); + int DoSendRequestHeadersComplete(); + int DoReadHeaders(); + int DoReadHeadersComplete(int result); + int DoOpen(); + + // Update the histograms. Can safely be called repeatedly, but should only + // be called after the stream has completed. + void UpdateHistograms(); + + // When a server-pushed stream is first created, this function is + // posted on the current MessageLoop to replay the data that the + // server has already sent. + void PushedStreamReplayData(); + + // Produces the SYN_STREAM frame for the stream. The stream must + // already be activated. + scoped_ptr<SpdyFrame> ProduceSynStreamFrame(); + + // Produce the initial HEADER frame for the stream with the given + // block. The stream must already be activated. + scoped_ptr<SpdyFrame> ProduceHeaderFrame( + scoped_ptr<SpdyHeaderBlock> header_block); + + // Queues the send for next frame of the remaining data in + // |pending_send_data_|. Must be called only when + // |pending_send_data_| is set. + void QueueNextDataFrame(); + + // Merge the given headers into |response_headers_| and calls + // OnResponseHeadersUpdated() on the delegate (if attached). + // Returns a status code; if it is an error, the stream was closed + // by this function. + int MergeWithResponseHeaders(const SpdyHeaderBlock& new_response_headers); + + const SpdyStreamType type_; + + base::WeakPtrFactory<SpdyStream> weak_ptr_factory_; + + // Sentinel variable used to make sure we don't get destroyed by a + // function called from DoLoop(). + bool in_do_loop_; + + // There is a small period of time between when a server pushed stream is + // first created, and the pushed data is replayed. Any data received during + // this time should continue to be buffered. + bool continue_buffering_data_; + + SpdyStreamId stream_id_; + const GURL url_; + const RequestPriority priority_; + size_t slot_; + + // Flow control variables. + bool send_stalled_by_flow_control_; + int32 send_window_size_; + int32 recv_window_size_; + int32 unacked_recv_window_bytes_; + + ScopedBandwidthMetrics metrics_; + + const base::WeakPtr<SpdySession> session_; + + // The transaction should own the delegate. + SpdyStream::Delegate* delegate_; + + // Whether or not we have more data to send on this stream. + SpdySendStatus send_status_; + + // The headers for the request to send. + // + // TODO(akalin): Hang onto this only until we send it. This + // necessitates stashing the URL separately. + scoped_ptr<SpdyHeaderBlock> request_headers_; + + // The data waiting to be sent. + scoped_refptr<DrainableIOBuffer> pending_send_data_; + + // The time at which the request was made that resulted in this response. + // For cached responses, this time could be "far" in the past. + base::Time request_time_; + + SpdyHeaderBlock response_headers_; + SpdyResponseHeadersStatus response_headers_status_; + base::Time response_time_; + + State io_state_; + + // Since we buffer the response, we also buffer the response status. + // Not valid until the stream is closed. + int response_status_; + + BoundNetLog net_log_; + + base::TimeTicks send_time_; + base::TimeTicks recv_first_byte_time_; + base::TimeTicks recv_last_byte_time_; + + // Number of data bytes that have been sent/received on this stream, not + // including frame overhead. Note that this does not count headers. + int send_bytes_; + int recv_bytes_; + + // Data received before delegate is attached. + ScopedVector<SpdyBuffer> pending_buffers_; + + std::string domain_bound_private_key_; + std::string domain_bound_cert_; + ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_; + + // When OnFrameWriteComplete() is called, these variables are set. + SpdyFrameType just_completed_frame_type_; + size_t just_completed_frame_size_; + + DISALLOW_COPY_AND_ASSIGN(SpdyStream); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_STREAM_H_ diff --git a/chromium/net/spdy/spdy_stream_test_util.cc b/chromium/net/spdy/spdy_stream_test_util.cc new file mode 100644 index 00000000000..835ac848b5b --- /dev/null +++ b/chromium/net/spdy/spdy_stream_test_util.cc @@ -0,0 +1,146 @@ +// 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 "net/spdy/spdy_stream_test_util.h" + +#include <cstddef> + +#include "base/stl_util.h" +#include "net/base/completion_callback.h" +#include "net/spdy/spdy_stream.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace test { + +ClosingDelegate::ClosingDelegate( + const base::WeakPtr<SpdyStream>& stream) : stream_(stream) { + DCHECK(stream_); +} + +ClosingDelegate::~ClosingDelegate() {} + +void ClosingDelegate::OnRequestHeadersSent() {} + +SpdyResponseHeadersStatus ClosingDelegate::OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) { + return RESPONSE_HEADERS_ARE_COMPLETE; +} + +void ClosingDelegate::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {} + +void ClosingDelegate::OnDataSent() {} + +void ClosingDelegate::OnClose(int status) { + DCHECK(stream_); + stream_->Close(); + // The |stream_| may still be alive (if it is our delegate). +} + +StreamDelegateBase::StreamDelegateBase( + const base::WeakPtr<SpdyStream>& stream) + : stream_(stream), + stream_id_(0), + send_headers_completed_(false) { +} + +StreamDelegateBase::~StreamDelegateBase() { +} + +void StreamDelegateBase::OnRequestHeadersSent() { + stream_id_ = stream_->stream_id(); + EXPECT_NE(stream_id_, 0u); + send_headers_completed_ = true; +} + +SpdyResponseHeadersStatus StreamDelegateBase::OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) { + EXPECT_EQ(stream_->type() != SPDY_PUSH_STREAM, send_headers_completed_); + response_headers_ = response_headers; + return RESPONSE_HEADERS_ARE_COMPLETE; +} + +void StreamDelegateBase::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) { + if (buffer) + received_data_queue_.Enqueue(buffer.Pass()); +} + +void StreamDelegateBase::OnDataSent() {} + +void StreamDelegateBase::OnClose(int status) { + if (!stream_.get()) + return; + stream_id_ = stream_->stream_id(); + stream_.reset(); + callback_.callback().Run(status); +} + +int StreamDelegateBase::WaitForClose() { + int result = callback_.WaitForResult(); + EXPECT_TRUE(!stream_.get()); + return result; +} + +std::string StreamDelegateBase::TakeReceivedData() { + size_t len = received_data_queue_.GetTotalSize(); + std::string received_data(len, '\0'); + if (len > 0) { + EXPECT_EQ( + len, + received_data_queue_.Dequeue(string_as_array(&received_data), len)); + } + return received_data; +} + +std::string StreamDelegateBase::GetResponseHeaderValue( + const std::string& name) const { + SpdyHeaderBlock::const_iterator it = response_headers_.find(name); + return (it == response_headers_.end()) ? std::string() : it->second; +} + +StreamDelegateDoNothing::StreamDelegateDoNothing( + const base::WeakPtr<SpdyStream>& stream) + : StreamDelegateBase(stream) {} + +StreamDelegateDoNothing::~StreamDelegateDoNothing() { +} + +StreamDelegateSendImmediate::StreamDelegateSendImmediate( + const base::WeakPtr<SpdyStream>& stream, + base::StringPiece data) + : StreamDelegateBase(stream), + data_(data) {} + +StreamDelegateSendImmediate::~StreamDelegateSendImmediate() { +} + +SpdyResponseHeadersStatus StreamDelegateSendImmediate::OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) { + SpdyResponseHeadersStatus status = + StreamDelegateBase::OnResponseHeadersUpdated(response_headers); + if (data_.data()) { + scoped_refptr<StringIOBuffer> buf(new StringIOBuffer(data_.as_string())); + stream()->SendData(buf.get(), buf->size(), MORE_DATA_TO_SEND); + } + return status; +} + +StreamDelegateWithBody::StreamDelegateWithBody( + const base::WeakPtr<SpdyStream>& stream, + base::StringPiece data) + : StreamDelegateBase(stream), + buf_(new StringIOBuffer(data.as_string())) {} + +StreamDelegateWithBody::~StreamDelegateWithBody() { +} + +void StreamDelegateWithBody::OnRequestHeadersSent() { + StreamDelegateBase::OnRequestHeadersSent(); + stream()->SendData(buf_.get(), buf_->size(), NO_MORE_DATA_TO_SEND); +} + +} // namespace test + +} // namespace net diff --git a/chromium/net/spdy/spdy_stream_test_util.h b/chromium/net/spdy/spdy_stream_test_util.h new file mode 100644 index 00000000000..2d0a198ffad --- /dev/null +++ b/chromium/net/spdy/spdy_stream_test_util.h @@ -0,0 +1,127 @@ +// 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. + +#ifndef NET_SPDY_SPDY_STREAM_TEST_UTIL_H_ +#define NET_SPDY_SPDY_STREAM_TEST_UTIL_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "net/spdy/spdy_read_queue.h" +#include "net/spdy/spdy_stream.h" + +namespace net { + +namespace test { + +// Delegate that calls Close() on |stream_| on OnClose. Used by tests +// to make sure that such an action is harmless. +class ClosingDelegate : public SpdyStream::Delegate { + public: + explicit ClosingDelegate(const base::WeakPtr<SpdyStream>& stream); + virtual ~ClosingDelegate(); + + // SpdyStream::Delegate implementation. + virtual void OnRequestHeadersSent() OVERRIDE; + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE; + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE; + virtual void OnDataSent() OVERRIDE; + virtual void OnClose(int status) OVERRIDE; + + // Returns whether or not the stream is closed. + bool StreamIsClosed() const { return !stream_.get(); } + + private: + base::WeakPtr<SpdyStream> stream_; +}; + +// Base class with shared functionality for test delegate +// implementations below. +class StreamDelegateBase : public SpdyStream::Delegate { + public: + explicit StreamDelegateBase(const base::WeakPtr<SpdyStream>& stream); + virtual ~StreamDelegateBase(); + + virtual void OnRequestHeadersSent() OVERRIDE; + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE; + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE; + virtual void OnDataSent() OVERRIDE; + virtual void OnClose(int status) OVERRIDE; + + // Waits for the stream to be closed and returns the status passed + // to OnClose(). + int WaitForClose(); + + // Drains all data from the underlying read queue and returns it as + // a string. + std::string TakeReceivedData(); + + // Returns whether or not the stream is closed. + bool StreamIsClosed() const { return !stream_.get(); } + + // Returns the stream's ID. If called when the stream is closed, + // returns the stream's ID when it was open. + SpdyStreamId stream_id() const { return stream_id_; } + + std::string GetResponseHeaderValue(const std::string& name) const; + bool send_headers_completed() const { return send_headers_completed_; } + + protected: + const base::WeakPtr<SpdyStream>& stream() { return stream_; } + + private: + base::WeakPtr<SpdyStream> stream_; + SpdyStreamId stream_id_; + TestCompletionCallback callback_; + bool send_headers_completed_; + SpdyHeaderBlock response_headers_; + SpdyReadQueue received_data_queue_; +}; + +// Test delegate that does nothing. Used to capture data about the +// stream, e.g. its id when it was open. +class StreamDelegateDoNothing : public StreamDelegateBase { + public: + StreamDelegateDoNothing(const base::WeakPtr<SpdyStream>& stream); + virtual ~StreamDelegateDoNothing(); +}; + +// Test delegate that sends data immediately in OnResponseHeadersUpdated(). +class StreamDelegateSendImmediate : public StreamDelegateBase { + public: + // |data| can be NULL. + StreamDelegateSendImmediate(const base::WeakPtr<SpdyStream>& stream, + base::StringPiece data); + virtual ~StreamDelegateSendImmediate(); + + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE; + + private: + base::StringPiece data_; +}; + +// Test delegate that sends body data. +class StreamDelegateWithBody : public StreamDelegateBase { + public: + StreamDelegateWithBody(const base::WeakPtr<SpdyStream>& stream, + base::StringPiece data); + virtual ~StreamDelegateWithBody(); + + virtual void OnRequestHeadersSent() OVERRIDE; + + private: + scoped_refptr<StringIOBuffer> buf_; +}; + +} // namespace test + +} // namespace net + +#endif // NET_SPDY_SPDY_STREAM_TEST_UTIL_H_ diff --git a/chromium/net/spdy/spdy_stream_unittest.cc b/chromium/net/spdy/spdy_stream_unittest.cc new file mode 100644 index 00000000000..c98ba937be0 --- /dev/null +++ b/chromium/net/spdy/spdy_stream_unittest.cc @@ -0,0 +1,999 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cstddef> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/strings/string_piece.h" +#include "net/base/completion_callback.h" +#include "net/base/net_log_unittest.h" +#include "net/base/request_priority.h" +#include "net/socket/next_proto.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/buffered_spdy_framer.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_stream.h" +#include "net/spdy/spdy_stream_test_util.h" +#include "net/spdy/spdy_test_util_common.h" +#include "testing/gtest/include/gtest/gtest.h" + +// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc +// +namespace net { + +namespace test { + +namespace { + +const char kStreamUrl[] = "http://www.google.com/"; +const char kPostBody[] = "\0hello!\xff"; +const size_t kPostBodyLength = arraysize(kPostBody); +const base::StringPiece kPostBodyStringPiece(kPostBody, kPostBodyLength); + +class SpdyStreamTest : public ::testing::Test, + public ::testing::WithParamInterface<NextProto> { + protected: + // A function that takes a SpdyStream and the number of bytes which + // will unstall the next frame completely. + typedef base::Callback<void(const base::WeakPtr<SpdyStream>&, int32)> + UnstallFunction; + + SpdyStreamTest() + : spdy_util_(GetParam()), + session_deps_(GetParam()), + offset_(0) {} + + base::WeakPtr<SpdySession> CreateDefaultSpdySession() { + SpdySessionKey key(HostPortPair("www.google.com", 80), + ProxyServer::Direct(), + kPrivacyModeDisabled); + return CreateInsecureSpdySession(session_, key, BoundNetLog()); + } + + virtual void TearDown() { + base::MessageLoop::current()->RunUntilIdle(); + } + + void RunResumeAfterUnstallRequestResponseTest( + const UnstallFunction& unstall_function); + + void RunResumeAfterUnstallBidirectionalTest( + const UnstallFunction& unstall_function); + + // Add{Read,Write}() populates lists that are eventually passed to a + // SocketData class. |frame| must live for the whole test. + + void AddRead(const SpdyFrame& frame) { + reads_.push_back(CreateMockRead(frame, offset_++)); + } + + void AddWrite(const SpdyFrame& frame) { + writes_.push_back(CreateMockWrite(frame, offset_++)); + } + + void AddReadEOF() { + reads_.push_back(MockRead(ASYNC, 0, offset_++)); + } + + MockRead* GetReads() { + return vector_as_array(&reads_); + } + + size_t GetNumReads() const { + return reads_.size(); + } + + MockWrite* GetWrites() { + return vector_as_array(&writes_); + } + + int GetNumWrites() const { + return writes_.size(); + } + + SpdyTestUtil spdy_util_; + SpdySessionDependencies session_deps_; + scoped_refptr<HttpNetworkSession> session_; + + private: + // Used by Add{Read,Write}() above. + std::vector<MockWrite> writes_; + std::vector<MockRead> reads_; + int offset_; +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + SpdyStreamTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +TEST_P(SpdyStreamTest, SendDataAfterOpen) { + GURL url(kStreamUrl); + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + AddRead(*resp); + + scoped_ptr<SpdyFrame> msg( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false)); + AddWrite(*msg); + + scoped_ptr<SpdyFrame> echo( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false)); + AddRead(*echo); + + AddReadEOF(); + + OrderedSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey())); + EXPECT_EQ("HTTP/1.1", + delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey())); + EXPECT_EQ(std::string(kPostBody, kPostBodyLength), + delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdyStreamTest, PushedStream) { + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + + AddReadEOF(); + + OrderedSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> spdy_session(CreateDefaultSpdySession()); + + // Conjure up a stream. + SpdyStream stream(SPDY_PUSH_STREAM, + spdy_session, + GURL(), + DEFAULT_PRIORITY, + kSpdyStreamInitialWindowSize, + kSpdyStreamInitialWindowSize, + BoundNetLog()); + stream.set_stream_id(2); + EXPECT_FALSE(stream.HasUrlFromHeaders()); + + // Set a couple of headers. + SpdyHeaderBlock response; + spdy_util_.AddUrlToHeaderBlock(kStreamUrl, &response); + stream.OnInitialResponseHeadersReceived( + response, base::Time::Now(), base::TimeTicks::Now()); + + // Send some basic headers. + SpdyHeaderBlock headers; + headers[spdy_util_.GetStatusKey()] = "200"; + headers[spdy_util_.GetVersionKey()] = "OK"; + stream.OnAdditionalResponseHeadersReceived(headers); + + EXPECT_TRUE(stream.HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream.GetUrlFromHeaders().spec()); + + StreamDelegateDoNothing delegate(stream.GetWeakPtr()); + stream.SetDelegate(&delegate); + + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey())); + + EXPECT_TRUE(spdy_session == NULL); +} + +TEST_P(SpdyStreamTest, StreamError) { + GURL url(kStreamUrl); + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + AddRead(*resp); + + scoped_ptr<SpdyFrame> msg( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false)); + AddWrite(*msg); + + scoped_ptr<SpdyFrame> echo( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false)); + AddRead(*echo); + + AddReadEOF(); + + CapturingBoundNetLog log; + + OrderedSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, log.bound()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + const SpdyStreamId stream_id = delegate.stream_id(); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey())); + EXPECT_EQ("HTTP/1.1", + delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey())); + EXPECT_EQ(std::string(kPostBody, kPostBodyLength), + delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); + + // Check that the NetLog was filled reasonably. + net::CapturingNetLog::CapturedEntryList entries; + log.GetEntries(&entries); + EXPECT_LT(0u, entries.size()); + + // Check that we logged SPDY_STREAM_ERROR correctly. + int pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_STREAM_ERROR, + net::NetLog::PHASE_NONE); + + int stream_id2; + ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &stream_id2)); + EXPECT_EQ(static_cast<int>(stream_id), stream_id2); +} + +// Make sure that large blocks of data are properly split up into +// frame-sized chunks for a request/response (i.e., an HTTP-like) +// stream. +TEST_P(SpdyStreamTest, SendLargeDataAfterOpenRequestResponse) { + GURL url(kStreamUrl); + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); + scoped_ptr<SpdyFrame> chunk( + spdy_util_.ConstructSpdyBodyFrame( + 1, chunk_data.data(), chunk_data.length(), false)); + AddWrite(*chunk); + AddWrite(*chunk); + + scoped_ptr<SpdyFrame> last_chunk( + spdy_util_.ConstructSpdyBodyFrame( + 1, chunk_data.data(), chunk_data.length(), true)); + AddWrite(*last_chunk); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + AddRead(*resp); + + AddReadEOF(); + + OrderedSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x'); + StreamDelegateWithBody delegate(stream, body_data); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey())); + EXPECT_EQ("HTTP/1.1", + delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey())); + EXPECT_EQ(std::string(), delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); +} + +// Make sure that large blocks of data are properly split up into +// frame-sized chunks for a bidirectional (i.e., non-HTTP-like) +// stream. +TEST_P(SpdyStreamTest, SendLargeDataAfterOpenBidirectional) { + GURL url(kStreamUrl); + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); + AddRead(*resp); + + std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); + scoped_ptr<SpdyFrame> chunk( + spdy_util_.ConstructSpdyBodyFrame( + 1, chunk_data.data(), chunk_data.length(), false)); + AddWrite(*chunk); + AddWrite(*chunk); + AddWrite(*chunk); + + AddReadEOF(); + + OrderedSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x'); + StreamDelegateSendImmediate delegate(stream, body_data); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey())); + EXPECT_EQ("HTTP/1.1", + delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey())); + EXPECT_EQ(std::string(), delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); +} + +// Receiving a header with uppercase ASCII should result in a protocol +// error. +TEST_P(SpdyStreamTest, UpperCaseHeaders) { + GURL url(kStreamUrl); + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + AddWrite(*syn); + + const char* const kExtraHeaders[] = {"X-UpperCase", "yes"}; + scoped_ptr<SpdyFrame> + reply(spdy_util_.ConstructSpdyGetSynReply(kExtraHeaders, 1, 1)); + AddRead(*reply); + + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); + AddWrite(*rst); + + AddReadEOF(); + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateDoNothing delegate(stream); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(kStreamUrl)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + data.RunFor(4); + + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose()); +} + +// Receiving a header with uppercase ASCII should result in a protocol +// error even for a push stream. +TEST_P(SpdyStreamTest, UpperCaseHeadersOnPush) { + GURL url(kStreamUrl); + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + AddWrite(*syn); + + scoped_ptr<SpdyFrame> + reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + AddRead(*reply); + + const char* const extra_headers[] = {"X-UpperCase", "yes"}; + scoped_ptr<SpdyFrame> + push(spdy_util_.ConstructSpdyPush(extra_headers, 1, 2, 1, kStreamUrl)); + AddRead(*push); + + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); + AddWrite(*rst); + + AddReadEOF(); + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateDoNothing delegate(stream); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(kStreamUrl)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + data.RunFor(4); + + base::WeakPtr<SpdyStream> push_stream; + EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog())); + EXPECT_FALSE(push_stream); + + data.RunFor(1); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); +} + +// Receiving a header with uppercase ASCII in a HEADERS frame should +// result in a protocol error. +TEST_P(SpdyStreamTest, UpperCaseHeadersInHeadersFrame) { + GURL url(kStreamUrl); + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + AddWrite(*syn); + + scoped_ptr<SpdyFrame> + reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + AddRead(*reply); + + scoped_ptr<SpdyFrame> + push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl)); + AddRead(*push); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)["X-UpperCase"] = "yes"; + scoped_ptr<SpdyFrame> headers_frame( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + AddRead(*headers_frame); + + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); + AddWrite(*rst); + + AddReadEOF(); + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateDoNothing delegate(stream); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(kStreamUrl)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + data.RunFor(3); + + base::WeakPtr<SpdyStream> push_stream; + EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog())); + EXPECT_TRUE(push_stream); + + data.RunFor(1); + + EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog())); + EXPECT_FALSE(push_stream); + + data.RunFor(2); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); +} + +// Receiving a duplicate header in a HEADERS frame should result in a +// protocol error. +TEST_P(SpdyStreamTest, DuplicateHeaders) { + GURL url(kStreamUrl); + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> syn( + spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); + AddWrite(*syn); + + scoped_ptr<SpdyFrame> + reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + AddRead(*reply); + + scoped_ptr<SpdyFrame> + push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl)); + AddRead(*push); + + scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); + (*late_headers)[spdy_util_.GetStatusKey()] = "500 Server Error"; + scoped_ptr<SpdyFrame> headers_frame( + spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), + false, + 2, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0)); + AddRead(*headers_frame); + + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); + AddWrite(*rst); + + AddReadEOF(); + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateDoNothing delegate(stream); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructGetHeaderBlock(kStreamUrl)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + data.RunFor(3); + + base::WeakPtr<SpdyStream> push_stream; + EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog())); + EXPECT_TRUE(push_stream); + + data.RunFor(1); + + EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog())); + EXPECT_FALSE(push_stream); + + data.RunFor(2); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); +} + +// The tests below are only for SPDY/3 and above. + +// Call IncreaseSendWindowSize on a stream with a large enough delta +// to overflow an int32. The SpdyStream should handle that case +// gracefully. +TEST_P(SpdyStreamTest, IncreaseSendWindowSizeOverflow) { + if (spdy_util_.protocol() < kProtoSPDY3) + return; + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + // Triggered by the overflowing call to IncreaseSendWindowSize + // below. + scoped_ptr<SpdyFrame> rst( + spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR)); + AddWrite(*rst); + + AddReadEOF(); + + CapturingBoundNetLog log; + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + GURL url(kStreamUrl); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, log.bound()); + ASSERT_TRUE(stream.get() != NULL); + StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); + stream->SetDelegate(&delegate); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + data.RunFor(1); + + int32 old_send_window_size = stream->send_window_size(); + ASSERT_GT(old_send_window_size, 0); + int32 delta_window_size = kint32max - old_send_window_size + 1; + stream->IncreaseSendWindowSize(delta_window_size); + EXPECT_EQ(NULL, stream.get()); + + data.RunFor(2); + + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose()); +} + +// Functions used with +// RunResumeAfterUnstall{RequestResponse,Bidirectional}Test(). + +void StallStream(const base::WeakPtr<SpdyStream>& stream) { + // Reduce the send window size to 0 to stall. + while (stream->send_window_size() > 0) { + stream->DecreaseSendWindowSize( + std::min(kMaxSpdyFrameChunkSize, stream->send_window_size())); + } +} + +void IncreaseStreamSendWindowSize(const base::WeakPtr<SpdyStream>& stream, + int32 delta_window_size) { + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + stream->IncreaseSendWindowSize(delta_window_size); + EXPECT_FALSE(stream->send_stalled_by_flow_control()); +} + +void AdjustStreamSendWindowSize(const base::WeakPtr<SpdyStream>& stream, + int32 delta_window_size) { + // Make sure that negative adjustments are handled properly. + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + stream->AdjustSendWindowSize(-delta_window_size); + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + stream->AdjustSendWindowSize(+delta_window_size); + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + stream->AdjustSendWindowSize(+delta_window_size); + EXPECT_FALSE(stream->send_stalled_by_flow_control()); +} + +// Given an unstall function, runs a test to make sure that a +// request/response (i.e., an HTTP-like) stream resumes after a stall +// and unstall. +void SpdyStreamTest::RunResumeAfterUnstallRequestResponseTest( + const UnstallFunction& unstall_function) { + GURL url(kStreamUrl); + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + scoped_ptr<SpdyFrame> body( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, true)); + AddWrite(*body); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + AddRead(*resp); + + AddReadEOF(); + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateWithBody delegate(stream, kPostBodyStringPiece); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + EXPECT_FALSE(stream->send_stalled_by_flow_control()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + StallStream(stream); + + data.RunFor(1); + + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + + unstall_function.Run(stream, kPostBodyLength); + + EXPECT_FALSE(stream->send_stalled_by_flow_control()); + + data.RunFor(3); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status")); + EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version")); + EXPECT_EQ(std::string(), delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseRequestResponse) { + if (spdy_util_.protocol() < kProtoSPDY3) + return; + + RunResumeAfterUnstallRequestResponseTest( + base::Bind(&IncreaseStreamSendWindowSize)); +} + +TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustRequestResponse) { + if (spdy_util_.protocol() < kProtoSPDY3) + return; + + RunResumeAfterUnstallRequestResponseTest( + base::Bind(&AdjustStreamSendWindowSize)); +} + +// Given an unstall function, runs a test to make sure that a +// bidirectional (i.e., non-HTTP-like) stream resumes after a stall +// and unstall. +void SpdyStreamTest::RunResumeAfterUnstallBidirectionalTest( + const UnstallFunction& unstall_function) { + GURL url(kStreamUrl); + + session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + scoped_ptr<SpdyFrame> req( + spdy_util_.ConstructSpdyPost( + kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0)); + AddWrite(*req); + + scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); + AddRead(*resp); + + scoped_ptr<SpdyFrame> msg( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false)); + AddWrite(*msg); + + scoped_ptr<SpdyFrame> echo( + spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false)); + AddRead(*echo); + + AddReadEOF(); + + DeterministicSocketData data(GetReads(), GetNumReads(), + GetWrites(), GetNumWrites()); + MockConnect connect_data(SYNCHRONOUS, OK); + data.set_connect_data(connect_data); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + + base::WeakPtr<SpdySession> session(CreateDefaultSpdySession()); + + base::WeakPtr<SpdyStream> stream = + CreateStreamSynchronously( + SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog()); + ASSERT_TRUE(stream.get() != NULL); + + StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); + stream->SetDelegate(&delegate); + + EXPECT_FALSE(stream->HasUrlFromHeaders()); + + scoped_ptr<SpdyHeaderBlock> headers( + spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength)); + EXPECT_EQ(ERR_IO_PENDING, + stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND)); + EXPECT_TRUE(stream->HasUrlFromHeaders()); + EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec()); + + data.RunFor(1); + + EXPECT_FALSE(stream->send_stalled_by_flow_control()); + + StallStream(stream); + + data.RunFor(1); + + EXPECT_TRUE(stream->send_stalled_by_flow_control()); + + unstall_function.Run(stream, kPostBodyLength); + + EXPECT_FALSE(stream->send_stalled_by_flow_control()); + + data.RunFor(3); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose()); + + EXPECT_TRUE(delegate.send_headers_completed()); + EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status")); + EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version")); + EXPECT_EQ(std::string(kPostBody, kPostBodyLength), + delegate.TakeReceivedData()); + EXPECT_TRUE(data.at_write_eof()); +} + +TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseBidirectional) { + if (spdy_util_.protocol() < kProtoSPDY3) + return; + + RunResumeAfterUnstallBidirectionalTest( + base::Bind(&IncreaseStreamSendWindowSize)); +} + +TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustBidirectional) { + if (spdy_util_.protocol() < kProtoSPDY3) + return; + + RunResumeAfterUnstallBidirectionalTest( + base::Bind(&AdjustStreamSendWindowSize)); +} + +} // namespace + +} // namespace test + +} // namespace net diff --git a/chromium/net/spdy/spdy_test_util_common.cc b/chromium/net/spdy/spdy_test_util_common.cc new file mode 100644 index 00000000000..95466847b93 --- /dev/null +++ b/chromium/net/spdy/spdy_test_util_common.cc @@ -0,0 +1,1235 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_test_util_common.h" + +#include <cstddef> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "net/cert/mock_cert_verifier.h" +#include "net/http/http_cache.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_transaction.h" +#include "net/http/http_server_properties_impl.h" +#include "net/socket/socket_test_util.h" +#include "net/socket/ssl_client_socket.h" +#include "net/socket/transport_client_socket_pool.h" +#include "net/spdy/buffered_spdy_framer.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_stream.h" + +namespace net { + +namespace { + +bool next_proto_is_spdy(NextProto next_proto) { + // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we + // stop supporting SPDY/1. + return next_proto >= kProtoSPDY2 && next_proto <= kProtoSPDYMaximumVersion; +} + +// Parses a URL into the scheme, host, and path components required for a +// SPDY request. +void ParseUrl(base::StringPiece url, std::string* scheme, std::string* host, + std::string* path) { + GURL gurl(url.as_string()); + path->assign(gurl.PathForRequest()); + scheme->assign(gurl.scheme()); + host->assign(gurl.host()); + if (gurl.has_port()) { + host->append(":"); + host->append(gurl.port()); + } +} + +} // namespace + +std::vector<NextProto> SpdyNextProtos() { + std::vector<NextProto> next_protos; + for (int i = kProtoMinimumVersion; i <= kProtoMaximumVersion; ++i) { + NextProto proto = static_cast<NextProto>(i); + if (proto != kProtoSPDY1 && proto != kProtoSPDY21) + next_protos.push_back(proto); + } + return next_protos; +} + +// Chop a frame into an array of MockWrites. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks) { + MockWrite* chunks = new MockWrite[num_chunks]; + int chunk_size = length / num_chunks; + for (int index = 0; index < num_chunks; index++) { + const char* ptr = data + (index * chunk_size); + if (index == num_chunks - 1) + chunk_size += length % chunk_size; // The last chunk takes the remainder. + chunks[index] = MockWrite(ASYNC, ptr, chunk_size); + } + return chunks; +} + +// Chop a SpdyFrame into an array of MockWrites. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks) { + return ChopWriteFrame(frame.data(), frame.size(), num_chunks); +} + +// Chop a frame into an array of MockReads. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const char* data, int length, int num_chunks) { + MockRead* chunks = new MockRead[num_chunks]; + int chunk_size = length / num_chunks; + for (int index = 0; index < num_chunks; index++) { + const char* ptr = data + (index * chunk_size); + if (index == num_chunks - 1) + chunk_size += length % chunk_size; // The last chunk takes the remainder. + chunks[index] = MockRead(ASYNC, ptr, chunk_size); + } + return chunks; +} + +// Chop a SpdyFrame into an array of MockReads. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks) { + return ChopReadFrame(frame.data(), frame.size(), num_chunks); +} + +// Adds headers and values to a map. +// |extra_headers| is an array of { name, value } pairs, arranged as strings +// where the even entries are the header names, and the odd entries are the +// header values. +// |headers| gets filled in from |extra_headers|. +void AppendToHeaderBlock(const char* const extra_headers[], + int extra_header_count, + SpdyHeaderBlock* headers) { + std::string this_header; + std::string this_value; + + if (!extra_header_count) + return; + + // Sanity check: Non-NULL header list. + DCHECK(NULL != extra_headers) << "NULL header value pair list"; + // Sanity check: Non-NULL header map. + DCHECK(NULL != headers) << "NULL header map"; + // Copy in the headers. + for (int i = 0; i < extra_header_count; i++) { + // Sanity check: Non-empty header. + DCHECK_NE('\0', *extra_headers[i * 2]) << "Empty header value pair"; + this_header = extra_headers[i * 2]; + std::string::size_type header_len = this_header.length(); + if (!header_len) + continue; + this_value = extra_headers[1 + (i * 2)]; + std::string new_value; + if (headers->find(this_header) != headers->end()) { + // More than one entry in the header. + // Don't add the header again, just the append to the value, + // separated by a NULL character. + + // Adjust the value. + new_value = (*headers)[this_header]; + // Put in a NULL separator. + new_value.append(1, '\0'); + // Append the new value. + new_value += this_value; + } else { + // Not a duplicate, just write the value. + new_value = this_value; + } + (*headers)[this_header] = new_value; + } +} + +// Create a MockWrite from the given SpdyFrame. +MockWrite CreateMockWrite(const SpdyFrame& req) { + return MockWrite(ASYNC, req.data(), req.size()); +} + +// Create a MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(const SpdyFrame& req, int seq) { + return CreateMockWrite(req, seq, ASYNC); +} + +// Create a MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode) { + return MockWrite(mode, req.data(), req.size(), seq); +} + +// Create a MockRead from the given SpdyFrame. +MockRead CreateMockRead(const SpdyFrame& resp) { + return MockRead(ASYNC, resp.data(), resp.size()); +} + +// Create a MockRead from the given SpdyFrame and sequence number. +MockRead CreateMockRead(const SpdyFrame& resp, int seq) { + return CreateMockRead(resp, seq, ASYNC); +} + +// Create a MockRead from the given SpdyFrame and sequence number. +MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode) { + return MockRead(mode, resp.data(), resp.size(), seq); +} + +// Combines the given SpdyFrames into the given char array and returns +// the total length. +int CombineFrames(const SpdyFrame** frames, int num_frames, + char* buff, int buff_len) { + int total_len = 0; + for (int i = 0; i < num_frames; ++i) { + total_len += frames[i]->size(); + } + DCHECK_LE(total_len, buff_len); + char* ptr = buff; + for (int i = 0; i < num_frames; ++i) { + int len = frames[i]->size(); + memcpy(ptr, frames[i]->data(), len); + ptr += len; + } + return total_len; +} + +namespace { + +class PriorityGetter : public BufferedSpdyFramerVisitorInterface { + public: + PriorityGetter() : priority_(0) {} + virtual ~PriorityGetter() {} + + SpdyPriority priority() const { + return priority_; + } + + virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE {} + virtual void OnStreamError(SpdyStreamId stream_id, + const std::string& description) OVERRIDE {} + virtual void OnSynStream(SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + bool fin, + bool unidirectional, + const SpdyHeaderBlock& headers) OVERRIDE { + priority_ = priority; + } + virtual void OnSynReply(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) OVERRIDE {} + virtual void OnHeaders(SpdyStreamId stream_id, + bool fin, + const SpdyHeaderBlock& headers) OVERRIDE {} + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len, + bool fin) OVERRIDE {} + virtual void OnSettings(bool clear_persisted) OVERRIDE {} + virtual void OnSetting( + SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE {} + virtual void OnPing(uint32 unique_id) OVERRIDE {} + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) OVERRIDE {} + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) OVERRIDE {} + virtual void OnWindowUpdate(SpdyStreamId stream_id, + uint32 delta_window_size) OVERRIDE {} + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id) OVERRIDE {} + + private: + SpdyPriority priority_; +}; + +} // namespace + +bool GetSpdyPriority(SpdyMajorVersion version, + const SpdyFrame& frame, + SpdyPriority* priority) { + BufferedSpdyFramer framer(version, false); + PriorityGetter priority_getter; + framer.set_visitor(&priority_getter); + size_t frame_size = frame.size(); + if (framer.ProcessInput(frame.data(), frame_size) != frame_size) { + return false; + } + *priority = priority_getter.priority(); + return true; +} + +base::WeakPtr<SpdyStream> CreateStreamSynchronously( + SpdyStreamType type, + const base::WeakPtr<SpdySession>& session, + const GURL& url, + RequestPriority priority, + const BoundNetLog& net_log) { + SpdyStreamRequest stream_request; + int rv = stream_request.StartRequest(type, session, url, priority, net_log, + CompletionCallback()); + return + (rv == OK) ? stream_request.ReleaseStream() : base::WeakPtr<SpdyStream>(); +} + +StreamReleaserCallback::StreamReleaserCallback() {} + +StreamReleaserCallback::~StreamReleaserCallback() {} + +CompletionCallback StreamReleaserCallback::MakeCallback( + SpdyStreamRequest* request) { + return base::Bind(&StreamReleaserCallback::OnComplete, + base::Unretained(this), + request); +} + +void StreamReleaserCallback::OnComplete( + SpdyStreamRequest* request, int result) { + if (result == OK) + request->ReleaseStream()->Cancel(); + SetResult(result); +} + +MockECSignatureCreator::MockECSignatureCreator(crypto::ECPrivateKey* key) + : key_(key) { +} + +bool MockECSignatureCreator::Sign(const uint8* data, + int data_len, + std::vector<uint8>* signature) { + std::vector<uint8> private_key_value; + key_->ExportValue(&private_key_value); + std::string head = "fakesignature"; + std::string tail = "/fakesignature"; + + signature->clear(); + signature->insert(signature->end(), head.begin(), head.end()); + signature->insert(signature->end(), private_key_value.begin(), + private_key_value.end()); + signature->insert(signature->end(), '-'); + signature->insert(signature->end(), data, data + data_len); + signature->insert(signature->end(), tail.begin(), tail.end()); + return true; +} + +bool MockECSignatureCreator::DecodeSignature( + const std::vector<uint8>& signature, + std::vector<uint8>* out_raw_sig) { + *out_raw_sig = signature; + return true; +} + +MockECSignatureCreatorFactory::MockECSignatureCreatorFactory() { + crypto::ECSignatureCreator::SetFactoryForTesting(this); +} + +MockECSignatureCreatorFactory::~MockECSignatureCreatorFactory() { + crypto::ECSignatureCreator::SetFactoryForTesting(NULL); +} + +crypto::ECSignatureCreator* MockECSignatureCreatorFactory::Create( + crypto::ECPrivateKey* key) { + return new MockECSignatureCreator(key); +} + +SpdySessionDependencies::SpdySessionDependencies(NextProto protocol) + : host_resolver(new MockCachingHostResolver), + cert_verifier(new MockCertVerifier), + transport_security_state(new TransportSecurityState), + proxy_service(ProxyService::CreateDirect()), + ssl_config_service(new SSLConfigServiceDefaults), + socket_factory(new MockClientSocketFactory), + deterministic_socket_factory(new DeterministicMockClientSocketFactory), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())), + enable_ip_pooling(true), + enable_compression(false), + enable_ping(false), + enable_user_alternate_protocol_ports(false), + protocol(protocol), + stream_initial_recv_window_size(kSpdyStreamInitialWindowSize), + time_func(&base::TimeTicks::Now), + net_log(NULL) { + DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol; + + // Note: The CancelledTransaction test does cleanup by running all + // tasks in the message loop (RunAllPending). Unfortunately, that + // doesn't clean up tasks on the host resolver thread; and + // TCPConnectJob is currently not cancellable. Using synchronous + // lookups allows the test to shutdown cleanly. Until we have + // cancellable TCPConnectJobs, use synchronous lookups. + host_resolver->set_synchronous_mode(true); +} + +SpdySessionDependencies::SpdySessionDependencies( + NextProto protocol, ProxyService* proxy_service) + : host_resolver(new MockHostResolver), + cert_verifier(new MockCertVerifier), + transport_security_state(new TransportSecurityState), + proxy_service(proxy_service), + ssl_config_service(new SSLConfigServiceDefaults), + socket_factory(new MockClientSocketFactory), + deterministic_socket_factory(new DeterministicMockClientSocketFactory), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())), + enable_ip_pooling(true), + enable_compression(false), + enable_ping(false), + enable_user_alternate_protocol_ports(false), + protocol(protocol), + stream_initial_recv_window_size(kSpdyStreamInitialWindowSize), + time_func(&base::TimeTicks::Now), + net_log(NULL) { + DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol; +} + +SpdySessionDependencies::~SpdySessionDependencies() {} + +// static +HttpNetworkSession* SpdySessionDependencies::SpdyCreateSession( + SpdySessionDependencies* session_deps) { + net::HttpNetworkSession::Params params = CreateSessionParams(session_deps); + params.client_socket_factory = session_deps->socket_factory.get(); + HttpNetworkSession* http_session = new HttpNetworkSession(params); + SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + return http_session; +} + +// static +HttpNetworkSession* SpdySessionDependencies::SpdyCreateSessionDeterministic( + SpdySessionDependencies* session_deps) { + net::HttpNetworkSession::Params params = CreateSessionParams(session_deps); + params.client_socket_factory = + session_deps->deterministic_socket_factory.get(); + HttpNetworkSession* http_session = new HttpNetworkSession(params); + SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + return http_session; +} + +// static +net::HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams( + SpdySessionDependencies* session_deps) { + DCHECK(next_proto_is_spdy(session_deps->protocol)) << + "Invalid protocol: " << session_deps->protocol; + + net::HttpNetworkSession::Params params; + params.host_resolver = session_deps->host_resolver.get(); + params.cert_verifier = session_deps->cert_verifier.get(); + params.transport_security_state = + session_deps->transport_security_state.get(); + params.proxy_service = session_deps->proxy_service.get(); + params.ssl_config_service = session_deps->ssl_config_service.get(); + params.http_auth_handler_factory = + session_deps->http_auth_handler_factory.get(); + params.http_server_properties = + session_deps->http_server_properties.GetWeakPtr(); + params.enable_spdy_compression = session_deps->enable_compression; + params.enable_spdy_ping_based_connection_checking = session_deps->enable_ping; + params.enable_user_alternate_protocol_ports = + session_deps->enable_user_alternate_protocol_ports; + params.spdy_default_protocol = session_deps->protocol; + params.spdy_stream_initial_recv_window_size = + session_deps->stream_initial_recv_window_size; + params.time_func = session_deps->time_func; + params.trusted_spdy_proxy = session_deps->trusted_spdy_proxy; + params.net_log = session_deps->net_log; + return params; +} + +SpdyURLRequestContext::SpdyURLRequestContext(NextProto protocol) + : storage_(this) { + DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol; + + storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver)); + storage_.set_cert_verifier(new MockCertVerifier); + storage_.set_transport_security_state(new TransportSecurityState); + storage_.set_proxy_service(ProxyService::CreateDirect()); + storage_.set_ssl_config_service(new SSLConfigServiceDefaults); + storage_.set_http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault( + host_resolver())); + storage_.set_http_server_properties( + scoped_ptr<HttpServerProperties>(new HttpServerPropertiesImpl())); + net::HttpNetworkSession::Params params; + params.client_socket_factory = &socket_factory_; + params.host_resolver = host_resolver(); + params.cert_verifier = cert_verifier(); + params.transport_security_state = transport_security_state(); + params.proxy_service = proxy_service(); + params.ssl_config_service = ssl_config_service(); + params.http_auth_handler_factory = http_auth_handler_factory(); + params.network_delegate = network_delegate(); + params.enable_spdy_compression = false; + params.enable_spdy_ping_based_connection_checking = false; + params.spdy_default_protocol = protocol; + params.http_server_properties = http_server_properties(); + scoped_refptr<HttpNetworkSession> network_session( + new HttpNetworkSession(params)); + SpdySessionPoolPeer pool_peer(network_session->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + storage_.set_http_transaction_factory(new HttpCache( + network_session.get(), HttpCache::DefaultBackend::InMemory(0))); +} + +SpdyURLRequestContext::~SpdyURLRequestContext() { +} + +bool HasSpdySession(SpdySessionPool* pool, const SpdySessionKey& key) { + return pool->FindAvailableSession(key, BoundNetLog()) != NULL; +} + +namespace { + +base::WeakPtr<SpdySession> CreateSpdySessionHelper( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + const BoundNetLog& net_log, + Error expected_status, + bool is_secure) { + EXPECT_FALSE(HasSpdySession(http_session->spdy_session_pool(), key)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams( + key.host_port_pair(), MEDIUM, false, false, + OnHostResolutionCallback())); + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + TestCompletionCallback callback; + + int rv = ERR_UNEXPECTED; + if (is_secure) { + SSLConfig ssl_config; + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> ssl_params( + new SSLSocketParams(transport_params, + socks_params, + http_proxy_params, + ProxyServer::SCHEME_DIRECT, + key.host_port_pair(), + ssl_config, + key.privacy_mode(), + 0, + false, + false)); + rv = connection->Init(key.host_port_pair().ToString(), + ssl_params, + MEDIUM, + callback.callback(), + http_session->GetSSLSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL), + net_log); + } else { + rv = connection->Init(key.host_port_pair().ToString(), + transport_params, + MEDIUM, + callback.callback(), + http_session->GetTransportSocketPool( + HttpNetworkSession::NORMAL_SOCKET_POOL), + net_log); + } + + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + + EXPECT_EQ(OK, rv); + + base::WeakPtr<SpdySession> spdy_session; + EXPECT_EQ( + expected_status, + http_session->spdy_session_pool()->CreateAvailableSessionFromSocket( + key, connection.Pass(), net_log, OK, &spdy_session, + is_secure)); + EXPECT_EQ(expected_status == OK, spdy_session != NULL); + EXPECT_EQ(expected_status == OK, + HasSpdySession(http_session->spdy_session_pool(), key)); + return spdy_session; +} + +} // namespace + +base::WeakPtr<SpdySession> CreateInsecureSpdySession( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + const BoundNetLog& net_log) { + return CreateSpdySessionHelper(http_session, key, net_log, + OK, false /* is_secure */); +} + +void TryCreateInsecureSpdySessionExpectingFailure( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + Error expected_error, + const BoundNetLog& net_log) { + DCHECK_LT(expected_error, ERR_IO_PENDING); + CreateSpdySessionHelper(http_session, key, net_log, + expected_error, false /* is_secure */); +} + +base::WeakPtr<SpdySession> CreateSecureSpdySession( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + const BoundNetLog& net_log) { + return CreateSpdySessionHelper(http_session, key, net_log, + OK, true /* is_secure */); +} + +namespace { + +// A ClientSocket used for CreateFakeSpdySession() below. +class FakeSpdySessionClientSocket : public MockClientSocket { + public: + FakeSpdySessionClientSocket(int read_result) + : MockClientSocket(BoundNetLog()), + read_result_(read_result) {} + + virtual ~FakeSpdySessionClientSocket() {} + + virtual int Read(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) OVERRIDE { + return read_result_; + } + + virtual int Write(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) OVERRIDE { + return ERR_IO_PENDING; + } + + // Return kProtoUnknown to use the pool's default protocol. + virtual NextProto GetNegotiatedProtocol() const OVERRIDE { + return kProtoUnknown; + } + + // The functions below are not expected to be called. + + virtual int Connect(const CompletionCallback& callback) OVERRIDE { + ADD_FAILURE(); + return ERR_UNEXPECTED; + } + + virtual bool WasEverUsed() const OVERRIDE { + ADD_FAILURE(); + return false; + } + + virtual bool UsingTCPFastOpen() const OVERRIDE { + ADD_FAILURE(); + return false; + } + + virtual bool WasNpnNegotiated() const OVERRIDE { + ADD_FAILURE(); + return false; + } + + virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE { + ADD_FAILURE(); + return false; + } + + private: + int read_result_; +}; + +base::WeakPtr<SpdySession> CreateFakeSpdySessionHelper( + SpdySessionPool* pool, + const SpdySessionKey& key, + Error expected_status) { + EXPECT_NE(expected_status, ERR_IO_PENDING); + EXPECT_FALSE(HasSpdySession(pool, key)); + base::WeakPtr<SpdySession> spdy_session; + scoped_ptr<ClientSocketHandle> handle(new ClientSocketHandle()); + handle->SetSocket(scoped_ptr<StreamSocket>(new FakeSpdySessionClientSocket( + expected_status == OK ? ERR_IO_PENDING : expected_status))); + EXPECT_EQ( + expected_status, + pool->CreateAvailableSessionFromSocket( + key, handle.Pass(), BoundNetLog(), OK, &spdy_session, + true /* is_secure */)); + EXPECT_EQ(expected_status == OK, spdy_session != NULL); + EXPECT_EQ(expected_status == OK, HasSpdySession(pool, key)); + return spdy_session; +} + +} // namespace + +base::WeakPtr<SpdySession> CreateFakeSpdySession(SpdySessionPool* pool, + const SpdySessionKey& key) { + return CreateFakeSpdySessionHelper(pool, key, OK); +} + +void TryCreateFakeSpdySessionExpectingFailure(SpdySessionPool* pool, + const SpdySessionKey& key, + Error expected_error) { + DCHECK_LT(expected_error, ERR_IO_PENDING); + CreateFakeSpdySessionHelper(pool, key, expected_error); +} + +SpdySessionPoolPeer::SpdySessionPoolPeer(SpdySessionPool* pool) : pool_(pool) { +} + +void SpdySessionPoolPeer::RemoveAliases(const SpdySessionKey& key) { + pool_->RemoveAliases(key); +} + +void SpdySessionPoolPeer::DisableDomainAuthenticationVerification() { + pool_->verify_domain_authentication_ = false; +} + +void SpdySessionPoolPeer::SetEnableSendingInitialData(bool enabled) { + pool_->enable_sending_initial_data_ = enabled; +} + +SpdyTestUtil::SpdyTestUtil(NextProto protocol) + : protocol_(protocol), + spdy_version_(NextProtoToSpdyMajorVersion(protocol)) { + DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol; +} + +void SpdyTestUtil::AddUrlToHeaderBlock(base::StringPiece url, + SpdyHeaderBlock* headers) const { + if (is_spdy2()) { + (*headers)["url"] = url.as_string(); + } else { + std::string scheme, host, path; + ParseUrl(url, &scheme, &host, &path); + (*headers)[GetSchemeKey()] = scheme; + (*headers)[GetHostKey()] = host; + (*headers)[GetPathKey()] = path; + } +} + +scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructGetHeaderBlock( + base::StringPiece url) const { + return ConstructHeaderBlock("GET", url, NULL); +} + +scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructGetHeaderBlockForProxy( + base::StringPiece url) const { + scoped_ptr<SpdyHeaderBlock> headers(ConstructGetHeaderBlock(url)); + if (is_spdy2()) + (*headers)[GetPathKey()] = url.data(); + return headers.Pass(); +} + +scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructHeadHeaderBlock( + base::StringPiece url, + int64 content_length) const { + return ConstructHeaderBlock("HEAD", url, &content_length); +} + +scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructPostHeaderBlock( + base::StringPiece url, + int64 content_length) const { + return ConstructHeaderBlock("POST", url, &content_length); +} + +scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructPutHeaderBlock( + base::StringPiece url, + int64 content_length) const { + return ConstructHeaderBlock("PUT", url, &content_length); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyFrame( + const SpdyHeaderInfo& header_info, + scoped_ptr<SpdyHeaderBlock> headers) const { + BufferedSpdyFramer framer(spdy_version_, header_info.compressed); + SpdyFrame* frame = NULL; + switch (header_info.kind) { + case DATA: + frame = framer.CreateDataFrame(header_info.id, header_info.data, + header_info.data_length, + header_info.data_flags); + break; + case SYN_STREAM: + { + size_t credential_slot = is_spdy2() ? 0 : header_info.credential_slot; + frame = framer.CreateSynStream(header_info.id, header_info.assoc_id, + header_info.priority, + credential_slot, + header_info.control_flags, + header_info.compressed, headers.get()); + } + break; + case SYN_REPLY: + frame = framer.CreateSynReply(header_info.id, header_info.control_flags, + header_info.compressed, headers.get()); + break; + case RST_STREAM: + frame = framer.CreateRstStream(header_info.id, header_info.status); + break; + case HEADERS: + frame = framer.CreateHeaders(header_info.id, header_info.control_flags, + header_info.compressed, headers.get()); + break; + default: + ADD_FAILURE(); + break; + } + return frame; +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyFrame(const SpdyHeaderInfo& header_info, + const char* const extra_headers[], + int extra_header_count, + const char* const tail_headers[], + int tail_header_count) const { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + AppendToHeaderBlock(extra_headers, extra_header_count, headers.get()); + if (tail_headers && tail_header_count) + AppendToHeaderBlock(tail_headers, tail_header_count, headers.get()); + return ConstructSpdyFrame(header_info, headers.Pass()); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyControlFrame( + scoped_ptr<SpdyHeaderBlock> headers, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority, + SpdyFrameType type, + SpdyControlFlags flags, + SpdyStreamId associated_stream_id) const { + EXPECT_GE(type, FIRST_CONTROL_TYPE); + EXPECT_LE(type, LAST_CONTROL_TYPE); + const SpdyHeaderInfo header_info = { + type, + stream_id, + associated_stream_id, + ConvertRequestPriorityToSpdyPriority(request_priority, spdy_version_), + 0, // credential slot + flags, + compressed, + RST_STREAM_INVALID, // status + NULL, // data + 0, // length + DATA_FLAG_NONE + }; + return ConstructSpdyFrame(header_info, headers.Pass()); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyControlFrame( + const char* const extra_headers[], + int extra_header_count, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority, + SpdyFrameType type, + SpdyControlFlags flags, + const char* const* tail_headers, + int tail_header_size, + SpdyStreamId associated_stream_id) const { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + AppendToHeaderBlock(extra_headers, extra_header_count, headers.get()); + if (tail_headers && tail_header_size) + AppendToHeaderBlock(tail_headers, tail_header_size / 2, headers.get()); + return ConstructSpdyControlFrame( + headers.Pass(), compressed, stream_id, + request_priority, type, flags, associated_stream_id); +} + +std::string SpdyTestUtil::ConstructSpdyReplyString( + const SpdyHeaderBlock& headers) const { + std::string reply_string; + for (SpdyHeaderBlock::const_iterator it = headers.begin(); + it != headers.end(); ++it) { + std::string key = it->first; + // Remove leading colon from "special" headers (for SPDY3 and + // above). + if (spdy_version() >= SPDY3 && key[0] == ':') + key = key.substr(1); + std::vector<std::string> values; + base::SplitString(it->second, '\0', &values); + for (std::vector<std::string>::const_iterator it2 = values.begin(); + it2 != values.end(); ++it2) { + reply_string += key + ": " + *it2 + "\n"; + } + } + return reply_string; +} + +SpdyFrame* SpdyTestUtil::ConstructSpdySettings( + const SettingsMap& settings) const { + return CreateFramer()->CreateSettings(settings); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyCredential( + const SpdyCredential& credential) const { + return CreateFramer()->CreateCredentialFrame(credential); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyPing(uint32 ping_id) const { + return CreateFramer()->CreatePingFrame(ping_id); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyGoAway() const { + return ConstructSpdyGoAway(0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyGoAway( + SpdyStreamId last_good_stream_id) const { + return CreateFramer()->CreateGoAway(last_good_stream_id, GOAWAY_OK); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyWindowUpdate( + const SpdyStreamId stream_id, uint32 delta_window_size) const { + return CreateFramer()->CreateWindowUpdate(stream_id, delta_window_size); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyRstStream( + SpdyStreamId stream_id, + SpdyRstStreamStatus status) const { + return CreateFramer()->CreateRstStream(stream_id, status); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyGet( + const char* const url, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority) const { + const SpdyHeaderInfo header_info = { + SYN_STREAM, + stream_id, + 0, // associated stream ID + ConvertRequestPriorityToSpdyPriority(request_priority, spdy_version_), + 0, // credential slot + CONTROL_FLAG_FIN, + compressed, + RST_STREAM_INVALID, // status + NULL, // data + 0, // length + DATA_FLAG_NONE + }; + return ConstructSpdyFrame(header_info, ConstructGetHeaderBlock(url)); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyGet(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + bool direct) const { + const bool spdy2 = is_spdy2(); + const char* url = (spdy2 && !direct) ? "http://www.google.com/" : "/"; + const char* const kStandardGetHeaders[] = { + GetMethodKey(), "GET", + GetHostKey(), "www.google.com", + GetSchemeKey(), "http", + GetVersionKey(), "HTTP/1.1", + GetPathKey(), url + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + compressed, + stream_id, + request_priority, + SYN_STREAM, + CONTROL_FLAG_FIN, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyConnect( + const char* const extra_headers[], + int extra_header_count, + int stream_id) const { + const char* const kConnectHeaders[] = { + GetMethodKey(), "CONNECT", + GetPathKey(), "www.google.com:443", + GetHostKey(), "www.google.com", + GetVersionKey(), "HTTP/1.1", + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + /*compressed*/ false, + stream_id, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + kConnectHeaders, + arraysize(kConnectHeaders), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url) { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + (*headers)["hello"] = "bye"; + (*headers)[GetStatusKey()] = "200 OK"; + (*headers)[GetVersionKey()] = "HTTP/1.1"; + AddUrlToHeaderBlock(url, headers.get()); + AppendToHeaderBlock(extra_headers, extra_header_count, headers.get()); + return ConstructSpdyControlFrame(headers.Pass(), + false, + stream_id, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + associated_stream_id); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url, + const char* status, + const char* location) { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + (*headers)["hello"] = "bye"; + (*headers)[GetStatusKey()] = status; + (*headers)[GetVersionKey()] = "HTTP/1.1"; + (*headers)["location"] = location; + AddUrlToHeaderBlock(url, headers.get()); + AppendToHeaderBlock(extra_headers, extra_header_count, headers.get()); + return ConstructSpdyControlFrame(headers.Pass(), + false, + stream_id, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + associated_stream_id); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyPushHeaders( + int stream_id, + const char* const extra_headers[], + int extra_header_count) { + const char* const kStandardGetHeaders[] = { + GetStatusKey(), "200 OK", + GetVersionKey(), "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdySynReplyError( + const char* const status, + const char* const* const extra_headers, + int extra_header_count, + int stream_id) { + const char* const kStandardGetHeaders[] = { + "hello", "bye", + GetStatusKey(), status, + GetVersionKey(), "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyGetSynReplyRedirect(int stream_id) { + static const char* const kExtraHeaders[] = { + "location", "http://www.foo.com/index.php", + }; + return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders, + arraysize(kExtraHeaders)/2, stream_id); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdySynReplyError(int stream_id) { + return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyGetSynReply( + const char* const extra_headers[], + int extra_header_count, + int stream_id) { + const char* const kStandardGetHeaders[] = { + "hello", "bye", + GetStatusKey(), "200", + GetVersionKey(), "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyPost(const char* url, + SpdyStreamId stream_id, + int64 content_length, + RequestPriority priority, + const char* const extra_headers[], + int extra_header_count) { + const SpdyHeaderInfo kSynStartHeader = { + SYN_STREAM, + stream_id, + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority(priority, spdy_version_), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, + false, // Compressed + RST_STREAM_INVALID, + NULL, // Data + 0, // Length + DATA_FLAG_NONE + }; + return ConstructSpdyFrame( + kSynStartHeader, ConstructPostHeaderBlock(url, content_length)); +} + +SpdyFrame* SpdyTestUtil::ConstructChunkedSpdyPost( + const char* const extra_headers[], + int extra_header_count) { + const char* post_headers[] = { + GetMethodKey(), "POST", + GetPathKey(), "/", + GetHostKey(), "www.google.com", + GetSchemeKey(), "http", + GetVersionKey(), "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + post_headers, + arraysize(post_headers), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyPostSynReply( + const char* const extra_headers[], + int extra_header_count) { + const char* const kStandardGetHeaders[] = { + "hello", "bye", + GetStatusKey(), "200", + GetPathKey(), "/index.php", + GetVersionKey(), "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + 0); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyBodyFrame(int stream_id, bool fin) { + SpdyFramer framer(spdy_version_); + return framer.CreateDataFrame( + stream_id, kUploadData, kUploadDataSize, + fin ? DATA_FLAG_FIN : DATA_FLAG_NONE); +} + +SpdyFrame* SpdyTestUtil::ConstructSpdyBodyFrame(int stream_id, + const char* data, + uint32 len, + bool fin) { + SpdyFramer framer(spdy_version_); + return framer.CreateDataFrame( + stream_id, data, len, fin ? DATA_FLAG_FIN : DATA_FLAG_NONE); +} + +SpdyFrame* SpdyTestUtil::ConstructWrappedSpdyFrame( + const scoped_ptr<SpdyFrame>& frame, + int stream_id) { + return ConstructSpdyBodyFrame(stream_id, frame->data(), + frame->size(), false); +} + +const SpdyHeaderInfo SpdyTestUtil::MakeSpdyHeader(SpdyFrameType type) { + const SpdyHeaderInfo kHeader = { + type, + 1, // Stream ID + 0, // Associated stream ID + ConvertRequestPriorityToSpdyPriority(LOWEST, spdy_version_), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + RST_STREAM_INVALID, + NULL, // Data + 0, // Length + DATA_FLAG_NONE + }; + return kHeader; +} + +scoped_ptr<SpdyFramer> SpdyTestUtil::CreateFramer() const { + return scoped_ptr<SpdyFramer>(new SpdyFramer(spdy_version_)); +} + +const char* SpdyTestUtil::GetMethodKey() const { + return is_spdy2() ? "method" : ":method"; +} + +const char* SpdyTestUtil::GetStatusKey() const { + return is_spdy2() ? "status" : ":status"; +} + +const char* SpdyTestUtil::GetHostKey() const { + return is_spdy2() ? "host" : ":host"; +} + +const char* SpdyTestUtil::GetSchemeKey() const { + return is_spdy2() ? "scheme" : ":scheme"; +} + +const char* SpdyTestUtil::GetVersionKey() const { + return is_spdy2() ? "version" : ":version"; +} + +const char* SpdyTestUtil::GetPathKey() const { + return is_spdy2() ? "url" : ":path"; +} + +scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructHeaderBlock( + base::StringPiece method, + base::StringPiece url, + int64* content_length) const { + std::string scheme, host, path; + ParseUrl(url.data(), &scheme, &host, &path); + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + (*headers)[GetMethodKey()] = method.as_string(); + (*headers)[GetPathKey()] = path.c_str(); + (*headers)[GetHostKey()] = host.c_str(); + (*headers)[GetSchemeKey()] = scheme.c_str(); + (*headers)[GetVersionKey()] = "HTTP/1.1"; + if (content_length) { + std::string length_str = base::Int64ToString(*content_length); + (*headers)["content-length"] = length_str; + } + return headers.Pass(); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_test_util_common.h b/chromium/net/spdy/spdy_test_util_common.h new file mode 100644 index 00000000000..aa833ff91f6 --- /dev/null +++ b/chromium/net/spdy/spdy_test_util_common.h @@ -0,0 +1,538 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_TEST_UTIL_COMMON_H_ +#define NET_SPDY_SPDY_TEST_UTIL_COMMON_H_ + +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "net/base/completion_callback.h" +#include "net/base/request_priority.h" +#include "net/base/test_completion_callback.h" +#include "net/cert/cert_verifier.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_network_session.h" +#include "net/http/http_response_info.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/transport_security_state.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/next_proto.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_protocol.h" +#include "net/ssl/ssl_config_service_defaults.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_storage.h" +#include "testing/gtest/include/gtest/gtest.h" + +class GURL; + +namespace net { + +class BoundNetLog; +class SpdySession; +class SpdySessionKey; +class SpdySessionPool; +class SpdyStream; +class SpdyStreamRequest; + +// Default upload data used by both, mock objects and framer when creating +// data frames. +const char kDefaultURL[] = "http://www.google.com"; +const char kUploadData[] = "hello!"; +const int kUploadDataSize = arraysize(kUploadData)-1; + +// SpdyNextProtos returns a vector of next protocols for negotiating +// SPDY. +std::vector<NextProto> SpdyNextProtos(); + +// Chop a frame into an array of MockWrites. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks); + +// Chop a SpdyFrame into an array of MockWrites. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks); + +// Chop a frame into an array of MockReads. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const char* data, int length, int num_chunks); + +// Chop a SpdyFrame into an array of MockReads. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks); + +// Adds headers and values to a map. +// |extra_headers| is an array of { name, value } pairs, arranged as strings +// where the even entries are the header names, and the odd entries are the +// header values. +// |headers| gets filled in from |extra_headers|. +void AppendToHeaderBlock(const char* const extra_headers[], + int extra_header_count, + SpdyHeaderBlock* headers); + +// Create an async MockWrite from the given SpdyFrame. +MockWrite CreateMockWrite(const SpdyFrame& req); + +// Create an async MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(const SpdyFrame& req, int seq); + +MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode); + +// Create a MockRead from the given SpdyFrame. +MockRead CreateMockRead(const SpdyFrame& resp); + +// Create a MockRead from the given SpdyFrame and sequence number. +MockRead CreateMockRead(const SpdyFrame& resp, int seq); + +MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode); + +// Combines the given SpdyFrames into the given char array and returns +// the total length. +int CombineFrames(const SpdyFrame** frames, int num_frames, + char* buff, int buff_len); + +// Returns the SpdyPriority embedded in the given frame. Returns true +// and fills in |priority| on success. +bool GetSpdyPriority(SpdyMajorVersion version, + const SpdyFrame& frame, + SpdyPriority* priority); + +// Tries to create a stream in |session| synchronously. Returns NULL +// on failure. +base::WeakPtr<SpdyStream> CreateStreamSynchronously( + SpdyStreamType type, + const base::WeakPtr<SpdySession>& session, + const GURL& url, + RequestPriority priority, + const BoundNetLog& net_log); + +// Helper class used by some tests to release a stream as soon as it's +// created. +class StreamReleaserCallback : public TestCompletionCallbackBase { + public: + StreamReleaserCallback(); + + virtual ~StreamReleaserCallback(); + + // Returns a callback that releases |request|'s stream. + CompletionCallback MakeCallback(SpdyStreamRequest* request); + + private: + void OnComplete(SpdyStreamRequest* request, int result); +}; + +const size_t kSpdyCredentialSlotUnused = 0; + +// This struct holds information used to construct spdy control and data frames. +struct SpdyHeaderInfo { + SpdyFrameType kind; + SpdyStreamId id; + SpdyStreamId assoc_id; + SpdyPriority priority; + size_t credential_slot; // SPDY3 only + SpdyControlFlags control_flags; + bool compressed; + SpdyRstStreamStatus status; + const char* data; + uint32 data_length; + SpdyDataFlags data_flags; +}; + +// An ECSignatureCreator that returns deterministic signatures. +class MockECSignatureCreator : public crypto::ECSignatureCreator { + public: + explicit MockECSignatureCreator(crypto::ECPrivateKey* key); + + // crypto::ECSignatureCreator + virtual bool Sign(const uint8* data, + int data_len, + std::vector<uint8>* signature) OVERRIDE; + virtual bool DecodeSignature(const std::vector<uint8>& signature, + std::vector<uint8>* out_raw_sig) OVERRIDE; + + private: + crypto::ECPrivateKey* key_; + + DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreator); +}; + +// An ECSignatureCreatorFactory creates MockECSignatureCreator. +class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory { + public: + MockECSignatureCreatorFactory(); + virtual ~MockECSignatureCreatorFactory(); + + // crypto::ECSignatureCreatorFactory + virtual crypto::ECSignatureCreator* Create( + crypto::ECPrivateKey* key) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory); +}; + +// Helper to manage the lifetimes of the dependencies for a +// HttpNetworkTransaction. +struct SpdySessionDependencies { + // Default set of dependencies -- "null" proxy service. + explicit SpdySessionDependencies(NextProto protocol); + + // Custom proxy service dependency. + SpdySessionDependencies(NextProto protocol, ProxyService* proxy_service); + + ~SpdySessionDependencies(); + + static HttpNetworkSession* SpdyCreateSession( + SpdySessionDependencies* session_deps); + static HttpNetworkSession* SpdyCreateSessionDeterministic( + SpdySessionDependencies* session_deps); + static HttpNetworkSession::Params CreateSessionParams( + SpdySessionDependencies* session_deps); + + // NOTE: host_resolver must be ordered before http_auth_handler_factory. + scoped_ptr<MockHostResolverBase> host_resolver; + scoped_ptr<CertVerifier> cert_verifier; + scoped_ptr<TransportSecurityState> transport_security_state; + scoped_ptr<ProxyService> proxy_service; + scoped_refptr<SSLConfigService> ssl_config_service; + scoped_ptr<MockClientSocketFactory> socket_factory; + scoped_ptr<DeterministicMockClientSocketFactory> deterministic_socket_factory; + scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory; + HttpServerPropertiesImpl http_server_properties; + bool enable_ip_pooling; + bool enable_compression; + bool enable_ping; + bool enable_user_alternate_protocol_ports; + NextProto protocol; + size_t stream_initial_recv_window_size; + SpdySession::TimeFunc time_func; + std::string trusted_spdy_proxy; + NetLog* net_log; +}; + +class SpdyURLRequestContext : public URLRequestContext { + public: + explicit SpdyURLRequestContext(NextProto protocol); + virtual ~SpdyURLRequestContext(); + + MockClientSocketFactory& socket_factory() { return socket_factory_; } + + private: + MockClientSocketFactory socket_factory_; + net::URLRequestContextStorage storage_; +}; + +// Equivalent to pool->GetIfExists(spdy_session_key, BoundNetLog()) != NULL. +bool HasSpdySession(SpdySessionPool* pool, const SpdySessionKey& key); + +// Creates a SPDY session for the given key and puts it in the SPDY +// session pool in |http_session|. A SPDY session for |key| must not +// already exist. +base::WeakPtr<SpdySession> CreateInsecureSpdySession( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + const BoundNetLog& net_log); + +// Tries to create a SPDY session for the given key but expects the +// attempt to fail with the given error. A SPDY session for |key| must +// not already exist. +void TryCreateInsecureSpdySessionExpectingFailure( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + Error expected_error, + const BoundNetLog& net_log); + +// Like CreateInsecureSpdySession(), but uses TLS. +base::WeakPtr<SpdySession> CreateSecureSpdySession( + const scoped_refptr<HttpNetworkSession>& http_session, + const SpdySessionKey& key, + const BoundNetLog& net_log); + +// Creates an insecure SPDY session for the given key and puts it in +// |pool|. The returned session will neither receive nor send any +// data. A SPDY session for |key| must not already exist. +base::WeakPtr<SpdySession> CreateFakeSpdySession(SpdySessionPool* pool, + const SpdySessionKey& key); + +// Tries to create an insecure SPDY session for the given key but +// expects the attempt to fail with the given error. The session will +// neither receive nor send any data. A SPDY session for |key| must +// not already exist. +void TryCreateFakeSpdySessionExpectingFailure(SpdySessionPool* pool, + const SpdySessionKey& key, + Error expected_error); + +class SpdySessionPoolPeer { + public: + explicit SpdySessionPoolPeer(SpdySessionPool* pool); + + void RemoveAliases(const SpdySessionKey& key); + void DisableDomainAuthenticationVerification(); + void SetEnableSendingInitialData(bool enabled); + + private: + SpdySessionPool* const pool_; + + DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer); +}; + +class SpdyTestUtil { + public: + explicit SpdyTestUtil(NextProto protocol); + + // Add the appropriate headers to put |url| into |block|. + void AddUrlToHeaderBlock(base::StringPiece url, + SpdyHeaderBlock* headers) const; + + scoped_ptr<SpdyHeaderBlock> ConstructGetHeaderBlock( + base::StringPiece url) const; + scoped_ptr<SpdyHeaderBlock> ConstructGetHeaderBlockForProxy( + base::StringPiece url) const; + scoped_ptr<SpdyHeaderBlock> ConstructHeadHeaderBlock( + base::StringPiece url, + int64 content_length) const; + scoped_ptr<SpdyHeaderBlock> ConstructPostHeaderBlock( + base::StringPiece url, + int64 content_length) const; + scoped_ptr<SpdyHeaderBlock> ConstructPutHeaderBlock( + base::StringPiece url, + int64 content_length) const; + + // Construct a SPDY frame. If it is a SYN_STREAM or SYN_REPLY frame (as + // specified in header_info.kind), the provided headers are included in the + // frame. + SpdyFrame* ConstructSpdyFrame( + const SpdyHeaderInfo& header_info, + scoped_ptr<SpdyHeaderBlock> headers) const; + + // Construct a SPDY frame. If it is a SYN_STREAM or SYN_REPLY frame (as + // specified in header_info.kind), the headers provided in extra_headers and + // (if non-NULL) tail_headers are concatenated and included in the frame. + // (extra_headers must always be non-NULL.) + SpdyFrame* ConstructSpdyFrame(const SpdyHeaderInfo& header_info, + const char* const extra_headers[], + int extra_header_count, + const char* const tail_headers[], + int tail_header_count) const; + + // Construct a generic SpdyControlFrame. + SpdyFrame* ConstructSpdyControlFrame( + scoped_ptr<SpdyHeaderBlock> headers, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority, + SpdyFrameType type, + SpdyControlFlags flags, + SpdyStreamId associated_stream_id) const; + + // Construct a generic SpdyControlFrame. + // + // Warning: extra_header_count is the number of header-value pairs in + // extra_headers (so half the number of elements), but tail_headers_size is + // the actual number of elements (both keys and values) in tail_headers. + // TODO(ttuttle): Fix this inconsistency. + SpdyFrame* ConstructSpdyControlFrame( + const char* const extra_headers[], + int extra_header_count, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority, + SpdyFrameType type, + SpdyControlFlags flags, + const char* const* tail_headers, + int tail_headers_size, + SpdyStreamId associated_stream_id) const; + + // Construct an expected SPDY reply string from the given headers. + std::string ConstructSpdyReplyString(const SpdyHeaderBlock& headers) const; + + // Construct an expected SPDY SETTINGS frame. + // |settings| are the settings to set. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdySettings(const SettingsMap& settings) const; + + // Construct an expected SPDY CREDENTIAL frame. + // |credential| is the credential to send. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdyCredential(const SpdyCredential& credential) const; + + // Construct a SPDY PING frame. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdyPing(uint32 ping_id) const; + + // Construct a SPDY GOAWAY frame with last_good_stream_id = 0. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdyGoAway() const; + + // Construct a SPDY GOAWAY frame with the specified last_good_stream_id. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdyGoAway(SpdyStreamId last_good_stream_id) const; + + // Construct a SPDY WINDOW_UPDATE frame. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdyWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) const; + + // Construct a SPDY RST_STREAM frame. + // Returns the constructed frame. The caller takes ownership of the frame. + SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id, + SpdyRstStreamStatus status) const; + + // Constructs a standard SPDY GET SYN frame, optionally compressed + // for the url |url|. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyGet(const char* const url, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority) const; + + SpdyFrame* ConstructSpdyGetForProxy(const char* const url, + bool compressed, + SpdyStreamId stream_id, + RequestPriority request_priority) const; + + // Constructs a standard SPDY GET SYN frame, optionally compressed. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. If |direct| is false, the + // the full url will be used instead of simply the path. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + bool direct) const; + + // Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. + SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[], + int extra_header_count, + int stream_id) const; + + // Constructs a standard SPDY push SYN frame. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url); + SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url, + const char* status, + const char* location); + + SpdyFrame* ConstructSpdyPushHeaders(int stream_id, + const char* const extra_headers[], + int extra_header_count); + + // Constructs a standard SPDY SYN_REPLY frame to match the SPDY GET. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], + int extra_header_count, + int stream_id); + + // Constructs a standard SPDY SYN_REPLY frame to match the SPDY GET. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id); + + // Constructs a standard SPDY SYN_REPLY frame with an Internal Server + // Error status code. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdySynReplyError(int stream_id); + + // Constructs a standard SPDY SYN_REPLY frame with the specified status code. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdySynReplyError(const char* const status, + const char* const* const extra_headers, + int extra_header_count, + int stream_id); + + // Constructs a standard SPDY POST SYN frame. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyPost(const char* url, + SpdyStreamId stream_id, + int64 content_length, + RequestPriority priority, + const char* const extra_headers[], + int extra_header_count); + + // Constructs a chunked transfer SPDY POST SYN frame. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[], + int extra_header_count); + + // Constructs a standard SPDY SYN_REPLY frame to match the SPDY POST. + // |extra_headers| are the extra header-value pairs, which typically + // will vary the most between calls. + // Returns a SpdyFrame. + SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[], + int extra_header_count); + + // Constructs a single SPDY data frame with the contents "hello!" + SpdyFrame* ConstructSpdyBodyFrame(int stream_id, + bool fin); + + // Constructs a single SPDY data frame with the given content. + SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data, + uint32 len, bool fin); + + // Wraps |frame| in the payload of a data frame in stream |stream_id|. + SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame, + int stream_id); + + const SpdyHeaderInfo MakeSpdyHeader(SpdyFrameType type); + + NextProto protocol() const { return protocol_; } + SpdyMajorVersion spdy_version() const { return spdy_version_; } + bool is_spdy2() const { return protocol_ < kProtoSPDY3; } + scoped_ptr<SpdyFramer> CreateFramer() const; + + const char* GetMethodKey() const; + const char* GetStatusKey() const; + const char* GetHostKey() const; + const char* GetSchemeKey() const; + const char* GetVersionKey() const; + const char* GetPathKey() const; + + private: + // |content_length| may be NULL, in which case the content-length + // header will be omitted. + scoped_ptr<SpdyHeaderBlock> ConstructHeaderBlock( + base::StringPiece method, + base::StringPiece url, + int64* content_length) const; + + const NextProto protocol_; + const SpdyMajorVersion spdy_version_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_TEST_UTIL_COMMON_H_ diff --git a/chromium/net/spdy/spdy_test_utils.cc b/chromium/net/spdy/spdy_test_utils.cc new file mode 100644 index 00000000000..e7eb8aca91b --- /dev/null +++ b/chromium/net/spdy/spdy_test_utils.cc @@ -0,0 +1,129 @@ +// 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 "net/spdy/spdy_test_utils.h" + +#include <cstring> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/sys_byteorder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace test { + +std::string HexDumpWithMarks(const unsigned char* data, int length, + const bool* marks, int mark_length) { + static const char kHexChars[] = "0123456789abcdef"; + static const int kColumns = 4; + + const int kSizeLimit = 1024; + if (length > kSizeLimit || mark_length > kSizeLimit) { + LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes."; + length = std::min(length, kSizeLimit); + mark_length = std::min(mark_length, kSizeLimit); + } + + std::string hex; + for (const unsigned char* row = data; length > 0; + row += kColumns, length -= kColumns) { + for (const unsigned char *p = row; p < row + 4; ++p) { + if (p < row + length) { + const bool mark = + (marks && (p - data) < mark_length && marks[p - data]); + hex += mark ? '*' : ' '; + hex += kHexChars[(*p & 0xf0) >> 4]; + hex += kHexChars[*p & 0x0f]; + hex += mark ? '*' : ' '; + } else { + hex += " "; + } + } + hex = hex + " "; + + for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p) + hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.'; + + hex = hex + '\n'; + } + return hex; +} + +void CompareCharArraysWithHexError( + const std::string& description, + const unsigned char* actual, + const int actual_len, + const unsigned char* expected, + const int expected_len) { + const int min_len = std::min(actual_len, expected_len); + const int max_len = std::max(actual_len, expected_len); + scoped_ptr<bool[]> marks(new bool[max_len]); + bool identical = (actual_len == expected_len); + for (int i = 0; i < min_len; ++i) { + if (actual[i] != expected[i]) { + marks[i] = true; + identical = false; + } else { + marks[i] = false; + } + } + for (int i = min_len; i < max_len; ++i) { + marks[i] = true; + } + if (identical) return; + ADD_FAILURE() + << "Description:\n" + << description + << "\n\nExpected:\n" + << HexDumpWithMarks(expected, expected_len, marks.get(), max_len) + << "\nActual:\n" + << HexDumpWithMarks(actual, actual_len, marks.get(), max_len); +} + +void SetFrameFlags(SpdyFrame* frame, uint8 flags, int spdy_version) { + switch (spdy_version) { + case 2: + case 3: + frame->data()[4] = flags; + break; + case 4: + frame->data()[3] = flags; + break; + default: + LOG(FATAL) << "Unsupported SPDY version."; + } +} + +void SetFrameLength(SpdyFrame* frame, size_t length, int spdy_version) { + switch (spdy_version) { + case 2: + case 3: + CHECK_EQ(0u, length & ~kLengthMask); + { + int32 wire_length = base::HostToNet32(length); + // The length field in SPDY 2 and 3 is a 24-bit (3B) integer starting at + // offset 5. + memcpy(frame->data() + 5, reinterpret_cast<char*>(&wire_length) + 1, 3); + } + break; + case 4: + CHECK_GT(1u<<16, length); + { + int32 wire_length = base::HostToNet16(static_cast<uint16>(length)); + memcpy(frame->data(), + reinterpret_cast<char*>(&wire_length), + sizeof(uint16)); + } + break; + default: + LOG(FATAL) << "Unsupported SPDY version."; + } +} + + +} // namespace test + +} // namespace net diff --git a/chromium/net/spdy/spdy_test_utils.h b/chromium/net/spdy/spdy_test_utils.h new file mode 100644 index 00000000000..b6e03936fae --- /dev/null +++ b/chromium/net/spdy/spdy_test_utils.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef NET_SPDY_TEST_UTILS_H_ +#define NET_SPDY_TEST_UTILS_H_ + +#include <string> + +#include "net/spdy/spdy_protocol.h" + +namespace net { + +namespace test { + +std::string HexDumpWithMarks(const unsigned char* data, int length, + const bool* marks, int mark_length); + +void CompareCharArraysWithHexError( + const std::string& description, + const unsigned char* actual, + const int actual_len, + const unsigned char* expected, + const int expected_len); + +void SetFrameFlags(SpdyFrame* frame, uint8 flags, int spdy_version); + +void SetFrameLength(SpdyFrame* frame, size_t length, int spdy_version); + +} // namespace test + +} // namespace net + +#endif // NET_SPDY_TEST_UTILS_H_ diff --git a/chromium/net/spdy/spdy_websocket_stream.cc b/chromium/net/spdy/spdy_websocket_stream.cc new file mode 100644 index 00000000000..c0eac28b6fb --- /dev/null +++ b/chromium/net/spdy/spdy_websocket_stream.cc @@ -0,0 +1,130 @@ +// 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 "net/spdy/spdy_websocket_stream.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_stream.h" +#include "url/gurl.h" + +namespace net { + +SpdyWebSocketStream::SpdyWebSocketStream( + const base::WeakPtr<SpdySession>& spdy_session, Delegate* delegate) + : weak_ptr_factory_(this), + spdy_session_(spdy_session), + pending_send_data_length_(0), + delegate_(delegate) { + DCHECK(spdy_session_.get()); + DCHECK(delegate_); +} + +SpdyWebSocketStream::~SpdyWebSocketStream() { + delegate_ = NULL; + Close(); +} + +int SpdyWebSocketStream::InitializeStream(const GURL& url, + RequestPriority request_priority, + const BoundNetLog& net_log) { + if (!spdy_session_) + return ERR_SOCKET_NOT_CONNECTED; + + int rv = stream_request_.StartRequest( + SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url, request_priority, net_log, + base::Bind(&SpdyWebSocketStream::OnSpdyStreamCreated, + weak_ptr_factory_.GetWeakPtr())); + + if (rv == OK) { + stream_ = stream_request_.ReleaseStream(); + DCHECK(stream_.get()); + stream_->SetDelegate(this); + } + return rv; +} + +int SpdyWebSocketStream::SendRequest(scoped_ptr<SpdyHeaderBlock> headers) { + if (!stream_.get()) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + int result = stream_->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND); + if (result < OK && result != ERR_IO_PENDING) + Close(); + return result; +} + +int SpdyWebSocketStream::SendData(const char* data, int length) { + if (!stream_.get()) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + DCHECK_GE(length, 0); + pending_send_data_length_ = static_cast<size_t>(length); + scoped_refptr<IOBuffer> buf(new IOBuffer(length)); + memcpy(buf->data(), data, length); + stream_->SendData(buf.get(), length, MORE_DATA_TO_SEND); + return ERR_IO_PENDING; +} + +void SpdyWebSocketStream::Close() { + if (stream_.get()) { + stream_->Close(); + DCHECK(!stream_.get()); + } +} + +void SpdyWebSocketStream::OnRequestHeadersSent() { + DCHECK(delegate_); + delegate_->OnSentSpdyHeaders(); +} + +SpdyResponseHeadersStatus SpdyWebSocketStream::OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) { + DCHECK(delegate_); + delegate_->OnSpdyResponseHeadersUpdated(response_headers); + return RESPONSE_HEADERS_ARE_COMPLETE; +} + +void SpdyWebSocketStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) { + DCHECK(delegate_); + delegate_->OnReceivedSpdyData(buffer.Pass()); +} + +void SpdyWebSocketStream::OnDataSent() { + DCHECK(delegate_); + delegate_->OnSentSpdyData(pending_send_data_length_); + pending_send_data_length_ = 0; +} + +void SpdyWebSocketStream::OnClose(int status) { + stream_.reset(); + + // Destruction without Close() call OnClose() with delegate_ being NULL. + if (!delegate_) + return; + Delegate* delegate = delegate_; + delegate_ = NULL; + delegate->OnCloseSpdyStream(); +} + +void SpdyWebSocketStream::OnSpdyStreamCreated(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + if (result == OK) { + stream_ = stream_request_.ReleaseStream(); + DCHECK(stream_.get()); + stream_->SetDelegate(this); + } + DCHECK(delegate_); + delegate_->OnCreatedSpdyStream(result); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_websocket_stream.h b/chromium/net/spdy/spdy_websocket_stream.h new file mode 100644 index 00000000000..169d1e481a8 --- /dev/null +++ b/chromium/net/spdy/spdy_websocket_stream.h @@ -0,0 +1,103 @@ +// 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. + +#ifndef NET_SPDY_SPDY_WEBSOCKET_STREAM_H_ +#define NET_SPDY_SPDY_WEBSOCKET_STREAM_H_ + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "net/base/completion_callback.h" +#include "net/base/request_priority.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_stream.h" + +namespace net { + +// The SpdyWebSocketStream is a WebSocket-specific type of stream known to a +// SpdySession. WebSocket's opening handshake is converted to SPDY's +// SYN_STREAM/SYN_REPLY. WebSocket frames are encapsulated as SPDY data frames. +class NET_EXPORT_PRIVATE SpdyWebSocketStream + : public SpdyStream::Delegate { + public: + // Delegate handles asynchronous events. + class NET_EXPORT_PRIVATE Delegate { + public: + // Called when InitializeStream() finishes asynchronously. This delegate is + // called if InitializeStream() returns ERR_IO_PENDING. |status| indicates + // network error. + virtual void OnCreatedSpdyStream(int status) = 0; + + // Called on corresponding to OnSendHeadersComplete() or SPDY's SYN frame + // has been sent. + virtual void OnSentSpdyHeaders() = 0; + + // Called on corresponding to OnResponseHeadersUpdated() or + // SPDY's SYN_STREAM, SYN_REPLY, or HEADERS frames are + // received. This callback may be called multiple times as SPDY's + // delegate does. + virtual void OnSpdyResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) = 0; + + // Called when data is sent. + virtual void OnSentSpdyData(size_t bytes_sent) = 0; + + // Called when data is received. + virtual void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) = 0; + + // Called when SpdyStream is closed. + virtual void OnCloseSpdyStream() = 0; + + protected: + virtual ~Delegate() {} + }; + + SpdyWebSocketStream(const base::WeakPtr<SpdySession>& spdy_session, + Delegate* delegate); + virtual ~SpdyWebSocketStream(); + + // Initializes SPDY stream for the WebSocket. + // It might create SPDY stream asynchronously. In this case, this method + // returns ERR_IO_PENDING and call OnCreatedSpdyStream delegate with result + // after completion. In other cases, delegate does not be called. + int InitializeStream(const GURL& url, + RequestPriority request_priority, + const BoundNetLog& stream_net_log); + + int SendRequest(scoped_ptr<SpdyHeaderBlock> headers); + int SendData(const char* data, int length); + void Close(); + + // SpdyStream::Delegate + virtual void OnRequestHeadersSent() OVERRIDE; + virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE; + virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE; + virtual void OnDataSent() OVERRIDE; + virtual void OnClose(int status) OVERRIDE; + + private: + friend class SpdyWebSocketStreamTest; + FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamTest, Basic); + + void OnSpdyStreamCreated(int status); + + base::WeakPtrFactory<SpdyWebSocketStream> weak_ptr_factory_; + SpdyStreamRequest stream_request_; + base::WeakPtr<SpdyStream> stream_; + const base::WeakPtr<SpdySession> spdy_session_; + size_t pending_send_data_length_; + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStream); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_WEBSOCKET_STREAM_H_ diff --git a/chromium/net/spdy/spdy_websocket_stream_unittest.cc b/chromium/net/spdy/spdy_websocket_stream_unittest.cc new file mode 100644 index 00000000000..e0661a05407 --- /dev/null +++ b/chromium/net/spdy/spdy_websocket_stream_unittest.cc @@ -0,0 +1,606 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_websocket_stream.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/base/completion_callback.h" +#include "net/proxy/proxy_server.h" +#include "net/socket/next_proto.h" +#include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_websocket_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +struct SpdyWebSocketStreamEvent { + enum EventType { + EVENT_CREATED, + EVENT_SENT_HEADERS, + EVENT_RECEIVED_HEADER, + EVENT_SENT_DATA, + EVENT_RECEIVED_DATA, + EVENT_CLOSE, + }; + SpdyWebSocketStreamEvent(EventType type, + const SpdyHeaderBlock& headers, + int result, + const std::string& data) + : event_type(type), + headers(headers), + result(result), + data(data) {} + + EventType event_type; + SpdyHeaderBlock headers; + int result; + std::string data; +}; + +class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate { + public: + explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback) + : callback_(callback) {} + virtual ~SpdyWebSocketStreamEventRecorder() {} + + typedef base::Callback<void(SpdyWebSocketStreamEvent*)> StreamEventCallback; + + void SetOnCreated(const StreamEventCallback& callback) { + on_created_ = callback; + } + void SetOnSentHeaders(const StreamEventCallback& callback) { + on_sent_headers_ = callback; + } + void SetOnReceivedHeader(const StreamEventCallback& callback) { + on_received_header_ = callback; + } + void SetOnSentData(const StreamEventCallback& callback) { + on_sent_data_ = callback; + } + void SetOnReceivedData(const StreamEventCallback& callback) { + on_received_data_ = callback; + } + void SetOnClose(const StreamEventCallback& callback) { + on_close_ = callback; + } + + virtual void OnCreatedSpdyStream(int result) OVERRIDE { + events_.push_back( + SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED, + SpdyHeaderBlock(), + result, + std::string())); + if (!on_created_.is_null()) + on_created_.Run(&events_.back()); + } + virtual void OnSentSpdyHeaders() OVERRIDE { + events_.push_back( + SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + SpdyHeaderBlock(), + OK, + std::string())); + if (!on_sent_data_.is_null()) + on_sent_data_.Run(&events_.back()); + } + virtual void OnSpdyResponseHeadersUpdated( + const SpdyHeaderBlock& response_headers) OVERRIDE { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + response_headers, + OK, + std::string())); + if (!on_received_header_.is_null()) + on_received_header_.Run(&events_.back()); + } + virtual void OnSentSpdyData(size_t bytes_sent) OVERRIDE { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + SpdyHeaderBlock(), + static_cast<int>(bytes_sent), + std::string())); + if (!on_sent_data_.is_null()) + on_sent_data_.Run(&events_.back()); + } + virtual void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) OVERRIDE { + std::string buffer_data; + size_t buffer_len = 0; + if (buffer) { + buffer_len = buffer->GetRemainingSize(); + buffer_data.append(buffer->GetRemainingData(), buffer_len); + } + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + SpdyHeaderBlock(), + buffer_len, + buffer_data)); + if (!on_received_data_.is_null()) + on_received_data_.Run(&events_.back()); + } + virtual void OnCloseSpdyStream() OVERRIDE { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_CLOSE, + SpdyHeaderBlock(), + OK, + std::string())); + if (!on_close_.is_null()) + on_close_.Run(&events_.back()); + if (!callback_.is_null()) + callback_.Run(OK); + } + + const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const { + return events_; + } + + private: + std::vector<SpdyWebSocketStreamEvent> events_; + StreamEventCallback on_created_; + StreamEventCallback on_sent_headers_; + StreamEventCallback on_received_header_; + StreamEventCallback on_sent_data_; + StreamEventCallback on_received_data_; + StreamEventCallback on_close_; + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder); +}; + +} // namespace + +class SpdyWebSocketStreamTest + : public ::testing::Test, + public ::testing::WithParamInterface<NextProto> { + public: + OrderedSocketData* data() { return data_.get(); } + + void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) { + // Record the actual stream_id. + created_stream_id_ = websocket_stream_->stream_->stream_id(); + websocket_stream_->SendData(kMessageFrame, kMessageFrameLength); + } + + void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) { + websocket_stream_->SendData(kClosingFrame, kClosingFrameLength); + } + + void DoClose(SpdyWebSocketStreamEvent* event) { + websocket_stream_->Close(); + } + + void DoSync(SpdyWebSocketStreamEvent* event) { + sync_callback_.callback().Run(OK); + } + + protected: + SpdyWebSocketStreamTest() + : spdy_util_(GetParam()), + spdy_settings_id_to_set_(SETTINGS_MAX_CONCURRENT_STREAMS), + spdy_settings_flags_to_set_(SETTINGS_FLAG_PLEASE_PERSIST), + spdy_settings_value_to_set_(1), + session_deps_(GetParam()), + stream_id_(0), + created_stream_id_(0) {} + virtual ~SpdyWebSocketStreamTest() {} + + virtual void SetUp() { + host_port_pair_.set_host("example.com"); + host_port_pair_.set_port(80); + spdy_session_key_ = SpdySessionKey(host_port_pair_, + ProxyServer::Direct(), + kPrivacyModeDisabled); + + spdy_settings_to_send_[spdy_settings_id_to_set_] = + SettingsFlagsAndValue( + SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_); + } + + virtual void TearDown() { + base::MessageLoop::current()->RunUntilIdle(); + } + + void Prepare(SpdyStreamId stream_id) { + stream_id_ = stream_id; + + request_frame_.reset(spdy_util_.ConstructSpdyWebSocketSynStream( + stream_id_, + "/echo", + "example.com", + "http://example.com/wsdemo")); + + response_frame_.reset( + spdy_util_.ConstructSpdyWebSocketSynReply(stream_id_)); + + message_frame_.reset(spdy_util_.ConstructSpdyWebSocketDataFrame( + kMessageFrame, + kMessageFrameLength, + stream_id_, + false)); + + closing_frame_.reset(spdy_util_.ConstructSpdyWebSocketDataFrame( + kClosingFrame, + kClosingFrameLength, + stream_id_, + false)); + } + + void InitSession(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count) { + data_.reset(new OrderedSocketData(reads, reads_count, + writes, writes_count)); + session_deps_.socket_factory->AddSocketDataProvider(data_.get()); + http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + session_ = CreateInsecureSpdySession( + http_session_, spdy_session_key_, BoundNetLog()); + } + + void SendRequest() { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock); + spdy_util_.SetHeader("path", "/echo", headers.get()); + spdy_util_.SetHeader("host", "example.com", headers.get()); + spdy_util_.SetHeader("version", "WebSocket/13", headers.get()); + spdy_util_.SetHeader("scheme", "ws", headers.get()); + spdy_util_.SetHeader("origin", "http://example.com/wsdemo", headers.get()); + websocket_stream_->SendRequest(headers.Pass()); + } + + SpdyWebSocketTestUtil spdy_util_; + SpdySettingsIds spdy_settings_id_to_set_; + SpdySettingsFlags spdy_settings_flags_to_set_; + uint32 spdy_settings_value_to_set_; + SettingsMap spdy_settings_to_send_; + SpdySessionDependencies session_deps_; + scoped_ptr<OrderedSocketData> data_; + scoped_refptr<HttpNetworkSession> http_session_; + base::WeakPtr<SpdySession> session_; + scoped_ptr<SpdyWebSocketStream> websocket_stream_; + SpdyStreamId stream_id_; + SpdyStreamId created_stream_id_; + scoped_ptr<SpdyFrame> request_frame_; + scoped_ptr<SpdyFrame> response_frame_; + scoped_ptr<SpdyFrame> message_frame_; + scoped_ptr<SpdyFrame> closing_frame_; + HostPortPair host_port_pair_; + SpdySessionKey spdy_session_key_; + TestCompletionCallback completion_callback_; + TestCompletionCallback sync_callback_; + + static const char kMessageFrame[]; + static const char kClosingFrame[]; + static const size_t kMessageFrameLength; + static const size_t kClosingFrameLength; +}; + +INSTANTIATE_TEST_CASE_P( + NextProto, + SpdyWebSocketStreamTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +// TODO(toyoshim): Replace old framing data to new one, then use HEADERS and +// data frames. +const char SpdyWebSocketStreamTest::kMessageFrame[] = "\x81\x05hello"; +const char SpdyWebSocketStreamTest::kClosingFrame[] = "\x88\0"; +const size_t SpdyWebSocketStreamTest::kMessageFrameLength = + arraysize(SpdyWebSocketStreamTest::kMessageFrame) - 1; +const size_t SpdyWebSocketStreamTest::kClosingFrameLength = + arraysize(SpdyWebSocketStreamTest::kClosingFrame) - 1; + +TEST_P(SpdyWebSocketStreamTest, Basic) { + Prepare(1); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3), + CreateMockWrite(*closing_frame_.get(), 5) + }; + + MockRead reads[] = { + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + // Skip sequence 6 to notify closing has been sent. + CreateMockRead(*closing_frame_.get(), 7), + MockRead(SYNCHRONOUS, 0, 8) // EOF cause OnCloseSpdyStream event. + }; + + InitSession(reads, arraysize(reads), writes, arraysize(writes)); + + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamTest::DoSendClosingFrame, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); + + ASSERT_TRUE(websocket_stream_->stream_.get()); + + SendRequest(); + + completion_callback_.WaitForResult(); + + EXPECT_EQ(stream_id_, created_stream_id_); + + websocket_stream_.reset(); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_EQ(7U, events.size()); + + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[0].event_type); + EXPECT_EQ(OK, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[2].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[4].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[4].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[5].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, + events[6].event_type); + EXPECT_EQ(OK, events[6].result); + + // EOF close SPDY session. + EXPECT_FALSE( + HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +TEST_P(SpdyWebSocketStreamTest, DestructionBeforeClose) { + Prepare(1); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3) + }; + + MockRead reads[] = { + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) + }; + + InitSession(reads, arraysize(reads), writes, arraysize(writes)); + + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamTest::DoSync, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); + + SendRequest(); + + sync_callback_.WaitForResult(); + + // WebSocketStream destruction remove its SPDY stream from the session. + EXPECT_TRUE(session_->IsStreamActive(stream_id_)); + websocket_stream_.reset(); + EXPECT_FALSE(session_->IsStreamActive(stream_id_)); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_GE(4U, events.size()); + + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[0].event_type); + EXPECT_EQ(OK, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[2].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + + EXPECT_TRUE( + HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +TEST_P(SpdyWebSocketStreamTest, DestructionAfterExplicitClose) { + Prepare(1); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3), + CreateMockWrite(*closing_frame_.get(), 5) + }; + + MockRead reads[] = { + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + MockRead(ASYNC, ERR_IO_PENDING, 6) + }; + + InitSession(reads, arraysize(reads), writes, arraysize(writes)); + + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamTest::DoClose, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); + + SendRequest(); + + completion_callback_.WaitForResult(); + + // SPDY stream has already been removed from the session by Close(). + EXPECT_FALSE(session_->IsStreamActive(stream_id_)); + websocket_stream_.reset(); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_EQ(5U, events.size()); + + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[0].event_type); + EXPECT_EQ(OK, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[2].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, events[4].event_type); + + EXPECT_TRUE( + HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_)); +} + +TEST_P(SpdyWebSocketStreamTest, IOPending) { + Prepare(1); + scoped_ptr<SpdyFrame> settings_frame( + spdy_util_.ConstructSpdySettings(spdy_settings_to_send_)); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3), + CreateMockWrite(*closing_frame_.get(), 5) + }; + + MockRead reads[] = { + CreateMockRead(*settings_frame.get(), 0), + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + CreateMockRead(*closing_frame_.get(), 6), + MockRead(SYNCHRONOUS, 0, 7) // EOF cause OnCloseSpdyStream event. + }; + + DeterministicSocketData data(reads, arraysize(reads), + writes, arraysize(writes)); + session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); + http_session_ = + SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); + + session_ = CreateInsecureSpdySession( + http_session_, spdy_session_key_, BoundNetLog()); + + // Create a dummy WebSocketStream which cause ERR_IO_PENDING to another + // WebSocketStream under test. + SpdyWebSocketStreamEventRecorder block_delegate((CompletionCallback())); + + scoped_ptr<SpdyWebSocketStream> block_stream( + new SpdyWebSocketStream(session_, &block_delegate)); + BoundNetLog block_net_log; + GURL block_url("ws://example.com/block"); + ASSERT_EQ(OK, + block_stream->InitializeStream(block_url, HIGHEST, block_net_log)); + + data.RunFor(1); + + // Create a WebSocketStream under test. + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnCreated( + base::Bind(&SpdyWebSocketStreamTest::DoSync, + base::Unretained(this))); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamTest::DoSendClosingFrame, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(ERR_IO_PENDING, websocket_stream_->InitializeStream( + url, HIGHEST, net_log)); + + // Delete the fist stream to allow create the second stream. + block_stream.reset(); + ASSERT_EQ(OK, sync_callback_.WaitForResult()); + + SendRequest(); + + data.RunFor(7); + completion_callback_.WaitForResult(); + + websocket_stream_.reset(); + + const std::vector<SpdyWebSocketStreamEvent>& block_events = + block_delegate.GetSeenEvents(); + ASSERT_EQ(0U, block_events.size()); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_EQ(8U, events.size()); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED, + events[0].event_type); + EXPECT_EQ(0, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[2].event_type); + EXPECT_EQ(OK, events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[4].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[4].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[5].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[6].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[6].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, + events[7].event_type); + EXPECT_EQ(OK, events[7].result); + + // EOF close SPDY session. + EXPECT_FALSE( + HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_)); + EXPECT_TRUE(data.at_read_eof()); + EXPECT_TRUE(data.at_write_eof()); +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_websocket_test_util.cc b/chromium/net/spdy/spdy_websocket_test_util.cc new file mode 100644 index 00000000000..0872e3f116e --- /dev/null +++ b/chromium/net/spdy/spdy_websocket_test_util.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_websocket_test_util.h" + +#include "net/spdy/buffered_spdy_framer.h" +#include "net/spdy/spdy_http_utils.h" + +namespace net { + +static const int kDefaultAssociatedStreamId = 0; +static const bool kDefaultCompressed = false; +static const char* const kDefaultDataPointer = NULL; +static const uint32 kDefaultDataLength = 0; +static const char** const kDefaultExtraHeaders = NULL; +static const int kDefaultExtraHeaderCount = 0; + +SpdyWebSocketTestUtil::SpdyWebSocketTestUtil( + NextProto protocol) : spdy_util_(protocol) {} + +std::string SpdyWebSocketTestUtil::GetHeader(const SpdyHeaderBlock& headers, + const std::string& key) const { + SpdyHeaderBlock::const_iterator it = headers.find(GetHeaderKey(key)); + return (it == headers.end()) ? "" : it->second; +} + +void SpdyWebSocketTestUtil::SetHeader( + const std::string& key, + const std::string& value, + SpdyHeaderBlock* headers) const { + (*headers)[GetHeaderKey(key)] = value; +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketSynStream( + int stream_id, + const char* path, + const char* host, + const char* origin) { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + SetHeader("path", path, headers.get()); + SetHeader("host", host, headers.get()); + SetHeader("version", "WebSocket/13", headers.get()); + SetHeader("scheme", "ws", headers.get()); + SetHeader("origin", origin, headers.get()); + return spdy_util_.ConstructSpdyControlFrame(headers.Pass(), + /*compressed*/ false, + stream_id, + HIGHEST, + SYN_STREAM, + CONTROL_FLAG_NONE, + 0); +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketSynReply( + int stream_id) { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + SetHeader("status", "101", headers.get()); + return spdy_util_.ConstructSpdyControlFrame(headers.Pass(), + false, + stream_id, + LOWEST, + SYN_REPLY, + CONTROL_FLAG_NONE, + 0); +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketHandshakeRequestFrame( + scoped_ptr<SpdyHeaderBlock> headers, + SpdyStreamId stream_id, + RequestPriority request_priority) { + // SPDY SYN_STREAM control frame header. + const SpdyHeaderInfo kSynStreamHeader = { + SYN_STREAM, + stream_id, + kDefaultAssociatedStreamId, + ConvertRequestPriorityToSpdyPriority(request_priority, 2), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, + kDefaultCompressed, + RST_STREAM_INVALID, + kDefaultDataPointer, + kDefaultDataLength, + DATA_FLAG_NONE + }; + + // Construct SPDY SYN_STREAM control frame. + return spdy_util_.ConstructSpdyFrame( + kSynStreamHeader, + headers.Pass()); +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketHandshakeResponseFrame( + scoped_ptr<SpdyHeaderBlock> headers, + SpdyStreamId stream_id, + RequestPriority request_priority) { + // SPDY SYN_REPLY control frame header. + const SpdyHeaderInfo kSynReplyHeader = { + SYN_REPLY, + stream_id, + kDefaultAssociatedStreamId, + ConvertRequestPriorityToSpdyPriority(request_priority, 2), + kSpdyCredentialSlotUnused, + CONTROL_FLAG_NONE, + kDefaultCompressed, + RST_STREAM_INVALID, + kDefaultDataPointer, + kDefaultDataLength, + DATA_FLAG_NONE + }; + + // Construct SPDY SYN_REPLY control frame. + return spdy_util_.ConstructSpdyFrame( + kSynReplyHeader, + headers.Pass()); +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketHeadersFrame( + int stream_id, + const char* length, + bool fin) { + scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock()); + SetHeader("opcode", "1", headers.get()); // text frame + SetHeader("length", length, headers.get()); + SetHeader("fin", fin ? "1" : "0", headers.get()); + return spdy_util_.ConstructSpdyControlFrame(headers.Pass(), + /*compression*/ false, + stream_id, + LOWEST, + HEADERS, + CONTROL_FLAG_NONE, + 0); +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketDataFrame( + const char* data, + int len, + SpdyStreamId stream_id, + bool fin) { + + // Construct SPDY data frame. + BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); + return framer.CreateDataFrame( + stream_id, + data, + len, + fin ? DATA_FLAG_FIN : DATA_FLAG_NONE); +} + +SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdySettings( + const SettingsMap& settings) const { + return spdy_util_.ConstructSpdySettings(settings); +} + +SpdyMajorVersion SpdyWebSocketTestUtil::spdy_version() const { + return spdy_util_.spdy_version(); +} + +std::string SpdyWebSocketTestUtil::GetHeaderKey( + const std::string& key) const { + return (spdy_util_.is_spdy2() ? "" : ":") + key; +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_websocket_test_util.h b/chromium/net/spdy/spdy_websocket_test_util.h new file mode 100644 index 00000000000..7a9a59e96e8 --- /dev/null +++ b/chromium/net/spdy/spdy_websocket_test_util.h @@ -0,0 +1,77 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ +#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ + +#include "net/base/request_priority.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_test_util_common.h" + +namespace net { + +class SpdyWebSocketTestUtil { + public: + explicit SpdyWebSocketTestUtil(NextProto protocol); + + // Returns the value corresponding to the given key (passed through + // GetHeaderKey()), or the empty string if none exists. + std::string GetHeader(const SpdyHeaderBlock& headers, + const std::string& key) const; + + // Adds the given key/value pair to |headers|, passing the key + // through GetHeaderKey(). + void SetHeader(const std::string& key, + const std::string& value, + SpdyHeaderBlock* headers) const; + + // Constructs a standard SPDY SYN_STREAM frame for WebSocket over + // SPDY opening handshake. + SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id, + const char* path, + const char* host, + const char* origin); + + // Constructs a standard SPDY SYN_REPLY packet to match the + // WebSocket over SPDY opening handshake. + SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id); + + // Constructs a WebSocket over SPDY handshake request packet. + SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame( + scoped_ptr<SpdyHeaderBlock> headers, + SpdyStreamId stream_id, + RequestPriority request_priority); + + // Constructs a WebSocket over SPDY handshake response packet. + SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame( + scoped_ptr<SpdyHeaderBlock> headers, + SpdyStreamId stream_id, + RequestPriority request_priority); + + // Constructs a SPDY HEADERS frame for a WebSocket frame over SPDY. + SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id, + const char* length, + bool fin); + + // Constructs a WebSocket over SPDY data packet. + SpdyFrame* ConstructSpdyWebSocketDataFrame(const char* data, + int len, + SpdyStreamId stream_id, + bool fin); + + // Forwards to |spdy_util_|. + SpdyFrame* ConstructSpdySettings(const SettingsMap& settings) const; + SpdyMajorVersion spdy_version() const; + + private: + // Modify the header key based on the SPDY version and return it. + std::string GetHeaderKey(const std::string& key) const; + + SpdyTestUtil spdy_util_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ diff --git a/chromium/net/spdy/spdy_write_queue.cc b/chromium/net/spdy/spdy_write_queue.cc new file mode 100644 index 00000000000..2ac42413232 --- /dev/null +++ b/chromium/net/spdy/spdy_write_queue.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_write_queue.h" + +#include <cstddef> + +#include "base/logging.h" +#include "net/spdy/spdy_buffer.h" +#include "net/spdy/spdy_buffer_producer.h" +#include "net/spdy/spdy_stream.h" + +namespace net { + +SpdyWriteQueue::PendingWrite::PendingWrite() : frame_producer(NULL) {} + +SpdyWriteQueue::PendingWrite::PendingWrite( + SpdyFrameType frame_type, + SpdyBufferProducer* frame_producer, + const base::WeakPtr<SpdyStream>& stream) + : frame_type(frame_type), + frame_producer(frame_producer), + stream(stream), + has_stream(stream.get() != NULL) {} + +SpdyWriteQueue::PendingWrite::~PendingWrite() {} + +SpdyWriteQueue::SpdyWriteQueue() {} + +SpdyWriteQueue::~SpdyWriteQueue() { + Clear(); +} + +bool SpdyWriteQueue::IsEmpty() const { + for (int i = 0; i < NUM_PRIORITIES; i++) { + if (!queue_[i].empty()) + return false; + } + return true; +} + +void SpdyWriteQueue::Enqueue(RequestPriority priority, + SpdyFrameType frame_type, + scoped_ptr<SpdyBufferProducer> frame_producer, + const base::WeakPtr<SpdyStream>& stream) { + if (stream.get()) + DCHECK_EQ(stream->priority(), priority); + queue_[priority].push_back( + PendingWrite(frame_type, frame_producer.release(), stream)); +} + +bool SpdyWriteQueue::Dequeue(SpdyFrameType* frame_type, + scoped_ptr<SpdyBufferProducer>* frame_producer, + base::WeakPtr<SpdyStream>* stream) { + for (int i = NUM_PRIORITIES - 1; i >= 0; --i) { + if (!queue_[i].empty()) { + PendingWrite pending_write = queue_[i].front(); + queue_[i].pop_front(); + *frame_type = pending_write.frame_type; + frame_producer->reset(pending_write.frame_producer); + *stream = pending_write.stream; + if (pending_write.has_stream) + DCHECK(stream->get()); + return true; + } + } + return false; +} + +void SpdyWriteQueue::RemovePendingWritesForStream( + const base::WeakPtr<SpdyStream>& stream) { + DCHECK(stream.get()); + if (DCHECK_IS_ON()) { + // |stream| should not have pending writes in a queue not matching + // its priority. + for (int i = 0; i < NUM_PRIORITIES; ++i) { + if (stream->priority() == i) + continue; + for (std::deque<PendingWrite>::const_iterator it = queue_[i].begin(); + it != queue_[i].end(); ++it) { + DCHECK_NE(it->stream.get(), stream.get()); + } + } + } + + // Do the actual deletion and removal, preserving FIFO-ness. + std::deque<PendingWrite>* queue = &queue_[stream->priority()]; + std::deque<PendingWrite>::iterator out_it = queue->begin(); + for (std::deque<PendingWrite>::const_iterator it = queue->begin(); + it != queue->end(); ++it) { + if (it->stream.get() == stream.get()) { + delete it->frame_producer; + } else { + *out_it = *it; + ++out_it; + } + } + queue->erase(out_it, queue->end()); +} + +void SpdyWriteQueue::RemovePendingWritesForStreamsAfter( + SpdyStreamId last_good_stream_id) { + for (int i = 0; i < NUM_PRIORITIES; ++i) { + // Do the actual deletion and removal, preserving FIFO-ness. + std::deque<PendingWrite>* queue = &queue_[i]; + std::deque<PendingWrite>::iterator out_it = queue->begin(); + for (std::deque<PendingWrite>::const_iterator it = queue->begin(); + it != queue->end(); ++it) { + if (it->stream.get() && (it->stream->stream_id() > last_good_stream_id || + it->stream->stream_id() == 0)) { + delete it->frame_producer; + } else { + *out_it = *it; + ++out_it; + } + } + queue->erase(out_it, queue->end()); + } +} + +void SpdyWriteQueue::Clear() { + for (int i = 0; i < NUM_PRIORITIES; ++i) { + for (std::deque<PendingWrite>::iterator it = queue_[i].begin(); + it != queue_[i].end(); ++it) { + delete it->frame_producer; + } + queue_[i].clear(); + } +} + +} // namespace net diff --git a/chromium/net/spdy/spdy_write_queue.h b/chromium/net/spdy/spdy_write_queue.h new file mode 100644 index 00000000000..3bceb298c01 --- /dev/null +++ b/chromium/net/spdy/spdy_write_queue.h @@ -0,0 +1,89 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SPDY_SPDY_WRITE_QUEUE_H_ +#define NET_SPDY_SPDY_WRITE_QUEUE_H_ + +#include <deque> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "net/base/net_export.h" +#include "net/base/request_priority.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +class SpdyBuffer; +class SpdyBufferProducer; +class SpdyStream; + +// A queue of SpdyBufferProducers to produce frames to write. Ordered +// by priority, and then FIFO. +class NET_EXPORT_PRIVATE SpdyWriteQueue { + public: + SpdyWriteQueue(); + ~SpdyWriteQueue(); + + // Returns whether there is anything in the write queue, + // i.e. whether the next call to Dequeue will return true. + bool IsEmpty() const; + + // Enqueues the given frame producer of the given type at the given + // priority associated with the given stream, which may be NULL if + // the frame producer is not associated with a stream. If |stream| + // is non-NULL, its priority must be equal to |priority|, and it + // must remain non-NULL until the write is dequeued or removed. + void Enqueue(RequestPriority priority, + SpdyFrameType frame_type, + scoped_ptr<SpdyBufferProducer> frame_producer, + const base::WeakPtr<SpdyStream>& stream); + + // Dequeues the frame producer with the highest priority that was + // enqueued the earliest and its associated stream. Returns true and + // fills in |frame_type|, |frame_producer|, and |stream| if + // successful -- otherwise, just returns false. + bool Dequeue(SpdyFrameType* frame_type, + scoped_ptr<SpdyBufferProducer>* frame_producer, + base::WeakPtr<SpdyStream>* stream); + + // Removes all pending writes for the given stream, which must be + // non-NULL. + void RemovePendingWritesForStream(const base::WeakPtr<SpdyStream>& stream); + + // Removes all pending writes for streams after |last_good_stream_id| + // and streams with no stream id. + void RemovePendingWritesForStreamsAfter(SpdyStreamId last_good_stream_id); + + // Removes all pending writes. + void Clear(); + + private: + // A struct holding a frame producer and its associated stream. + struct PendingWrite { + SpdyFrameType frame_type; + // This has to be a raw pointer since we store this in an STL + // container. + SpdyBufferProducer* frame_producer; + base::WeakPtr<SpdyStream> stream; + // Whether |stream| was non-NULL when enqueued. + bool has_stream; + + PendingWrite(); + PendingWrite(SpdyFrameType frame_type, + SpdyBufferProducer* frame_producer, + const base::WeakPtr<SpdyStream>& stream); + ~PendingWrite(); + }; + + // The actual write queue, binned by priority. + std::deque<PendingWrite> queue_[NUM_PRIORITIES]; + + DISALLOW_COPY_AND_ASSIGN(SpdyWriteQueue); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_WRITE_QUEUE_H_ diff --git a/chromium/net/spdy/spdy_write_queue_unittest.cc b/chromium/net/spdy/spdy_write_queue_unittest.cc new file mode 100644 index 00000000000..6d6cb3cd3b5 --- /dev/null +++ b/chromium/net/spdy/spdy_write_queue_unittest.cc @@ -0,0 +1,252 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_write_queue.h" + +#include <cstddef> +#include <cstring> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "net/base/net_log.h" +#include "net/base/request_priority.h" +#include "net/spdy/spdy_buffer_producer.h" +#include "net/spdy/spdy_stream.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace net { + +namespace { + +class SpdyWriteQueueTest : public ::testing::Test {}; + +// Makes a SpdyFrameProducer producing a frame with the data in the +// given string. +scoped_ptr<SpdyBufferProducer> StringToProducer(const std::string& s) { + scoped_ptr<char[]> data(new char[s.size()]); + std::memcpy(data.get(), s.data(), s.size()); + return scoped_ptr<SpdyBufferProducer>( + new SimpleBufferProducer( + scoped_ptr<SpdyBuffer>( + new SpdyBuffer( + scoped_ptr<SpdyFrame>( + new SpdyFrame(data.release(), s.size(), true)))))); +} + +// Makes a SpdyBufferProducer producing a frame with the data in the +// given int (converted to a string). +scoped_ptr<SpdyBufferProducer> IntToProducer(int i) { + return StringToProducer(base::IntToString(i)); +} + +// Produces a frame with the given producer and returns a copy of its +// data as a string. +std::string ProducerToString(scoped_ptr<SpdyBufferProducer> producer) { + scoped_ptr<SpdyBuffer> buffer = producer->ProduceBuffer(); + return std::string(buffer->GetRemainingData(), buffer->GetRemainingSize()); +} + +// Produces a frame with the given producer and returns a copy of its +// data as an int (converted from a string). +int ProducerToInt(scoped_ptr<SpdyBufferProducer> producer) { + int i = 0; + EXPECT_TRUE(base::StringToInt(ProducerToString(producer.Pass()), &i)); + return i; +} + +// Makes a SpdyStream with the given priority and a NULL SpdySession +// -- be careful to not call any functions that expect the session to +// be there. +SpdyStream* MakeTestStream(RequestPriority priority) { + return new SpdyStream( + SPDY_BIDIRECTIONAL_STREAM, base::WeakPtr<SpdySession>(), + GURL(), priority, 0, 0, BoundNetLog()); +} + +// Add some frame producers of different priority. The producers +// should be dequeued in priority order with their associated stream. +TEST_F(SpdyWriteQueueTest, DequeuesByPriority) { + SpdyWriteQueue write_queue; + + scoped_ptr<SpdyBufferProducer> producer_low = StringToProducer("LOW"); + scoped_ptr<SpdyBufferProducer> producer_medium = StringToProducer("MEDIUM"); + scoped_ptr<SpdyBufferProducer> producer_highest = StringToProducer("HIGHEST"); + + scoped_ptr<SpdyStream> stream_medium(MakeTestStream(MEDIUM)); + scoped_ptr<SpdyStream> stream_highest(MakeTestStream(HIGHEST)); + + // A NULL stream should still work. + write_queue.Enqueue( + LOW, SYN_STREAM, producer_low.Pass(), base::WeakPtr<SpdyStream>()); + write_queue.Enqueue( + MEDIUM, SYN_REPLY, producer_medium.Pass(), stream_medium->GetWeakPtr()); + write_queue.Enqueue( + HIGHEST, RST_STREAM, producer_highest.Pass(), + stream_highest->GetWeakPtr()); + + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(RST_STREAM, frame_type); + EXPECT_EQ("HIGHEST", ProducerToString(frame_producer.Pass())); + EXPECT_EQ(stream_highest, stream.get()); + + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(SYN_REPLY, frame_type); + EXPECT_EQ("MEDIUM", ProducerToString(frame_producer.Pass())); + EXPECT_EQ(stream_medium, stream.get()); + + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(SYN_STREAM, frame_type); + EXPECT_EQ("LOW", ProducerToString(frame_producer.Pass())); + EXPECT_EQ(NULL, stream.get()); + + EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); +} + +// Add some frame producers with the same priority. The producers +// should be dequeued in FIFO order with their associated stream. +TEST_F(SpdyWriteQueueTest, DequeuesFIFO) { + SpdyWriteQueue write_queue; + + scoped_ptr<SpdyBufferProducer> producer1 = IntToProducer(1); + scoped_ptr<SpdyBufferProducer> producer2 = IntToProducer(2); + scoped_ptr<SpdyBufferProducer> producer3 = IntToProducer(3); + + scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY)); + scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY)); + scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY)); + + write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, producer1.Pass(), + stream1->GetWeakPtr()); + write_queue.Enqueue(DEFAULT_PRIORITY, SYN_REPLY, producer2.Pass(), + stream2->GetWeakPtr()); + write_queue.Enqueue(DEFAULT_PRIORITY, RST_STREAM, producer3.Pass(), + stream3->GetWeakPtr()); + + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(SYN_STREAM, frame_type); + EXPECT_EQ(1, ProducerToInt(frame_producer.Pass())); + EXPECT_EQ(stream1, stream.get()); + + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(SYN_REPLY, frame_type); + EXPECT_EQ(2, ProducerToInt(frame_producer.Pass())); + EXPECT_EQ(stream2, stream.get()); + + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(RST_STREAM, frame_type); + EXPECT_EQ(3, ProducerToInt(frame_producer.Pass())); + EXPECT_EQ(stream3, stream.get()); + + EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); +} + +// Enqueue a bunch of writes and then call +// RemovePendingWritesForStream() on one of the streams. No dequeued +// write should be for that stream. +TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStream) { + SpdyWriteQueue write_queue; + + scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY)); + scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY)); + + for (int i = 0; i < 100; ++i) { + base::WeakPtr<SpdyStream> stream = + (((i % 3) == 0) ? stream1 : stream2)->GetWeakPtr(); + write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), stream); + } + + write_queue.RemovePendingWritesForStream(stream2->GetWeakPtr()); + + for (int i = 0; i < 100; i += 3) { + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); + EXPECT_EQ(SYN_STREAM, frame_type); + EXPECT_EQ(i, ProducerToInt(frame_producer.Pass())); + EXPECT_EQ(stream1, stream.get()); + } + + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); +} + +// Enqueue a bunch of writes and then call +// RemovePendingWritesForStreamsAfter(). No dequeued write should be for +// those streams without a stream id, or with a stream_id after that +// argument. +TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStreamsAfter) { + SpdyWriteQueue write_queue; + + scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY)); + stream1->set_stream_id(1); + scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY)); + stream2->set_stream_id(3); + scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY)); + stream3->set_stream_id(5); + // No stream id assigned. + scoped_ptr<SpdyStream> stream4(MakeTestStream(DEFAULT_PRIORITY)); + base::WeakPtr<SpdyStream> streams[] = { + stream1->GetWeakPtr(), stream2->GetWeakPtr(), + stream3->GetWeakPtr(), stream4->GetWeakPtr() + }; + + for (int i = 0; i < 100; ++i) { + write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), + streams[i % arraysize(streams)]); + } + + write_queue.RemovePendingWritesForStreamsAfter(stream1->stream_id()); + + for (int i = 0; i < 100; i += arraysize(streams)) { + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)) + << "Unable to Dequeue i: " << i; + EXPECT_EQ(SYN_STREAM, frame_type); + EXPECT_EQ(i, ProducerToInt(frame_producer.Pass())); + EXPECT_EQ(stream1, stream.get()); + } + + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); +} + +// Enqueue a bunch of writes and then call Clear(). The write queue +// should clean up the memory properly, and Dequeue() should return +// false. +TEST_F(SpdyWriteQueueTest, Clear) { + SpdyWriteQueue write_queue; + + for (int i = 0; i < 100; ++i) { + write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), + base::WeakPtr<SpdyStream>()); + } + + write_queue.Clear(); + + SpdyFrameType frame_type = DATA; + scoped_ptr<SpdyBufferProducer> frame_producer; + base::WeakPtr<SpdyStream> stream; + EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); +} + +} // namespace + +} // namespace net |