summaryrefslogtreecommitdiff
path: root/chromium/net/spdy
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/net/spdy
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/net/spdy')
-rw-r--r--chromium/net/spdy/buffered_spdy_framer.cc331
-rw-r--r--chromium/net/spdy/buffered_spdy_framer.h262
-rw-r--r--chromium/net/spdy/buffered_spdy_framer_unittest.cc283
-rw-r--r--chromium/net/spdy/spdy_bitmasks.h31
-rw-r--r--chromium/net/spdy/spdy_buffer.cc105
-rw-r--r--chromium/net/spdy/spdy_buffer.h104
-rw-r--r--chromium/net/spdy/spdy_buffer_producer.cc27
-rw-r--r--chromium/net/spdy/spdy_buffer_producer.h50
-rw-r--r--chromium/net/spdy/spdy_buffer_unittest.cc137
-rw-r--r--chromium/net/spdy/spdy_credential_builder.cc86
-rw-r--r--chromium/net/spdy/spdy_credential_builder.h36
-rw-r--r--chromium/net/spdy/spdy_credential_builder_unittest.cc149
-rw-r--r--chromium/net/spdy/spdy_credential_state.cc69
-rw-r--r--chromium/net/spdy/spdy_credential_state.h56
-rw-r--r--chromium/net/spdy/spdy_credential_state_unittest.cc108
-rw-r--r--chromium/net/spdy/spdy_frame_builder.cc191
-rw-r--r--chromium/net/spdy/spdy_frame_builder.h129
-rw-r--r--chromium/net/spdy/spdy_frame_builder_test.cc62
-rw-r--r--chromium/net/spdy/spdy_frame_reader.cc184
-rw-r--r--chromium/net/spdy/spdy_frame_reader.h123
-rw-r--r--chromium/net/spdy/spdy_frame_reader_test.cc249
-rw-r--r--chromium/net/spdy/spdy_framer.cc2361
-rw-r--r--chromium/net/spdy/spdy_framer.h716
-rw-r--r--chromium/net/spdy/spdy_framer_test.cc4592
-rw-r--r--chromium/net/spdy/spdy_header_block.cc52
-rw-r--r--chromium/net/spdy/spdy_header_block.h36
-rw-r--r--chromium/net/spdy/spdy_header_block_unittest.cc31
-rw-r--r--chromium/net/spdy/spdy_http_stream.cc532
-rw-r--r--chromium/net/spdy/spdy_http_stream.h167
-rw-r--r--chromium/net/spdy/spdy_http_stream_unittest.cc898
-rw-r--r--chromium/net/spdy/spdy_http_utils.cc203
-rw-r--r--chromium/net/spdy/spdy_http_utils.h59
-rw-r--r--chromium/net/spdy/spdy_http_utils_unittest.cc67
-rw-r--r--chromium/net/spdy/spdy_network_transaction_unittest.cc6405
-rw-r--r--chromium/net/spdy/spdy_priority_forest.h527
-rw-r--r--chromium/net/spdy/spdy_priority_forest_test.cc282
-rw-r--r--chromium/net/spdy/spdy_protocol.cc82
-rw-r--r--chromium/net/spdy/spdy_protocol.h804
-rw-r--r--chromium/net/spdy/spdy_protocol_test.cc86
-rw-r--r--chromium/net/spdy/spdy_proxy_client_socket.cc520
-rw-r--r--chromium/net/spdy/spdy_proxy_client_socket.h175
-rw-r--r--chromium/net/spdy/spdy_proxy_client_socket_unittest.cc1435
-rw-r--r--chromium/net/spdy/spdy_read_queue.cc59
-rw-r--r--chromium/net/spdy/spdy_read_queue.h51
-rw-r--r--chromium/net/spdy/spdy_read_queue_unittest.cc106
-rw-r--r--chromium/net/spdy/spdy_session.cc2917
-rw-r--r--chromium/net/spdy/spdy_session.h1143
-rw-r--r--chromium/net/spdy/spdy_session_key.cc50
-rw-r--r--chromium/net/spdy/spdy_session_key.h58
-rw-r--r--chromium/net/spdy/spdy_session_pool.cc399
-rw-r--r--chromium/net/spdy/spdy_session_pool.h235
-rw-r--r--chromium/net/spdy/spdy_session_pool_unittest.cc494
-rw-r--r--chromium/net/spdy/spdy_session_test_util.cc38
-rw-r--r--chromium/net/spdy/spdy_session_test_util.h46
-rw-r--r--chromium/net/spdy/spdy_session_unittest.cc4116
-rw-r--r--chromium/net/spdy/spdy_stream.cc1035
-rw-r--r--chromium/net/spdy/spdy_stream.h557
-rw-r--r--chromium/net/spdy/spdy_stream_test_util.cc146
-rw-r--r--chromium/net/spdy/spdy_stream_test_util.h127
-rw-r--r--chromium/net/spdy/spdy_stream_unittest.cc999
-rw-r--r--chromium/net/spdy/spdy_test_util_common.cc1235
-rw-r--r--chromium/net/spdy/spdy_test_util_common.h538
-rw-r--r--chromium/net/spdy/spdy_test_utils.cc129
-rw-r--r--chromium/net/spdy/spdy_test_utils.h34
-rw-r--r--chromium/net/spdy/spdy_websocket_stream.cc130
-rw-r--r--chromium/net/spdy/spdy_websocket_stream.h103
-rw-r--r--chromium/net/spdy/spdy_websocket_stream_unittest.cc606
-rw-r--r--chromium/net/spdy/spdy_websocket_test_util.cc164
-rw-r--r--chromium/net/spdy/spdy_websocket_test_util.h77
-rw-r--r--chromium/net/spdy/spdy_write_queue.cc132
-rw-r--r--chromium/net/spdy/spdy_write_queue.h89
-rw-r--r--chromium/net/spdy/spdy_write_queue_unittest.cc252
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(&current_frame_stream_id_);
+ }
+ DCHECK(successful_read);
+
+ successful_read = reader->ReadUInt8(&current_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(&current_frame_flags_);
+ DCHECK(successful_read);
+
+ successful_read = reader->ReadUInt31(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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