Initial import of Cobalt 2.8885 2016-07-27
diff --git a/src/net/spdy/buffered_spdy_framer.cc b/src/net/spdy/buffered_spdy_framer.cc
new file mode 100644
index 0000000..50a0d93
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer.cc
@@ -0,0 +1,301 @@
+// 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 {
+
+BufferedSpdyFramer::BufferedSpdyFramer(int 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::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;
+ bool parsed_headers = spdy_framer_.ParseHeaderBlockInBuffer(
+ header_buffer_, header_buffer_used_, &headers);
+ if (!parsed_headers) {
+ 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(const SpdyDataFrame* frame) {
+ frames_received_++;
+ header_stream_id_ = frame->stream_id();
+}
+
+void BufferedSpdyFramer::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags) {
+ visitor_->OnStreamFrameData(stream_id, data, len, flags);
+}
+
+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,
+ SpdyStatusCodes 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,
+ int delta_window_size) {
+ visitor_->OnWindowUpdate(stream_id, delta_window_size);
+}
+
+void BufferedSpdyFramer::OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) {
+ visitor_->OnControlFrameCompressed(uncompressed_frame, compressed_frame);
+}
+
+
+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 spdy_framer_.MessageFullyRead();
+}
+
+bool BufferedSpdyFramer::HasError() {
+ return spdy_framer_.HasError();
+}
+
+SpdySynStreamControlFrame* 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);
+}
+
+SpdySynReplyControlFrame* BufferedSpdyFramer::CreateSynReply(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ return spdy_framer_.CreateSynReply(stream_id, flags, compressed, headers);
+}
+
+SpdyRstStreamControlFrame* BufferedSpdyFramer::CreateRstStream(
+ SpdyStreamId stream_id,
+ SpdyStatusCodes status) const {
+ return spdy_framer_.CreateRstStream(stream_id, status);
+}
+
+SpdySettingsControlFrame* BufferedSpdyFramer::CreateSettings(
+ const SettingsMap& values) const {
+ return spdy_framer_.CreateSettings(values);
+}
+
+SpdyPingControlFrame* BufferedSpdyFramer::CreatePingFrame(
+ uint32 unique_id) const {
+ return spdy_framer_.CreatePingFrame(unique_id);
+}
+
+SpdyGoAwayControlFrame* BufferedSpdyFramer::CreateGoAway(
+ SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const {
+ return spdy_framer_.CreateGoAway(last_accepted_stream_id, status);
+}
+
+SpdyHeadersControlFrame* BufferedSpdyFramer::CreateHeaders(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ return spdy_framer_.CreateHeaders(stream_id, flags, compressed, headers);
+}
+
+SpdyWindowUpdateControlFrame* BufferedSpdyFramer::CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const {
+ return spdy_framer_.CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+SpdyCredentialControlFrame* BufferedSpdyFramer::CreateCredentialFrame(
+ const SpdyCredential& credential) const {
+ return spdy_framer_.CreateCredentialFrame(credential);
+}
+
+SpdyDataFrame* 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();
+}
+
+bool BufferedSpdyFramer::IsCompressible(const SpdyFrame& frame) const {
+ return spdy_framer_.IsCompressible(frame);
+}
+
+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/src/net/spdy/buffered_spdy_framer.h b/src/net/spdy/buffered_spdy_framer.h
new file mode 100644
index 0000000..ea64151
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer.h
@@ -0,0 +1,219 @@
+// 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/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+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.
+ // 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,
+ SpdyDataFlags flags) = 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, SpdyStatusCodes 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,
+ int delta_window_size) = 0;
+
+ // Called after a control frame has been compressed to allow the visitor
+ // to record compression statistics.
+ virtual void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) = 0;
+
+ protected:
+ virtual ~BufferedSpdyFramerVisitorInterface() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramerVisitorInterface);
+};
+
+class NET_EXPORT_PRIVATE BufferedSpdyFramer
+ : public SpdyFramerVisitorInterface {
+ public:
+ BufferedSpdyFramer(int 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);
+
+ // 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,
+ SpdyDataFlags flags) OVERRIDE;
+ virtual void OnSetting(
+ SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
+ virtual void OnPing(uint32 unique_id) OVERRIDE;
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status) OVERRIDE;
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE;
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ int delta_window_size) OVERRIDE;
+ virtual void OnDataFrameHeader(const SpdyDataFrame* frame) OVERRIDE;
+
+ // Called after a control frame has been compressed to allow the visitor
+ // to record compression statistics.
+ virtual void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) 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();
+ SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status) const;
+ SpdySettingsControlFrame* CreateSettings(const SettingsMap& values) const;
+ SpdyPingControlFrame* CreatePingFrame(uint32 unique_id) const;
+ SpdyGoAwayControlFrame* CreateGoAway(
+ SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const;
+ SpdyHeadersControlFrame* CreateHeaders(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdyWindowUpdateControlFrame* CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const;
+ SpdyCredentialControlFrame* CreateCredentialFrame(
+ const SpdyCredential& credential) const;
+ SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id,
+ const char* data,
+ uint32 len,
+ SpdyDataFlags flags);
+ SpdyPriority GetHighestPriority() const;
+ bool IsCompressible(const SpdyFrame& frame) const;
+
+ 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 {
+ SpdyControlType 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/src/net/spdy/buffered_spdy_framer_spdy2_unittest.cc b/src/net/spdy/buffered_spdy_framer_spdy2_unittest.cc
new file mode 100644
index 0000000..4bbdf6e
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer_spdy2_unittest.cc
@@ -0,0 +1,268 @@
+// 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_spdy2.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+namespace {
+
+class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface {
+ public:
+ TestBufferedSpdyVisitor()
+ : buffered_spdy_framer_(2, true),
+ error_count_(0),
+ setting_count_(0),
+ syn_frame_count_(0),
+ syn_reply_frame_count_(0),
+ headers_frame_count_(0),
+ header_stream_id_(-1) {
+ }
+
+ void OnError(SpdyFramer::SpdyError error_code) {
+ LOG(INFO) << "SpdyFramer Error: " << error_code;
+ error_count_++;
+ }
+
+ void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) {
+ LOG(INFO) << "SpdyFramer Error on stream: " << stream_id << " "
+ << description;
+ error_count_++;
+ }
+
+ void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ syn_frame_count_++;
+ headers_ = headers;
+ }
+
+ void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ syn_reply_frame_count_++;
+ headers_ = headers;
+ }
+
+ void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ headers_frame_count_++;
+ headers_ = headers;
+ }
+
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags) {
+ LOG(FATAL) << "Unexpected OnStreamFrameData call.";
+ }
+
+ void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+ setting_count_++;
+ }
+
+ void OnPing(uint32 unique_id) {}
+
+ void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {}
+
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {}
+
+ void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) {
+ }
+
+ bool OnCredentialFrameData(const char*, size_t) {
+ LOG(FATAL) << "Unexpected OnCredentialFrameData call.";
+ return false;
+ }
+
+ void OnDataFrameHeader(const SpdyDataFrame* frame) {
+ LOG(FATAL) << "Unexpected OnDataFrameHeader call.";
+ }
+
+ void OnRstStream(const SpdyRstStreamControlFrame& frame) {}
+ void OnGoAway(const SpdyGoAwayControlFrame& frame) {}
+ void OnPing(const SpdyPingControlFrame& frame) {}
+ void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {}
+ void OnCredential(const SpdyCredentialControlFrame& 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;
+ if (buffered_spdy_framer_.state() == SpdyFramer::SPDY_DONE)
+ buffered_spdy_framer_.Reset();
+ }
+ }
+
+ 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 BufferedSpdyFramerSpdy2Test : public PlatformTest {
+ 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;
+ }
+};
+
+TEST_F(BufferedSpdyFramerSpdy2Test, OnSetting) {
+ SpdyFramer framer(2);
+ 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;
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.setting_count_);
+}
+
+TEST_F(BufferedSpdyFramerSpdy2Test, ReadSynStreamHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["aa"] = "vv";
+ headers["bb"] = "ww";
+ BufferedSpdyFramer framer(2, true);
+ scoped_ptr<SpdySynStreamControlFrame> 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;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+ 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_F(BufferedSpdyFramerSpdy2Test, ReadSynReplyHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ BufferedSpdyFramer framer(2, true);
+ scoped_ptr<SpdySynReplyControlFrame> control_frame(
+ framer.CreateSynReply(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+ 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_F(BufferedSpdyFramerSpdy2Test, ReadHeadersHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ BufferedSpdyFramer framer(2, true);
+ scoped_ptr<SpdyHeadersControlFrame> control_frame(
+ framer.CreateHeaders(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+ 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/src/net/spdy/buffered_spdy_framer_spdy3_unittest.cc b/src/net/spdy/buffered_spdy_framer_spdy3_unittest.cc
new file mode 100644
index 0000000..ca9edf3
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer_spdy3_unittest.cc
@@ -0,0 +1,269 @@
+// 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_spdy3.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+namespace {
+
+class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface {
+ public:
+ TestBufferedSpdyVisitor()
+ : buffered_spdy_framer_(3, true),
+ error_count_(0),
+ setting_count_(0),
+ syn_frame_count_(0),
+ syn_reply_frame_count_(0),
+ headers_frame_count_(0),
+ header_stream_id_(-1) {
+ }
+
+ void OnError(SpdyFramer::SpdyError error_code) {
+ LOG(INFO) << "SpdyFramer Error: " << error_code;
+ error_count_++;
+ }
+
+ void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) {
+ LOG(INFO) << "SpdyFramer Error on stream: " << stream_id << " "
+ << description;
+ error_count_++;
+ }
+
+ void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ syn_frame_count_++;
+ headers_ = headers;
+ }
+
+ void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ syn_reply_frame_count_++;
+ headers_ = headers;
+ }
+
+ void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ headers_frame_count_++;
+ headers_ = headers;
+ }
+
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags) {
+ LOG(FATAL) << "Unexpected OnStreamFrameData call.";
+ }
+
+ void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+ setting_count_++;
+ }
+
+ void OnPing(uint32 unique_id) {}
+
+ void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {}
+
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {}
+
+ void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) {
+ }
+
+ bool OnCredentialFrameData(const char*, size_t) {
+ LOG(FATAL) << "Unexpected OnCredentialFrameData call.";
+ return false;
+ }
+
+ void OnDataFrameHeader(const SpdyDataFrame* frame) {
+ LOG(FATAL) << "Unexpected OnDataFrameHeader call.";
+ }
+
+ void OnRstStream(const SpdyRstStreamControlFrame& frame) {}
+ void OnGoAway(const SpdyGoAwayControlFrame& frame) {}
+ void OnPing(const SpdyPingControlFrame& frame) {}
+ void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {}
+ void OnCredential(const SpdyCredentialControlFrame& 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;
+ if (buffered_spdy_framer_.state() == SpdyFramer::SPDY_DONE)
+ buffered_spdy_framer_.Reset();
+ }
+ }
+
+ 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 BufferedSpdyFramerSpdy3Test : public PlatformTest {
+ 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;
+ }
+};
+
+TEST_F(BufferedSpdyFramerSpdy3Test, OnSetting) {
+ SpdyFramer framer(3);
+ framer.set_enable_compression(false);
+ 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;
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.setting_count_);
+}
+
+TEST_F(BufferedSpdyFramerSpdy3Test, ReadSynStreamHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["aa"] = "vv";
+ headers["bb"] = "ww";
+ BufferedSpdyFramer framer(3, true);
+ scoped_ptr<SpdySynStreamControlFrame> 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;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+ 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_F(BufferedSpdyFramerSpdy3Test, ReadSynReplyHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ BufferedSpdyFramer framer(3, true);
+ scoped_ptr<SpdySynReplyControlFrame> control_frame(
+ framer.CreateSynReply(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+ 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_F(BufferedSpdyFramerSpdy3Test, ReadHeadersHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ BufferedSpdyFramer framer(3, true);
+ scoped_ptr<SpdyHeadersControlFrame> control_frame(
+ framer.CreateHeaders(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+ 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/src/net/spdy/spdy_bitmasks.h b/src/net/spdy/spdy_bitmasks.h
new file mode 100644
index 0000000..72fb948
--- /dev/null
+++ b/src/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/src/net/spdy/spdy_credential_builder.cc b/src/net/spdy/spdy_credential_builder.cc
new file mode 100644
index 0000000..8ddda97
--- /dev/null
+++ b/src/net/spdy/spdy_credential_builder.cc
@@ -0,0 +1,90 @@
+// 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/string_piece.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/base/net_errors.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_framer.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,
+ SSLClientCertType type,
+ const std::string& key,
+ const std::string& cert,
+ size_t slot,
+ SpdyCredential* credential) {
+ if (type != CLIENT_CERT_ECDSA_SIGN)
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+
+ 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/src/net/spdy/spdy_credential_builder.h b/src/net/spdy/spdy_credential_builder.h
new file mode 100644
index 0000000..278d239
--- /dev/null
+++ b/src/net/spdy/spdy_credential_builder.h
@@ -0,0 +1,38 @@
+// 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"
+#include "net/base/ssl_client_cert_type.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,
+ SSLClientCertType type,
+ 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/src/net/spdy/spdy_credential_builder_unittest.cc b/src/net/spdy/spdy_credential_builder_unittest.cc
new file mode 100644
index 0000000..067fdb7
--- /dev/null
+++ b/src/net/spdy/spdy_credential_builder_unittest.cc
@@ -0,0 +1,171 @@
+// 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_signature_creator.h"
+#include "crypto/ec_private_key.h"
+#include "net/base/asn1_util.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+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;
+ std::vector<uint8> requested_cert_types;
+ requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
+ SSLClientCertType cert_type;
+ ServerBoundCertService::RequestHandle request_handle;
+ int rv = server_bound_cert_service->GetDomainBoundCert(
+ "https://www.google.com", requested_cert_types, &cert_type, key, cert,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type);
+
+ sequenced_worker_pool->Shutdown();
+}
+
+} // namespace
+
+class SpdyCredentialBuilderTest : public testing::Test {
+ public:
+ SpdyCredentialBuilderTest() {
+ CreateCertAndKey(&cert_, &key_);
+ }
+
+ protected:
+ int BuildWithType(SSLClientCertType type) {
+ return SpdyCredentialBuilder::Build(
+ MockClientSocket::kTlsUnique, type, key_, cert_, kSlot, &credential_);
+ }
+
+ int Build() {
+ return BuildWithType(CLIENT_CERT_ECDSA_SIGN);
+ }
+
+ 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_SucceedsWithECDSACert DISABLED_SucceedsWithECDSACert
+#else
+#define MAYBE_SucceedsWithECDSACert SucceedsWithECDSACert
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SucceedsWithECDSACert) {
+ EXPECT_EQ(OK, BuildWithType(CLIENT_CERT_ECDSA_SIGN));
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_FailsWithRSACert DISABLED_FailsWithRSACert
+#else
+#define MAYBE_FailsWithRSACert FailsWithRSACert
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_FailsWithRSACert) {
+ EXPECT_EQ(ERR_BAD_SSL_CLIENT_AUTH_CERT,
+ BuildWithType(CLIENT_CERT_RSA_SIGN));
+}
+
+#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/src/net/spdy/spdy_credential_state.cc b/src/net/spdy/spdy_credential_state.cc
new file mode 100644
index 0000000..943d732
--- /dev/null
+++ b/src/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/string_util.h"
+#include "net/base/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/src/net/spdy/spdy_credential_state.h b/src/net/spdy/spdy_credential_state.h
new file mode 100644
index 0000000..1460eac
--- /dev/null
+++ b/src/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 "googleurl/src/gurl.h"
+#include "net/base/net_export.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/src/net/spdy/spdy_credential_state_unittest.cc b/src/net/spdy/spdy_credential_state_unittest.cc
new file mode 100644
index 0000000..b512921
--- /dev/null
+++ b/src/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/src/net/spdy/spdy_frame_builder.cc b/src/net/spdy/spdy_frame_builder.cc
new file mode 100644
index 0000000..04bf62c
--- /dev/null
+++ b/src/net/spdy/spdy_frame_builder.cc
@@ -0,0 +1,111 @@
+// 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 "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace {
+
+// Creates a FlagsAndLength.
+FlagsAndLength CreateFlagsAndLength(SpdyControlFlags 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(SpdyControlType type,
+ SpdyControlFlags flags,
+ int spdy_version,
+ size_t size)
+ : buffer_(new char[size]),
+ capacity_(size),
+ length_(0) {
+ FlagsAndLength flags_length = CreateFlagsAndLength(
+ flags, size - SpdyFrame::kHeaderSize);
+ WriteUInt16(kControlFlagMask | spdy_version);
+ WriteUInt16(type);
+ WriteBytes(&flags_length, sizeof(flags_length));
+}
+
+SpdyFrameBuilder::SpdyFrameBuilder(SpdyStreamId stream_id,
+ SpdyDataFlags flags,
+ size_t size)
+ : buffer_(new char[size]),
+ capacity_(size),
+ length_(0) {
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ WriteUInt32(stream_id);
+ size_t length = size - SpdyFrame::kHeaderSize;
+ DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(length);
+ DCHECK_EQ(0, flags & ~kDataFlagsMask);
+ flags_length.flags_[0] = flags;
+ WriteBytes(&flags_length, sizeof(flags_length));
+}
+
+SpdyFrameBuilder::~SpdyFrameBuilder() {
+ delete[] buffer_;
+}
+
+char* SpdyFrameBuilder::BeginWrite(size_t length) {
+ size_t offset = length_;
+ size_t needed_size = length_ + length;
+ if (needed_size > capacity_)
+ return NULL;
+
+#ifdef ARCH_CPU_64_BITS
+ DCHECK_LE(length, std::numeric_limits<uint32>::max());
+#endif
+
+ return buffer_ + offset;
+}
+
+void SpdyFrameBuilder::EndWrite(char* dest, int length) {
+}
+
+bool SpdyFrameBuilder::WriteBytes(const void* data, uint32 data_len) {
+ if (data_len > kLengthMask) {
+ return false;
+ }
+
+ char* dest = BeginWrite(data_len);
+ if (!dest)
+ return false;
+
+ memcpy(dest, data, data_len);
+
+ EndWrite(dest, data_len);
+ length_ += data_len;
+ return true;
+}
+
+bool SpdyFrameBuilder::WriteString(const std::string& value) {
+ if (value.size() > 0xffff)
+ 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());
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_frame_builder.h b/src/net/spdy/spdy_frame_builder.h
new file mode 100644
index 0000000..affdbe4
--- /dev/null
+++ b/src/net/spdy/spdy_frame_builder.h
@@ -0,0 +1,132 @@
+// 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/string_piece.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+// This class provides facilities for basic binary value packing and unpacking
+// 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:
+ ~SpdyFrameBuilder();
+
+ // Initializes a SpdyFrameBuilder with a buffer of given size,
+ // populate with a SPDY control frame header based on
+ // |type|, |flags|, and |spdy_version|.
+ SpdyFrameBuilder(SpdyControlType type, SpdyControlFlags flags,
+ int spdy_version, size_t size);
+
+ // Initiailizes a SpdyFrameBuilder with a buffer of given size,
+ // populated with a SPDY data frame header based on
+ // |stream_id| and |flags|.
+ SpdyFrameBuilder(SpdyStreamId stream_id, SpdyDataFlags flags, size_t size);
+
+ // Returns the size of the SpdyFrameBuilder's data.
+ size_t length() const { return length_; }
+
+ // Takes the buffer from the SpdyFrameBuilder.
+ SpdyFrame* take() {
+ SpdyFrame* rv = new SpdyFrame(buffer_, true);
+ buffer_ = NULL;
+ 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);
+
+ // Write an integer to a particular offset in the data buffer.
+ bool WriteUInt32ToOffset(int offset, uint32 value) {
+ value = htonl(value);
+ return WriteBytesToOffset(offset, &value, sizeof(value));
+ }
+
+ // Write to a particular offset in the data buffer.
+ bool WriteBytesToOffset(int offset, const void* data, uint32 data_len) {
+ if (offset + data_len > length_)
+ return false;
+ char *ptr = buffer_ + offset;
+ memcpy(ptr, data, data_len);
+ return true;
+ }
+
+ // Returns true if the given iterator could point to data with the given
+ // length. If there is no room for the given data before the end of the
+ // payload, returns false.
+ bool IteratorHasRoomFor(const void* iter, int len) const {
+ const char* end_of_region = reinterpret_cast<const char*>(iter) + len;
+ if (len < 0 ||
+ iter < buffer_ ||
+ iter > end_of_payload() ||
+ iter > end_of_region ||
+ end_of_region > end_of_payload())
+ return false;
+
+ // Watch out for overflow in pointer calculation, which wraps.
+ return (iter <= end_of_region) && (end_of_region <= end_of_payload());
+ }
+
+ protected:
+ size_t capacity() const {
+ return capacity_;
+ }
+
+ const char* end_of_payload() const { return buffer_ + length_; }
+
+ // Completes the write operation by padding the data with NULL bytes until it
+ // is padded. Should be paired with BeginWrite, but it does not necessarily
+ // have to be called after the data is written.
+ void EndWrite(char* dest, int length);
+
+ // Moves the iterator by the given number of bytes.
+ static void UpdateIter(void** iter, int bytes) {
+ *iter = static_cast<char*>(*iter) + bytes;
+ }
+
+ private:
+ // Returns the location that the data should be written at, or NULL if there
+ // is not enough room. Call EndWrite with the returned offset and the given
+ // length to pad out for the next write.
+ char* BeginWrite(size_t length);
+
+ char* buffer_;
+ size_t capacity_; // Allocation size of payload (or -1 if buffer is const).
+ size_t length_; // current length of the buffer
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_FRAME_BUILDER_H_
diff --git a/src/net/spdy/spdy_frame_reader.cc b/src/net/spdy/spdy_frame_reader.cc
new file mode 100644
index 0000000..57bf9eb
--- /dev/null
+++ b/src/net/spdy/spdy_frame_reader.cc
@@ -0,0 +1,126 @@
+// 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"
+
+namespace net {
+
+SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len)
+ : data_(data),
+ len_(len),
+ ofs_(0) {
+}
+
+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::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::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/src/net/spdy/spdy_frame_reader.h b/src/net/spdy/spdy_frame_reader.h
new file mode 100644
index 0000000..6db7a08
--- /dev/null
+++ b/src/net/spdy/spdy_frame_reader.h
@@ -0,0 +1,94 @@
+// 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/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 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 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);
+
+ // Returns true if the entirety of the underlying buffer has been read via
+ // Read*() calls.
+ bool IsDoneReading() const;
+
+ 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/src/net/spdy/spdy_frame_reader_test.cc b/src/net/spdy/spdy_frame_reader_test.cc
new file mode 100644
index 0000000..c72e5be
--- /dev/null
+++ b/src/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/src/net/spdy/spdy_framer.cc b/src/net/spdy/spdy_framer.cc
new file mode 100644
index 0000000..0d9049d
--- /dev/null
+++ b/src/net/spdy/spdy_framer.cc
@@ -0,0 +1,1881 @@
+// 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"
+
+#if defined(USE_SYSTEM_ZLIB)
+#include <zlib.h>
+#else
+#include "third_party/zlib/zlib.h"
+#endif
+
+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;
+
+} // namespace
+
+const int SpdyFramer::kMinSpdyVersion = 2;
+const int SpdyFramer::kMaxSpdyVersion = 3;
+const SpdyStreamId SpdyFramer::kInvalidStream = -1;
+const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024;
+const size_t SpdyFramer::kControlFrameBufferSize =
+ sizeof(SpdySynStreamControlFrameBlock);
+const size_t SpdyFramer::kMaxControlFrameSize = 16 * 1024;
+
+#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(int version)
+ : state_(SPDY_RESET),
+ previous_state_(SPDY_RESET),
+ error_code_(SPDY_NO_ERROR),
+ remaining_data_(0),
+ remaining_control_payload_(0),
+ remaining_control_header_(0),
+ current_frame_buffer_(new char[kControlFrameBufferSize]),
+ current_frame_len_(0),
+ enable_compression_(true),
+ visitor_(NULL),
+ display_protocol_("SPDY"),
+ spdy_version_(version),
+ syn_frame_processed_(false),
+ probable_http_response_(false) {
+ DCHECK_GE(kMaxSpdyVersion, version);
+ DCHECK_LE(kMinSpdyVersion, version);
+}
+
+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_ = 0;
+ remaining_control_payload_ = 0;
+ remaining_control_header_ = 0;
+ current_frame_len_ = 0;
+ settings_scratch_.Reset();
+}
+
+const char* SpdyFramer::StateToString(int state) {
+ switch (state) {
+ case SPDY_ERROR:
+ return "ERROR";
+ case SPDY_DONE:
+ return "DONE";
+ 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";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+const char* SpdyFramer::StatusCodeToString(int status_code) {
+ switch (status_code) {
+ case INVALID:
+ return "INVALID";
+ case PROTOCOL_ERROR:
+ return "PROTOCOL_ERROR";
+ case INVALID_STREAM:
+ return "INVALID_STREAM";
+ case REFUSED_STREAM:
+ return "REFUSED_STREAM";
+ case UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case CANCEL:
+ return "CANCEL";
+ case INTERNAL_ERROR:
+ return "INTERNAL_ERROR";
+ case FLOW_CONTROL_ERROR:
+ return "FLOW_CONTROL_ERROR";
+ case STREAM_IN_USE:
+ return "STREAM_IN_USE";
+ case STREAM_ALREADY_CLOSED:
+ return "STREAM_ALREADY_CLOSED";
+ case INVALID_CREDENTIALS:
+ return "INVALID_CREDENTIALS";
+ case FRAME_TOO_LARGE:
+ return "FRAME_TOO_LARGE";
+ }
+ return "UNKNOWN_STATUS";
+}
+
+const char* SpdyFramer::ControlTypeToString(SpdyControlType type) {
+ switch (type) {
+ 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 NUM_CONTROL_FRAME_TYPES:
+ break;
+ }
+ 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:
+ case SPDY_DONE:
+ 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) 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_len_ == 0 &&
+ remaining_data_ == 0 &&
+ remaining_control_payload_ == 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;
+ SpdyFrame current_frame(current_frame_buffer_.get(), false);
+
+ // Update current frame buffer as needed.
+ if (current_frame_len_ < SpdyFrame::kHeaderSize) {
+ size_t bytes_desired = SpdyFrame::kHeaderSize - current_frame_len_;
+ UpdateCurrentFrameBuffer(&data, &len, bytes_desired);
+ }
+
+ if (current_frame_len_ < SpdyFrame::kHeaderSize) {
+ // TODO(rch): remove this empty block
+ // Do nothing.
+ } else {
+ remaining_data_ = current_frame.length();
+
+ // This is just a sanity check for help debugging early frame errors.
+ if (remaining_data_ > 1000000u) {
+ // The strncmp for 5 is safe because we only hit this point if we
+ // have SpdyFrame::kHeaderSize (8) bytes
+ if (!syn_frame_processed_ &&
+ strncmp(current_frame_buffer_.get(), "HTTP/", 5) == 0) {
+ LOG(WARNING) << "Unexpected HTTP response to spdy 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 (!current_frame.is_control_frame()) {
+ SpdyDataFrame data_frame(current_frame_buffer_.get(), false);
+ visitor_->OnDataFrameHeader(&data_frame);
+
+ if (current_frame.length() > 0) {
+ CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME);
+ } else {
+ // Empty data frame.
+ if (current_frame.flags() & DATA_FLAG_FIN) {
+ visitor_->OnStreamFrameData(data_frame.stream_id(),
+ NULL, 0, DATA_FLAG_FIN);
+ }
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ } else {
+ ProcessControlFrameHeader();
+ }
+ }
+ return original_len - len;
+}
+
+void SpdyFramer::ProcessControlFrameHeader() {
+ DCHECK_EQ(SPDY_NO_ERROR, error_code_);
+ DCHECK_LE(static_cast<size_t>(SpdyFrame::kHeaderSize), current_frame_len_);
+ SpdyControlFrame current_control_frame(current_frame_buffer_.get(), false);
+
+ // We check version before we check validity: version can never be 'invalid',
+ // it can only be unsupported.
+ if (current_control_frame.version() != spdy_version_) {
+ DLOG(INFO) << "Unsupported SPDY version " << current_control_frame.version()
+ << " (expected " << spdy_version_ << ")";
+ set_error(SPDY_UNSUPPORTED_VERSION);
+ return;
+ }
+
+ // Next up, check to see if we have valid data. This should be after version
+ // checking (otherwise if the the type were out of bounds due to a version
+ // upgrade we would misclassify the error) and before checking the type
+ // (type can definitely be out of bounds)
+ if (!current_control_frame.AppearsToBeAValidControlFrame()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ return;
+ }
+
+ if (current_control_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.
+ switch (current_control_frame.type()) {
+ case SYN_STREAM:
+ if (current_control_frame.length() <
+ SpdySynStreamControlFrame::size() - SpdyControlFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case SYN_REPLY:
+ if (current_control_frame.length() <
+ SpdySynReplyControlFrame::size() - SpdyControlFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case RST_STREAM:
+ if (current_control_frame.length() !=
+ SpdyRstStreamControlFrame::size() - SpdyFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ 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_control_frame.length() <
+ SpdySettingsControlFrame::size() - SpdyControlFrame::kHeaderSize ||
+ (current_control_frame.length() % 8 != 4)) {
+ DLOG(WARNING) << "Invalid length for SETTINGS frame: "
+ << current_control_frame.length();
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ }
+ break;
+ case GOAWAY:
+ {
+ // SPDY 2 GOAWAY frames are 4 bytes smaller than in SPDY 3. We account
+ // for this difference via a separate offset variable, since
+ // SpdyGoAwayControlFrame::size() returns the SPDY 3 size.
+ const size_t goaway_offset = (protocol_version() < 3) ? 4 : 0;
+ if (current_control_frame.length() + goaway_offset !=
+ SpdyGoAwayControlFrame::size() - SpdyFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+ case HEADERS:
+ if (current_control_frame.length() <
+ SpdyHeadersControlFrame::size() - SpdyControlFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case WINDOW_UPDATE:
+ if (current_control_frame.length() !=
+ SpdyWindowUpdateControlFrame::size() -
+ SpdyControlFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case PING:
+ if (current_control_frame.length() !=
+ SpdyPingControlFrame::size() - SpdyControlFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ case CREDENTIAL:
+ if (current_control_frame.length() <
+ SpdyCredentialControlFrame::size() - SpdyControlFrame::kHeaderSize)
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ default:
+ LOG(WARNING) << "Valid " << display_protocol_
+ << " control frame with unhandled type: "
+ << current_control_frame.type();
+ DLOG(FATAL);
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+
+ if (state_ == SPDY_ERROR) {
+ return;
+ }
+
+ remaining_control_payload_ = current_control_frame.length();
+ const size_t total_frame_size =
+ remaining_control_payload_ + SpdyFrame::kHeaderSize;
+ if (total_frame_size > kMaxControlFrameSize) {
+ DLOG(WARNING) << "Received control frame with way too big of a payload: "
+ << total_frame_size;
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ return;
+ }
+
+ if (current_control_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_control_frame.type()) {
+ case SYN_STREAM:
+ syn_frame_processed_ = true;
+ frame_size_without_variable_data = SpdySynStreamControlFrame::size();
+ break;
+ case SYN_REPLY:
+ syn_frame_processed_ = true;
+ frame_size_without_variable_data = SpdySynReplyControlFrame::size();
+ // SPDY 2 had two bytes of unused space preceeding payload.
+ if (spdy_version_ < 3) {
+ frame_size_without_variable_data += 2;
+ }
+ break;
+ case HEADERS:
+ frame_size_without_variable_data = SpdyHeadersControlFrame::size();
+ // SPDY 2 had two bytes of unused space preceeding payload.
+ if (spdy_version_ < 3) {
+ frame_size_without_variable_data += 2;
+ }
+ break;
+ case SETTINGS:
+ frame_size_without_variable_data = SpdySettingsControlFrame::size();
+ break;
+ default:
+ frame_size_without_variable_data = -1;
+ break;
+ }
+
+ if ((frame_size_without_variable_data == -1) &&
+ (total_frame_size > 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_len_));
+ remaining_control_header_ = frame_size_without_variable_data -
+ current_frame_len_;
+ remaining_control_payload_ += SpdyFrame::kHeaderSize -
+ frame_size_without_variable_data;
+ 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);
+ DCHECK_GE(kControlFrameBufferSize, current_frame_len_ + bytes_to_read);
+ memcpy(current_frame_buffer_.get() + current_frame_len_,
+ *data,
+ bytes_to_read);
+ current_frame_len_ += bytes_to_read;
+ *data += bytes_to_read;
+ *len -= bytes_to_read;
+ return bytes_to_read;
+}
+
+size_t SpdyFramer::GetSerializedLength(const SpdyHeaderBlock* headers) const {
+ 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 SpdyHeaderBlock* headers) const {
+ 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) {
+ bool wrote_header;
+ if (spdy_version_ < 3) {
+ wrote_header = frame->WriteString(it->first);
+ wrote_header &= frame->WriteString(it->second);
+ } else {
+ wrote_header = frame->WriteStringPiece32(it->first);
+ wrote_header &= frame->WriteStringPiece32(it->second);
+ }
+ DCHECK(wrote_header);
+ }
+}
+
+// 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);
+ }
+ 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;
+ }
+
+ if (remaining_control_header_ == 0) {
+ SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+ switch (control_frame.type()) {
+ case SYN_STREAM:
+ {
+ SpdySynStreamControlFrame* syn_stream_frame =
+ reinterpret_cast<SpdySynStreamControlFrame*>(&control_frame);
+ // TODO(hkhalil): Check that invalid flag bits are unset?
+ visitor_->OnSynStream(
+ syn_stream_frame->stream_id(),
+ syn_stream_frame->associated_stream_id(),
+ syn_stream_frame->priority(),
+ syn_stream_frame->credential_slot(),
+ (syn_stream_frame->flags() & CONTROL_FLAG_FIN) != 0,
+ (syn_stream_frame->flags() & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
+ }
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ break;
+ case SYN_REPLY:
+ {
+ SpdySynReplyControlFrame* syn_reply_frame =
+ reinterpret_cast<SpdySynReplyControlFrame*>(&control_frame);
+ visitor_->OnSynReply(
+ syn_reply_frame->stream_id(),
+ (syn_reply_frame->flags() & CONTROL_FLAG_FIN) != 0);
+ }
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ break;
+ case HEADERS:
+ {
+ SpdyHeadersControlFrame* headers_frame =
+ reinterpret_cast<SpdyHeadersControlFrame*>(&control_frame);
+ visitor_->OnHeaders(
+ headers_frame->stream_id(),
+ (headers_frame->flags() & CONTROL_FLAG_FIN) != 0);
+ }
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ break;
+ case SETTINGS:
+ CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD);
+ 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_);
+ SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+
+ bool processed_successfully = true;
+ DCHECK(control_frame.type() == SYN_STREAM ||
+ control_frame.type() == SYN_REPLY ||
+ control_frame.type() == HEADERS);
+ size_t process_bytes = std::min(data_len, remaining_control_payload_);
+ if (process_bytes > 0) {
+ if (enable_compression_) {
+ processed_successfully = IncrementallyDecompressControlFrameHeaderData(
+ &control_frame, data, process_bytes);
+ } else {
+ processed_successfully = IncrementallyDeliverControlFrameHeaderData(
+ &control_frame, data, process_bytes);
+ }
+
+ remaining_control_payload_ -= process_bytes;
+ remaining_data_ -= process_bytes;
+ }
+
+ // Handle the case that there is no futher data in this frame.
+ if (remaining_control_payload_ == 0 && processed_successfully) {
+ // The complete header block has been delivered. We send a zero-length
+ // OnControlFrameHeaderData() to indicate this.
+ visitor_->OnControlFrameHeaderData(
+ GetControlFrameStreamId(&control_frame), NULL, 0);
+
+ // If this is a FIN, tell the caller.
+ if (control_frame.flags() & CONTROL_FLAG_FIN) {
+ visitor_->OnStreamFrameData(GetControlFrameStreamId(&control_frame),
+ NULL, 0, DATA_FLAG_FIN);
+ }
+
+ 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_);
+ SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+ DCHECK_EQ(SETTINGS, control_frame.type());
+ size_t unprocessed_bytes = std::min(data_len, remaining_control_payload_);
+ 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_control_payload_ -= processed_bytes;
+ if (remaining_control_payload_ == 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;
+ if (remaining_control_payload_) {
+ size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
+ remaining_control_payload_);
+ remaining_control_payload_ -= bytes_read;
+ remaining_data_ -= bytes_read;
+ if (remaining_control_payload_ == 0) {
+ SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+ DCHECK(!control_frame.has_header_block());
+ // Use frame-specific handlers.
+ switch (control_frame.type()) {
+ case PING: {
+ SpdyPingControlFrame* ping_frame =
+ reinterpret_cast<SpdyPingControlFrame*>(&control_frame);
+ visitor_->OnPing(ping_frame->unique_id());
+ }
+ break;
+ case WINDOW_UPDATE: {
+ SpdyWindowUpdateControlFrame *window_update_frame =
+ reinterpret_cast<SpdyWindowUpdateControlFrame*>(&control_frame);
+ visitor_->OnWindowUpdate(window_update_frame->stream_id(),
+ window_update_frame->delta_window_size());
+ }
+ break;
+ case RST_STREAM: {
+ SpdyRstStreamControlFrame* rst_stream_frame =
+ reinterpret_cast<SpdyRstStreamControlFrame*>(&control_frame);
+ visitor_->OnRstStream(rst_stream_frame->stream_id(),
+ rst_stream_frame->status());
+ }
+ break;
+ case GOAWAY: {
+ SpdyGoAwayControlFrame* go_away_frame =
+ reinterpret_cast<SpdyGoAwayControlFrame*>(&control_frame);
+ if (spdy_version_ < 3) {
+ visitor_->OnGoAway(go_away_frame->last_accepted_stream_id(),
+ GOAWAY_OK);
+ } else {
+ visitor_->OnGoAway(go_away_frame->last_accepted_stream_id(),
+ go_away_frame->status());
+ }
+ }
+ break;
+ default:
+ // Unreachable.
+ LOG(FATAL) << "Unhandled control frame " << control_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) {
+ // Process only up to the end of this CREDENTIAL frame.
+ len = std::min(len, remaining_control_payload_);
+ bool processed_succesfully = visitor_->OnCredentialFrameData(data, len);
+ remaining_control_payload_ -= len;
+ remaining_data_ -= len;
+ if (!processed_succesfully) {
+ set_error(SPDY_CREDENTIAL_FRAME_CORRUPT);
+ } else if (remaining_control_payload_ == 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;
+
+ SpdyDataFrame current_data_frame(current_frame_buffer_.get(), false);
+ if (remaining_data_ > 0) {
+ size_t amount_to_forward = std::min(remaining_data_, 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_data_frame.stream_id(),
+ data, amount_to_forward, SpdyDataFlags());
+ }
+ }
+ data += amount_to_forward;
+ len -= amount_to_forward;
+ remaining_data_ -= 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_ &&
+ current_data_frame.flags() & DATA_FLAG_FIN) {
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+ NULL, 0, DATA_FLAG_FIN);
+ }
+ }
+
+ if (remaining_data_ == 0) {
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ return original_len - len;
+}
+
+bool 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 false;
+ }
+ num_headers = temp;
+ } else {
+ if (!reader.ReadUInt32(&num_headers)) {
+ DLOG(INFO) << "Unable to read number of headers.";
+ return false;
+ }
+ }
+
+ // 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 false;
+ }
+ 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 false;
+ }
+ 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 false;
+ }
+
+ // Store header.
+ (*block)[name] = value;
+ }
+ return true;
+}
+
+// TODO(hkhalil): Remove, or move to test utils kit.
+/* static */
+bool SpdyFramer::ParseSettings(const SpdySettingsControlFrame* frame,
+ SettingsMap* settings) {
+ DCHECK_EQ(frame->type(), SETTINGS);
+ DCHECK(settings);
+
+ SpdyFrameReader parser(frame->header_block(), frame->header_block_len());
+ for (size_t index = 0; index < frame->num_entries(); ++index) {
+ uint32 id_and_flags_wire;
+ uint32 value;
+ // SettingsFlagsAndId accepts off-the-wire (network byte order) data, so we
+ // use ReadBytes() instead of ReadUInt32() as the latter calls ntohl().
+ if (!parser.ReadBytes(&id_and_flags_wire, 4)) {
+ return false;
+ }
+ if (!parser.ReadUInt32(&value))
+ return false;
+ SettingsFlagsAndId flags_and_id =
+ SettingsFlagsAndId::FromWireFormat(frame->version(), id_and_flags_wire);
+ SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
+ SpdySettingsFlags flags =
+ static_cast<SpdySettingsFlags>(flags_and_id.flags());
+ (*settings)[id] = SettingsFlagsAndValue(flags, value);
+ }
+ return true;
+}
+
+/* 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;
+}
+
+SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
+ SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_EQ(0u, associated_stream_id & ~kStreamIdMask);
+
+ // Find our length.
+ size_t frame_size = SpdySynStreamControlFrame::size() +
+ GetSerializedLength(headers);
+
+ SpdyFrameBuilder frame(SYN_STREAM, flags, spdy_version_, frame_size);
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(associated_stream_id);
+ // Cap as appropriate.
+ if (priority > GetLowestPriority()) {
+ DLOG(DFATAL) << "Priority out-of-bounds.";
+ priority = GetLowestPriority();
+ }
+ // Priority is 2 bits for <spdy3, 3 bits otherwise.
+ frame.WriteUInt8(priority << ((spdy_version_ < 3) ? 6 : 5));
+ frame.WriteUInt8((spdy_version_ < 3) ? 0 : credential_slot);
+ WriteHeaderBlock(&frame, headers);
+ DCHECK_EQ(frame.length(), frame_size);
+
+ scoped_ptr<SpdySynStreamControlFrame> syn_frame(
+ reinterpret_cast<SpdySynStreamControlFrame*>(frame.take()));
+ if (compressed) {
+ return reinterpret_cast<SpdySynStreamControlFrame*>(
+ CompressControlFrame(*syn_frame.get(), headers));
+ }
+ return syn_frame.release();
+}
+
+SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+
+ // Find our length.
+ size_t frame_size = SpdySynReplyControlFrame::size() +
+ GetSerializedLength(headers);
+ // In SPDY 2, there were 2 unused bytes before payload.
+ if (spdy_version_ < 3) {
+ frame_size += 2;
+ }
+
+ SpdyFrameBuilder frame(SYN_REPLY, flags, spdy_version_, frame_size);
+ frame.WriteUInt32(stream_id);
+ if (spdy_version_ < 3) {
+ frame.WriteUInt16(0); // Unused
+ }
+ WriteHeaderBlock(&frame, headers);
+ DCHECK_EQ(frame.length(), frame_size);
+
+ scoped_ptr<SpdySynReplyControlFrame> reply_frame(
+ reinterpret_cast<SpdySynReplyControlFrame*>(frame.take()));
+ if (compressed) {
+ return reinterpret_cast<SpdySynReplyControlFrame*>(
+ CompressControlFrame(*reply_frame.get(), headers));
+ }
+ return reply_frame.release();
+}
+
+SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(
+ SpdyStreamId stream_id,
+ SpdyStatusCodes status) const {
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_NE(status, INVALID);
+ DCHECK_LT(status, NUM_STATUS_CODES);
+
+ size_t frame_size = SpdyRstStreamControlFrame::size();
+ SpdyFrameBuilder frame(RST_STREAM, CONTROL_FLAG_NONE, spdy_version_,
+ frame_size);
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(status);
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take());
+}
+
+SpdySettingsControlFrame* SpdyFramer::CreateSettings(
+ const SettingsMap& values) const {
+ size_t frame_size = SpdySettingsControlFrame::size() + 8 * values.size();
+ SpdyFrameBuilder frame(SETTINGS, CONTROL_FLAG_NONE, spdy_version_,
+ frame_size);
+ frame.WriteUInt32(values.size());
+ for (SettingsMap::const_iterator it = values.begin();
+ it != values.end();
+ it++) {
+ SettingsFlagsAndId flags_and_id(it->second.first, it->first);
+ uint32 id_and_flags_wire = flags_and_id.GetWireFormat(spdy_version_);
+ frame.WriteBytes(&id_and_flags_wire, 4);
+ frame.WriteUInt32(it->second.second);
+ }
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdySettingsControlFrame*>(frame.take());
+}
+
+SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const {
+ size_t frame_size = SpdyPingControlFrame::size();
+ SpdyFrameBuilder frame(PING, CONTROL_FLAG_NONE, spdy_version_, frame_size);
+ frame.WriteUInt32(unique_id);
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdyPingControlFrame*>(frame.take());
+}
+
+SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway(
+ SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const {
+ DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask);
+
+ // SPDY 2 GOAWAY frames are 4 bytes smaller than in SPDY 3. We account for
+ // this difference via a separate offset variable, since
+ // SpdyGoAwayControlFrame::size() returns the SPDY 3 size.
+ const size_t goaway_offset = (protocol_version() < 3) ? 4 : 0;
+ size_t frame_size = SpdyGoAwayControlFrame::size() - goaway_offset;
+ SpdyFrameBuilder frame(GOAWAY, CONTROL_FLAG_NONE, spdy_version_, frame_size);
+ frame.WriteUInt32(last_accepted_stream_id);
+ if (protocol_version() >= 3) {
+ frame.WriteUInt32(status);
+ }
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take());
+}
+
+SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ // Basically the same as CreateSynReply().
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+
+ // Find our length.
+ size_t frame_size = SpdyHeadersControlFrame::size() +
+ GetSerializedLength(headers);
+ // In SPDY 2, there were 2 unused bytes before payload.
+ if (spdy_version_ < 3) {
+ frame_size += 2;
+ }
+
+ SpdyFrameBuilder frame(HEADERS, flags, spdy_version_, frame_size);
+ frame.WriteUInt32(stream_id);
+ if (spdy_version_ < 3) {
+ frame.WriteUInt16(0); // Unused
+ }
+ WriteHeaderBlock(&frame, headers);
+ DCHECK_EQ(frame.length(), frame_size);
+
+ scoped_ptr<SpdyHeadersControlFrame> headers_frame(
+ reinterpret_cast<SpdyHeadersControlFrame*>(frame.take()));
+ if (compressed) {
+ return reinterpret_cast<SpdyHeadersControlFrame*>(
+ CompressControlFrame(*headers_frame.get(), headers));
+ }
+ return headers_frame.release();
+}
+
+SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const {
+ DCHECK_GT(stream_id, 0u);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_GT(delta_window_size, 0u);
+ DCHECK_LE(delta_window_size,
+ static_cast<uint32>(kSpdyStreamMaximumWindowSize));
+
+ size_t frame_size = SpdyWindowUpdateControlFrame::size();
+ SpdyFrameBuilder frame(WINDOW_UPDATE, CONTROL_FLAG_NONE, spdy_version_,
+ frame_size);
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(delta_window_size);
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take());
+}
+
+SpdyCredentialControlFrame* SpdyFramer::CreateCredentialFrame(
+ const SpdyCredential& credential) const {
+ // Calculate the size of the frame by adding the size of the
+ // variable length data to the size of the fixed length data.
+ size_t frame_size = SpdyCredentialControlFrame::size() +
+ credential.proof.length();
+ DCHECK_EQ(SpdyCredentialControlFrame::size(), 14u);
+ for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
+ cert != credential.certs.end();
+ ++cert) {
+ frame_size += sizeof(uint32); // size of the cert_length field
+ frame_size += cert->length(); // size of the cert_data field
+ }
+
+ SpdyFrameBuilder frame(CREDENTIAL, CONTROL_FLAG_NONE, spdy_version_,
+ frame_size);
+ frame.WriteUInt16(credential.slot);
+ frame.WriteUInt32(credential.proof.size());
+ frame.WriteBytes(credential.proof.c_str(), credential.proof.size());
+ for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
+ cert != credential.certs.end();
+ ++cert) {
+ frame.WriteUInt32(cert->length());
+ frame.WriteBytes(cert->c_str(), cert->length());
+ }
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdyCredentialControlFrame*>(frame.take());
+}
+
+SpdyDataFrame* SpdyFramer::CreateDataFrame(
+ SpdyStreamId stream_id,
+ const char* data,
+ uint32 len, SpdyDataFlags flags) const {
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ size_t frame_size = SpdyDataFrame::size() + len;
+ SpdyFrameBuilder frame(stream_id, flags, frame_size);
+ frame.WriteBytes(data, len);
+ DCHECK_EQ(frame.length(), frame_size);
+ return reinterpret_cast<SpdyDataFrame*>(frame.take());
+}
+
+// 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();
+}
+
+bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame,
+ int* payload_length,
+ int* header_length,
+ const char** payload) const {
+ size_t frame_size;
+ if (frame.is_control_frame()) {
+ const SpdyControlFrame& control_frame =
+ reinterpret_cast<const SpdyControlFrame&>(frame);
+ switch (control_frame.type()) {
+ case SYN_STREAM:
+ {
+ const SpdySynStreamControlFrame& syn_frame =
+ reinterpret_cast<const SpdySynStreamControlFrame&>(frame);
+ frame_size = SpdySynStreamControlFrame::size();
+ *payload_length = syn_frame.header_block_len();
+ *header_length = frame_size;
+ *payload = frame.data() + *header_length;
+ }
+ break;
+ case SYN_REPLY:
+ {
+ const SpdySynReplyControlFrame& syn_frame =
+ reinterpret_cast<const SpdySynReplyControlFrame&>(frame);
+ frame_size = SpdySynReplyControlFrame::size();
+ *payload_length = syn_frame.header_block_len();
+ *header_length = frame_size;
+ *payload = frame.data() + *header_length;
+ // SPDY 2 had two bytes of unused space preceeding payload.
+ if (spdy_version_ < 3) {
+ *header_length += 2;
+ *payload += 2;
+ }
+ }
+ break;
+ case HEADERS:
+ {
+ const SpdyHeadersControlFrame& headers_frame =
+ reinterpret_cast<const SpdyHeadersControlFrame&>(frame);
+ frame_size = SpdyHeadersControlFrame::size();
+ *payload_length = headers_frame.header_block_len();
+ *header_length = frame_size;
+ *payload = frame.data() + *header_length;
+ // SPDY 2 had two bytes of unused space preceeding payload.
+ if (spdy_version_ < 3) {
+ *header_length += 2;
+ *payload += 2;
+ }
+ }
+ break;
+ default:
+ // TODO(mbelshe): set an error?
+ return false; // We can't compress this frame!
+ }
+ } else {
+ frame_size = SpdyFrame::kHeaderSize;
+ *header_length = frame_size;
+ *payload_length = frame.length();
+ *payload = frame.data() + SpdyFrame::kHeaderSize;
+ }
+ return true;
+}
+
+SpdyControlFrame* SpdyFramer::CompressControlFrame(
+ const SpdyControlFrame& frame,
+ const SpdyHeaderBlock* headers) {
+ z_stream* compressor = GetHeaderCompressor();
+ if (!compressor)
+ return NULL;
+
+ int payload_length;
+ int header_length;
+ const char* payload;
+
+ base::StatsCounter compressed_frames("spdy.CompressedFrames");
+ base::StatsCounter pre_compress_bytes("spdy.PreCompressSize");
+ base::StatsCounter post_compress_bytes("spdy.PostCompressSize");
+
+ if (!enable_compression_)
+ return reinterpret_cast<SpdyControlFrame*>(DuplicateFrame(frame));
+
+ if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
+ return NULL;
+
+ // Create an output frame.
+ int compressed_max_size = deflateBound(compressor, payload_length);
+ // Since we'll be performing lots of flushes when compressing the data,
+ // zlib's lower bounds may be insufficient.
+ compressed_max_size *= 2;
+
+ size_t new_frame_size = header_length + compressed_max_size;
+ if ((frame.type() == SYN_REPLY || frame.type() == HEADERS) &&
+ spdy_version_ < 3) {
+ new_frame_size += 2;
+ }
+ DCHECK_GE(new_frame_size, frame.length() + SpdyFrame::kHeaderSize);
+ scoped_ptr<SpdyControlFrame> new_frame(new SpdyControlFrame(new_frame_size));
+ memcpy(new_frame->data(), frame.data(),
+ frame.length() + SpdyFrame::kHeaderSize);
+
+ // 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*>(const_cast<char*>(payload));
+ compressor->avail_in = payload_length;
+#endif // defined(USE_SYSTEM_ZLIB)
+ compressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
+ header_length;
+ 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;
+ return NULL;
+ }
+#else // !defined(USE_SYSTEM_ZLIB)
+ WriteHeaderBlockToZ(headers, compressor);
+#endif // !defined(USE_SYSTEM_ZLIB)
+ int compressed_size = compressed_max_size - compressor->avail_out;
+
+ // We trust zlib. Also, we can't do anything about it.
+ // See http://www.zlib.net/zlib_faq.html#faq36
+ (void)VALGRIND_MAKE_MEM_DEFINED(new_frame->data() + header_length,
+ compressed_size);
+
+ new_frame->set_length(
+ header_length + compressed_size - SpdyFrame::kHeaderSize);
+
+ pre_compress_bytes.Add(payload_length);
+ post_compress_bytes.Add(new_frame->length());
+
+ compressed_frames.Increment();
+
+ if (visitor_)
+ visitor_->OnControlFrameCompressed(frame, *new_frame);
+
+ return new_frame.release();
+}
+
+// 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(
+ const SpdyControlFrame* control_frame,
+ 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;
+ const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+ 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(
+ const SpdyControlFrame* control_frame, const char* data, size_t len) {
+ bool read_successfully = true;
+ const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+ 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;
+}
+
+SpdyFrame* SpdyFramer::DuplicateFrame(const SpdyFrame& frame) {
+ int size = SpdyFrame::kHeaderSize + frame.length();
+ SpdyFrame* new_frame = new SpdyFrame(size);
+ memcpy(new_frame->data(), frame.data(), size);
+ return new_frame;
+}
+
+bool SpdyFramer::IsCompressible(const SpdyFrame& frame) const {
+ // The important frames to compress are those which contain large
+ // amounts of compressible data - namely the headers in the SYN_STREAM
+ // and SYN_REPLY.
+ if (frame.is_control_frame()) {
+ const SpdyControlFrame& control_frame =
+ reinterpret_cast<const SpdyControlFrame&>(frame);
+ return control_frame.type() == SYN_STREAM ||
+ control_frame.type() == SYN_REPLY ||
+ control_frame.type() == HEADERS;
+ }
+
+ // We don't compress Data frames.
+ return false;
+}
+
+size_t SpdyFramer::GetMinimumControlFrameSize(int version,
+ SpdyControlType type) {
+ switch (type) {
+ case SYN_STREAM:
+ return SpdySynStreamControlFrame::size();
+ case SYN_REPLY:
+ return SpdySynReplyControlFrame::size();
+ case RST_STREAM:
+ return SpdyRstStreamControlFrame::size();
+ case SETTINGS:
+ return SpdySettingsControlFrame::size();
+ case NOOP:
+ // Even though NOOP is no longer supported, we still correctly report its
+ // size so that it can be handled correctly as incoming data if
+ // implementations so desire.
+ return SpdyFrame::kHeaderSize;
+ case PING:
+ return SpdyPingControlFrame::size();
+ case GOAWAY:
+ if (version < 3) {
+ // SPDY 2 GOAWAY is smaller by 32 bits. Since
+ // SpdyGoAwayControlFrame::size() returns the size for SPDY 3, we adjust
+ // before returning here.
+ return SpdyGoAwayControlFrame::size() - 4;
+ } else {
+ return SpdyGoAwayControlFrame::size();
+ }
+ case HEADERS:
+ return SpdyHeadersControlFrame::size();
+ case WINDOW_UPDATE:
+ return SpdyWindowUpdateControlFrame::size();
+ case CREDENTIAL:
+ return SpdyCredentialControlFrame::size();
+ case NUM_CONTROL_FRAME_TYPES:
+ break;
+ }
+ LOG(ERROR) << "Unknown control frame type " << type;
+ return std::numeric_limits<size_t>::max();
+}
+
+/* static */
+SpdyStreamId SpdyFramer::GetControlFrameStreamId(
+ const SpdyControlFrame* control_frame) {
+ SpdyStreamId stream_id = kInvalidStream;
+ if (control_frame != NULL) {
+ switch (control_frame->type()) {
+ case SYN_STREAM:
+ stream_id = reinterpret_cast<const SpdySynStreamControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case SYN_REPLY:
+ stream_id = reinterpret_cast<const SpdySynReplyControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case HEADERS:
+ stream_id = reinterpret_cast<const SpdyHeadersControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case RST_STREAM:
+ stream_id = reinterpret_cast<const SpdyRstStreamControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case WINDOW_UPDATE:
+ stream_id = reinterpret_cast<const SpdyWindowUpdateControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ // All of the following types are not part of a particular stream.
+ // They all fall through to the invalid control frame type case.
+ // (The default case isn't used so that the compile will break if a new
+ // control frame type is added but not included here.)
+ case SETTINGS:
+ case NOOP:
+ case PING:
+ case GOAWAY:
+ case CREDENTIAL:
+ case NUM_CONTROL_FRAME_TYPES: // makes compiler happy
+ break;
+ }
+ }
+ return stream_id;
+}
+
+void SpdyFramer::set_enable_compression(bool value) {
+ enable_compression_ = value;
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_framer.h b/src/net/spdy/spdy_framer.h
new file mode 100644
index 0000000..0c834fd
--- /dev/null
+++ b/src/net/spdy/spdy_framer.h
@@ -0,0 +1,609 @@
+// 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 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.
+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, and
+// HEADER) 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 or OnHeaders 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 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 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 chunk of header data is available. This is called
+ // after OnSynStream, OnSynReply or OnHeaders().
+ // |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 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* header_data,
+ size_t len) = 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(const SpdyDataFrame* frame) = 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,
+ SpdyDataFlags flags) = 0;
+
+ // 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 RST_STREAM frame has been parsed.
+ virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes 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,
+ int delta_window_size) = 0;
+
+ // Called after a control frame has been compressed to allow the visitor
+ // to record compression statistics.
+ virtual void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) = 0;
+};
+
+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_DONE,
+ 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.
+
+ LAST_ERROR, // Must be the last entry in the enum.
+ };
+
+ // The minimum supported SPDY version that SpdyFramer can speak.
+ static const int kMinSpdyVersion;
+
+ // The maximum supported SPDY version that SpdyFramer can speak.
+ static const int kMaxSpdyVersion;
+
+ // 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;
+
+ // Create a new Framer, provided a SPDY version.
+ explicit SpdyFramer(int 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;
+ }
+
+ // 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 MessageFullyRead() const {
+ return state_ == SPDY_DONE || state_ == SPDY_AUTO_RESET;
+ }
+ 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 true if successfully parsed, false otherwise.
+ bool ParseHeaderBlockInBuffer(const char* header_data,
+ size_t header_length,
+ SpdyHeaderBlock* block) const;
+
+ // Create a SpdySynStreamControlFrame.
+ // |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.
+ SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+
+ // Create a SpdySynReplyControlFrame.
+ // |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.
+ SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+
+ SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status) const;
+
+ // Creates an instance of SpdySettingsControlFrame. The SETTINGS frame is
+ // used to communicate name/value pairs relevant to the communication channel.
+ SpdySettingsControlFrame* CreateSettings(const SettingsMap& values) const;
+
+ // Creates an instance of SpdyPingControlFrame. The unique_id is used to
+ // identify the ping request/response.
+ SpdyPingControlFrame* CreatePingFrame(uint32 unique_id) const;
+
+ // Creates an instance of SpdyGoAwayControlFrame. 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.
+ SpdyGoAwayControlFrame* CreateGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const;
+
+ // Creates an instance of SpdyHeadersControlFrame. The HEADERS frame is used
+ // for sending additional headers outside of a SYN_STREAM/SYN_REPLY. The
+ // arguments are the same as for CreateSynReply.
+ SpdyHeadersControlFrame* CreateHeaders(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+
+ // Creates an instance of SpdyWindowUpdateControlFrame. The WINDOW_UPDATE
+ // frame is used to implement per stream flow control in SPDY.
+ SpdyWindowUpdateControlFrame* CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const;
+
+ // Creates an instance of SpdyCredentialControlFrame. 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.
+ SpdyCredentialControlFrame* CreateCredentialFrame(
+ const SpdyCredential& credential) const;
+
+ // Given a SpdySettingsControlFrame, extract the settings.
+ // Returns true on successful parse, false otherwise.
+ static bool ParseSettings(const SpdySettingsControlFrame* frame,
+ SettingsMap* settings);
+
+ // Given a SpdyCredentialControlFrame'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);
+
+ // 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.
+ SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id, const char* data,
+ uint32 len, SpdyDataFlags flags) const;
+
+ // 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);
+
+ // Create a copy of a frame.
+ // Returned frame must be freed with "delete".
+ SpdyFrame* DuplicateFrame(const SpdyFrame& frame);
+
+ // Returns true if a frame could be compressed.
+ bool IsCompressible(const SpdyFrame& frame) const;
+
+ // Get the minimum size of the control frame for the given control frame
+ // type. This is useful for validating frame blocks.
+ static size_t GetMinimumControlFrameSize(int version, SpdyControlType type);
+
+ // Get the stream ID for the given control frame (SYN_STREAM, SYN_REPLY, and
+ // HEADERS). If the control frame is NULL or of another type, this
+ // function returns kInvalidStream.
+ static SpdyStreamId GetControlFrameStreamId(
+ const SpdyControlFrame* control_frame);
+
+ // For ease of testing and experimentation we can tweak compression on/off.
+ void set_enable_compression(bool value);
+
+ // Used only in log messages.
+ void set_display_protocol(const std::string& protocol) {
+ display_protocol_ = protocol;
+ }
+
+ // 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* ControlTypeToString(SpdyControlType type);
+
+ int protocol_version() const { return spdy_version_; }
+
+ bool probable_http_response() const { return probable_http_response_; }
+
+ SpdyPriority GetLowestPriority() const {
+ return spdy_version_ < 3 ? 3U : 7U;
+ }
+ SpdyPriority GetHighestPriority() const { return 0; }
+
+ 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 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();
+ bool ProcessSetting(const char* data); // Always passed exactly 8 bytes.
+
+ // Get (and lazily initialize) the ZLib state.
+ z_stream* GetHeaderCompressor();
+ z_stream* GetHeaderDecompressor();
+
+ // 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(
+ const SpdyControlFrame* frame,
+ const char* data,
+ size_t len);
+
+ // 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(const SpdyControlFrame* frame,
+ 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);
+
+ // Retrieve serialized length of SpdyHeaderBlock.
+ size_t GetSerializedLength(const SpdyHeaderBlock* headers) const;
+
+ // Serializes a SpdyHeaderBlock.
+ void WriteHeaderBlock(SpdyFrameBuilder* frame,
+ const SpdyHeaderBlock* headers) const;
+
+ void WriteHeaderBlockToZ(const SpdyHeaderBlock* headers,
+ z_stream* out) const;
+
+ // Set the error code and moves the framer into the error state.
+ void set_error(SpdyError error);
+
+ // Given a frame, breakdown the variable payload length, the static header
+ // header length, and variable payload pointer.
+ bool GetFrameBoundaries(const SpdyFrame& frame, int* payload_length,
+ int* header_length, const char** payload) const;
+
+ // Returns a new SpdyControlFrame with the compressed payload of |frame|.
+ SpdyControlFrame* CompressControlFrame(const SpdyControlFrame& frame,
+ const SpdyHeaderBlock* headers);
+
+ // 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;
+
+ // 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.
+ static const size_t kMaxControlFrameSize;
+
+ SpdyState state_;
+ SpdyState previous_state_;
+ SpdyError error_code_;
+ size_t remaining_data_;
+
+ // The number of bytes remaining to read from the current control frame's
+ // payload.
+ size_t remaining_control_payload_;
+
+ // 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_array<char> current_frame_buffer_;
+ size_t current_frame_len_; // Number of bytes read into the current_frame_.
+
+ // 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_;
+
+ std::string display_protocol_;
+
+ // The SPDY version to be spoken/understood by this framer. We support only
+ // integer versions here, as major version numbers indicate framer-layer
+ // incompatibility and minor version numbers indicate application-layer
+ // incompatibility.
+ const int 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/src/net/spdy/spdy_framer_test.cc b/src/net/spdy/spdy_framer_test.cc
new file mode 100644
index 0000000..e0b0d57
--- /dev/null
+++ b/src/net/spdy/spdy_framer_test.cc
@@ -0,0 +1,3218 @@
+// 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/memory/scoped_ptr.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_frame_builder.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;
+
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+ MOCK_METHOD1(OnError, void(SpdyFramer* framer));
+ 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(OnHeaders, void(SpdyStreamId stream_id, bool fin));
+ MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len));
+ MOCK_METHOD2(OnCredentialFrameData, bool(const char* header_data,
+ size_t len));
+ MOCK_METHOD1(OnDataFrameHeader, void(const SpdyDataFrame* frame));
+ MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags));
+ MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value));
+ MOCK_METHOD1(OnPing, void(uint32 unique_id));
+ MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id,
+ SpdyStatusCodes status));
+ MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status));
+ MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id,
+ int delta_window_size));
+ MOCK_METHOD2(OnControlFrameCompressed,
+ void(const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame));
+};
+
+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);
+ size_t input_size = frame.length() + SpdyFrame::kHeaderSize;
+ CHECK_EQ(input_size, framer->ProcessInput(frame.data(), input_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, true);
+ decompressed_frame->set_length(visitor.size() - SpdyFrame::kHeaderSize);
+ return decompressed_frame;
+ }
+
+ class DecompressionVisitor : public SpdyFramerVisitorInterface {
+ public:
+ explicit DecompressionVisitor(int version)
+ : version_(version), buffer_(NULL), size_(0), finished_(false) {
+ }
+
+ void ResetBuffer() {
+ CHECK(buffer_.get() == NULL);
+ CHECK_EQ(0u, size_);
+ CHECK(!finished_);
+ buffer_.reset(new char[kMaxDecompressedSize]);
+ }
+
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 slot,
+ bool fin,
+ bool unidirectional) {
+ SpdyFramer framer(version_);
+ const SpdyHeaderBlock null_headers;
+ int flags = CONTROL_FLAG_NONE;
+ if (fin) {
+ flags &= CONTROL_FLAG_FIN;
+ }
+ if (unidirectional) {
+ flags &= CONTROL_FLAG_UNIDIRECTIONAL;
+ }
+ scoped_ptr<SpdySynStreamControlFrame> frame(
+ framer.CreateSynStream(stream_id,
+ associated_stream_id,
+ priority,
+ slot,
+ static_cast<SpdyControlFlags>(flags),
+ false,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), SpdySynStreamControlFrame::size());
+ size_ += SpdySynStreamControlFrame::size();
+ }
+
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) {
+ SpdyFramer framer(version_);
+ const SpdyHeaderBlock null_headers;
+ int flags = CONTROL_FLAG_NONE;
+ if (fin) {
+ flags &= CONTROL_FLAG_FIN;
+ }
+ scoped_ptr<SpdyHeadersControlFrame> frame(
+ framer.CreateHeaders(stream_id,
+ static_cast<SpdyControlFlags>(flags),
+ false,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), SpdyHeadersControlFrame::size());
+ size_ += SpdySynStreamControlFrame::size();
+ }
+
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) {
+ SpdyFramer framer(version_);
+ const SpdyHeaderBlock null_headers;
+ int flags = CONTROL_FLAG_NONE;
+ if (fin) {
+ flags &= CONTROL_FLAG_FIN;
+ }
+ scoped_ptr<SpdyHeadersControlFrame> frame(
+ framer.CreateHeaders(stream_id,
+ static_cast<SpdyControlFlags>(flags),
+ false,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), SpdyHeadersControlFrame::size());
+ size_ += SpdySynStreamControlFrame::size();
+ }
+
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ 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 bool OnCredentialFrameData(const char* header_data,
+ size_t len) {
+ LOG(FATAL) << "Unexpected CREDENTIAL Frame";
+ return false;
+ }
+
+ virtual void OnError(SpdyFramer* framer) { LOG(FATAL); }
+ virtual void OnDataFrameHeader(const SpdyDataFrame* frame) {
+ LOG(FATAL) << "Unexpected data frame header";
+ }
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags) {
+ LOG(FATAL);
+ }
+ virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+ LOG(FATAL);
+ }
+ virtual void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) {
+ }
+ virtual void OnPing(uint32 unique_id) {
+ LOG(FATAL);
+ }
+ virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
+ LOG(FATAL);
+ }
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {
+ LOG(FATAL);
+ }
+ virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
+ LOG(FATAL);
+ }
+
+ char* ReleaseBuffer() {
+ CHECK(finished_);
+ return buffer_.release();
+ }
+
+ size_t size() const {
+ CHECK(finished_);
+ return size_;
+ }
+
+ private:
+ int version_;
+ scoped_array<char> buffer_;
+ size_t size_;
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor);
+ };
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil);
+};
+
+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 = min(length, kSizeLimit);
+ mark_length = min(mark_length, kSizeLimit);
+ }
+
+ 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 string& description,
+ const unsigned char* actual,
+ const int actual_len,
+ const unsigned char* expected,
+ const int expected_len) {
+ const int min_len = min(actual_len, expected_len);
+ const int max_len = max(actual_len, expected_len);
+ scoped_array<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);
+}
+
+class TestSpdyVisitor : public SpdyFramerVisitorInterface {
+ public:
+ static const size_t kDefaultHeaderBufferSize = 16 * 1024;
+ static const size_t kDefaultCredentialBufferSize = 16 * 1024;
+
+ explicit TestSpdyVisitor(int 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),
+ data_bytes_(0),
+ fin_frame_count_(0),
+ fin_flag_count_(0),
+ zero_length_data_frame_count_(0),
+ header_blocks_count_(0),
+ control_frame_header_data_count_(0),
+ zero_length_control_frame_header_data_count_(0),
+ data_frame_count_(0),
+ header_buffer_(new char[kDefaultHeaderBufferSize]),
+ header_buffer_length_(0),
+ header_buffer_size_(kDefaultHeaderBufferSize),
+ header_stream_id_(-1),
+ header_control_type_(NUM_CONTROL_FRAME_TYPES),
+ header_buffer_valid_(false),
+ credential_buffer_(new char[kDefaultCredentialBufferSize]),
+ credential_buffer_length_(0),
+ credential_buffer_size_(kDefaultCredentialBufferSize) {
+ }
+
+ void OnError(SpdyFramer* f) {
+ LOG(INFO) << "SpdyFramer Error: "
+ << SpdyFramer::ErrorCodeToString(f->error_code());
+ error_count_++;
+ }
+
+ void OnDataFrameHeader(const SpdyDataFrame* frame) {
+ data_frame_count_++;
+ header_stream_id_ = frame->stream_id();
+ }
+
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags) {
+ 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 void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) {
+ syn_frame_count_++;
+ InitHeaderStreaming(SYN_STREAM, stream_id);
+ if (fin) {
+ fin_flag_count_++;
+ }
+ }
+
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) {
+ syn_reply_frame_count_++;
+ InitHeaderStreaming(HEADERS, stream_id);
+ if (fin) {
+ fin_flag_count_++;
+ }
+ }
+
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) {
+ headers_frame_count_++;
+ InitHeaderStreaming(SYN_REPLY, stream_id);
+ if (fin) {
+ fin_flag_count_++;
+ }
+ }
+
+ virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+ setting_count_++;
+ }
+
+ virtual void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) {
+ }
+
+ virtual void OnPing(uint32 unique_id) {
+ DLOG(FATAL);
+ }
+
+ virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
+ fin_frame_count_++;
+ }
+
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {
+ goaway_count_++;
+ }
+
+ virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
+ last_window_update_stream_ = stream_id;
+ last_window_update_delta_ = delta_window_size;
+ }
+
+ bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ ++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_);
+ bool parsed_headers = framer_.ParseHeaderBlockInBuffer(
+ header_buffer_.get(), header_buffer_length_, &headers_);
+ DCHECK(parsed_headers);
+ 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;
+ }
+
+ bool OnCredentialFrameData(const char* credential_data,
+ size_t len) {
+ if (len == 0) {
+ if (!framer_.ParseCredentialData(credential_buffer_.get(),
+ credential_buffer_length_,
+ &credential_)) {
+ ++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;
+ }
+
+ // 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;
+ if (framer_.state() == SpdyFramer::SPDY_DONE)
+ framer_.Reset();
+ }
+ }
+
+ void InitHeaderStreaming(SpdyControlType header_control_type,
+ SpdyStreamId stream_id) {
+ 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 control_frame_buffer_max_size() {
+ return SpdyFramer::kMaxControlFrameSize;
+ }
+
+ 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_;
+ int last_window_update_stream_;
+ int last_window_update_delta_;
+ 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 header_blocks_count_;
+ 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_;
+
+ // Header block streaming state:
+ scoped_array<char> header_buffer_;
+ size_t header_buffer_length_;
+ size_t header_buffer_size_;
+ SpdyStreamId header_stream_id_;
+ SpdyControlType header_control_type_;
+ bool header_buffer_valid_;
+ SpdyHeaderBlock headers_;
+
+ scoped_array<char> credential_buffer_;
+ size_t credential_buffer_length_;
+ size_t credential_buffer_size_;
+ SpdyCredential credential_;
+};
+
+} // namespace net
+
+using test::CompareCharArraysWithHexError;
+using test::SpdyFramerTestUtil;
+using test::TestSpdyVisitor;
+
+TEST(SpdyFrameBuilderTest, WriteLimits) {
+ SpdyFrameBuilder builder(1, DATA_FLAG_NONE, kLengthMask + 8);
+ // Data frame header is 8 bytes
+ EXPECT_EQ(8u, builder.length());
+ const string kLargeData(kLengthMask, 'A');
+ builder.WriteUInt32(kLengthMask);
+ EXPECT_EQ(12u, builder.length());
+ EXPECT_TRUE(builder.WriteBytes(kLargeData.data(), kLengthMask - 4));
+ EXPECT_EQ(kLengthMask + 8u, builder.length());
+}
+
+enum SpdyFramerTestTypes {
+ SPDY2 = 2,
+ SPDY3 = 3,
+};
+
+class SpdyFramerTest
+ : public ::testing::TestWithParam<SpdyFramerTestTypes> {
+ protected:
+ virtual void SetUp() {
+ spdy_version_ = GetParam();
+ }
+
+ 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());
+ int actual_len = actual_frame.length() + SpdyFrame::kHeaderSize;
+ CompareCharArraysWithHexError(
+ description, actual, actual_len, expected, expected_len);
+ }
+
+ // 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; }
+
+ // Version of SPDY protocol to be used.
+ unsigned char spdy_version_;
+};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
+ SpdyFramerTest,
+ ::testing::Values(SPDY2, SPDY3));
+
+TEST_P(SpdyFramerTest, IsCompressible) {
+ SpdyFramer framer(spdy_version_);
+ for (SpdyControlType type = SYN_STREAM;
+ type < NUM_CONTROL_FRAME_TYPES;
+ type = static_cast<SpdyControlType>(type + 1)) {
+ SpdyFrameBuilder frame(type, CONTROL_FLAG_NONE, spdy_version_, 1024);
+ scoped_ptr<SpdyControlFrame> control_frame(
+ reinterpret_cast<SpdyControlFrame*>(frame.take()));
+ EXPECT_EQ(control_frame->has_header_block(),
+ framer.IsCompressible(*control_frame))
+ << "Frame type: " << type;
+ }
+}
+
+// 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_);
+
+ // Encode the header block into a SynStream frame.
+ scoped_ptr<SpdySynStreamControlFrame> 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);
+ string serialized_headers(frame->header_block(), frame->header_block_len());
+ SpdyHeaderBlock new_headers;
+ EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+ 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_);
+
+ // Encode the header block into a SynStream frame.
+ scoped_ptr<SpdySynStreamControlFrame> 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);
+
+ string serialized_headers(frame->header_block(), frame->header_block_len());
+ SpdyHeaderBlock new_headers;
+ EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+ serialized_headers.size() - 2,
+ &new_headers));
+}
+
+TEST_P(SpdyFramerTest, OutOfOrderHeaders) {
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
+
+ frame.WriteUInt32(3); // stream_id
+ 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.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
+ string serialized_headers(syn_frame.header_block(),
+ syn_frame.header_block_len());
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+ EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+ serialized_headers.size(),
+ &new_headers));
+}
+
+TEST_P(SpdyFramerTest, CreateCredential) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "CREDENTIAL frame";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 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',
+ };
+ 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));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, ParseCredentialFrameData) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 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',
+ };
+ SpdyCredentialControlFrame frame(reinterpret_cast<char*>(kFrameData),
+ false);
+ SpdyCredential credential;
+ EXPECT_TRUE(SpdyFramer::ParseCredentialData(frame.payload(), frame.length(),
+ &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) {
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
+
+ frame.WriteUInt32(3); // stream_id
+ 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.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
+ string serialized_headers(syn_frame.header_block(),
+ syn_frame.header_block_len());
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+ // This should fail because duplicate headers are verboten by the spec.
+ EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+ serialized_headers.size(),
+ &new_headers));
+}
+
+TEST_P(SpdyFramerTest, MultiValueHeader) {
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
+
+ frame.WriteUInt32(3); // stream_id
+ 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.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
+ string serialized_headers(syn_frame.header_block(),
+ syn_frame.header_block_len());
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+ EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+ 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";
+
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(true);
+ scoped_ptr<SpdySynStreamControlFrame> frame1(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ scoped_ptr<SpdySynStreamControlFrame> frame2(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+
+ // Expect the second frame to be more compact than the first.
+ EXPECT_LE(frame2->length(), frame1->length());
+
+ // Decompress the first frame
+ scoped_ptr<SpdyFrame> frame3(SpdyFramerTestUtil::DecompressFrame(
+ &framer, *frame1.get()));
+
+ // Decompress the second frame
+ scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame(
+ &framer, *frame2.get()));
+
+ // Expect frames 3 & 4 to be the same.
+ EXPECT_EQ(0,
+ memcmp(frame3->data(), frame4->data(),
+ SpdyFrame::kHeaderSize + frame3->length()));
+
+
+ // Expect frames 3 to be the same as a uncompressed frame created
+ // from scratch.
+ scoped_ptr<SpdySynStreamControlFrame> uncompressed_frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ EXPECT_EQ(frame3->length(), uncompressed_frame->length());
+ EXPECT_EQ(0,
+ memcmp(frame3->data(), uncompressed_frame->data(),
+ SpdyFrame::kHeaderSize + uncompressed_frame->length()));
+}
+
+TEST_P(SpdyFramerTest, Basic) {
+ const unsigned char kV2Input[] = {
+ 0x80, spdy_version_, 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_, 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_, 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_, 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_, 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_, 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_, 0x00, 0x08, // HEADERS on Stream #1
+ 0x00, 0x00, 0x00, 0x22,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 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_, 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_, 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_, 0x00, 0x03, // RST_STREAM on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+ } else {
+ visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+ }
+
+ 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_, 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_, 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_, 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_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x00, 0x00, 0x00, 0x16,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 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,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+ } else {
+ visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+ }
+
+ 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_, 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_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x01, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'a', 'a',
+ 0x00, 0x02, 'b', 'b',
+ };
+ const unsigned char kV3Input[] = {
+ 0x80, spdy_version_, 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_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x01, 0x00, 0x00, 0x1a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 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 {
+ visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+ }
+
+ 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);
+ scoped_ptr<SpdySynStreamControlFrame> syn_frame_1(
+ send_framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 0, // priority
+ 0, // credential slot
+ flags,
+ true, // compress
+ &block));
+ EXPECT_TRUE(syn_frame_1.get() != NULL);
+
+ // SYN_STREAM #2
+ block[kHeader3] = kValue3;
+ scoped_ptr<SpdySynStreamControlFrame> 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<SpdySynStreamControlFrame> syn_frame;
+ scoped_ptr<string> serialized_headers;
+ SpdyHeaderBlock decompressed_headers;
+
+ // Decompress SYN_STREAM #1
+ decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
+ &recv_framer, *syn_frame_1.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ EXPECT_TRUE(decompressed->is_control_frame());
+ EXPECT_EQ(SYN_STREAM,
+ reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type());
+ syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false));
+ serialized_headers.reset(new string(syn_frame->header_block(),
+ syn_frame->header_block_len()));
+ EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(),
+ 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);
+ EXPECT_TRUE(decompressed->is_control_frame());
+ EXPECT_EQ(SYN_STREAM,
+ reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type());
+ syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false));
+ serialized_headers.reset(new string(syn_frame->header_block(),
+ syn_frame->header_block_len()));
+ decompressed_headers.clear();
+ EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(),
+ 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->length() + SpdyFrame::kHeaderSize);
+ data = reinterpret_cast<const unsigned char*>(send_frame->data());
+ visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::kHeaderSize);
+
+ 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->length() + SpdyFrame::kHeaderSize;
+ ++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->length() + SpdyFrame::kHeaderSize;
+ ++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<SpdyWindowUpdateControlFrame> window_update_frame(
+ framer.CreateWindowUpdate(1, 0x12345678));
+
+ const unsigned char expected_data_frame[] = {
+ 0x80, spdy_version_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x12, 0x34, 0x56, 0x78
+ };
+
+ EXPECT_EQ(16u, window_update_frame->size());
+ EXPECT_EQ(0,
+ memcmp(window_update_frame->data(), expected_data_frame, 16));
+}
+
+TEST_P(SpdyFramerTest, CreateDataFrame) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "'hello' data frame, no FIN";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ const char bytes[] = "hello";
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, bytes, strlen(bytes), DATA_FLAG_NONE));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Data frame with negative data byte, no FIN";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0xff
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "\xff", 1, DATA_FLAG_NONE));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "'hello' data frame, with FIN";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "hello", 5, DATA_FLAG_FIN));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Empty data frame";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "", 0, DATA_FLAG_NONE));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "Data frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x01, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 0x7fffffff, "hello", 5, DATA_FLAG_FIN));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ 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_array<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_, 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_, 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'
+ };
+ scoped_ptr<SpdySynStreamControlFrame> frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ framer.GetLowestPriority(),
+ kCre, // credential slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame with a 0-length header name, highest pri, FIN, "
+ "max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[""] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_, 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_, 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'
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(0x7fffffff, // stream id
+ 0x7fffffff, // associated stream id
+ framer.GetHighestPriority(),
+ 0, // credential slot
+ CONTROL_FLAG_FIN,
+ false, // compress
+ &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+
+ {
+ 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_, 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_, 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
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(0x7fffffff, // stream id
+ 0x7fffffff, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_FIN,
+ false, // compress
+ &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+
+// 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_, 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_, 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,
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ priority,
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+#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_, 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_, 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'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 1, CONTROL_FLAG_NONE, false, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_REPLY frame with a 0-length header name, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[""] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_, 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_, 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'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+
+ {
+ 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_, 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_, 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
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+
+// 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_, 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_, 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,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 1, CONTROL_FLAG_NONE, true, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+#endif // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateRstStream) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "RST_STREAM frame";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyRstStreamControlFrame> frame(
+ framer.CreateRstStream(1, PROTOCOL_ERROR));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
+ PROTOCOL_ERROR));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max status code";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x06,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
+ INTERNAL_ERROR));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateSettings) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "Network byte order SETTINGS frame";
+
+ uint32 kValue = 0x0a0b0c0d;
+ SpdySettingsFlags kFlags = static_cast<SpdySettingsFlags>(0x04);
+ SpdySettingsIds kId = static_cast<SpdySettingsIds>(0x030201);
+
+ SettingsMap settings;
+ settings[kId] = SettingsFlagsAndValue(kFlags, kValue);
+
+ EXPECT_EQ(kFlags, settings[kId].first);
+ EXPECT_EQ(kValue, settings[kId].second);
+
+ const unsigned char kFrameDatav2[] = {
+ 0x80, spdy_version_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04,
+ 0x0a, 0x0b, 0x0c, 0x0d,
+ };
+
+ const unsigned char kFrameDatav3[] = {
+ 0x80, spdy_version_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x03, 0x02, 0x01,
+ 0x0a, 0x0b, 0x0c, 0x0d,
+ };
+
+ scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kFrameDatav2 : kFrameDatav3,
+ arraysize(kFrameDatav3)); // Size is unchanged among versions.
+ EXPECT_EQ(SpdyFramer::kInvalidStream,
+ SpdyFramer::GetControlFrameStreamId(frame.get()));
+
+ // Make sure that ParseSettings also works as advertised.
+ SettingsMap parsed_settings;
+ EXPECT_TRUE(framer.ParseSettings(frame.get(), &parsed_settings));
+ EXPECT_EQ(settings.size(), parsed_settings.size());
+ EXPECT_EQ(kFlags, parsed_settings[kId].first);
+ EXPECT_EQ(kValue, parsed_settings[kId].second);
+ }
+
+ {
+ const char kDescription[] = "Basic SETTINGS frame";
+
+ SettingsMap settings;
+#if defined(__LB_SHELL__)
+ // second argument is expected to be in network byte order
+ AddSpdySettingFromWireFormat(
+ &settings, htonl(0x00000000), 0x00000001); // 1st Setting
+ AddSpdySettingFromWireFormat(
+ &settings, htonl(0x02000001), 0x00000002); // 2nd Setting
+ AddSpdySettingFromWireFormat(
+ &settings, htonl(0x03000002), 0x00000003); // 3rd Setting
+ AddSpdySettingFromWireFormat(
+ &settings, htonl(0x04000003), 0xff000004); // 4th Setting
+ AddSpdySettingFromWireFormat(
+ &settings, htonl(0x050000ff), 0x00000005); // 5th Setting
+ AddSpdySettingFromWireFormat(
+ &settings, htonl(0xffffffff), 0x00000006); // 6th Setting
+#else
+ AddSpdySettingFromWireFormat(
+ &settings, 0x00000000, 0x00000001); // 1st Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0x01000002, 0x00000002); // 2nd Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0x02000003, 0x00000003); // 3rd Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0x03000004, 0xff000004); // 4th Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0xff000005, 0x00000005); // 5th Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0xffffffff, 0x00000006); // 6th Setting
+#endif
+
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x34,
+ 0x00, 0x00, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x00, // 1st Setting
+ 0x00, 0x00, 0x00, 0x01,
+ 0x02, 0x00, 0x00, 0x01, // 2nd Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x02, // 3rd Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x04, 0x00, 0x00, 0x03, // 4th Setting
+ 0xff, 0x00, 0x00, 0x04,
+ 0x05, 0x00, 0x00, 0xff, // 5th Setting
+ 0x00, 0x00, 0x00, 0x05,
+ 0xff, 0xff, 0xff, 0xff, // 6th Setting
+ 0x00, 0x00, 0x00, 0x06,
+ };
+ scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings));
+ CompareFrame(kDescription,
+ *frame,
+ kFrameData,
+ arraysize(kFrameData));
+ EXPECT_EQ(SpdyFramer::kInvalidStream,
+ SpdyFramer::GetControlFrameStreamId(frame.get()));
+ }
+
+ {
+ const char kDescription[] = "Empty SETTINGS frame";
+
+ SettingsMap settings;
+
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreatePingFrame) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "PING frame";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x12, 0x34, 0x56, 0x78,
+ };
+ scoped_ptr<SpdyPingControlFrame> frame(framer.CreatePingFrame(0x12345678u));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ EXPECT_EQ(SpdyFramer::kInvalidStream,
+ SpdyFramer::GetControlFrameStreamId(frame.get()));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateGoAway) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "GOAWAY frame";
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyGoAwayControlFrame> frame(framer.CreateGoAway(0, GOAWAY_OK));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ EXPECT_EQ(SpdyFramer::kInvalidStream,
+ SpdyFramer::GetControlFrameStreamId(frame.get()));
+ }
+
+ {
+ const char kDescription[] = "GOAWAY frame with max stream ID, status";
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF,
+ GOAWAY_INTERNAL_ERROR));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+
+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_, 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_, 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'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 1, CONTROL_FLAG_NONE, false, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header name, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[""] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_, 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_, 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'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+
+ {
+ 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_, 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_, 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
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+
+// 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_, 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_, 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,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 1, CONTROL_FLAG_NONE, true, &headers));
+ CompareFrame(kDescription,
+ *frame,
+ IsSpdy2() ? kV2FrameData : kV3FrameData,
+ IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+ }
+}
+#endif // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateWindowUpdate) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyWindowUpdateControlFrame> frame(
+ framer.CreateWindowUpdate(1, 1));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, DuplicateFrame) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "PING frame";
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x12, 0x34, 0x56, 0x78,
+ };
+ scoped_ptr<SpdyFrame> frame1(framer.CreatePingFrame(0x12345678u));
+ CompareFrame(kDescription, *frame1, kFrameData, arraysize(kFrameData));
+
+ scoped_ptr<SpdyFrame> frame2(framer.DuplicateFrame(*frame1));
+ CompareFrame(kDescription, *frame2, kFrameData, arraysize(kFrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedSynStreamHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["aa"] = "vv";
+ headers["bb"] = "ww";
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdySynStreamControlFrame> 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->length() + SpdyControlFrame::kHeaderSize);
+ 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<SpdySynReplyControlFrame> 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->length() + SpdyControlFrame::kHeaderSize);
+ 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<SpdyHeadersControlFrame> 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->length() + SpdyControlFrame::kHeaderSize);
+ 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<SpdyHeadersControlFrame> 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->length() + SpdyControlFrame::kHeaderSize);
+ 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) {
+ SpdyHeaderBlock headers;
+ // Size a header value to just fit inside the control frame buffer:
+ // SPDY 2 SPDY 3
+ // SYN_STREAM header: 18 bytes 18 bytes
+ // Serialized header block:
+ // # headers 2 bytes (uint16) 4 bytes (uint32)
+ // name length 2 bytes (uint16) 4 bytes (uint32)
+ // name text ("aa") 2 bytes 2 bytes
+ // value length 2 bytes (uint16) 4 bytes (uint32)
+ // --- ---
+ // 26 bytes 32 bytes
+ const size_t overhead = IsSpdy2() ? 26 : 32;
+ const size_t big_value_size =
+ TestSpdyVisitor::control_frame_buffer_max_size() - overhead;
+ std::string big_value(big_value_size, 'x');
+ headers["aa"] = big_value.c_str();
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdySynStreamControlFrame> control_frame(
+ 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);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ 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(big_value_size, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ControlFrameTooLarge) {
+ SpdyHeaderBlock headers;
+ // See size calculation for test above. This is one byte larger, which
+ // should exceed the control frame buffer capacity by that one byte.
+ const size_t overhead = IsSpdy2() ? 25 : 31;
+ const size_t kBigValueSize =
+ TestSpdyVisitor::control_frame_buffer_max_size() - overhead;
+ std::string big_value(kBigValueSize, 'x');
+ headers["aa"] = big_value.c_str();
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdySynStreamControlFrame> control_frame(
+ 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);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_FALSE(visitor.header_buffer_valid_);
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE,
+ visitor.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 big_value_size = kHeaderBufferSize * 2;
+ std::string big_value(big_value_size, 'x');
+ headers["aa"] = big_value.c_str();
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdySynStreamControlFrame> 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->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_FALSE(visitor.header_buffer_valid_);
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE,
+ visitor.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_);
+ // 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<SpdySynStreamControlFrame> 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->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(SpdyFramer::SPDY_DECOMPRESS_FAILURE, visitor.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.
+ size_t overhead = SpdyFramer::kControlFrameBufferSize;
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyGoAwayControlFrame> goaway(framer.CreateGoAway(1, GOAWAY_OK));
+ goaway->set_length(goaway->length() + overhead);
+ string pad('A', overhead);
+ TestSpdyVisitor visitor(spdy_version_);
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(goaway->data()),
+ goaway->length() - overhead + SpdyControlFrame::kHeaderSize);
+ visitor.SimulateInFramer(
+ reinterpret_cast<const unsigned char*>(pad.c_str()),
+ overhead);
+
+ EXPECT_EQ(1, visitor.error_count_); // This generated an error.
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME,
+ visitor.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));
+ control_frame->set_length(0);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ // 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));
+ control_frame->set_length(5);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ // 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->length() + SpdyControlFrame::kHeaderSize);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+
+ // Read all at once.
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ 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->length() +
+ SpdyControlFrame::kHeaderSize;
+ 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_, 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_, 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,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData));
+ } else {
+ visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+ }
+ 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_, 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_, 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,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData));
+ } else {
+ visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+ }
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(1, visitor.setting_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadWindowUpdate) {
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyWindowUpdateControlFrame> control_frame(
+ framer.CreateWindowUpdate(1, 2));
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_EQ(1, visitor.last_window_update_stream_);
+ EXPECT_EQ(2, 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->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(control_frame->length(), 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->length() + SpdyFrame::kHeaderSize;
+ ++idx) {
+ visitor.SimulateInFramer(data + idx, 1);
+ ASSERT_EQ(0, visitor.error_count_);
+ }
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(control_frame->length(), 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;
+ control_frame->set_length(0);
+ unsigned char* data =
+ reinterpret_cast<unsigned char*>(control_frame->data());
+ visitor.SimulateInFramer(data, SpdyControlFrame::kHeaderSize);
+ 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 = SpdyControlFrame::kHeaderSize + 4;
+ data[offset] = 0xFF; // Proof length is past the end of the frame
+ visitor.SimulateInFramer(
+ data, control_frame->length() + SpdyControlFrame::kHeaderSize);
+ 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 = SpdyControlFrame::kHeaderSize + credential.proof.length();
+ data[offset] = 0xFF; // Certificate length is past the end of the frame
+ visitor.SimulateInFramer(
+ data, control_frame->length() + SpdyControlFrame::kHeaderSize);
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+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) {
+ SpdyFramer framer(spdy_version_);
+ char garbage_frame[256];
+ memset(garbage_frame, ~0, sizeof(garbage_frame));
+ SpdyControlFrame control_frame(&garbage_frame[0], false);
+ control_frame.set_version(spdy_version_);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ sizeof(garbage_frame));
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, StateToStringTest) {
+ EXPECT_STREQ("ERROR",
+ SpdyFramer::StateToString(SpdyFramer::SPDY_ERROR));
+ EXPECT_STREQ("DONE",
+ SpdyFramer::StateToString(SpdyFramer::SPDY_DONE));
+ 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("UNKNOWN_ERROR",
+ SpdyFramer::ErrorCodeToString(SpdyFramer::LAST_ERROR));
+}
+
+TEST_P(SpdyFramerTest, StatusCodeToStringTest) {
+ EXPECT_STREQ("INVALID",
+ SpdyFramer::StatusCodeToString(INVALID));
+ EXPECT_STREQ("PROTOCOL_ERROR",
+ SpdyFramer::StatusCodeToString(PROTOCOL_ERROR));
+ EXPECT_STREQ("INVALID_STREAM",
+ SpdyFramer::StatusCodeToString(INVALID_STREAM));
+ EXPECT_STREQ("REFUSED_STREAM",
+ SpdyFramer::StatusCodeToString(REFUSED_STREAM));
+ EXPECT_STREQ("UNSUPPORTED_VERSION",
+ SpdyFramer::StatusCodeToString(UNSUPPORTED_VERSION));
+ EXPECT_STREQ("CANCEL",
+ SpdyFramer::StatusCodeToString(CANCEL));
+ EXPECT_STREQ("INTERNAL_ERROR",
+ SpdyFramer::StatusCodeToString(INTERNAL_ERROR));
+ EXPECT_STREQ("FLOW_CONTROL_ERROR",
+ SpdyFramer::StatusCodeToString(FLOW_CONTROL_ERROR));
+ EXPECT_STREQ("UNKNOWN_STATUS",
+ SpdyFramer::StatusCodeToString(NUM_STATUS_CODES));
+}
+
+TEST_P(SpdyFramerTest, ControlTypeToStringTest) {
+ EXPECT_STREQ("SYN_STREAM",
+ SpdyFramer::ControlTypeToString(SYN_STREAM));
+ EXPECT_STREQ("SYN_REPLY",
+ SpdyFramer::ControlTypeToString(SYN_REPLY));
+ EXPECT_STREQ("RST_STREAM",
+ SpdyFramer::ControlTypeToString(RST_STREAM));
+ EXPECT_STREQ("SETTINGS",
+ SpdyFramer::ControlTypeToString(SETTINGS));
+ EXPECT_STREQ("NOOP",
+ SpdyFramer::ControlTypeToString(NOOP));
+ EXPECT_STREQ("PING",
+ SpdyFramer::ControlTypeToString(PING));
+ EXPECT_STREQ("GOAWAY",
+ SpdyFramer::ControlTypeToString(GOAWAY));
+ EXPECT_STREQ("HEADERS",
+ SpdyFramer::ControlTypeToString(HEADERS));
+ EXPECT_STREQ("WINDOW_UPDATE",
+ SpdyFramer::ControlTypeToString(WINDOW_UPDATE));
+ EXPECT_STREQ("CREDENTIAL",
+ SpdyFramer::ControlTypeToString(CREDENTIAL));
+ EXPECT_STREQ("UNKNOWN_CONTROL_TYPE",
+ SpdyFramer::ControlTypeToString(NUM_CONTROL_FRAME_TYPES));
+}
+
+TEST_P(SpdyFramerTest, GetMinimumControlFrameSizeTest) {
+ EXPECT_EQ(SpdySynStreamControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ SYN_STREAM));
+ EXPECT_EQ(SpdySynReplyControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ SYN_REPLY));
+ EXPECT_EQ(SpdyRstStreamControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ RST_STREAM));
+ EXPECT_EQ(SpdySettingsControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ SETTINGS));
+
+#if defined(__LB_SHELL__) || defined(COBALT)
+ // Compiler quirk: <function> declared using a type with no linkage,
+ // must be defined in this translation unit
+ unsigned int spdy_frame_header_size = SpdyFrame::kHeaderSize;
+ EXPECT_EQ(spdy_frame_header_size,
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ NOOP));
+#else
+ EXPECT_EQ(SpdyFrame::kHeaderSize,
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ NOOP));
+#endif
+ EXPECT_EQ(SpdyPingControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ PING));
+ size_t goaway_size = SpdyGoAwayControlFrame::size();
+ if (IsSpdy2()) {
+ // SPDY 2 GOAWAY is smaller by 32 bits.
+ goaway_size -= 4;
+ }
+ EXPECT_EQ(goaway_size,
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ GOAWAY));
+ EXPECT_EQ(SpdyHeadersControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ HEADERS));
+ EXPECT_EQ(SpdyWindowUpdateControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ WINDOW_UPDATE));
+ EXPECT_EQ(SpdyCredentialControlFrame::size(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ CREDENTIAL));
+ EXPECT_EQ(numeric_limits<size_t>::max(),
+ SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+ NUM_CONTROL_FRAME_TYPES));
+}
+
+TEST_P(SpdyFramerTest, CatchProbableHttpResponse) {
+ {
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ // This won't cause an error at the framer level. It will cause
+ // flag validation errors at the Visitor::OnDataFrameHeader level.
+ EXPECT_CALL(visitor, OnDataFrameHeader(_));
+ framer.ProcessInput("HTTP/1.1", 8);
+ EXPECT_TRUE(framer.probable_http_response());
+ EXPECT_EQ(SpdyFramer::SPDY_FORWARD_STREAM_FRAME, framer.state());
+ }
+ {
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ // This won't cause an error at the framer level. It will cause
+ // flag validation errors at the Visitor::OnDataFrameHeader level.
+ EXPECT_CALL(visitor, OnDataFrameHeader(_));
+ framer.ProcessInput("HTTP/1.0", 8);
+ EXPECT_TRUE(framer.probable_http_response());
+ EXPECT_EQ(SpdyFramer::SPDY_FORWARD_STREAM_FRAME, framer.state());
+ }
+}
+
+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));
+ frame->set_flags(flags);
+
+ // Flags are just passed along since they need to be validated at
+ // a higher protocol layer.
+ EXPECT_CALL(visitor, OnDataFrameHeader(_));
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5, SpdyDataFlags()));
+ if (flags & DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, DATA_FLAG_FIN));
+ }
+
+ size_t frame_size = frame->length() + SpdyFrame::kHeaderSize;
+ framer.ProcessInput(frame->data(), frame_size);
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code());
+ }
+}
+
+TEST_P(SpdyFramerTest, EmptySynStream) {
+ SpdyHeaderBlock headers;
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnControlFrameCompressed(_, _));
+ scoped_ptr<SpdySynStreamControlFrame>
+ frame(framer.CreateSynStream(1, 0, 1, 0, CONTROL_FLAG_NONE, true,
+ &headers));
+ // Adjust size to remove the name/value block.
+ frame->set_length(
+ SpdySynStreamControlFrame::size() - SpdyFrame::kHeaderSize);
+
+ EXPECT_CALL(visitor, OnSynStream(1, 0, 1, 0, false, false));
+ EXPECT_CALL(visitor, OnControlFrameHeaderData(1, NULL, 0));
+
+ size_t frame_size = frame->length() + SpdyFrame::kHeaderSize;
+ framer.ProcessInput(frame->data(), frame_size);
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, 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_));
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_header_block.cc b/src/net/spdy/spdy_header_block.cc
new file mode 100644
index 0000000..3680cb4
--- /dev/null
+++ b/src/net/spdy/spdy_header_block.cc
@@ -0,0 +1,51 @@
+// 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"
+
+namespace net {
+
+Value* SpdyHeaderBlockNetLogCallback(
+ const SpdyHeaderBlock* headers,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ DictionaryValue* headers_dict = new DictionaryValue();
+ for (SpdyHeaderBlock::const_iterator it = headers->begin();
+ it != headers->end(); ++it) {
+ headers_dict->SetWithoutPathExpansion(
+ it->first, new StringValue(it->second));
+ }
+ 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::key_iterator it = header_dict->begin_keys();
+ it != header_dict->end_keys();
+ ++it) {
+ std::string value;
+ if (!header_dict->GetString(*it, &(*headers)[*it])) {
+ headers->clear();
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_header_block.h b/src/net/spdy/spdy_header_block.h
new file mode 100644
index 0000000..8ae8fa3
--- /dev/null
+++ b/src/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/src/net/spdy/spdy_header_block_unittest.cc b/src/net/spdy/spdy_header_block_unittest.cc
new file mode 100644
index 0000000..3cfef16
--- /dev/null
+++ b/src/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/src/net/spdy/spdy_http_stream.cc b/src/net/spdy/spdy_http_stream.cc
new file mode 100644
index 0000000..ab1b32f
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream.cc
@@ -0,0 +1,534 @@
+// 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/bind_helpers.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/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_session.h"
+
+namespace net {
+
+SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session,
+ bool direct)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+ stream_(NULL),
+ spdy_session_(spdy_session),
+ request_info_(NULL),
+ has_upload_data_(false),
+ response_info_(NULL),
+ download_finished_(false),
+ response_headers_received_(false),
+ user_buffer_len_(0),
+ buffered_read_callback_pending_(false),
+ more_read_data_pending_(false),
+ direct_(direct) { }
+
+void SpdyHttpStream::InitializeWithExistingStream(SpdyStream* spdy_stream) {
+ stream_ = spdy_stream;
+ stream_->SetDelegate(this);
+ response_headers_received_ = true;
+}
+
+SpdyHttpStream::~SpdyHttpStream() {
+ if (stream_)
+ stream_->DetachDelegate();
+}
+
+int SpdyHttpStream::InitializeStream(const HttpRequestInfo* request_info,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback) {
+ DCHECK(!stream_.get());
+ if (spdy_session_->IsClosed())
+ 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;
+ }
+
+ if (stream_.get())
+ return OK;
+
+ return spdy_session_->CreateStream(request_info_->url,
+ request_info_->priority, &stream_,
+ stream_net_log, callback);
+}
+
+const HttpResponseInfo* SpdyHttpStream::GetResponseInfo() const {
+ return response_info_;
+}
+
+UploadProgress SpdyHttpStream::GetUploadProgress() const {
+ if (!request_info_ || !request_info_->upload_data_stream)
+ 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());
+ CHECK(!stream_->cancelled());
+
+ if (stream_->closed())
+ return stream_->response_status();
+
+ // Check if we already have the response headers. If so, return synchronously.
+ if(stream_->response_received()) {
+ CHECK(stream_->is_idle());
+ 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) {
+ CHECK(stream_->is_idle());
+ CHECK(buf);
+ CHECK(buf_len);
+ CHECK(!callback.is_null());
+
+ // If we have data buffered, complete the IO immediately.
+ if (!response_body_.empty()) {
+ int bytes_read = 0;
+ while (!response_body_.empty() && buf_len > 0) {
+ scoped_refptr<IOBufferWithSize> data = response_body_.front();
+ const int bytes_to_copy = std::min(buf_len, data->size());
+ memcpy(&(buf->data()[bytes_read]), data->data(), bytes_to_copy);
+ buf_len -= bytes_to_copy;
+ if (bytes_to_copy == data->size()) {
+ response_body_.pop_front();
+ } else {
+ const int bytes_remaining = data->size() - bytes_to_copy;
+ IOBufferWithSize* new_buffer = new IOBufferWithSize(bytes_remaining);
+ memcpy(new_buffer->data(), &(data->data()[bytes_to_copy]),
+ bytes_remaining);
+ response_body_.pop_front();
+ response_body_.push_front(make_scoped_refptr(new_buffer));
+ }
+ bytes_read += bytes_to_copy;
+ }
+ stream_->IncreaseRecvWindowSize(bytes_read);
+ return bytes_read;
+ } else if (stream_->closed()) {
+ return stream_->response_status();
+ }
+
+ CHECK(callback_.is_null());
+ CHECK(!user_buffer_);
+ 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();
+}
+
+HttpStream* SpdyHttpStream::RenewStreamForAuth() {
+ return NULL;
+}
+
+bool SpdyHttpStream::IsResponseBodyComplete() const {
+ if (!stream_)
+ return false;
+ return stream_->closed();
+}
+
+bool SpdyHttpStream::CanFindEndOfResponse() const {
+ return true;
+}
+
+bool SpdyHttpStream::IsMoreDataBuffered() const {
+ return false;
+}
+
+bool SpdyHttpStream::IsConnectionReused() const {
+ return spdy_session_->IsReused();
+}
+
+void SpdyHttpStream::SetConnectionReused() {
+ // SPDY doesn't need an indicator here.
+}
+
+bool SpdyHttpStream::IsConnectionReusable() const {
+ // SPDY streams aren't considered reusable.
+ return false;
+}
+
+int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ base::Time request_time = base::Time::Now();
+ CHECK(stream_.get());
+
+ stream_->SetDelegate(this);
+
+ 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()));
+ stream_->set_spdy_headers(headers.Pass());
+
+ 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(!has_upload_data_);
+ has_upload_data_ = request_info_->upload_data_stream &&
+ (request_info_->upload_data_stream->size() ||
+ request_info_->upload_data_stream->is_chunked());
+ if (has_upload_data_) {
+ // Use kMaxSpdyFrameChunkSize as the buffer size, since the request
+ // body data is written with this size at a time.
+ raw_request_body_buf_ = new IOBufferWithSize(kMaxSpdyFrameChunkSize);
+ // The request body buffer is empty at first.
+ request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_, 0);
+ }
+
+ CHECK(!callback.is_null());
+ CHECK(!stream_->cancelled());
+ CHECK(response);
+
+ if (!stream_->pushed() && stream_->closed()) {
+ if (stream_->response_status() == OK)
+ return ERR_FAILED;
+ else
+ return stream_->response_status();
+ }
+
+ // 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);
+
+ result = stream_->SendRequest(has_upload_data_);
+ if (result == ERR_IO_PENDING) {
+ CHECK(callback_.is_null());
+ callback_ = callback;
+ }
+ return result;
+}
+
+void SpdyHttpStream::Cancel() {
+ if (spdy_session_)
+ spdy_session_->CancelPendingCreateStreams(&stream_);
+ callback_.Reset();
+ if (stream_)
+ stream_->Cancel();
+}
+
+int SpdyHttpStream::SendData() {
+ CHECK(request_info_ && request_info_->upload_data_stream);
+ CHECK_EQ(0, request_body_buf_->BytesRemaining());
+
+ // Read the data from the request body stream.
+ const int bytes_read = request_info_->upload_data_stream->Read(
+ raw_request_body_buf_, raw_request_body_buf_->size(),
+ base::Bind(
+ base::IgnoreResult(&SpdyHttpStream::OnRequestBodyReadCompleted),
+ weak_factory_.GetWeakPtr()));
+
+ if (bytes_read == ERR_IO_PENDING)
+ return ERR_IO_PENDING;
+ // ERR_IO_PENDING is the only possible error.
+ DCHECK_GE(bytes_read, 0);
+ return OnRequestBodyReadCompleted(bytes_read);
+}
+
+bool SpdyHttpStream::OnSendHeadersComplete(int status) {
+ if (!callback_.is_null())
+ DoCallback(status);
+ return !has_upload_data_;
+}
+
+int SpdyHttpStream::OnSendBody() {
+ CHECK(request_info_ && request_info_->upload_data_stream);
+ const bool eof = request_info_->upload_data_stream->IsEOF();
+ if (request_body_buf_->BytesRemaining() > 0) {
+ return stream_->WriteStreamData(
+ request_body_buf_,
+ request_body_buf_->BytesRemaining(),
+ eof ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+ }
+
+ // The entire body data has been sent.
+ if (eof)
+ return OK;
+
+ return SendData();
+}
+
+int SpdyHttpStream::OnSendBodyComplete(int status, bool* eof) {
+ // |status| is the number of bytes written to the SPDY stream.
+ CHECK(request_info_ && request_info_->upload_data_stream);
+ *eof = false;
+
+ if (status > 0) {
+ request_body_buf_->DidConsume(status);
+ if (request_body_buf_->BytesRemaining()) {
+ // Go back to OnSendBody() to send the remaining data.
+ return OK;
+ }
+ }
+
+ // Check if the entire body data has been sent.
+ *eof = (request_info_->upload_data_stream->IsEOF() &&
+ !request_body_buf_->BytesRemaining());
+ return OK;
+}
+
+int SpdyHttpStream::OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) {
+ if (!response_info_) {
+ DCHECK(stream_->pushed());
+ push_response_info_.reset(new HttpResponseInfo);
+ response_info_ = push_response_info_.get();
+ }
+
+ // If the response is already received, these headers are too late.
+ if (response_headers_received_) {
+ LOG(WARNING) << "SpdyHttpStream headers received after response started.";
+ return OK;
+ }
+
+ // TODO(mbelshe): This is the time of all headers received, not just time
+ // to first byte.
+ response_info_->response_time = base::Time::Now();
+
+ if (!SpdyHeadersToHttpResponse(response, stream_->GetProtocolVersion(),
+ response_info_)) {
+ // We might not have complete headers yet.
+ return ERR_INCOMPLETE_SPDY_HEADERS;
+ }
+
+ response_headers_received_ = true;
+ // 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_->vary_data.Init(*request_info_, *response_info_->headers);
+ // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control
+ // frame has been received and processed. Move to framer?
+ response_info_->response_time = response_time;
+
+ if (!callback_.is_null())
+ DoCallback(status);
+
+ return status;
+}
+
+void SpdyHttpStream::OnHeadersSent() {
+ // For HTTP streams, no HEADERS frame is sent from the client.
+ NOTREACHED();
+}
+
+int SpdyHttpStream::OnDataReceived(const char* data, int length) {
+ // SpdyStream won't call us with data if the header block didn't contain a
+ // valid set of headers. So we don't expect to not have headers received
+ // here.
+ if (!response_headers_received_)
+ return ERR_INCOMPLETE_SPDY_HEADERS;
+
+ // 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_->closed() || stream_->pushed());
+ if (length > 0) {
+ // Save the received data.
+ IOBufferWithSize* io_buffer = new IOBufferWithSize(length);
+ memcpy(io_buffer->data(), data, length);
+ response_body_.push_back(make_scoped_refptr(io_buffer));
+
+ if (user_buffer_) {
+ // 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();
+ }
+ }
+ return OK;
+}
+
+void SpdyHttpStream::OnDataSent(int length) {
+ // For HTTP streams, no data is sent from the client while in the OPEN state,
+ // so it is never called.
+ NOTREACHED();
+}
+
+void SpdyHttpStream::OnClose(int status) {
+ 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);
+}
+
+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);
+ 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;
+
+ int bytes_buffered = 0;
+ std::list<scoped_refptr<IOBufferWithSize> >::const_iterator it;
+ for (it = response_body_.begin();
+ it != response_body_.end() && bytes_buffered < user_buffer_len_;
+ ++it)
+ bytes_buffered += (*it)->size();
+
+ return bytes_buffered < user_buffer_len_;
+}
+
+bool SpdyHttpStream::DoBufferedReadCallback() {
+ weak_factory_.InvalidateWeakPtrs();
+ buffered_read_callback_pending_ = false;
+
+ // If the transaction is cancelled or errored out, we don't need to complete
+ // the read.
+ if (!stream_ || stream_->response_status() != OK || stream_->cancelled())
+ 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_) {
+ rv = ReadResponseBody(user_buffer_, 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);
+}
+
+int SpdyHttpStream::OnRequestBodyReadCompleted(int status) {
+ DCHECK_GE(status, 0);
+
+ request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_, status);
+
+ const bool eof = request_info_->upload_data_stream->IsEOF();
+ return stream_->WriteStreamData(request_body_buf_,
+ request_body_buf_->BytesRemaining(),
+ eof ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+void SpdyHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+ DCHECK(stream_);
+ bool using_npn;
+ NextProto protocol_negotiated = kProtoUnknown;
+ stream_->GetSSLInfo(ssl_info, &using_npn, &protocol_negotiated);
+}
+
+void SpdyHttpStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ DCHECK(stream_);
+ stream_->GetSSLCertRequestInfo(cert_request_info);
+}
+
+bool SpdyHttpStream::IsSpdyHttpStream() const {
+ return true;
+}
+
+void SpdyHttpStream::Drain(HttpNetworkSession* session) {
+ Close(false);
+ delete this;
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_http_stream.h b/src/net/spdy/spdy_http_stream.h
new file mode 100644
index 0000000..ddd2ccb
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream.h
@@ -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.
+
+#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_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:
+ SpdyHttpStream(SpdySession* spdy_session, bool direct);
+ virtual ~SpdyHttpStream();
+
+ // Initializes this SpdyHttpStream by wrapping an existing SpdyStream.
+ void InitializeWithExistingStream(SpdyStream* spdy_stream);
+
+ 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,
+ 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;
+ virtual bool IsMoreDataBuffered() const OVERRIDE;
+ virtual bool IsConnectionReused() const OVERRIDE;
+ virtual void SetConnectionReused() OVERRIDE;
+ virtual bool IsConnectionReusable() const OVERRIDE;
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+ virtual void LogNumRttVsBytesMetrics() const OVERRIDE {}
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+
+ // SpdyStream::Delegate implementation.
+ virtual bool OnSendHeadersComplete(int status) OVERRIDE;
+ virtual int OnSendBody() OVERRIDE;
+ virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE;
+ virtual void OnHeadersSent() OVERRIDE;
+ virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE;
+ virtual void OnDataSent(int length) OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ private:
+ // Reads the data (whether chunked or not) from the request body stream and
+ // sends the data by calling WriteStreamData on the underlying SpdyStream.
+ int SendData();
+
+ // Call the user callback.
+ void DoCallback(int rv);
+
+ int OnRequestBodyReadCompleted(int status);
+
+ void ScheduleBufferedReadCallback();
+
+ // Returns true if the callback is invoked.
+ bool DoBufferedReadCallback();
+ bool ShouldWaitForMoreBufferedData() const;
+
+ base::WeakPtrFactory<SpdyHttpStream> weak_factory_;
+ scoped_refptr<SpdyStream> stream_;
+ scoped_refptr<SpdySession> spdy_session_;
+
+ // The request to send.
+ const HttpRequestInfo* request_info_;
+
+ bool has_upload_data_;
+
+ // |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_;
+
+ bool download_finished_;
+ bool response_headers_received_; // Indicates waiting for more HEADERS.
+
+ // We buffer the response body as it arrives asynchronously from the stream.
+ // TODO(mbelshe): is this infinite buffering?
+ std::list<scoped_refptr<IOBufferWithSize> > response_body_;
+
+ 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> raw_request_body_buf_;
+ // Wraps raw_request_body_buf_ to read the remaining data progressively.
+ scoped_refptr<DrainableIOBuffer> request_body_buf_;
+
+ // 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/src/net/spdy/spdy_http_stream_spdy2_unittest.cc b/src/net/spdy/spdy_http_stream_spdy2_unittest.cc
new file mode 100644
index 0000000..544f686
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream_spdy2_unittest.cc
@@ -0,0 +1,375 @@
+// 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 "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "crypto/signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_element_reader.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+class SpdyHttpStreamSpdy2Test : public testing::Test {
+ public:
+ OrderedSocketData* data() { return data_.get(); }
+ protected:
+ SpdyHttpStreamSpdy2Test() {}
+
+ virtual void TearDown() {
+ crypto::ECSignatureCreator::SetFactoryForTesting(NULL);
+ UploadDataStream::ResetMergeChunks();
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ void set_merge_chunks(bool merge) {
+ UploadDataStream::set_merge_chunks(merge);
+ }
+
+ int InitSession(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ HostPortPair& host_port_pair) {
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ data_.reset(new OrderedSocketData(reads, reads_count,
+ writes, writes_count));
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+ transport_params_ = new TransportSocketParams(host_port_pair,
+ MEDIUM, false, false,
+ OnHostResolutionCallback());
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair.ToString(),
+ transport_params_,
+ MEDIUM,
+ callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ return session_->InitializeWithSocket(connection.release(), false, OK);
+ }
+
+ SpdySessionDependencies session_deps_;
+ scoped_ptr<OrderedSocketData> data_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ scoped_refptr<SpdySession> session_;
+ scoped_refptr<TransportSocketParams> transport_params_;
+};
+
+TEST_F(SpdyHttpStreamSpdy2Test, SendRequest) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(SYNCHRONOUS, 0, 3) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ 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_.get(), true));
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // 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(http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyHttpStreamSpdy2Test, SendChunkedPost) {
+ set_merge_chunks(false);
+
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*chunk1, 1), // POST upload frames
+ CreateMockWrite(*chunk2, 2),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 3),
+ CreateMockRead(*chunk1, 4),
+ CreateMockRead(*chunk2, 5),
+ MockRead(SYNCHRONOUS, 0, 6) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_stream;
+
+ ASSERT_EQ(OK, upload_stream.InitSync());
+
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ SpdyHttpStream http_stream(session_.get(), true);
+ ASSERT_EQ(
+ OK,
+ http_stream.InitializeStream(&request, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
+ headers, &response, callback.callback()));
+ EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // This triggers the MockWrite and read 2
+ callback.WaitForResult();
+
+ // This triggers read 3. The empty read causes the session to shut down.
+ data()->CompleteRead();
+ MessageLoop::current()->RunUntilIdle();
+
+ // Because we abandoned the stream, we don't expect to find a session in the
+ // pool anymore.
+ EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+ 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_F(SpdyHttpStreamSpdy2Test, DelayedSendChunkedPost) {
+ set_merge_chunks(false);
+
+ const char kUploadData1[] = "12345678";
+ const int kUploadData1Size = arraysize(kUploadData1)-1;
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(
+ ConstructSpdyBodyFrame(1, kUploadData1, kUploadData1Size, false));
+ scoped_ptr<SpdyFrame> chunk3(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(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);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ DeterministicMockClientSocketFactory* socket_factory =
+ session_deps_.deterministic_socket_factory.get();
+ socket_factory->AddSocketDataProvider(&data);
+
+ http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+ session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+ transport_params_ = new TransportSocketParams(host_port_pair,
+ MEDIUM, false, false,
+ OnHostResolutionCallback());
+
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair.ToString(),
+ transport_params_,
+ MEDIUM,
+ callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ callback.WaitForResult();
+ EXPECT_EQ(OK,
+ session_->InitializeWithSocket(connection.release(), false, OK));
+
+ 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.InitSync());
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(
+ new SpdyHttpStream(session_.get(), true));
+ ASSERT_EQ(OK, http_stream->InitializeStream(&request,
+ net_log,
+ CompletionCallback()));
+
+ 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(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // Complete the initial request write and the first chunk.
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_GT(callback.WaitForResult(), 0);
+
+ // 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.
+ data.RunFor(2);
+
+ // Read response headers.
+ data.RunFor(1);
+ ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+ // Read and check |chunk1| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(buf1,
+ kUploadDataSize,
+ callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+ // Read and check |chunk2| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size));
+ ASSERT_EQ(kUploadData1Size,
+ http_stream->ReadResponseBody(buf2,
+ kUploadData1Size,
+ callback.callback()));
+ EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
+
+ // Read and check |chunk3| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(buf3,
+ kUploadDataSize,
+ callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
+
+ // Finish reading the |EOF|.
+ data.RunFor(1);
+ ASSERT_TRUE(response.headers.get());
+ ASSERT_EQ(200, response.headers->response_code());
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
+TEST_F(SpdyHttpStreamSpdy2Test, 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(ConstructSpdyGet(base_url, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(SYNCHRONOUS, 0, 3) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ 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, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+
+ const SpdyHeaderBlock& spdy_header =
+ http_stream->stream()->spdy_headers();
+ if (spdy_header.find("url") != spdy_header.end())
+ EXPECT_EQ("/foo?query=what", spdy_header.find("url")->second);
+ else
+ FAIL() << "No url is set in spdy_header!";
+
+ // 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(http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+// TODO(willchan): Write a longer test for SpdyStream that exercises all
+// methods.
+
+} // namespace net
diff --git a/src/net/spdy/spdy_http_stream_spdy3_unittest.cc b/src/net/spdy/spdy_http_stream_spdy3_unittest.cc
new file mode 100644
index 0000000..70c406d
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream_spdy3_unittest.cc
@@ -0,0 +1,729 @@
+// 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 "base/memory/scoped_ptr.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/asn1_util.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_element_reader.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.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_spdy3.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+class SpdyHttpStreamSpdy3Test : public testing::Test {
+ public:
+ OrderedSocketData* data() { return data_.get(); }
+
+ protected:
+ virtual void TearDown() {
+ UploadDataStream::ResetMergeChunks();
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ void set_merge_chunks(bool merge) {
+ UploadDataStream::set_merge_chunks(merge);
+ }
+
+ int InitSession(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ HostPortPair& host_port_pair) {
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ data_.reset(new OrderedSocketData(reads, reads_count,
+ writes, writes_count));
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+ transport_params_ = new TransportSocketParams(host_port_pair,
+ MEDIUM, false, false,
+ OnHostResolutionCallback());
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair.ToString(),
+ transport_params_,
+ MEDIUM,
+ callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ return session_->InitializeWithSocket(connection.release(), false, OK);
+ }
+
+ void TestSendCredentials(
+ ServerBoundCertService* server_bound_cert_service,
+ const std::string& cert,
+ const std::string& proof);
+
+ SpdySessionDependencies session_deps_;
+ scoped_ptr<OrderedSocketData> data_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ scoped_refptr<SpdySession> session_;
+ scoped_refptr<TransportSocketParams> transport_params_;
+
+ private:
+ MockECSignatureCreatorFactory ec_signature_creator_factory_;
+};
+
+TEST_F(SpdyHttpStreamSpdy3Test, SendRequest) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(SYNCHRONOUS, 0, 3) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ 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_.get(), true));
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // 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(http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyHttpStreamSpdy3Test, SendChunkedPost) {
+ set_merge_chunks(false);
+
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*chunk1, 1), // POST upload frames
+ CreateMockWrite(*chunk2, 2),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 3),
+ CreateMockRead(*chunk1, 4),
+ CreateMockRead(*chunk2, 5),
+ MockRead(SYNCHRONOUS, 0, 6) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_stream;
+
+ ASSERT_EQ(OK, upload_stream.InitSync());
+
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ SpdyHttpStream http_stream(session_.get(), true);
+ ASSERT_EQ(
+ OK,
+ http_stream.InitializeStream(&request, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
+ headers, &response, callback.callback()));
+ EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // This triggers the MockWrite and read 2
+ callback.WaitForResult();
+
+ // This triggers read 3. The empty read causes the session to shut down.
+ data()->CompleteRead();
+ MessageLoop::current()->RunUntilIdle();
+
+ // Because we abandoned the stream, we don't expect to find a session in the
+ // pool anymore.
+ EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+ 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_F(SpdyHttpStreamSpdy3Test, DelayedSendChunkedPost) {
+ set_merge_chunks(false);
+
+ const char kUploadData1[] = "12345678";
+ const int kUploadData1Size = arraysize(kUploadData1)-1;
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(
+ ConstructSpdyBodyFrame(1, kUploadData1, kUploadData1Size, false));
+ scoped_ptr<SpdyFrame> chunk3(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(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);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ DeterministicMockClientSocketFactory* socket_factory =
+ session_deps_.deterministic_socket_factory.get();
+ socket_factory->AddSocketDataProvider(&data);
+
+ http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+ session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+ transport_params_ = new TransportSocketParams(host_port_pair,
+ MEDIUM, false, false,
+ OnHostResolutionCallback());
+
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair.ToString(),
+ transport_params_,
+ MEDIUM,
+ callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ callback.WaitForResult();
+ EXPECT_EQ(OK,
+ session_->InitializeWithSocket(connection.release(), false, OK));
+
+ 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.InitSync());
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(
+ new SpdyHttpStream(session_.get(), true));
+ ASSERT_EQ(OK, http_stream->InitializeStream(&request,
+ net_log,
+ CompletionCallback()));
+
+ 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(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // Complete the initial request write and the first chunk.
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_GT(callback.WaitForResult(), 0);
+
+ // 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.
+ data.RunFor(2);
+
+ // Read response headers.
+ data.RunFor(1);
+ ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+ // Read and check |chunk1| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(buf1,
+ kUploadDataSize,
+ callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+ // Read and check |chunk2| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size));
+ ASSERT_EQ(kUploadData1Size,
+ http_stream->ReadResponseBody(buf2,
+ kUploadData1Size,
+ callback.callback()));
+ EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
+
+ // Read and check |chunk3| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(buf3,
+ kUploadDataSize,
+ callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
+
+ // Finish reading the |EOF|.
+ data.RunFor(1);
+ ASSERT_TRUE(response.headers.get());
+ ASSERT_EQ(200, response.headers->response_code());
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
+// made available is handled correctly.
+TEST_F(SpdyHttpStreamSpdy3Test, DelayedSendChunkedPostWithWindowUpdate) {
+ set_merge_chunks(false);
+
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*chunk1, 1),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> window_update(
+ 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);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ DeterministicMockClientSocketFactory* socket_factory =
+ session_deps_.deterministic_socket_factory.get();
+ socket_factory->AddSocketDataProvider(&data);
+
+ http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+ session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+ transport_params_ = new TransportSocketParams(host_port_pair,
+ MEDIUM, false, false,
+ OnHostResolutionCallback());
+
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair.ToString(),
+ transport_params_,
+ MEDIUM,
+ callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ callback.WaitForResult();
+ EXPECT_EQ(OK,
+ session_->InitializeWithSocket(connection.release(), false, OK));
+
+ 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.InitSync());
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(
+ new SpdyHttpStream(session_.get(), true));
+ ASSERT_EQ(OK, http_stream->InitializeStream(&request, net_log,
+ CompletionCallback()));
+
+ 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(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // Complete the initial request write and first chunk.
+ data.RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_GT(callback.WaitForResult(), 0);
+
+ // 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.
+ 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.
+ data.RunFor(1);
+ ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+ // Read and check |chunk1| response.
+ data.RunFor(1);
+ scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(buf1,
+ kUploadDataSize,
+ callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+ // Finish reading the |EOF|.
+ data.RunFor(1);
+ ASSERT_TRUE(response.headers.get());
+ ASSERT_EQ(200, response.headers->response_code());
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
+TEST_F(SpdyHttpStreamSpdy3Test, 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(ConstructSpdyGet(base_url, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(SYNCHRONOUS, 0, 3) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ 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, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+
+ const SpdyHeaderBlock& spdy_header =
+ http_stream->stream()->spdy_headers();
+ if (spdy_header.find(":path") != spdy_header.end())
+ EXPECT_EQ("/foo?query=what", spdy_header.find(":path")->second);
+ else
+ FAIL() << "No url is set in spdy_header!";
+
+ // 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(http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+namespace {
+
+void GetECServerBoundCertAndProof(
+ const std::string& origin,
+ ServerBoundCertService* server_bound_cert_service,
+ std::string* cert,
+ std::string* proof) {
+ TestCompletionCallback callback;
+ std::vector<uint8> requested_cert_types;
+ requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
+ SSLClientCertType cert_type;
+ std::string key;
+ ServerBoundCertService::RequestHandle request_handle;
+ int rv = server_bound_cert_service->GetDomainBoundCert(
+ origin, requested_cert_types, &cert_type, &key, cert, callback.callback(),
+ &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type);
+
+ SpdyCredential credential;
+ SpdyCredentialBuilder::Build(MockClientSocket::kTlsUnique, cert_type, key,
+ *cert, 2, &credential);
+
+ cert->assign(credential.certs[0]);
+ proof->assign(credential.proof);
+}
+
+} // namespace
+
+// Constructs a standard SPDY SYN_STREAM frame for a GET request with
+// a credential set.
+SpdyFrame* ConstructCredentialRequestFrame(int slot, const GURL& url,
+ int stream_id) {
+ const SpdyHeaderInfo syn_headers = {
+ SYN_STREAM,
+ stream_id,
+ 0,
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+ slot,
+ CONTROL_FLAG_FIN,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+
+ // TODO(rch): this is ugly. Clean up.
+ std::string str_path = url.PathForRequest();
+ std::string str_scheme = url.scheme();
+ std::string str_host = url.host();
+ if (url.has_port()) {
+ str_host += ":";
+ str_host += url.port();
+ }
+ scoped_array<char> req(new char[str_path.size() + 1]);
+ scoped_array<char> scheme(new char[str_scheme.size() + 1]);
+ scoped_array<char> host(new char[str_host.size() + 1]);
+ memcpy(req.get(), str_path.c_str(), str_path.size());
+ memcpy(scheme.get(), str_scheme.c_str(), str_scheme.size());
+ memcpy(host.get(), str_host.c_str(), str_host.size());
+ req.get()[str_path.size()] = '\0';
+ scheme.get()[str_scheme.size()] = '\0';
+ host.get()[str_host.size()] = '\0';
+
+ const char* const headers[] = {
+ ":method",
+ "GET",
+ ":path",
+ req.get(),
+ ":host",
+ host.get(),
+ ":scheme",
+ scheme.get(),
+ ":version",
+ "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ syn_headers, NULL, 0, headers, arraysize(headers)/2);
+}
+
+// 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 SpdyHttpStreamSpdy3Test::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(
+ 1, GURL(kUrl1), 1));
+ scoped_ptr<SpdyFrame> credential(ConstructSpdyCredential(cred));
+ scoped_ptr<SpdyFrame> req2(ConstructCredentialRequestFrame(
+ 2, GURL(kUrl2), 3));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*credential.get(), 2),
+ CreateMockWrite(*req2.get(), 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> resp2(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)));
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+ 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 = kProtoSPDY3;
+ socket_factory->AddSSLSocketDataProvider(&ssl);
+ http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+ session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+ transport_params_ = new TransportSocketParams(host_port_pair,
+ MEDIUM, false, false,
+ OnHostResolutionCallback());
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ 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,
+ host_port_pair,
+ ssl_config,
+ 0,
+ false,
+ false));
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair.ToString(),
+ ssl_params,
+ MEDIUM,
+ callback.callback(),
+ http_session_->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ callback.WaitForResult();
+ EXPECT_EQ(OK,
+ session_->InitializeWithSocket(connection.release(), true, OK));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(kUrl1);
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(
+ new SpdyHttpStream(session_.get(), true));
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+ // EXPECT_FALSE(session_->NeedsCredentials(request.url));
+ // GURL new_origin(kUrl2);
+ // EXPECT_TRUE(session_->NeedsCredentials(new_origin));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+ data.RunFor(2);
+ callback.WaitForResult();
+
+ // Start up second request for resource on a new origin.
+ scoped_ptr<SpdyHttpStream> http_stream2(
+ new SpdyHttpStream(session_.get(), true));
+ request.url = GURL(kUrl2);
+ ASSERT_EQ(
+ OK,
+ http_stream2->InitializeStream(&request, 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());
+}
+
+TEST_F(SpdyHttpStreamSpdy3Test, SendCredentialsEC) {
+ 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("http://www.gmail.com/",
+ server_bound_cert_service.get(),
+ &cert, &proof);
+
+ TestSendCredentials(server_bound_cert_service.get(), cert, proof);
+
+ 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/src/net/spdy/spdy_http_utils.cc b/src/net/spdy/spdy_http_utils.cc
new file mode 100644
index 0000000..8a51685
--- /dev/null
+++ b/src/net/spdy/spdy_http_utils.cc
@@ -0,0 +1,186 @@
+// 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/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/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;
+
+ response->response_time = base::Time::Now();
+
+ 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);
+ }
+}
+
+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())
+ ? "" : scheme + "://" + host_port + path;
+ return GURL(url);
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_http_utils.h b/src/net/spdy/spdy_http_utils.h
new file mode 100644
index 0000000..fabf4a2
--- /dev/null
+++ b/src/net/spdy/spdy_http_utils.h
@@ -0,0 +1,49 @@
+// 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 "googleurl/src/gurl.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"
+
+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 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);
+
+NET_EXPORT_PRIVATE SpdyPriority ConvertRequestPriorityToSpdyPriority(
+ RequestPriority priority,
+ int protocol_version);
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_HTTP_UTILS_H_
diff --git a/src/net/spdy/spdy_http_utils_unittest.cc b/src/net/spdy/spdy_http_utils_unittest.cc
new file mode 100644
index 0000000..2419de1
--- /dev/null
+++ b/src/net/spdy/spdy_http_utils_unittest.cc
@@ -0,0 +1,30 @@
+// 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 "testing/platform_test.h"
+
+namespace net {
+
+namespace test {
+
+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));
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/src/net/spdy/spdy_io_buffer.cc b/src/net/spdy/spdy_io_buffer.cc
new file mode 100644
index 0000000..f0c14b1
--- /dev/null
+++ b/src/net/spdy/spdy_io_buffer.cc
@@ -0,0 +1,45 @@
+// 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_io_buffer.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+// static
+uint64 SpdyIOBuffer::order_ = 0;
+
+SpdyIOBuffer::SpdyIOBuffer(
+ IOBuffer* buffer, int size, RequestPriority priority, SpdyStream* stream)
+ : buffer_(new DrainableIOBuffer(buffer, size)),
+ priority_(priority),
+ position_(++order_),
+ stream_(stream) {}
+
+SpdyIOBuffer::SpdyIOBuffer() : priority_(HIGHEST), position_(0), stream_(NULL) {
+}
+
+SpdyIOBuffer::SpdyIOBuffer(const SpdyIOBuffer& rhs) {
+ buffer_ = rhs.buffer_;
+ priority_ = rhs.priority_;
+ position_ = rhs.position_;
+ stream_ = rhs.stream_;
+}
+
+SpdyIOBuffer::~SpdyIOBuffer() {}
+
+SpdyIOBuffer& SpdyIOBuffer::operator=(const SpdyIOBuffer& rhs) {
+ buffer_ = rhs.buffer_;
+ priority_ = rhs.priority_;
+ position_ = rhs.position_;
+ stream_ = rhs.stream_;
+ return *this;
+}
+
+void SpdyIOBuffer::release() {
+ buffer_ = NULL;
+ stream_ = NULL;
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_io_buffer.h b/src/net/spdy/spdy_io_buffer.h
new file mode 100644
index 0000000..9bbee16
--- /dev/null
+++ b/src/net/spdy/spdy_io_buffer.h
@@ -0,0 +1,63 @@
+// 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_IO_BUFFER_H_
+#define NET_SPDY_SPDY_IO_BUFFER_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+
+namespace net {
+
+class SpdyStream;
+
+// A class for managing SPDY IO buffers. These buffers need to be prioritized
+// so that the SpdySession sends them in the right order. Further, they need
+// to track the SpdyStream which they are associated with so that incremental
+// completion of the IO can notify the appropriate stream of completion.
+class NET_EXPORT_PRIVATE SpdyIOBuffer {
+ public:
+ // Constructor
+ // |buffer| is the actual data buffer.
+ // |size| is the size of the data buffer.
+ // |priority| is the priority of this buffer.
+ // |stream| is a pointer to the stream which is managing this buffer.
+ SpdyIOBuffer(IOBuffer* buffer, int size, RequestPriority priority,
+ SpdyStream* stream);
+ // Declare this instead of using the default so that we avoid needing to
+ // include spdy_stream.h.
+ SpdyIOBuffer(const SpdyIOBuffer& rhs);
+ SpdyIOBuffer();
+ ~SpdyIOBuffer();
+ // Declare this instead of using the default so that we avoid needing to
+ // include spdy_stream.h.
+ SpdyIOBuffer& operator=(const SpdyIOBuffer& rhs);
+
+ // Accessors.
+ DrainableIOBuffer* buffer() const { return buffer_; }
+ size_t size() const { return buffer_->size(); }
+ void release();
+ RequestPriority priority() const { return priority_; }
+ const scoped_refptr<SpdyStream>& stream() const { return stream_; }
+
+ // Comparison operator to support sorting.
+ bool operator<(const SpdyIOBuffer& other) const {
+ if (priority_ != other.priority_)
+ return priority_ < other.priority_;
+ return position_ > other.position_;
+ }
+
+ private:
+ scoped_refptr<DrainableIOBuffer> buffer_;
+ RequestPriority priority_;
+ uint64 position_;
+ scoped_refptr<SpdyStream> stream_;
+ static uint64 order_; // Maintains a FIFO order for equal priorities.
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_IO_BUFFER_H_
diff --git a/src/net/spdy/spdy_network_transaction_spdy2_unittest.cc b/src/net/spdy/spdy_network_transaction_spdy2_unittest.cc
new file mode 100644
index 0000000..07708dc
--- /dev/null
+++ b/src/net/spdy/spdy_network_transaction_spdy2_unittest.cc
@@ -0,0 +1,5780 @@
+// 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/http/http_network_transaction.h"
+
+#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 "net/base/auth.h"
+#include "net/base/net_log_unittest.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_transaction_unittest.h"
+#include "net/socket/client_socket_pool_base.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_spdy2.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy2;
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+enum SpdyNetworkTransactionSpdy2TestTypes {
+ SPDYNPN,
+ SPDYNOSSL,
+ SPDYSSL,
+};
+
+class SpdyNetworkTransactionSpdy2Test
+ : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy2TestTypes> {
+ protected:
+
+ virtual void SetUp() {
+ google_get_request_initialized_ = false;
+ google_post_request_initialized_ = false;
+ google_chunked_post_request_initialized_ = false;
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ virtual void TearDown() {
+ UploadDataStream::ResetMergeChunks();
+ // Empty the current queue.
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ void set_merge_chunks(bool merge) {
+ UploadDataStream::set_merge_chunks(merge);
+ }
+
+ 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,
+ const BoundNetLog& log,
+ SpdyNetworkTransactionSpdy2TestTypes test_type,
+ SpdySessionDependencies* session_deps)
+ : request_(request),
+ session_deps_(session_deps == NULL ?
+ new SpdySessionDependencies() : session_deps),
+ session_(SpdySessionDependencies::SpdyCreateSession(
+ session_deps_.get())),
+ log_(log),
+ test_type_(test_type),
+ deterministic_(false),
+ spdy_enabled_(true) {
+ switch (test_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(new SpdySessionDependencies());
+ 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<std::string> next_protos;
+ next_protos.push_back("http/1.1");
+ next_protos.push_back("spdy/2");
+
+ switch (test_type_) {
+ case SPDYNPN:
+ session_->http_server_properties()->SetAlternateProtocol(
+ HostPortPair("www.google.com", 80), 443,
+ NPN_SPDY_2);
+ 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(session_));
+ }
+
+ // Start the transaction, read some data, finish.
+ void RunDefaultTest() {
+ output_.rv = trans_->Start(&request_, callback.callback(), log_);
+
+ // We expect an IO Pending or some sort of error.
+ EXPECT_LT(output_.rv, 0);
+ if (output_.rv != ERR_IO_PENDING)
+ return;
+
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy);
+ if (test_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_type_ == SPDYNPN)
+ ssl_provider->SetNextProto(kProtoSPDY2);
+
+ ssl_vector_.push_back(ssl_provider);
+ if (test_type_ == SPDYNPN || test_type_ == SPDYSSL)
+ session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_provider);
+
+ session_deps_->socket_factory->AddSocketDataProvider(data);
+ if (test_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_type_ == SPDYNPN)
+ ssl_provider->SetNextProto(kProtoSPDY2);
+
+ ssl_vector_.push_back(ssl_provider);
+ if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) {
+ session_deps_->deterministic_socket_factory->
+ AddSSLSocketDataProvider(ssl_provider);
+ }
+ session_deps_->deterministic_socket_factory->AddSocketDataProvider(data);
+ if (test_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_; }
+ SpdyNetworkTransactionSpdy2TestTypes test_type() const {
+ return test_type_;
+ }
+
+ private:
+ typedef std::vector<StaticSocketDataProvider*> DataVector;
+ typedef ScopedVector<SSLSocketDataProvider> SSLVector;
+ typedef ScopedVector<StaticSocketDataProvider> AlternateVector;
+ typedef ScopedVector<DeterministicSocketData> AlternateDeterministicVector;
+ HttpRequestInfo request_;
+ 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_;
+ SpdyNetworkTransactionSpdy2TestTypes test_type_;
+ 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_) {
+ 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(
+ 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);
+
+ 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(
+ 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));
+ upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, false);
+ upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, true);
+
+ 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, 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();
+ MessageLoop::current()->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_type() == SPDYNPN ? 443 : 80;
+ HostPortPair host_port_pair(url.host(), port);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ BoundNetLog log;
+ const scoped_refptr<HttpNetworkSession>& session = helper.session();
+ SpdySessionPool* pool(session->spdy_session_pool());
+ EXPECT_TRUE(pool->HasSession(pair));
+ scoped_refptr<SpdySession> spdy_session(pool->Get(pair, log));
+ ASSERT_TRUE(spdy_session.get() != 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,
+ std::string& expected) {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ MessageLoop::current()->RunUntilIdle();
+
+ // The data for the pushed path may be coming in more than 1 packet. 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(session));
+ 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();
+ }
+
+ 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.
+INSTANTIATE_TEST_CASE_P(Spdy,
+ SpdyNetworkTransactionSpdy2Test,
+ ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN));
+
+
+// Verify HttpNetworkTransaction constructor.
+TEST_P(SpdyNetworkTransactionSpdy2Test, Constructor) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, Get) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, GetAtEachPriority) {
+ for (RequestPriority p = MINIMUM_PRIORITY; p < NUM_PRIORITIES;
+ p = RequestPriority(p + 1)) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, p));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ const int spdy_prio = reinterpret_cast<SpdySynStreamControlFrame*>(
+ req.get())->priority();
+ // this repeats the RequestPriority-->SpdyPriority mapping from
+ // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make
+ // sure it's being done right.
+ 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();
+ }
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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();
+ http_req.priority = p;
+
+ NormalSpdyTransactionHelper helper(http_req, 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(SpdyNetworkTransactionSpdy2Test, ThreeGets) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<SpdyFrame> fbody3(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, TwoGetsLateBinding) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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 != 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(SpdyNetworkTransactionSpdy2Test, TwoGetsLateBindingFromPreconnect) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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, 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 != 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 != 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(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrent) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, FourGetsWithMaxConcurrentPriority) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req4(
+ ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
+ scoped_ptr<SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true));
+
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7));
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(7, false));
+ scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans4(
+ new HttpNetworkTransaction(helper.session()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+ TestCompletionCallback callback4;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+ HttpRequestInfo httpreq4 = CreateGetRequest();
+ httpreq4.priority = HIGHEST;
+
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentDelete) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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),
+ ALLOW_THIS_IN_INITIALIZER_LIST(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(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentSocketClose) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fin_body(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session());
+ HttpNetworkTransaction trans2(helper.session());
+ HttpNetworkTransaction* trans3(new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, 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, 2), // Priority
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ const char* const kPutHeaders[] = {
+ "method", "PUT",
+ "url", "/",
+ "host", "www.google.com",
+ "scheme", "http",
+ "version", "HTTP/1.1",
+ "content-length", "0"
+ };
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+ kPutHeaders, arraysize(kPutHeaders) / 2));
+ MockWrite writes[] = {
+ CreateMockWrite(*req)
+ };
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardGetHeaders[] = {
+ "status", "200",
+ "version", "HTTP/1.1"
+ "content-length", "1234"
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+ NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request,
+ 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(SpdyNetworkTransactionSpdy2Test, 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, 2), // Priority
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ const char* const kHeadHeaders[] = {
+ "method", "HEAD",
+ "url", "/",
+ "host", "www.google.com",
+ "scheme", "http",
+ "version", "HTTP/1.1",
+ "content-length", "0"
+ };
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+ kHeadHeaders, arraysize(kHeadHeaders) / 2));
+ MockWrite writes[] = {
+ CreateMockWrite(*req)
+ };
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardGetHeaders[] = {
+ "status", "200",
+ "version", "HTTP/1.1"
+ "content-length", "1234"
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+ NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request,
+ 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(SpdyNetworkTransactionSpdy2Test, Post) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, FilePost) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, ComplexPost) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, ChunkedPost) {
+ set_merge_chunks(false);
+
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*chunk1),
+ CreateMockWrite(*chunk2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*chunk1),
+ CreateMockRead(*chunk2),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+ 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!hello!", out.response_data);
+}
+
+// Test that a POST without any post data works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, NullPost) {
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ // 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(ConstructSpdyPost(0, NULL, 0));
+ // Set the FIN bit since there will be no body.
+ req->set_flags(CONTROL_FLAG_FIN);
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> body(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,
+ 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(SpdyNetworkTransactionSpdy2Test, 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("http://www.google.com/");
+ request.upload_data_stream = &stream;
+
+ const uint64 kContentLength = 0;
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kContentLength, NULL, 0));
+ // Set the FIN bit since there will be no body.
+ req->set_flags(CONTROL_FLAG_FIN);
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> body(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, 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(SpdyNetworkTransactionSpdy2Test, 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("http://www.google.com/");
+ request.upload_data_stream = &stream;
+
+ scoped_ptr<SpdyFrame> stream_reply(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> stream_body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream_reply, 1),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*body, 2),
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreatePostRequest(),
+ 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(2);
+ 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(SpdyNetworkTransactionSpdy2Test, SocketWriteReturnsZero) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0, SYNCHRONOUS),
+ MockWrite(SYNCHRONOUS, 0, 0, 2),
+ CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, ResponseWithoutSynReply) {
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads), NULL, 0);
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, 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(SpdyNetworkTransactionSpdy2Test, ResponseWithTwoSynReplies) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, ResetReplyWithTransferEncoding) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ const char* const headers[] = {
+ "transfer-encoding", "chuncked"
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(headers, 1, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, ResetPushWithTransferEncoding) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char* const headers[] = {"url", "http://www.google.com/1",
+ "transfer-encoding", "chunked"};
+ scoped_ptr<SpdyFrame> push(ConstructSpdyPush(headers, arraysize(headers) / 2,
+ 2, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, CancelledTransaction) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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.
+ MessageLoop::current()->RunUntilIdle();
+ helper.VerifyDataNotConsumed();
+}
+
+// Verify that the client sends a Rst Frame upon cancelling the stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, CancelledTransactionSendRst) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0, SYNCHRONOUS),
+ CreateMockWrite(*rst, 2, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, StartTransactionOnReadCallback) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+ MockWrite writes2[] = { CreateMockWrite(*req) };
+
+ // The indicated length of this packet is longer than its actual length. When
+ // the session receives an empty packet 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(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(),
+ 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, kSize,
+ base::Bind(&SpdyNetworkTransactionSpdy2Test::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(SpdyNetworkTransactionSpdy2Test, DeleteSessionOnReadCallback) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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, kSize,
+ base::Bind(&SpdyNetworkTransactionSpdy2Test::DeleteSessionCallback,
+ base::Unretained(&helper)));
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ data.CompleteRead();
+
+ // Finish running rest of tasks.
+ MessageLoop::current()->RunUntilIdle();
+ helper.VerifyDataConsumed();
+}
+
+// Send a spdy request to www.google.com that gets redirected to www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy2Test, RedirectGetRequest) {
+ // These are headers which the net::URLRequest tacks on.
+ const char* const kExtraHeaders[] = {
+ "accept-encoding",
+ "gzip,deflate",
+ };
+ const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+ const char* const kStandardGetHeaders[] = {
+ "host",
+ "www.google.com",
+ "method",
+ "GET",
+ "scheme",
+ "http",
+ "url",
+ "/",
+ "user-agent",
+ "",
+ "version",
+ "HTTP/1.1"
+ };
+ const char* const kStandardGetHeaders2[] = {
+ "host",
+ "www.foo.com",
+ "method",
+ "GET",
+ "scheme",
+ "http",
+ "url",
+ "/index.php",
+ "user-agent",
+ "",
+ "version",
+ "HTTP/1.1"
+ };
+
+ // Setup writes/reads to www.google.com
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(
+ kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyPacket(
+ kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2));
+ scoped_ptr<SpdyFrame> resp(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(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(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;
+ 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();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ r.FollowDeferredRedirect();
+ MessageLoop::current()->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());
+}
+
+// Detect response with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeaders) {
+ scoped_ptr<SpdyFrame>
+ syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*syn, 0),
+ CreateMockWrite(*rst, 2),
+ };
+
+ const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ reply(ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+ MockRead reads[] = {
+ CreateMockRead(*reply, 1),
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect response with upper case headers in a HEADERS frame and reset the
+// stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeadersInHeadersFrame) {
+ scoped_ptr<SpdyFrame>
+ syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*syn, 0),
+ CreateMockWrite(*rst, 2),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ "status", "200 OK",
+ "version", "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "X-UpperCase", "yes",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect push stream with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeadersOnPush) {
+ scoped_ptr<SpdyFrame>
+ syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*syn, 0),
+ CreateMockWrite(*rst, 2),
+ };
+
+ scoped_ptr<SpdyFrame>
+ reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char* const extra_headers[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ push(ConstructSpdyPush(extra_headers, 1, 2, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply, 1),
+ CreateMockRead(*push, 1),
+ CreateMockRead(*body, 1),
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+}
+
+// Send a spdy request to www.google.com. Get a pushed stream that redirects to
+// www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy2Test, RedirectServerPush) {
+ // These are headers which the net::URLRequest tacks on.
+ const char* const kExtraHeaders[] = {
+ "accept-encoding",
+ "gzip,deflate",
+ };
+ const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+ const char* const kStandardGetHeaders[] = {
+ "host",
+ "www.google.com",
+ "method",
+ "GET",
+ "scheme",
+ "http",
+ "url",
+ "/",
+ "user-agent",
+ "",
+ "version",
+ "HTTP/1.1"
+ };
+
+ // Setup writes/reads to www.google.com
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyPacket(kSynStartHeader,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> rep(
+ ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat",
+ "301 Moved Permanently",
+ "http://www.foo.com/index.php"));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, 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
+ const char* const kStandardGetHeaders2[] = {
+ "host",
+ "www.foo.com",
+ "method",
+ "GET",
+ "scheme",
+ "http",
+ "url",
+ "/index.php",
+ "user-agent",
+ "",
+ "version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame> req2(
+ ConstructSpdyPacket(kSynStartHeader,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders2,
+ arraysize(kStandardGetHeaders2) / 2));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(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;
+ {
+ net::URLRequest r(
+ GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+ spdy_url_request_context.socket_factory().
+ AddSocketDataProvider(&data);
+
+ r.Start();
+ MessageLoop::current()->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();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(1, d2.received_redirect_count());
+
+ r2.FollowDeferredRedirect();
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy2Test, ServerPushSingleDataFrame) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushBeforeSynReply) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_reply, 3),
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushSingleDataFrame2) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 5),
+ CreateMockRead(*stream1_body, 4, 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushServerAborted) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, 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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushDuplicate) {
+ // Verify that we don't leak streams and that we properly send a reset
+ // if the server pushes the same stream twice.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream3_rst(ConstructSpdyRstStream(4, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream3_rst, 5),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame>
+ stream3_syn(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),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushMultipleDataFrame) {
+ static const unsigned char kPushBodyFrame1[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x1F, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ static const char kPushBodyFrame2[] = " my darling";
+ static const char kPushBodyFrame3[] = " hello";
+ static const char kPushBodyFrame4[] = " my baby";
+
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+ arraysize(kPushBodyFrame1), 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+ arraysize(kPushBodyFrame2) - 1, 5),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+ arraysize(kPushBodyFrame3) - 1, 6),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+ arraysize(kPushBodyFrame4) - 1, 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,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test,
+ ServerPushMultipleDataFrameInterrupted) {
+ static const unsigned char kPushBodyFrame1[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x1F, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ static const char kPushBodyFrame2[] = " my darling";
+ static const char kPushBodyFrame3[] = " hello";
+ static const char kPushBodyFrame4[] = " my baby";
+
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+ arraysize(kPushBodyFrame1), 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+ arraysize(kPushBodyFrame2) - 1, 5),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+ arraysize(kPushBodyFrame3) - 1, 7),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+ arraysize(kPushBodyFrame4) - 1, 8),
+ CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS),
+ MockRead(ASYNC, ERR_IO_PENDING, 10) // 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,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID0) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID9) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, INVALID_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushNoURL) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL, 0, 2, 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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyHeaders) {
+ struct SynReplyHeadersTests {
+ int num_headers;
+ const char* extra_headers[5];
+ const char* expected_headers;
+ } test_cases[] = {
+ // This uses a multi-valued cookie header.
+ { 2,
+ { "cookie", "val1",
+ "cookie", "val2", // will get appended separated by NULL
+ NULL
+ },
+ "cookie: val1\n"
+ "cookie: val2\n"
+ "hello: bye\n"
+ "status: 200\n"
+ "version: HTTP/1.1\n"
+ },
+ // This is the minimalist set of headers.
+ { 0,
+ { NULL },
+ "hello: bye\n"
+ "status: 200\n"
+ "version: HTTP/1.1\n"
+ },
+ // Headers with a comma separated list.
+ { 1,
+ { "cookie", "val1,val2",
+ NULL
+ },
+ "cookie: val1,val2\n"
+ "hello: bye\n"
+ "status: 200\n"
+ "version: HTTP/1.1\n"
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdyGetSynReply(test_cases[i].extra_headers,
+ test_cases[i].num_headers,
+ 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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, lines;
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ lines.append(name);
+ lines.append(": ");
+ lines.append(value);
+ lines.append("\n");
+ }
+ EXPECT_EQ(std::string(test_cases[i].expected_headers), lines);
+ }
+}
+
+// Verify that various SynReply headers parse vary fields correctly
+// through the HTTP layer, and the response matches the request.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyHeadersVary) {
+ static const SpdyHeaderInfo syn_reply_info = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ 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",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Multiple vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 5 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { "vary", "friend",
+ "vary", "enemy",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Test a '*' vary field.
+ &syn_reply_info,
+ false,
+ { 1, 4 },
+ { { "cookie", "val1,val2",
+ NULL
+ },
+ { "vary", "*",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Multiple comma-separated vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 4 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { "vary", "friend,enemy",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ }
+ }
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> frame_req(
+ ConstructSpdyGet(test_cases[i].extra_headers[0],
+ test_cases[i].num_headers[0],
+ false, 1, LOWEST));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*frame_req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> frame_reply(
+ ConstructSpdyPacket(*test_cases[i].syn_reply,
+ test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ NULL,
+ 0));
+
+ scoped_ptr<SpdyFrame> body(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,
+ 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.
+ char reply_buffer[256] = "";
+ ConstructSpdyReplyString(test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ reply_buffer,
+ 256);
+
+ EXPECT_EQ(std::string(reply_buffer), lines) << i;
+ }
+}
+
+// Verify that we don't crash on invalid SynReply responses.
+TEST_P(SpdyNetworkTransactionSpdy2Test, InvalidSynReply) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ 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",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ },
+ },
+ // SYN_REPLY missing version header
+ { 2,
+ { "status", "200",
+ "url", "/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(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdyPacket(kSynStartHeader,
+ NULL, 0,
+ test_cases[i].headers,
+ test_cases[i].num_headers));
+ scoped_ptr<SpdyFrame> body(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv);
+ }
+}
+
+// Verify that we don't crash on some corrupt frames.
+TEST_P(SpdyNetworkTransactionSpdy2Test, CorruptFrameSessionError) {
+ // This is the length field that's too short.
+ scoped_ptr<SpdyFrame> syn_reply_wrong_length(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ syn_reply_wrong_length->set_length(syn_reply_wrong_length->length() - 4);
+
+ 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(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ MockWrite(ASYNC, 0, 0) // EOF
+ };
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*test_cases[i].syn_reply),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(SpdyNetworkTransactionSpdy2Test, WriteError) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(),
+ 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(SpdyNetworkTransactionSpdy2Test, PartialWrite) {
+ // Chop the SYN_STREAM frame into 5 chunks.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ const int kChunks = 5;
+ scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks));
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, DecompressFailureOnSynReply) {
+ scoped_ptr<SpdyFrame> compressed(
+ ConstructSpdyGet(NULL, 0, true, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*compressed),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ SpdySessionDependencies* session_deps = new SpdySessionDependencies();
+ session_deps->enable_compression = true;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(SpdyNetworkTransactionSpdy2Test, NetLog) {
+ static const char* const kExtraHeaders[] = {
+ "user-agent", "Chrome",
+ };
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(kExtraHeaders, 1, false, 1,
+ LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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);
+
+ 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("host: www.google.com");
+ expected.push_back("url: /");
+ expected.push_back("scheme: http");
+ expected.push_back("version: HTTP/1.1");
+ expected.push_back("method: 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(SpdyNetworkTransactionSpdy2Test, BufferFull) {
+ BufferedSpdyFramer framer(2, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy2Test, Buffering) {
+ BufferedSpdyFramer framer(2, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy2Test, BufferedAll) {
+ BufferedSpdyFramer framer(2, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 5 data frames in a single read.
+ scoped_ptr<SpdyFrame> syn_reply(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ syn_reply->set_flags(CONTROL_FLAG_NONE); // turn off FIN bit
+ 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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy2Test, BufferedClosed) {
+ BufferedSpdyFramer framer(2, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Verify the case where we buffer data and cancel the transaction.
+TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedCancelled) {
+ BufferedSpdyFramer framer(2, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy2Test, SettingsSaved) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kExtraHeaders[] = {
+ "status", "200",
+ "version", "HTTP/1.1"
+ };
+
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> reply(
+ ConstructSpdyPacket(kSynReplyInfo,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ NULL,
+ 0));
+
+ 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(ConstructSpdySettings(settings));
+ }
+
+ scoped_ptr<SpdyFrame> body(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(SpdyNetworkTransactionSpdy2Test, SettingsPlayback) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* kExtraHeaders[] = {
+ "status", "200",
+ "version", "HTTP/1.1"
+ };
+
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), 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());
+
+ 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 SETTINGS frame.
+ const SettingsMap& settings =
+ spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair);
+ scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*settings_frame),
+ CreateMockWrite(*req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> reply(
+ ConstructSpdyPacket(kSynReplyInfo,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ NULL,
+ 0));
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, 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 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(SpdyNetworkTransactionSpdy2Test, GoAwayWithActiveStream) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> go_away(ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*go_away),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_ABORTED, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, CloseWithActiveStream) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, ProxyConnect) {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.session_deps().reset(new SpdySessionDependencies(
+ 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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()) {
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, 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(),
+ 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(new SpdySessionDependencies(
+ 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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 != 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());
+ HostPortProxyPair session_pool_key_direct(
+ host_port_pair, ProxyServer::Direct());
+ EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct));
+ HostPortProxyPair session_pool_key_proxy(
+ host_port_pair,
+ ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP));
+ EXPECT_FALSE(spdy_session_pool->HasSession(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(ConstructSpdyGet(
+ "http://www.google.com/foo.dat", false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(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()) {
+ 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(new SpdySessionDependencies());
+ // Ensure that this transaction uses the same SpdySessionPool.
+ scoped_refptr<HttpNetworkSession> session_proxy(
+ SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get()));
+ NormalSpdyTransactionHelper helper_proxy(request_proxy,
+ 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 != 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(SpdyNetworkTransactionSpdy2Test, VerifyRetryOnConnectionReset) {
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data1);
+ helper.AddData(&data2);
+ helper.RunPreTestSetup();
+
+ for (int i = 0; i < 2; ++i) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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.
+ MessageLoop::current()->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 != 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(SpdyNetworkTransactionSpdy2Test, SpdyOnOffToggle) {
+ net::HttpStreamFactory::set_spdy_enabled(true);
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(),
+ 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(SpdyNetworkTransactionSpdy2Test, 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(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ const char* const kExtraAuthorizationHeaders[] = {
+ "authorization",
+ "Basic Zm9vOmJhcg==",
+ };
+ scoped_ptr<SpdyFrame> req_get_authorization(
+ ConstructSpdyGet(
+ kExtraAuthorizationHeaders,
+ arraysize(kExtraAuthorizationHeaders) / 2,
+ false, 3, LOWEST));
+ 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(
+ ConstructSpdySynReplyError(
+ "401 Authentication Required",
+ kExtraAuthenticationHeaders,
+ arraysize(kExtraAuthenticationHeaders) / 2,
+ 1));
+ scoped_ptr<SpdyFrame> body_authentication(
+ ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body_data(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, 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 != 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 != NULL);
+ EXPECT_EQ(200, response_restart->headers->response_code());
+ EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushWithHeaders) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ "url",
+ "http://www.google.com/foo.dat",
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ "status",
+ "200",
+ "version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_headers, 4),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 6),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushClaimBeforeHeaders) {
+ // We push a stream and attempt to claim it before the headers come down.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ "url",
+ "http://www.google.com/foo.dat",
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ "status",
+ "200",
+ "version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers, 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(3);
+ MessageLoop::current()->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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != 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(SpdyNetworkTransactionSpdy2Test, ServerPushWithTwoHeaderFrames) {
+ // We push a stream and attempt to claim it before the headers come down.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ "url",
+ "http://www.google.com/foo.dat",
+ };
+ static const char* const kMiddleHeaders[] = {
+ "hello",
+ "bye",
+ };
+ static const char* const kLateHeaders[] = {
+ "status",
+ "200",
+ "version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+ arraysize(kMiddleHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream2_headers2(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers1, 4),
+ CreateMockRead(*stream2_headers2, 5),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(3);
+ MessageLoop::current()->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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+ // Verify we got all the headers
+ EXPECT_TRUE(response2.headers->HasHeaderValue(
+ "url",
+ "http://www.google.com/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(SpdyNetworkTransactionSpdy2Test, ServerPushWithNoStatusHeaderFrames) {
+ // We push a stream and attempt to claim it before the headers come down.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ "url",
+ "http://www.google.com/foo.dat",
+ };
+ static const char* const kMiddleHeaders[] = {
+ "hello",
+ "bye",
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+ arraysize(kMiddleHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers1, 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 5),
+ MockRead(ASYNC, 0, 6), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(2);
+ MessageLoop::current()->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 != 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(SpdyNetworkTransactionSpdy2Test, SynReplyWithHeaders) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ static const char* const kInitialHeaders[] = {
+ "status",
+ "200 OK",
+ "version",
+ "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(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(),
+ 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(SpdyNetworkTransactionSpdy2Test, SynReplyWithLateHeaders) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ static const char* const kInitialHeaders[] = {
+ "status",
+ "200 OK",
+ "version",
+ "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> stream1_body2(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(),
+ 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!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithDuplicateLateHeaders) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ static const char* const kInitialHeaders[] = {
+ "status",
+ "200 OK",
+ "version",
+ "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "status",
+ "500 Server Error",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> stream1_body2(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, 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
+ };
+
+
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+
+ 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(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> push_rst(
+ ConstructSpdyRstStream(2, REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*push_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ url_to_push));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(2, CANCEL));
+
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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(
+ new SpdySessionDependencies());
+ session_deps->trusted_spdy_proxy = "123.45.67.89:8080";
+ NormalSpdyTransactionHelper helper(request,
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+ }
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, RetryAfterRefused) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ CreateMockWrite(*req2, 3),
+ };
+
+ scoped_ptr<SpdyFrame> refused(
+ ConstructSpdyRstStream(1, REFUSED_STREAM));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, HIGHEST));
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, MEDIUM));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 3),
+ CreateMockWrite(*req3, 4),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(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(),
+ 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();
+ info1.priority = LOWEST;
+ 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.
+ MessageLoop::current()->RunUntilIdle();
+
+ // Now, start both new transactions
+ HttpRequestInfo info2 = CreateGetRequest();
+ info2.priority = MEDIUM;
+ TestCompletionCallback callback2;
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ MessageLoop::current()->RunUntilIdle();
+
+ HttpRequestInfo info3 = CreateGetRequest();
+ info3.priority = HIGHEST;
+ TestCompletionCallback callback3;
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+ rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ MessageLoop::current()->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();
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_network_transaction_spdy3_unittest.cc b/src/net/spdy/spdy_network_transaction_spdy3_unittest.cc
new file mode 100644
index 0000000..197ba7d
--- /dev/null
+++ b/src/net/spdy/spdy_network_transaction_spdy3_unittest.cc
@@ -0,0 +1,6364 @@
+// 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/http/http_network_transaction.h"
+
+#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 "net/base/auth.h"
+#include "net/base/net_log_unittest.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_transaction_unittest.h"
+#include "net/socket/client_socket_pool_base.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_spdy3.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+enum SpdyNetworkTransactionSpdy3TestTypes {
+ SPDYNPN,
+ SPDYNOSSL,
+ SPDYSSL,
+};
+
+class SpdyNetworkTransactionSpdy3Test
+ : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy3TestTypes> {
+ protected:
+
+ virtual void SetUp() {
+ google_get_request_initialized_ = false;
+ google_post_request_initialized_ = false;
+ google_chunked_post_request_initialized_ = false;
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ virtual void TearDown() {
+ UploadDataStream::ResetMergeChunks();
+ // Empty the current queue.
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ void set_merge_chunks(bool merge) {
+ UploadDataStream::set_merge_chunks(merge);
+ }
+
+ 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,
+ const BoundNetLog& log,
+ SpdyNetworkTransactionSpdy3TestTypes test_type,
+ SpdySessionDependencies* session_deps)
+ : request_(request),
+ session_deps_(session_deps == NULL ?
+ new SpdySessionDependencies() : session_deps),
+ session_(SpdySessionDependencies::SpdyCreateSession(
+ session_deps_.get())),
+ log_(log),
+ test_type_(test_type),
+ deterministic_(false),
+ spdy_enabled_(true) {
+ switch (test_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(new SpdySessionDependencies());
+ 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<std::string> next_protos;
+ next_protos.push_back("http/1.1");
+ next_protos.push_back("spdy/2");
+ next_protos.push_back("spdy/3");
+
+ switch (test_type_) {
+ case SPDYNPN:
+ session_->http_server_properties()->SetAlternateProtocol(
+ HostPortPair("www.google.com", 80), 443,
+ NPN_SPDY_3);
+ 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(session_));
+ }
+
+ // Start the transaction, read some data, finish.
+ void RunDefaultTest() {
+ output_.rv = trans_->Start(&request_, callback.callback(), log_);
+
+ // We expect an IO Pending or some sort of error.
+ EXPECT_LT(output_.rv, 0);
+ if (output_.rv != ERR_IO_PENDING)
+ return;
+
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy);
+ if (test_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_type_ == SPDYNPN)
+ ssl_provider->SetNextProto(kProtoSPDY3);
+
+ ssl_vector_.push_back(ssl_provider);
+ if (test_type_ == SPDYNPN || test_type_ == SPDYSSL)
+ session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_provider);
+
+ session_deps_->socket_factory->AddSocketDataProvider(data);
+ if (test_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_type_ == SPDYNPN)
+ ssl_provider->SetNextProto(kProtoSPDY3);
+
+ ssl_vector_.push_back(ssl_provider);
+ if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) {
+ session_deps_->deterministic_socket_factory->
+ AddSSLSocketDataProvider(ssl_provider);
+ }
+ session_deps_->deterministic_socket_factory->AddSocketDataProvider(data);
+ if (test_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_; }
+ SpdyNetworkTransactionSpdy3TestTypes test_type() const {
+ return test_type_;
+ }
+
+ private:
+ typedef std::vector<StaticSocketDataProvider*> DataVector;
+ typedef ScopedVector<SSLSocketDataProvider> SSLVector;
+ typedef ScopedVector<StaticSocketDataProvider> AlternateVector;
+ typedef ScopedVector<DeterministicSocketData> AlternateDeterministicVector;
+ HttpRequestInfo request_;
+ 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_;
+ SpdyNetworkTransactionSpdy3TestTypes test_type_;
+ 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_) {
+ 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(
+ 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);
+
+ 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(
+ 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));
+ upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, false);
+ upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, true);
+
+ 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, 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();
+ MessageLoop::current()->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_type() == SPDYNPN ? 443 : 80;
+ HostPortPair host_port_pair(url.host(), port);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ BoundNetLog log;
+ const scoped_refptr<HttpNetworkSession>& session = helper.session();
+ SpdySessionPool* pool(session->spdy_session_pool());
+ EXPECT_TRUE(pool->HasSession(pair));
+ scoped_refptr<SpdySession> spdy_session(pool->Get(pair, log));
+ ASSERT_TRUE(spdy_session.get() != 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,
+ std::string& expected) {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ MessageLoop::current()->RunUntilIdle();
+
+ // The data for the pushed path may be coming in more than 1 packet. 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(session));
+ 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();
+ }
+
+ 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.
+INSTANTIATE_TEST_CASE_P(Spdy,
+ SpdyNetworkTransactionSpdy3Test,
+ ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN));
+
+
+// Verify HttpNetworkTransaction constructor.
+TEST_P(SpdyNetworkTransactionSpdy3Test, Constructor) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, Get) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, GetAtEachPriority) {
+ for (RequestPriority p = MINIMUM_PRIORITY; p < NUM_PRIORITIES;
+ p = RequestPriority(p + 1)) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, p));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ const int spdy_prio = reinterpret_cast<SpdySynStreamControlFrame*>(
+ req.get())->priority();
+ // this repeats the RequestPriority-->SpdyPriority mapping from
+ // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make
+ // sure it's being done right.
+ 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(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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();
+ http_req.priority = p;
+
+ NormalSpdyTransactionHelper helper(http_req, 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(SpdyNetworkTransactionSpdy3Test, ThreeGets) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<SpdyFrame> fbody3(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, TwoGetsLateBinding) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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 != 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(SpdyNetworkTransactionSpdy3Test, TwoGetsLateBindingFromPreconnect) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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, 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 != 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 != 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(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrent) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, FourGetsWithMaxConcurrentPriority) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req4(
+ ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
+ scoped_ptr<SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true));
+
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7));
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(7, false));
+ scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans4(
+ new HttpNetworkTransaction(helper.session()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+ TestCompletionCallback callback4;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+ HttpRequestInfo httpreq4 = CreateGetRequest();
+ httpreq4.priority = HIGHEST;
+
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentDelete) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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),
+ ALLOW_THIS_IN_INITIALIZER_LIST(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(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentSocketClose) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fin_body(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+
+ SettingsMap settings;
+ const size_t max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(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(),
+ 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(helper.session());
+ HttpNetworkTransaction trans2(helper.session());
+ HttpNetworkTransaction* trans3(new HttpNetworkTransaction(helper.session()));
+
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, 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, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ const char* const kPutHeaders[] = {
+ ":method", "PUT",
+ ":path", "/",
+ ":host", "www.google.com",
+ ":scheme", "http",
+ ":version", "HTTP/1.1",
+ "content-length", "0"
+ };
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+ kPutHeaders, arraysize(kPutHeaders) / 2));
+ MockWrite writes[] = {
+ CreateMockWrite(*req)
+ };
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardRespHeaders[] = {
+ ":status", "200",
+ ":version", "HTTP/1.1"
+ "content-length", "1234"
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+ NULL, 0, kStandardRespHeaders, arraysize(kStandardRespHeaders) / 2));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request,
+ 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(SpdyNetworkTransactionSpdy3Test, 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, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ const char* const kHeadHeaders[] = {
+ ":method", "HEAD",
+ ":path", "/",
+ ":host", "www.google.com",
+ ":scheme", "http",
+ ":version", "HTTP/1.1",
+ "content-length", "0"
+ };
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+ kHeadHeaders, arraysize(kHeadHeaders) / 2));
+ MockWrite writes[] = {
+ CreateMockWrite(*req)
+ };
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kStandardRespHeaders[] = {
+ ":status", "200",
+ ":version", "HTTP/1.1"
+ "content-length", "1234"
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+ NULL, 0, kStandardRespHeaders, arraysize(kStandardRespHeaders) / 2));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request,
+ 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(SpdyNetworkTransactionSpdy3Test, Post) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, FilePost) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, ComplexPost) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, ChunkedPost) {
+ set_merge_chunks(false);
+
+ scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*chunk1),
+ CreateMockWrite(*chunk2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*chunk1),
+ CreateMockRead(*chunk2),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+ 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!hello!", out.response_data);
+}
+
+// Test that a POST without any post data works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, NullPost) {
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ // 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(ConstructSpdyPost(0, NULL, 0));
+ // Set the FIN bit since there will be no body.
+ req->set_flags(CONTROL_FLAG_FIN);
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> body(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,
+ 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(SpdyNetworkTransactionSpdy3Test, 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("http://www.google.com/");
+ request.upload_data_stream = &stream;
+
+ const uint64 kContentLength = 0;
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kContentLength, NULL, 0));
+ // Set the FIN bit since there will be no body.
+ req->set_flags(CONTROL_FLAG_FIN);
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> body(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, 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(SpdyNetworkTransactionSpdy3Test, 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("http://www.google.com/");
+ request.upload_data_stream = &stream;
+
+ scoped_ptr<SpdyFrame> stream_reply(ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> stream_body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream_reply, 1),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*body, 2),
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreatePostRequest(),
+ 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(2);
+ 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(SpdyNetworkTransactionSpdy3Test, SocketWriteReturnsZero) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0, SYNCHRONOUS),
+ MockWrite(SYNCHRONOUS, 0, 0, 2),
+ CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, ResponseWithoutSynReply) {
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads), NULL, 0);
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, 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(SpdyNetworkTransactionSpdy3Test, ResponseWithTwoSynReplies) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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 != 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 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(SpdyNetworkTransactionSpdy3Test, WindowUpdateReceived) {
+ static int kFrameCount = 2;
+ scoped_ptr<std::string> content(
+ new std::string(kMaxSpdyFrameChunkSize, 'a'));
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPost(
+ kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0));
+ scoped_ptr<SpdyFrame> body(
+ ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false));
+ scoped_ptr<SpdyFrame> body_end(
+ ConstructSpdyBodyFrame(1, content->c_str(), content->size(), true));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body),
+ CreateMockWrite(*body_end),
+ };
+
+ static const int32 kDeltaWindowSize = 0xff;
+ static const int kDeltaCount = 4;
+ scoped_ptr<SpdyFrame> window_update(
+ ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+ scoped_ptr<SpdyFrame> window_update_dummy(
+ ConstructSpdyWindowUpdate(2, kDeltaWindowSize));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*window_update_dummy),
+ CreateMockRead(*window_update_dummy),
+ CreateMockRead(*window_update_dummy),
+ CreateMockRead(*window_update), // Four updates, therefore window
+ CreateMockRead(*window_update), // size should increase by
+ CreateMockRead(*window_update), // kDeltaWindowSize * 4
+ CreateMockRead(*window_update),
+ CreateMockRead(*resp),
+ CreateMockRead(*body_end),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(0, 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, 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) +
+ kDeltaWindowSize * kDeltaCount -
+ kMaxSpdyFrameChunkSize * kFrameCount,
+ stream->stream()->send_window_size());
+ helper.VerifyDataConsumed();
+}
+
+// Test that received data frames and sent WINDOW_UPDATE frames change
+// the recv_window_size_ correctly.
+TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateSent) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> window_update(
+ ConstructSpdyWindowUpdate(1, kUploadDataSize));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*window_update),
+ };
+
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body_no_fin(
+ ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> body_fin(
+ 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),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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) - kUploadDataSize,
+ stream->stream()->recv_window_size());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+
+ // Force sending of WINDOW_UPDATE by setting initial_recv_window_size to a
+ // small value.
+ stream->stream()->set_initial_recv_window_size(kUploadDataSize / 2);
+
+ // 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(kUploadDataSize));
+ rv = trans->Read(buf, kUploadDataSize, CompletionCallback());
+ EXPECT_EQ(kUploadDataSize, rv);
+ std::string content(buf->data(), buf->data()+kUploadDataSize);
+ EXPECT_STREQ(kUploadData, content.c_str());
+
+ // Schedule the reading of empty data frame with FIN
+ data.CompleteRead();
+
+ // Force write of WINDOW_UPDATE which was scheduled during the above
+ // read.
+ MessageLoop::current()->RunUntilIdle();
+
+ // Read EOF.
+ data.CompleteRead();
+
+ helper.VerifyDataConsumed();
+}
+
+// Test that WINDOW_UPDATE frame causing overflow is handled correctly.
+TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateOverflow) {
+ // 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(ConstructSpdyPost(
+ kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0));
+ scoped_ptr<SpdyFrame> body(
+ ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, 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(
+ 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,
+ 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(SpdyNetworkTransactionSpdy3Test, FlowControlStallResume) {
+ // 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(ConstructSpdyPost(
+ kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0));
+
+ // Full frames.
+ scoped_ptr<SpdyFrame> body1(
+ ConstructSpdyBodyFrame(1, content.c_str(), content.size(), false));
+
+ // Last frame to zero out the window size.
+ scoped_ptr<SpdyFrame> body2(
+ ConstructSpdyBodyFrame(1, content.c_str(), last_frame_size, false));
+
+ // Data frame to be sent once WINDOW_UPDATE frame is received.
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(1, true));
+
+ // Fill in mock writes.
+ scoped_array<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> window_update(
+ ConstructSpdyWindowUpdate(1, kUploadDataSize));
+ scoped_ptr<SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*window_update),
+ CreateMockRead(*window_update),
+ CreateMockRead(*reply),
+ CreateMockRead(*body2),
+ CreateMockRead(*body3),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ // 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, arraysize(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, 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);
+
+ MessageLoop::current()->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).
+ EXPECT_FALSE(stream->stream()->body_sent());
+
+ 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(SpdyNetworkTransactionSpdy3Test, FlowControlStallResumeAfterSettings) {
+ // 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(ConstructSpdyPost(
+ kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0));
+
+ // Full frames.
+ scoped_ptr<SpdyFrame> body1(
+ ConstructSpdyBodyFrame(1, content.c_str(), content.size(), false));
+
+ // Last frame to zero out the window size.
+ scoped_ptr<SpdyFrame> body2(
+ ConstructSpdyBodyFrame(1, content.c_str(), last_frame_size, false));
+
+ // Data frame to be sent once SETTINGS frame is received.
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(1, true));
+
+ // Fill in mock writes.
+ scoped_array<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 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(ConstructSpdySettings(settings));
+ scoped_ptr<SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame_large),
+ CreateMockRead(*reply),
+ CreateMockRead(*body2),
+ CreateMockRead(*body3),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ // 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, arraysize(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, 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);
+
+ MessageLoop::current()->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).
+ EXPECT_FALSE(stream->stream()->body_sent());
+ EXPECT_TRUE(stream->stream()->stalled_by_flow_control());
+
+ data.ForceNextRead(); // Read in SETTINGS frame to unstall.
+ rv = callback.WaitForResult();
+ helper.VerifyDataConsumed();
+ EXPECT_FALSE(stream->stream()->stalled_by_flow_control());
+}
+
+// Test we correctly handle the case where the SETTINGS frame results in a
+// negative send window size.
+TEST_P(SpdyNetworkTransactionSpdy3Test, FlowControlNegativeSendWindowSize) {
+ // 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(ConstructSpdyPost(
+ kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0));
+
+ // Full frames.
+ scoped_ptr<SpdyFrame> body1(
+ ConstructSpdyBodyFrame(1, content.c_str(), content.size(), false));
+
+ // Last frame to zero out the window size.
+ scoped_ptr<SpdyFrame> body2(
+ ConstructSpdyBodyFrame(1, content.c_str(), last_frame_size, false));
+
+ // Data frame to be sent once SETTINGS frame is received.
+ scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(1, true));
+
+ // Fill in mock writes.
+ scoped_array<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 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(
+ ConstructSpdySettings(new_settings));
+ // Construct read frame for WINDOW_UPDATE that makes the send_window_size
+ // postive.
+ scoped_ptr<SpdyFrame> window_update_init_size(
+ ConstructSpdyWindowUpdate(1, kSpdyStreamInitialWindowSize));
+ scoped_ptr<SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame_small),
+ CreateMockRead(*window_update_init_size),
+ CreateMockRead(*reply),
+ CreateMockRead(*body2),
+ CreateMockRead(*body3),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ // 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, arraysize(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, 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);
+
+ MessageLoop::current()->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).
+ EXPECT_FALSE(stream->stream()->body_sent());
+
+ data.ForceNextRead(); // Read in WINDOW_UPDATE or SETTINGS frame.
+ rv = callback.WaitForResult();
+ helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ResetReplyWithTransferEncoding) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ const char* const headers[] = {
+ "transfer-encoding", "chuncked"
+ };
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(headers, 1, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, ResetPushWithTransferEncoding) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char* const headers[] = {":scheme", "http",
+ ":host", "www.google.com",
+ ":path", "/1",
+ "transfer-encoding", "chunked"};
+ scoped_ptr<SpdyFrame> push(ConstructSpdyPush(headers, arraysize(headers) / 2,
+ 2, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, CancelledTransaction) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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.
+ MessageLoop::current()->RunUntilIdle();
+ helper.VerifyDataNotConsumed();
+}
+
+// Verify that the client sends a Rst Frame upon cancelling the stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, CancelledTransactionSendRst) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0, SYNCHRONOUS),
+ CreateMockWrite(*rst, 2, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, StartTransactionOnReadCallback) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+ MockWrite writes2[] = { CreateMockWrite(*req) };
+
+ // The indicated length of this packet is longer than its actual length. When
+ // the session receives an empty packet 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(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(),
+ 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, kSize,
+ base::Bind(&SpdyNetworkTransactionSpdy3Test::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(SpdyNetworkTransactionSpdy3Test, DeleteSessionOnReadCallback) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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, kSize,
+ base::Bind(&SpdyNetworkTransactionSpdy3Test::DeleteSessionCallback,
+ base::Unretained(&helper)));
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ data.CompleteRead();
+
+ // Finish running rest of tasks.
+ MessageLoop::current()->RunUntilIdle();
+ helper.VerifyDataConsumed();
+}
+
+// Send a spdy request to www.google.com that gets redirected to www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy3Test, RedirectGetRequest) {
+ // These are headers which the net::URLRequest tacks on.
+ const char* const kExtraHeaders[] = {
+ "accept-encoding",
+ "gzip,deflate",
+ };
+ const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+ const char* const kStandardGetHeaders[] = {
+ ":host",
+ "www.google.com",
+ ":method",
+ "GET",
+ ":scheme",
+ "http",
+ ":path",
+ "/",
+ "user-agent",
+ "",
+ ":version",
+ "HTTP/1.1"
+ };
+ const char* const kStandardGetHeaders2[] = {
+ ":host",
+ "www.foo.com",
+ ":method",
+ "GET",
+ ":scheme",
+ "http",
+ ":path",
+ "/index.php",
+ "user-agent",
+ "",
+ ":version",
+ "HTTP/1.1"
+ };
+
+ // Setup writes/reads to www.google.com
+ scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(
+ kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyPacket(
+ kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2));
+ scoped_ptr<SpdyFrame> resp(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(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(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;
+ 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();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ r.FollowDeferredRedirect();
+ MessageLoop::current()->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());
+}
+
+// Detect response with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeaders) {
+ scoped_ptr<SpdyFrame>
+ syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*syn, 0),
+ CreateMockWrite(*rst, 2),
+ };
+
+ const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ reply(ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+ MockRead reads[] = {
+ CreateMockRead(*reply, 1),
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect response with upper case headers in a HEADERS frame and reset the
+// stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeadersInHeadersFrame) {
+ scoped_ptr<SpdyFrame>
+ syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*syn, 0),
+ CreateMockWrite(*rst, 2),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ ":status", "200 OK",
+ ":version", "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "X-UpperCase", "yes",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect push stream with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeadersOnPush) {
+ scoped_ptr<SpdyFrame>
+ syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*syn, 0),
+ CreateMockWrite(*rst, 2),
+ };
+
+ scoped_ptr<SpdyFrame>
+ reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char* const extra_headers[] = {
+ "X-UpperCase", "yes"
+ };
+ scoped_ptr<SpdyFrame>
+ push(ConstructSpdyPush(extra_headers, arraysize(extra_headers) / 2,
+ 2, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply, 1),
+ CreateMockRead(*push, 1),
+ CreateMockRead(*body, 1),
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+}
+
+// Send a spdy request to www.google.com. Get a pushed stream that redirects to
+// www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy3Test, RedirectServerPush) {
+ // These are headers which the net::URLRequest tacks on.
+ const char* const kExtraHeaders[] = {
+ "accept-encoding",
+ "gzip,deflate",
+ };
+ const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+ const char* const kStandardGetHeaders[] = {
+ ":host",
+ "www.google.com",
+ ":method",
+ "GET",
+ ":scheme",
+ "http",
+ ":path",
+ "/",
+ "user-agent",
+ "",
+ ":version",
+ "HTTP/1.1"
+ };
+
+ // Setup writes/reads to www.google.com
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyPacket(kSynStartHeader,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> rep(
+ ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat",
+ "301 Moved Permanently",
+ "http://www.foo.com/index.php"));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, 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
+ const char* const kStandardGetHeaders2[] = {
+ ":host",
+ "www.foo.com",
+ ":method",
+ "GET",
+ ":scheme",
+ "http",
+ ":path",
+ "/index.php",
+ "user-agent",
+ "",
+ ":version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame> req2(
+ ConstructSpdyPacket(kSynStartHeader,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ kStandardGetHeaders2,
+ arraysize(kStandardGetHeaders2) / 2));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(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;
+ {
+ net::URLRequest r(
+ GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+ spdy_url_request_context.socket_factory().
+ AddSocketDataProvider(&data);
+
+ r.Start();
+ MessageLoop::current()->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();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(1, d2.received_redirect_count());
+
+ r2.FollowDeferredRedirect();
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy3Test, ServerPushSingleDataFrame) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushBeforeSynReply) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_reply, 3),
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushSingleDataFrame2) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 5),
+ CreateMockRead(*stream1_body, 4, 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushServerAborted) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, 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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushDuplicate) {
+ // Verify that we don't leak streams and that we properly send a reset
+ // if the server pushes the same stream twice.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream3_rst(ConstructSpdyRstStream(4, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream3_rst, 5),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame>
+ stream3_syn(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),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushMultipleDataFrame) {
+ static const unsigned char kPushBodyFrame1[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x1F, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ static const char kPushBodyFrame2[] = " my darling";
+ static const char kPushBodyFrame3[] = " hello";
+ static const char kPushBodyFrame4[] = " my baby";
+
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+ arraysize(kPushBodyFrame1), 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+ arraysize(kPushBodyFrame2) - 1, 5),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+ arraysize(kPushBodyFrame3) - 1, 6),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+ arraysize(kPushBodyFrame4) - 1, 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,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test,
+ ServerPushMultipleDataFrameInterrupted) {
+ static const unsigned char kPushBodyFrame1[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x1F, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ static const char kPushBodyFrame2[] = " my darling";
+ static const char kPushBodyFrame3[] = " hello";
+ static const char kPushBodyFrame4[] = " my baby";
+
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+ arraysize(kPushBodyFrame1), 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+ arraysize(kPushBodyFrame2) - 1, 5),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+ arraysize(kPushBodyFrame3) - 1, 7),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+ arraysize(kPushBodyFrame4) - 1, 8),
+ CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS),
+ MockRead(ASYNC, ERR_IO_PENDING, 10) // 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,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushInvalidAssociatedStreamID0) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushInvalidAssociatedStreamID9) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, INVALID_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushNoURL) {
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame>
+ stream2_rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL, 0, 2, 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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyHeaders) {
+ struct SynReplyHeadersTests {
+ int num_headers;
+ const char* extra_headers[5];
+ const char* expected_headers;
+ } test_cases[] = {
+ // This uses a multi-valued cookie header.
+ { 2,
+ { "cookie", "val1",
+ "cookie", "val2", // will get appended separated by NULL
+ NULL
+ },
+ "status: 200\n"
+ "version: HTTP/1.1\n"
+ "cookie: val1\n"
+ "cookie: val2\n"
+ "hello: bye\n"
+ },
+ // This is the minimalist set of headers.
+ { 0,
+ { NULL },
+ "status: 200\n"
+ "version: HTTP/1.1\n"
+ "hello: bye\n"
+ },
+ // Headers with a comma separated list.
+ { 1,
+ { "cookie", "val1,val2",
+ NULL
+ },
+ "status: 200\n"
+ "version: HTTP/1.1\n"
+ "cookie: val1,val2\n"
+ "hello: bye\n"
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdyGetSynReply(test_cases[i].extra_headers,
+ test_cases[i].num_headers,
+ 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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, lines;
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ lines.append(name);
+ lines.append(": ");
+ lines.append(value);
+ lines.append("\n");
+ }
+ EXPECT_EQ(std::string(test_cases[i].expected_headers), lines);
+ }
+}
+
+// Verify that various SynReply headers parse vary fields correctly
+// through the HTTP layer, and the response matches the request.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyHeadersVary) {
+ static const SpdyHeaderInfo syn_reply_info = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ 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
+ },
+ { ":status", "200",
+ ":version", "HTTP/1.1",
+ "vary", "cookie",
+ "url", "/index.php",
+ NULL
+ }
+ }
+ }, { // Multiple vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 5 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { ":status", "200",
+ ":version", "HTTP/1.1",
+ "vary", "friend",
+ "vary", "enemy",
+ "url", "/index.php",
+ NULL
+ }
+ }
+ }, { // Test a '*' vary field.
+ &syn_reply_info,
+ false,
+ { 1, 4 },
+ { { "cookie", "val1,val2",
+ NULL
+ },
+ { ":status", "200",
+ ":version", "HTTP/1.1",
+ "vary", "*",
+ "url", "/index.php",
+ NULL
+ }
+ }
+ }, { // Multiple comma-separated vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 4 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { ":status", "200",
+ ":version", "HTTP/1.1",
+ "vary", "friend,enemy",
+ "url", "/index.php",
+ NULL
+ }
+ }
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> frame_req(
+ ConstructSpdyGet(test_cases[i].extra_headers[0],
+ test_cases[i].num_headers[0],
+ false, 1, LOWEST));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*frame_req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> frame_reply(
+ ConstructSpdyPacket(*test_cases[i].syn_reply,
+ test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ NULL,
+ 0));
+
+ scoped_ptr<SpdyFrame> body(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,
+ 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.
+ char reply_buffer[256] = "";
+ ConstructSpdyReplyString(test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ reply_buffer,
+ 256);
+
+ EXPECT_EQ(std::string(reply_buffer), lines) << i;
+ }
+}
+
+// Verify that we don't crash on invalid SynReply responses.
+TEST_P(SpdyNetworkTransactionSpdy3Test, InvalidSynReply) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ 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",
+ "url", "/index.php",
+ "version", "HTTP/1.1",
+ NULL
+ },
+ },
+ // SYN_REPLY missing version header
+ { 2,
+ { ":status", "200",
+ "url", "/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(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(
+ ConstructSpdyPacket(kSynStartHeader,
+ NULL, 0,
+ test_cases[i].headers,
+ test_cases[i].num_headers));
+ scoped_ptr<SpdyFrame> body(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv);
+ }
+}
+
+// Verify that we don't crash on some corrupt frames.
+TEST_P(SpdyNetworkTransactionSpdy3Test, CorruptFrameSessionError) {
+ // This is the length field that's too short.
+ scoped_ptr<SpdyFrame> syn_reply_wrong_length(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ syn_reply_wrong_length->set_length(syn_reply_wrong_length->length() - 4);
+
+ 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(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ MockWrite(ASYNC, 0, 0) // EOF
+ };
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*test_cases[i].syn_reply),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(SpdyNetworkTransactionSpdy3Test, WriteError) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(),
+ 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(SpdyNetworkTransactionSpdy3Test, PartialWrite) {
+ // Chop the SYN_STREAM frame into 5 chunks.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ const int kChunks = 5;
+ scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks));
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, DecompressFailureOnSynReply) {
+ scoped_ptr<SpdyFrame> compressed(
+ ConstructSpdyGet(NULL, 0, true, 1, LOWEST));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*compressed),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ SpdySessionDependencies* session_deps = new SpdySessionDependencies();
+ session_deps->enable_compression = true;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(SpdyNetworkTransactionSpdy3Test, NetLog) {
+ static const char* const kExtraHeaders[] = {
+ "user-agent", "Chrome",
+ };
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(kExtraHeaders, 1, false, 1,
+ LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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);
+
+ 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(":host: www.google.com");
+ expected.push_back(":path: /");
+ expected.push_back(":scheme: http");
+ expected.push_back(":version: HTTP/1.1");
+ expected.push_back(":method: 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(SpdyNetworkTransactionSpdy3Test, BufferFull) {
+ BufferedSpdyFramer framer(3, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy3Test, Buffering) {
+ BufferedSpdyFramer framer(3, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy3Test, BufferedAll) {
+ BufferedSpdyFramer framer(3, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 5 data frames in a single read.
+ scoped_ptr<SpdyFrame> syn_reply(
+ ConstructSpdyGetSynReply(NULL, 0, 1));
+ syn_reply->set_flags(CONTROL_FLAG_NONE); // turn off FIN bit
+ 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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy3Test, BufferedClosed) {
+ BufferedSpdyFramer framer(3, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Verify the case where we buffer data and cancel the transaction.
+TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedCancelled) {
+ BufferedSpdyFramer framer(3, false);
+
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ 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(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(),
+ 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 != 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, 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.
+ MessageLoop::current()->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(SpdyNetworkTransactionSpdy3Test, SettingsSaved) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* const kExtraHeaders[] = {
+ ":status", "200",
+ ":version", "HTTP/1.1"
+ };
+
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> reply(
+ ConstructSpdyPacket(kSynReplyInfo,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ NULL,
+ 0));
+
+ 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(ConstructSpdySettings(settings));
+ }
+
+ scoped_ptr<SpdyFrame> body(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(SpdyNetworkTransactionSpdy3Test, SettingsPlayback) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ static const char* kExtraHeaders[] = {
+ ":status", "200",
+ ":version", "HTTP/1.1"
+ };
+
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), 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());
+
+ 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 SETTINGS frame.
+ const SettingsMap& settings =
+ spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair);
+ scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*settings_frame),
+ CreateMockWrite(*req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> reply(
+ ConstructSpdyPacket(kSynReplyInfo,
+ kExtraHeaders,
+ arraysize(kExtraHeaders) / 2,
+ NULL,
+ 0));
+
+ scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, 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 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(SpdyNetworkTransactionSpdy3Test, GoAwayWithActiveStream) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> go_away(ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*go_away),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_ABORTED, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, CloseWithActiveStream) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(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(),
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, ProxyConnect) {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.session_deps().reset(new SpdySessionDependencies(
+ 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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()) {
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, 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(),
+ 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(new SpdySessionDependencies(
+ 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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 != 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());
+ HostPortProxyPair session_pool_key_direct(
+ host_port_pair, ProxyServer::Direct());
+ EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct));
+ HostPortProxyPair session_pool_key_proxy(
+ host_port_pair,
+ ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP));
+ EXPECT_FALSE(spdy_session_pool->HasSession(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(ConstructSpdyGet(
+ "http://www.google.com/foo.dat", false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(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()) {
+ 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(new SpdySessionDependencies());
+ // Ensure that this transaction uses the same SpdySessionPool.
+ scoped_refptr<HttpNetworkSession> session_proxy(
+ SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get()));
+ NormalSpdyTransactionHelper helper_proxy(request_proxy,
+ 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 != 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(SpdyNetworkTransactionSpdy3Test, VerifyRetryOnConnectionReset) {
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data1);
+ helper.AddData(&data2);
+ helper.RunPreTestSetup();
+
+ for (int i = 0; i < 2; ++i) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(helper.session()));
+
+ 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.
+ MessageLoop::current()->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 != 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(SpdyNetworkTransactionSpdy3Test, SpdyOnOffToggle) {
+ net::HttpStreamFactory::set_spdy_enabled(true);
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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(),
+ 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(SpdyNetworkTransactionSpdy3Test, 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(
+ ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ const char* const kExtraAuthorizationHeaders[] = {
+ "authorization",
+ "Basic Zm9vOmJhcg==",
+ };
+ scoped_ptr<SpdyFrame> req_get_authorization(
+ ConstructSpdyGet(
+ kExtraAuthorizationHeaders,
+ arraysize(kExtraAuthorizationHeaders) / 2,
+ false, 3, LOWEST));
+ 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(
+ ConstructSpdySynReplyError(
+ "401 Authentication Required",
+ kExtraAuthenticationHeaders,
+ arraysize(kExtraAuthenticationHeaders) / 2,
+ 1));
+ scoped_ptr<SpdyFrame> body_authentication(
+ ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body_data(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, 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 != 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 != NULL);
+ EXPECT_EQ(200, response_restart->headers->response_code());
+ EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushWithHeaders) {
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ ":scheme", "http",
+ ":host", "www.google.com",
+ ":path", "/foo.dat",
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ ":status",
+ "200",
+ ":version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_headers, 4),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 6),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushClaimBeforeHeaders) {
+ // We push a stream and attempt to claim it before the headers come down.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ ":scheme", "http",
+ ":host", "www.google.com",
+ ":path", "/foo.dat"
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ ":status",
+ "200",
+ ":version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers, 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(3);
+ MessageLoop::current()->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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != 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(SpdyNetworkTransactionSpdy3Test, ServerPushWithTwoHeaderFrames) {
+ // We push a stream and attempt to claim it before the headers come down.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ ":scheme", "http",
+ ":host", "www.google.com",
+ ":path", "/foo.dat"
+ };
+ static const char* const kMiddleHeaders[] = {
+ "hello",
+ "bye",
+ };
+ static const char* const kLateHeaders[] = {
+ ":status",
+ "200",
+ ":version",
+ "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+ arraysize(kMiddleHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream2_headers2(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers1, 4),
+ CreateMockRead(*stream2_headers2, 5),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(3);
+ MessageLoop::current()->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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+ // Verify we got all the headers
+ 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(SpdyNetworkTransactionSpdy3Test, ServerPushWithNoStatusHeaderFrames) {
+ // We push a stream and attempt to claim it before the headers come down.
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ static const char* const kInitialHeaders[] = {
+ ":scheme", "http",
+ ":host", "www.google.com",
+ ":path", "/foo.dat"
+ };
+ static const char* const kMiddleHeaders[] = {
+ "hello",
+ "bye",
+ };
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 1));
+ scoped_ptr<SpdyFrame>
+ stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+ arraysize(kMiddleHeaders) / 2,
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers1, 4),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 5),
+ MockRead(ASYNC, 0, 6), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(),
+ 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(helper.session()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(2);
+ MessageLoop::current()->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 != 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(SpdyNetworkTransactionSpdy3Test, SynReplyWithHeaders) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ static const char* const kInitialHeaders[] = {
+ ":status",
+ "200 OK",
+ ":version",
+ "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(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(),
+ 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(SpdyNetworkTransactionSpdy3Test, SynReplyWithLateHeaders) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ static const char* const kInitialHeaders[] = {
+ ":status",
+ "200 OK",
+ ":version",
+ "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ "hello",
+ "bye",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> stream1_body2(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(),
+ 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!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithDuplicateLateHeaders) {
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ static const char* const kInitialHeaders[] = {
+ ":status",
+ "200 OK",
+ ":version",
+ "HTTP/1.1"
+ };
+ static const char* const kLateHeaders[] = {
+ ":status",
+ "500 Server Error",
+ };
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+ arraysize(kInitialHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame>
+ stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+ arraysize(kLateHeaders) / 2,
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ NULL,
+ 0,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> stream1_body2(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(),
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, 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
+ };
+
+
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+
+ 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(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> push_rst(
+ ConstructSpdyRstStream(2, REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*push_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ url_to_push));
+ scoped_ptr<SpdyFrame> rst(
+ ConstructSpdyRstStream(2, CANCEL));
+
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+ arraysize(kPushBodyFrame), 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(
+ new SpdySessionDependencies());
+ session_deps->trusted_spdy_proxy = "123.45.67.89:8080";
+ NormalSpdyTransactionHelper helper(request,
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+ }
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, RetryAfterRefused) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ CreateMockWrite(*req2, 3),
+ };
+
+ scoped_ptr<SpdyFrame> refused(
+ ConstructSpdyRstStream(1, REFUSED_STREAM));
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body(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(),
+ 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 != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, 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 they order they were created.
+ scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, HIGHEST));
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, MEDIUM));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 3),
+ CreateMockWrite(*req3, 4),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(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(),
+ 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();
+ info1.priority = LOWEST;
+ 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.
+ MessageLoop::current()->RunUntilIdle();
+
+ // Now, start both new transactions
+ HttpRequestInfo info2 = CreateGetRequest();
+ info2.priority = MEDIUM;
+ TestCompletionCallback callback2;
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(helper.session()));
+ rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ MessageLoop::current()->RunUntilIdle();
+
+ HttpRequestInfo info3 = CreateGetRequest();
+ info3.priority = HIGHEST;
+ TestCompletionCallback callback3;
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(helper.session()));
+ rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ MessageLoop::current()->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();
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_protocol.h b/src/net/spdy/spdy_protocol.h
new file mode 100644
index 0000000..30daee3
--- /dev/null
+++ b/src/net/spdy/spdy_protocol.h
@@ -0,0 +1,1065 @@
+// 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.
+
+#ifndef NET_SPDY_SPDY_PROTOCOL_H_
+#define NET_SPDY_SPDY_PROTOCOL_H_
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+// Data Frame Format
+// +----------------------------------+
+// |0| Stream-ID (31bits) |
+// +----------------------------------+
+// | flags (8) | Length (24 bits) |
+// +----------------------------------+
+// | Data |
+// +----------------------------------+
+//
+// Control Frame Format
+// +----------------------------------+
+// |1| Version(15bits) | Type(16bits) |
+// +----------------------------------+
+// | flags (8) | Length (24 bits) |
+// +----------------------------------+
+// | Data |
+// +----------------------------------+
+//
+// Control Frame: SYN_STREAM
+// +----------------------------------+
+// |1|000000000000001|0000000000000001|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 12
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// |X|Associated-To-Stream-ID (31bits)|
+// +----------------------------------+
+// |Pri| unused | Length (16bits)|
+// +----------------------------------+
+//
+// Control Frame: SYN_REPLY
+// +----------------------------------+
+// |1|000000000000001|0000000000000010|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 8
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// | unused (16 bits)| Length (16bits)|
+// +----------------------------------+
+//
+// Control Frame: RST_STREAM
+// +----------------------------------+
+// |1|000000000000001|0000000000000011|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 4
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// | Status code (32 bits) |
+// +----------------------------------+
+//
+// Control Frame: SETTINGS
+// +----------------------------------+
+// |1|000000000000001|0000000000000100|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) |
+// +----------------------------------+
+// | # of entries (32) |
+// +----------------------------------+
+//
+// Control Frame: NOOP
+// +----------------------------------+
+// |1|000000000000001|0000000000000101|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 0
+// +----------------------------------+
+//
+// Control Frame: PING
+// +----------------------------------+
+// |1|000000000000001|0000000000000110|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 4
+// +----------------------------------+
+// | Unique id (32 bits) |
+// +----------------------------------+
+//
+// Control Frame: GOAWAY
+// +----------------------------------+
+// |1|000000000000001|0000000000000111|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 4
+// +----------------------------------+
+// |X| Last-accepted-stream-id |
+// +----------------------------------+
+//
+// Control Frame: HEADERS
+// +----------------------------------+
+// |1|000000000000001|0000000000001000|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 8
+// +----------------------------------+
+// |X| Stream-ID (31 bits) |
+// +----------------------------------+
+// | unused (16 bits)| Length (16bits)|
+// +----------------------------------+
+//
+// Control Frame: WINDOW_UPDATE
+// +----------------------------------+
+// |1|000000000000001|0000000000001001|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | = 8
+// +----------------------------------+
+// |X| Stream-ID (31 bits) |
+// +----------------------------------+
+// | Delta-Window-Size (32 bits) |
+// +----------------------------------+
+//
+// Control Frame: CREDENTIAL
+// +----------------------------------+
+// |1|000000000000001|0000000000001010|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 12
+// +----------------------------------+
+// | Slot (16 bits) | |
+// +-----------------+ |
+// | Proof Length (32 bits) |
+// +----------------------------------+
+// | Proof |
+// +----------------------------------+ <+
+// | Certificate Length (32 bits) | |
+// +----------------------------------+ | Repeated until end of frame
+// | Certificate | |
+// +----------------------------------+ <+
+//
+
+namespace net {
+
+// Initial window size for a Spdy stream
+const int32 kSpdyStreamInitialWindowSize = 64 * 1024; // 64 KBytes
+
+// Maximum window size for a Spdy stream
+const int32 kSpdyStreamMaximumWindowSize = 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);
+
+// Note: all protocol data structures are on-the-wire format. That means that
+// data is stored in network-normalized order. Readers must use the
+// accessors provided or call ntohX() functions.
+
+// Types of Spdy Control Frames.
+enum SpdyControlType {
+ SYN_STREAM = 1,
+ SYN_REPLY,
+ RST_STREAM,
+ SETTINGS,
+ NOOP, // Because it is valid in SPDY/2, kept for identifiability/enum order.
+ PING,
+ GOAWAY,
+ HEADERS,
+ WINDOW_UPDATE,
+ CREDENTIAL,
+ NUM_CONTROL_FRAME_TYPES
+};
+
+// 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, as used in control frames (primarily RST_STREAM).
+// TODO(hkhalil): Rename to SpdyRstStreamStatus
+enum SpdyStatusCodes {
+ INVALID = 0,
+ PROTOCOL_ERROR = 1,
+ INVALID_STREAM = 2,
+ REFUSED_STREAM = 3,
+ UNSUPPORTED_VERSION = 4,
+ CANCEL = 5,
+ INTERNAL_ERROR = 6,
+ FLOW_CONTROL_ERROR = 7,
+ STREAM_IN_USE = 8,
+ STREAM_ALREADY_CLOSED = 9,
+ INVALID_CREDENTIALS = 10,
+ FRAME_TOO_LARGE = 11,
+ NUM_STATUS_CODES = 12
+};
+
+enum SpdyGoAwayStatus {
+ GOAWAY_INVALID = -1,
+ GOAWAY_OK = 0,
+ GOAWAY_PROTOCOL_ERROR = 1,
+ GOAWAY_INTERNAL_ERROR = 2,
+ GOAWAY_NUM_STATUS_CODES = 3
+};
+
+// A SPDY stream id is a 31 bit entity.
+typedef uint32 SpdyStreamId;
+
+// 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;
+
+// -------------------------------------------------------------------------
+// These structures mirror the protocol structure definitions.
+
+// For the control data structures, we pack so that sizes match the
+// protocol over-the-wire sizes.
+#pragma pack(push)
+#pragma pack(1)
+
+// A special structure for the 8 bit flags and 24 bit length fields.
+union FlagsAndLength {
+ uint8 flags_[4]; // 8 bits
+ uint32 length_; // 24 bits
+};
+
+// The basic SPDY Frame structure.
+struct SpdyFrameBlock {
+ union {
+ struct {
+ uint16 version_;
+ uint16 type_;
+ } control_;
+ struct {
+ SpdyStreamId stream_id_;
+ } data_;
+ };
+ FlagsAndLength flags_length_;
+};
+
+// A SYN_STREAM Control Frame structure.
+struct SpdySynStreamControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ SpdyStreamId associated_stream_id_;
+ SpdyPriority priority_;
+ uint8 credential_slot_;
+};
+
+// A SYN_REPLY Control Frame structure.
+struct SpdySynReplyControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+};
+
+// A RST_STREAM Control Frame structure.
+struct SpdyRstStreamControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ uint32 status_;
+};
+
+// A SETTINGS Control Frame structure.
+struct SpdySettingsControlFrameBlock : SpdyFrameBlock {
+ uint32 num_entries_;
+ // Variable data here.
+};
+
+// A PING Control Frame structure.
+struct SpdyPingControlFrameBlock : SpdyFrameBlock {
+ uint32 unique_id_;
+};
+
+// TODO(avd): remove this struct
+// A CREDENTIAL Control Frame structure.
+struct SpdyCredentialControlFrameBlock : SpdyFrameBlock {
+ uint16 slot_;
+ uint32 proof_len_;
+ // Variable data here.
+ // proof data
+ // for each certificate: unit32 certificate_len + certificate_data[i]
+};
+
+// A GOAWAY Control Frame structure.
+struct SpdyGoAwayControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId last_accepted_stream_id_;
+ SpdyGoAwayStatus status_;
+};
+
+// A HEADERS Control Frame structure.
+struct SpdyHeadersControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+};
+
+// A WINDOW_UPDATE Control Frame structure
+struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock {
+ SpdyStreamId stream_id_;
+ uint32 delta_window_size_;
+};
+
+#pragma pack(pop)
+
+// -------------------------------------------------------------------------
+// Wrapper classes for various SPDY frames.
+
+// All Spdy Frame types derive from this SpdyFrame class.
+class SpdyFrame {
+ public:
+ // Create a SpdyFrame for a given sized buffer.
+ explicit SpdyFrame(size_t size) : frame_(NULL), owns_buffer_(true) {
+ DCHECK_GE(size, sizeof(struct SpdyFrameBlock));
+ char* buffer = new char[size];
+ memset(buffer, 0, size);
+ frame_ = reinterpret_cast<struct SpdyFrameBlock*>(buffer);
+ }
+
+ // 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, bool owns_buffer)
+ : frame_(reinterpret_cast<struct SpdyFrameBlock*>(data)),
+ owns_buffer_(owns_buffer) {
+ DCHECK(frame_);
+ }
+
+ ~SpdyFrame() {
+ if (owns_buffer_) {
+ char* buffer = reinterpret_cast<char*>(frame_);
+ delete [] buffer;
+ }
+ 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 reinterpret_cast<char*>(frame_); }
+
+ uint8 flags() const { return frame_->flags_length_.flags_[0]; }
+ void set_flags(uint8 flags) { frame_->flags_length_.flags_[0] = flags; }
+
+ uint32 length() const {
+ return ntohl(frame_->flags_length_.length_) & kLengthMask;
+ }
+
+ void set_length(uint32 length) {
+ DCHECK_EQ(0u, (length & ~kLengthMask));
+ /*
+ +----------------------------------+
+ | Flags (8) | Length (24 bits) |
+ +----------------------------------+
+ */
+ frame_->flags_length_.length_ = htonl((length & kLengthMask)
+ | (flags() << 24));
+ }
+
+ bool is_control_frame() const {
+ return (ntohs(frame_->control_.version_) & kControlFlagMask) ==
+ kControlFlagMask;
+ }
+
+ // The size of the SpdyFrameBlock structure.
+ // Every SpdyFrame* class has a static size() method for accessing
+ // the size of the data structure which will be sent over the wire.
+ // Note: this is not the same as sizeof(SpdyFrame).
+ enum { kHeaderSize = sizeof(struct SpdyFrameBlock) };
+
+ protected:
+ SpdyFrameBlock* frame_;
+
+ private:
+ bool owns_buffer_;
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrame);
+};
+
+// A Data Frame.
+class SpdyDataFrame : public SpdyFrame {
+ public:
+ SpdyDataFrame() : SpdyFrame(size()) {}
+ SpdyDataFrame(char* data, bool owns_buffer)
+ : SpdyFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(frame_->data_.stream_id_) & kStreamIdMask;
+ }
+
+ // Note that setting the stream id sets the control bit to false.
+ // As stream id should always be set, this means the control bit
+ // should always be set correctly.
+ void set_stream_id(SpdyStreamId id) {
+ DCHECK_EQ(0u, (id & ~kStreamIdMask));
+ frame_->data_.stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ // Returns the size of the SpdyFrameBlock structure.
+ // Note: this is not the size of the SpdyDataFrame class.
+ static size_t size() { return SpdyFrame::kHeaderSize; }
+
+ const char* payload() const {
+ return reinterpret_cast<const char*>(frame_) + size();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyDataFrame);
+};
+
+// A Control Frame.
+class SpdyControlFrame : public SpdyFrame {
+ public:
+ explicit SpdyControlFrame(size_t size) : SpdyFrame(size) {}
+ SpdyControlFrame(char* data, bool owns_buffer)
+ : SpdyFrame(data, owns_buffer) {}
+
+ // Callers can use this method to check if the frame appears to be a valid
+ // frame. Does not guarantee that there are no errors.
+ bool AppearsToBeAValidControlFrame() const {
+ // Right now we only check if the frame has an out-of-bounds type.
+ uint16 type = ntohs(block()->control_.type_);
+ // NOOP is not a 'valid' control frame in SPDY/3 and beyond.
+ return type >= SYN_STREAM &&
+ type < NUM_CONTROL_FRAME_TYPES &&
+ (version() == 2 || type != NOOP);
+ }
+
+ uint16 version() const {
+ const int kVersionMask = 0x7fff;
+ return static_cast<uint16>(
+ ntohs((block()->control_.version_)) & kVersionMask);
+ }
+
+ void set_version(uint16 version) {
+ /*
+ +----------------------------------+
+ |C| Version(15bits) | Type(16bits) |
+ +----------------------------------+
+ */
+ DCHECK_EQ(0U, version & kControlFlagMask);
+ mutable_block()->control_.version_ = htons(kControlFlagMask | version);
+ }
+
+ SpdyControlType type() const {
+ uint16 type = ntohs(block()->control_.type_);
+ LOG_IF(DFATAL, type < SYN_STREAM || type >= NUM_CONTROL_FRAME_TYPES)
+ << "Invalid control frame type " << type;
+ return static_cast<SpdyControlType>(type);
+ }
+
+ void set_type(SpdyControlType type) {
+ DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES);
+ mutable_block()->control_.type_ = htons(static_cast<uint16>(type));
+ }
+
+ // Returns true if this control frame is of a type that has a header block,
+ // otherwise it returns false.
+ bool has_header_block() const {
+ return type() == SYN_STREAM || type() == SYN_REPLY || type() == HEADERS;
+ }
+
+ private:
+ const struct SpdyFrameBlock* block() const {
+ return frame_;
+ }
+ struct SpdyFrameBlock* mutable_block() {
+ return frame_;
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyControlFrame);
+};
+
+// A SYN_STREAM frame.
+class SpdySynStreamControlFrame : public SpdyControlFrame {
+ public:
+ SpdySynStreamControlFrame() : SpdyControlFrame(size()) {}
+ SpdySynStreamControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ SpdyStreamId associated_stream_id() const {
+ return ntohl(block()->associated_stream_id_) & kStreamIdMask;
+ }
+
+ void set_associated_stream_id(SpdyStreamId id) {
+ mutable_block()->associated_stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ SpdyPriority priority() const {
+ if (version() < 3) {
+ return (block()->priority_ & kSpdy2PriorityMask) >> 6;
+ } else {
+ return (block()->priority_ & kSpdy3PriorityMask) >> 5;
+ }
+ }
+
+ uint8 credential_slot() const {
+ if (version() < 3) {
+ return 0;
+ } else {
+ return block()->credential_slot_;
+ }
+ }
+
+ void set_credential_slot(uint8 credential_slot) {
+ DCHECK(version() >= 3);
+ mutable_block()->credential_slot_ = credential_slot;
+ }
+
+ // The number of bytes in the header block beyond the frame header length.
+ int header_block_len() const {
+ return static_cast<int>(length() - (size() - SpdyFrame::kHeaderSize));
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the SpdySynStreamControlFrameBlock structure.
+ // Note: this is not the size of the SpdySynStreamControlFrame class.
+ static size_t size() { return sizeof(SpdySynStreamControlFrameBlock); }
+
+ private:
+ const struct SpdySynStreamControlFrameBlock* block() const {
+ return static_cast<SpdySynStreamControlFrameBlock*>(frame_);
+ }
+ struct SpdySynStreamControlFrameBlock* mutable_block() {
+ return static_cast<SpdySynStreamControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdySynStreamControlFrame);
+};
+
+// A SYN_REPLY frame.
+class SpdySynReplyControlFrame : public SpdyControlFrame {
+ public:
+ SpdySynReplyControlFrame() : SpdyControlFrame(size()) {}
+ SpdySynReplyControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ int header_block_len() const {
+ size_t header_block_len = length() - (size() - SpdyFrame::kHeaderSize);
+ // SPDY 2 had 2 bytes of unused space preceeding the header block.
+ if (version() < 3) {
+ header_block_len -= 2;
+ }
+ return static_cast<int>(header_block_len);
+ }
+
+ const char* header_block() const {
+ const char* header_block = reinterpret_cast<const char*>(block()) + size();
+ // SPDY 2 had 2 bytes of unused space preceeding the header block.
+ if (version() < 3) {
+ header_block += 2;
+ }
+ return header_block;
+ }
+
+ // Returns the size of the SpdySynReplyControlFrameBlock structure.
+ // Note: this is not the size of the SpdySynReplyControlFrame class.
+ static size_t size() { return sizeof(SpdySynReplyControlFrameBlock); }
+
+ private:
+ const struct SpdySynReplyControlFrameBlock* block() const {
+ return static_cast<SpdySynReplyControlFrameBlock*>(frame_);
+ }
+ struct SpdySynReplyControlFrameBlock* mutable_block() {
+ return static_cast<SpdySynReplyControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdySynReplyControlFrame);
+};
+
+// A RST_STREAM frame.
+class SpdyRstStreamControlFrame : public SpdyControlFrame {
+ public:
+ SpdyRstStreamControlFrame() : SpdyControlFrame(size()) {}
+ SpdyRstStreamControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ SpdyStatusCodes status() const {
+ SpdyStatusCodes status =
+ static_cast<SpdyStatusCodes>(ntohl(block()->status_));
+ if (status < INVALID || status >= NUM_STATUS_CODES) {
+ status = INVALID;
+ }
+ return status;
+ }
+ void set_status(SpdyStatusCodes status) {
+ mutable_block()->status_ = htonl(static_cast<uint32>(status));
+ }
+
+ // Returns the size of the SpdyRstStreamControlFrameBlock structure.
+ // Note: this is not the size of the SpdyRstStreamControlFrame class.
+ static size_t size() { return sizeof(SpdyRstStreamControlFrameBlock); }
+
+ private:
+ const struct SpdyRstStreamControlFrameBlock* block() const {
+ return static_cast<SpdyRstStreamControlFrameBlock*>(frame_);
+ }
+ struct SpdyRstStreamControlFrameBlock* mutable_block() {
+ return static_cast<SpdyRstStreamControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamControlFrame);
+};
+
+class SpdySettingsControlFrame : public SpdyControlFrame {
+ public:
+ SpdySettingsControlFrame() : SpdyControlFrame(size()) {}
+ SpdySettingsControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ uint32 num_entries() const {
+ return ntohl(block()->num_entries_);
+ }
+
+ void set_num_entries(int val) {
+ mutable_block()->num_entries_ = htonl(static_cast<uint32>(val));
+ }
+
+ int header_block_len() const {
+ return static_cast<int>(length() - (size() - SpdyFrame::kHeaderSize));
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the SpdySettingsControlFrameBlock structure.
+ // Note: this is not the size of the SpdySettingsControlFrameBlock class.
+ static size_t size() { return sizeof(SpdySettingsControlFrameBlock); }
+
+ private:
+ const struct SpdySettingsControlFrameBlock* block() const {
+ return static_cast<SpdySettingsControlFrameBlock*>(frame_);
+ }
+ struct SpdySettingsControlFrameBlock* mutable_block() {
+ return static_cast<SpdySettingsControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame);
+};
+
+class SpdyPingControlFrame : public SpdyControlFrame {
+ public:
+ SpdyPingControlFrame() : SpdyControlFrame(size()) {}
+ SpdyPingControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ uint32 unique_id() const {
+ return ntohl(block()->unique_id_);
+ }
+
+ void set_unique_id(uint32 unique_id) {
+ mutable_block()->unique_id_ = htonl(unique_id);
+ }
+
+ static size_t size() { return sizeof(SpdyPingControlFrameBlock); }
+
+ private:
+ const struct SpdyPingControlFrameBlock* block() const {
+ return static_cast<SpdyPingControlFrameBlock*>(frame_);
+ }
+ struct SpdyPingControlFrameBlock* mutable_block() {
+ return static_cast<SpdyPingControlFrameBlock*>(frame_);
+ }
+};
+
+class SpdyCredentialControlFrame : public SpdyControlFrame {
+ public:
+ SpdyCredentialControlFrame() : SpdyControlFrame(size()) {}
+ SpdyCredentialControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ const char* payload() const {
+ return reinterpret_cast<const char*>(block()) + SpdyFrame::kHeaderSize;
+ }
+
+ static size_t size() { return sizeof(SpdyCredentialControlFrameBlock); }
+
+ private:
+ const struct SpdyCredentialControlFrameBlock* block() const {
+ return static_cast<SpdyCredentialControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyCredentialControlFrame);
+};
+
+class SpdyGoAwayControlFrame : public SpdyControlFrame {
+ public:
+ SpdyGoAwayControlFrame() : SpdyControlFrame(size()) {}
+ SpdyGoAwayControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId last_accepted_stream_id() const {
+ return ntohl(block()->last_accepted_stream_id_) & kStreamIdMask;
+ }
+
+ SpdyGoAwayStatus status() const {
+ if (version() < 3) {
+ LOG(DFATAL) << "Attempted to access status of SPDY 2 GOAWAY.";
+ return GOAWAY_INVALID;
+ } else {
+ uint32 status = ntohl(block()->status_);
+ if (status >= GOAWAY_NUM_STATUS_CODES) {
+ return GOAWAY_INVALID;
+ } else {
+ return static_cast<SpdyGoAwayStatus>(status);
+ }
+ }
+ }
+
+ void set_last_accepted_stream_id(SpdyStreamId id) {
+ mutable_block()->last_accepted_stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ static size_t size() { return sizeof(SpdyGoAwayControlFrameBlock); }
+
+ private:
+ const struct SpdyGoAwayControlFrameBlock* block() const {
+ return static_cast<SpdyGoAwayControlFrameBlock*>(frame_);
+ }
+ struct SpdyGoAwayControlFrameBlock* mutable_block() {
+ return static_cast<SpdyGoAwayControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayControlFrame);
+};
+
+// A HEADERS frame.
+class SpdyHeadersControlFrame : public SpdyControlFrame {
+ public:
+ SpdyHeadersControlFrame() : SpdyControlFrame(size()) {}
+ SpdyHeadersControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ // The number of bytes in the header block beyond the frame header length.
+ int header_block_len() const {
+ size_t header_block_len = length() - (size() - SpdyFrame::kHeaderSize);
+ // SPDY 2 had 2 bytes of unused space preceeding the header block.
+ if (version() < 3) {
+ header_block_len -= 2;
+ }
+ return static_cast<int>(header_block_len);
+ }
+
+ const char* header_block() const {
+ const char* header_block = reinterpret_cast<const char*>(block()) + size();
+ // SPDY 2 had 2 bytes of unused space preceeding the header block.
+ if (version() < 3) {
+ header_block += 2;
+ }
+ return header_block;
+ }
+
+ // Returns the size of the SpdyHeadersControlFrameBlock structure.
+ // Note: this is not the size of the SpdyHeadersControlFrame class.
+ static size_t size() { return sizeof(SpdyHeadersControlFrameBlock); }
+
+ private:
+ const struct SpdyHeadersControlFrameBlock* block() const {
+ return static_cast<SpdyHeadersControlFrameBlock*>(frame_);
+ }
+ struct SpdyHeadersControlFrameBlock* mutable_block() {
+ return static_cast<SpdyHeadersControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(SpdyHeadersControlFrame);
+};
+
+// A WINDOW_UPDATE frame.
+class SpdyWindowUpdateControlFrame : public SpdyControlFrame {
+ public:
+ SpdyWindowUpdateControlFrame() : SpdyControlFrame(size()) {}
+ SpdyWindowUpdateControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ SpdyStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(SpdyStreamId id) {
+ mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ uint32 delta_window_size() const {
+ return ntohl(block()->delta_window_size_);
+ }
+
+ void set_delta_window_size(uint32 delta_window_size) {
+ mutable_block()->delta_window_size_ = htonl(delta_window_size);
+ }
+
+ // Returns the size of the SpdyWindowUpdateControlFrameBlock structure.
+ // Note: this is not the size of the SpdyWindowUpdateControlFrame class.
+ static size_t size() { return sizeof(SpdyWindowUpdateControlFrameBlock); }
+
+ private:
+ const struct SpdyWindowUpdateControlFrameBlock* block() const {
+ return static_cast<SpdyWindowUpdateControlFrameBlock*>(frame_);
+ }
+ struct SpdyWindowUpdateControlFrameBlock* mutable_block() {
+ return static_cast<SpdyWindowUpdateControlFrameBlock*>(frame_);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWindowUpdateControlFrame);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_PROTOCOL_H_
diff --git a/src/net/spdy/spdy_protocol_test.cc b/src/net/spdy/spdy_protocol_test.cc
new file mode 100644
index 0000000..fba50d7
--- /dev/null
+++ b/src/net/spdy/spdy_protocol_test.cc
@@ -0,0 +1,403 @@
+// 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 "base/memory/scoped_ptr.h"
+#include "net/spdy/spdy_bitmasks.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/platform_test.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) {
+#if defined(__LB_SHELL__) || defined(COBALT)
+ // Compiler quirk: <function> declared using a type with no linkage,
+ // must be defined in this translation unit
+ unsigned int spdy_frame_header_size = SpdyFrame::kHeaderSize;
+ EXPECT_EQ(8u, spdy_frame_header_size);
+#else
+ EXPECT_EQ(8u, SpdyFrame::kHeaderSize);
+#endif
+ EXPECT_EQ(8u, SpdyDataFrame::size());
+#if defined(__LB_SHELL__) || defined(COBALT)
+ // Compiler quirk: <function> declared using a type with no linkage,
+ // must be defined in this translation unit
+ unsigned int spdy_control_frame_header_size = SpdyFrame::kHeaderSize;
+ EXPECT_EQ(8u, spdy_control_frame_header_size);
+#else
+ EXPECT_EQ(8u, SpdyControlFrame::kHeaderSize);
+#endif
+ EXPECT_EQ(18u, SpdySynStreamControlFrame::size());
+ EXPECT_EQ(12u, SpdySynReplyControlFrame::size());
+ EXPECT_EQ(16u, SpdyRstStreamControlFrame::size());
+ EXPECT_EQ(12u, SpdySettingsControlFrame::size());
+ EXPECT_EQ(12u, SpdyPingControlFrame::size());
+ EXPECT_EQ(16u, SpdyGoAwayControlFrame::size());
+ EXPECT_EQ(12u, SpdyHeadersControlFrame::size());
+ EXPECT_EQ(16u, SpdyWindowUpdateControlFrame::size());
+ EXPECT_EQ(4u, sizeof(FlagsAndLength));
+ 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);
+}
+
+// Test some of the protocol helper functions
+TEST_P(SpdyProtocolTest, FrameStructs) {
+ SpdyFrame frame(SpdyFrame::kHeaderSize);
+ frame.set_length(12345);
+ frame.set_flags(10);
+ EXPECT_EQ(12345u, frame.length());
+ EXPECT_EQ(10u, frame.flags());
+ EXPECT_FALSE(frame.is_control_frame());
+
+ frame.set_length(0);
+ frame.set_flags(10);
+ EXPECT_EQ(0u, frame.length());
+ EXPECT_EQ(10u, frame.flags());
+ EXPECT_FALSE(frame.is_control_frame());
+}
+
+TEST_P(SpdyProtocolTest, DataFrameStructs) {
+ SpdyDataFrame data_frame;
+ data_frame.set_stream_id(12345);
+ EXPECT_EQ(12345u, data_frame.stream_id());
+}
+
+TEST_P(SpdyProtocolTest, ControlFrameStructs) {
+ SpdyFramer framer(spdy_version_);
+ SpdyHeaderBlock headers;
+
+ const uint8 credential_slot = IsSpdy2() ? 0 : 5;
+
+ scoped_ptr<SpdySynStreamControlFrame> syn_frame(framer.CreateSynStream(
+ 123, 456, 2, credential_slot, CONTROL_FLAG_FIN, false, &headers));
+ EXPECT_EQ(framer.protocol_version(), syn_frame->version());
+ EXPECT_TRUE(syn_frame->is_control_frame());
+ EXPECT_EQ(SYN_STREAM, syn_frame->type());
+ EXPECT_EQ(123u, syn_frame->stream_id());
+ EXPECT_EQ(456u, syn_frame->associated_stream_id());
+ EXPECT_EQ(2u, syn_frame->priority());
+ EXPECT_EQ(credential_slot, syn_frame->credential_slot());
+ EXPECT_EQ(IsSpdy2() ? 2 : 4, syn_frame->header_block_len());
+ EXPECT_EQ(1u, syn_frame->flags());
+ syn_frame->set_associated_stream_id(999u);
+ EXPECT_EQ(123u, syn_frame->stream_id());
+ EXPECT_EQ(999u, syn_frame->associated_stream_id());
+
+ scoped_ptr<SpdySynReplyControlFrame> syn_reply(
+ framer.CreateSynReply(123, CONTROL_FLAG_NONE, false, &headers));
+ EXPECT_EQ(framer.protocol_version(), syn_reply->version());
+ EXPECT_TRUE(syn_reply->is_control_frame());
+ EXPECT_EQ(SYN_REPLY, syn_reply->type());
+ EXPECT_EQ(123u, syn_reply->stream_id());
+ EXPECT_EQ(IsSpdy2() ? 2 : 4, syn_reply->header_block_len());
+ EXPECT_EQ(0, syn_reply->flags());
+
+ scoped_ptr<SpdyRstStreamControlFrame> rst_frame(
+ framer.CreateRstStream(123, PROTOCOL_ERROR));
+ EXPECT_EQ(framer.protocol_version(), rst_frame->version());
+ EXPECT_TRUE(rst_frame->is_control_frame());
+ EXPECT_EQ(RST_STREAM, rst_frame->type());
+ EXPECT_EQ(123u, rst_frame->stream_id());
+ EXPECT_EQ(PROTOCOL_ERROR, rst_frame->status());
+ rst_frame->set_status(INVALID_STREAM);
+ EXPECT_EQ(INVALID_STREAM, rst_frame->status());
+ EXPECT_EQ(0, rst_frame->flags());
+
+ const uint32 kUniqueId = 1234567u;
+ const uint32 kUniqueId2 = 31415926u;
+ scoped_ptr<SpdyPingControlFrame> ping_frame(
+ framer.CreatePingFrame(kUniqueId));
+ EXPECT_EQ(framer.protocol_version(), ping_frame->version());
+ EXPECT_TRUE(ping_frame->is_control_frame());
+ EXPECT_EQ(PING, ping_frame->type());
+ EXPECT_EQ(kUniqueId, ping_frame->unique_id());
+ ping_frame->set_unique_id(kUniqueId2);
+ EXPECT_EQ(kUniqueId2, ping_frame->unique_id());
+
+ scoped_ptr<SpdyGoAwayControlFrame> goaway_frame(
+ framer.CreateGoAway(123, GOAWAY_INTERNAL_ERROR));
+ EXPECT_EQ(framer.protocol_version(), goaway_frame->version());
+ EXPECT_TRUE(goaway_frame->is_control_frame());
+ EXPECT_EQ(GOAWAY, goaway_frame->type());
+ EXPECT_EQ(123u, goaway_frame->last_accepted_stream_id());
+ if (!IsSpdy2()) {
+ EXPECT_EQ(GOAWAY_INTERNAL_ERROR, goaway_frame->status());
+ }
+
+ scoped_ptr<SpdyHeadersControlFrame> headers_frame(
+ framer.CreateHeaders(123, CONTROL_FLAG_NONE, false, &headers));
+ EXPECT_EQ(framer.protocol_version(), headers_frame->version());
+ EXPECT_TRUE(headers_frame->is_control_frame());
+ EXPECT_EQ(HEADERS, headers_frame->type());
+ EXPECT_EQ(123u, headers_frame->stream_id());
+ EXPECT_EQ(IsSpdy2() ? 2 : 4, headers_frame->header_block_len());
+ EXPECT_EQ(0, headers_frame->flags());
+
+ scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
+ framer.CreateWindowUpdate(123, 456));
+ EXPECT_EQ(framer.protocol_version(), window_update_frame->version());
+ EXPECT_TRUE(window_update_frame->is_control_frame());
+ EXPECT_EQ(WINDOW_UPDATE, window_update_frame->type());
+ EXPECT_EQ(123u, window_update_frame->stream_id());
+ EXPECT_EQ(456u, window_update_frame->delta_window_size());
+}
+
+TEST_P(SpdyProtocolTest, TestDataFrame) {
+ SpdyDataFrame frame;
+
+ // Set the stream ID to various values.
+ frame.set_stream_id(0);
+ EXPECT_EQ(0u, frame.stream_id());
+ EXPECT_FALSE(frame.is_control_frame());
+ frame.set_stream_id(~0 & kStreamIdMask);
+ EXPECT_EQ(~0 & kStreamIdMask, frame.stream_id());
+ EXPECT_FALSE(frame.is_control_frame());
+
+ // Set length to various values. Make sure that when you set_length(x),
+ // length() == x. Also make sure the flags are unaltered.
+ memset(frame.data(), '1', SpdyDataFrame::size());
+ int8 flags = frame.flags();
+ frame.set_length(0);
+ EXPECT_EQ(0u, frame.length());
+ EXPECT_EQ(flags, frame.flags());
+ frame.set_length(kLengthMask);
+ EXPECT_EQ(kLengthMask, frame.length());
+ EXPECT_EQ(flags, frame.flags());
+ frame.set_length(5u);
+ EXPECT_EQ(5u, frame.length());
+ EXPECT_EQ(flags, frame.flags());
+
+ // Set flags to various values. Make sure that when you set_flags(x),
+ // flags() == x. Also make sure the length is unaltered.
+ memset(frame.data(), '1', SpdyDataFrame::size());
+ uint32 length = frame.length();
+ frame.set_flags(0u);
+ EXPECT_EQ(0u, frame.flags());
+ EXPECT_EQ(length, frame.length());
+ int8 all_flags = ~0;
+ frame.set_flags(all_flags);
+ flags = frame.flags();
+ EXPECT_EQ(all_flags, flags);
+ EXPECT_EQ(length, frame.length());
+ frame.set_flags(5u);
+ EXPECT_EQ(5u, frame.flags());
+ EXPECT_EQ(length, frame.length());
+}
+
+// Test various types of SETTINGS frames.
+TEST_P(SpdyProtocolTest, TestSpdySettingsFrame) {
+ SpdyFramer framer(spdy_version_);
+
+ // Create a settings frame with no settings.
+ SettingsMap settings;
+ scoped_ptr<SpdySettingsControlFrame> settings_frame(
+ framer.CreateSettings(settings));
+ EXPECT_EQ(framer.protocol_version(), settings_frame->version());
+ EXPECT_TRUE(settings_frame->is_control_frame());
+ EXPECT_EQ(SETTINGS, settings_frame->type());
+ EXPECT_EQ(0u, settings_frame->num_entries());
+
+ // We'll add several different ID/Flag combinations and then verify
+ // that they encode and decode properly.
+ SettingsFlagsAndId ids[] = {
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, 0x00000000),
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, 0xffffffff),
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, 0xff000001),
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, 0x01000002),
+ SettingsFlagsAndId(6, 9)
+ };
+
+ for (size_t index = 0; index < arraysize(ids); ++index) {
+ SettingsFlagsAndId flags_and_id = ids[index];
+ SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
+ SpdySettingsFlags flags =
+ static_cast<SpdySettingsFlags>(flags_and_id.flags());
+ settings[id] = SettingsFlagsAndValue(flags, index);
+ settings_frame.reset(framer.CreateSettings(settings));
+ EXPECT_EQ(framer.protocol_version(), settings_frame->version());
+ EXPECT_TRUE(settings_frame->is_control_frame());
+ EXPECT_EQ(SETTINGS, settings_frame->type());
+ EXPECT_EQ(index + 1, settings_frame->num_entries());
+
+ SettingsMap parsed_settings;
+ EXPECT_TRUE(framer.ParseSettings(settings_frame.get(), &parsed_settings));
+ EXPECT_EQ(settings.size(), parsed_settings.size());
+ for (SettingsMap::const_iterator it = parsed_settings.begin();
+ it != parsed_settings.end();
+ it++) {
+ SettingsMap::const_iterator it2 = settings.find(it->first);
+ EXPECT_EQ(it->first, it2->first);
+ SettingsFlagsAndValue parsed = it->second;
+ SettingsFlagsAndValue created = it2->second;
+ EXPECT_EQ(created.first, parsed.first);
+ EXPECT_EQ(created.second, parsed.second);
+ }
+ }
+}
+
+TEST_P(SpdyProtocolTest, HasHeaderBlock) {
+ SpdyControlFrame frame(SpdyControlFrame::kHeaderSize);
+ for (SpdyControlType type = SYN_STREAM;
+ type < NUM_CONTROL_FRAME_TYPES;
+ type = static_cast<SpdyControlType>(type + 1)) {
+ frame.set_type(type);
+ if (type == SYN_STREAM || type == SYN_REPLY || type == HEADERS) {
+ EXPECT_TRUE(frame.has_header_block());
+ } else {
+ EXPECT_FALSE(frame.has_header_block());
+ }
+ }
+}
+
+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));
+
+// Make sure that overflows both die in debug mode, and do not cause problems
+// in opt mode. Note: The EXPECT_DEBUG_DEATH call does not work on Win32 yet,
+// so we comment it out.
+TEST_P(SpdyProtocolDeathTest, TestDataFrame) {
+ SpdyDataFrame frame;
+
+ frame.set_stream_id(0);
+ // TODO(mbelshe): implement EXPECT_DEBUG_DEATH on windows.
+#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(DCHECK_ALWAYS_ON)
+ EXPECT_DEBUG_DEATH(frame.set_stream_id(~0), "");
+#else
+ EXPECT_DEATH(frame.set_stream_id(~0), "");
+#endif
+#endif
+ EXPECT_FALSE(frame.is_control_frame());
+
+ frame.set_flags(0);
+#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(DCHECK_ALWAYS_ON)
+ EXPECT_DEBUG_DEATH(frame.set_length(~0), "");
+#else
+ EXPECT_DEATH(frame.set_length(~0), "");
+#endif
+#endif
+ EXPECT_EQ(0, frame.flags());
+}
+
+TEST_P(SpdyProtocolDeathTest, TestSpdyControlFrameStreamId) {
+ SpdyControlFrame frame_store(SpdySynStreamControlFrame::size());
+ memset(frame_store.data(), '1', SpdyControlFrame::kHeaderSize);
+ SpdySynStreamControlFrame* frame =
+ reinterpret_cast<SpdySynStreamControlFrame*>(&frame_store);
+
+ // Set the stream ID to various values.
+ frame->set_stream_id(0);
+ EXPECT_EQ(0u, frame->stream_id());
+ EXPECT_FALSE(frame->is_control_frame());
+ frame->set_stream_id(kStreamIdMask);
+ EXPECT_EQ(kStreamIdMask, frame->stream_id());
+ EXPECT_FALSE(frame->is_control_frame());
+}
+
+TEST_P(SpdyProtocolDeathTest, TestSpdyControlFrameVersion) {
+ const unsigned int kVersionMask = 0x7fff;
+ SpdyControlFrame frame(SpdySynStreamControlFrame::size());
+ memset(frame.data(), '1', SpdyControlFrame::kHeaderSize);
+
+ // Set the version to various values, and make sure it does not affect the
+ // type.
+ frame.set_type(SYN_STREAM);
+ frame.set_version(0);
+ EXPECT_EQ(0, frame.version());
+ EXPECT_TRUE(frame.is_control_frame());
+ EXPECT_EQ(SYN_STREAM, frame.type());
+
+ SpdySynStreamControlFrame* syn_stream =
+ reinterpret_cast<SpdySynStreamControlFrame*>(&frame);
+ syn_stream->set_stream_id(~0 & kVersionMask);
+ EXPECT_EQ(~0 & kVersionMask, syn_stream->stream_id());
+ EXPECT_TRUE(frame.is_control_frame());
+ EXPECT_EQ(SYN_STREAM, frame.type());
+}
+
+TEST_P(SpdyProtocolDeathTest, TestSpdyControlFrameType) {
+ SpdyControlFrame frame(SpdyControlFrame::kHeaderSize);
+ memset(frame.data(), 255, SpdyControlFrame::kHeaderSize);
+
+ // type() should be out of bounds.
+ EXPECT_FALSE(frame.AppearsToBeAValidControlFrame());
+
+ frame.set_version(spdy_version_);
+ uint16 version = frame.version();
+
+ for (int i = SYN_STREAM; i <= WINDOW_UPDATE; ++i) {
+ frame.set_type(static_cast<SpdyControlType>(i));
+ EXPECT_EQ(i, static_cast<int>(frame.type()));
+ if (!IsSpdy2() && i == NOOP) {
+ // NOOP frames aren't 'valid'.
+ EXPECT_FALSE(frame.AppearsToBeAValidControlFrame());
+ } else {
+ EXPECT_TRUE(frame.AppearsToBeAValidControlFrame());
+ }
+ // Make sure setting type does not alter the version block.
+ EXPECT_EQ(version, frame.version());
+ EXPECT_TRUE(frame.is_control_frame());
+ }
+}
+
+TEST_P(SpdyProtocolDeathTest, TestRstStreamStatusBounds) {
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyRstStreamControlFrame> rst_frame;
+
+ rst_frame.reset(framer.CreateRstStream(123, PROTOCOL_ERROR));
+ EXPECT_EQ(PROTOCOL_ERROR, rst_frame->status());
+
+ rst_frame->set_status(INVALID);
+ EXPECT_EQ(INVALID, rst_frame->status());
+
+ rst_frame->set_status(
+ static_cast<SpdyStatusCodes>(INVALID - 1));
+ EXPECT_EQ(INVALID, rst_frame->status());
+
+ rst_frame->set_status(NUM_STATUS_CODES);
+ EXPECT_EQ(INVALID, rst_frame->status());
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_proxy_client_socket.cc b/src/net/spdy/spdy_proxy_client_socket.cc
new file mode 100644
index 0000000..0d62ce7
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket.cc
@@ -0,0 +1,591 @@
+// 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/logging.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.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/spdy/spdy_http_utils.h"
+
+namespace net {
+
+SpdyProxyClientSocket::SpdyProxyClientSocket(
+ SpdyStream* spdy_stream,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ const GURL& url,
+ const HostPortPair& proxy_server,
+ 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_(NULL),
+ write_buffer_len_(0),
+ write_bytes_outstanding_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+ net_log_(spdy_stream->net_log()) {
+ request_.method = "CONNECT";
+ request_.url = url;
+ if (!user_agent.empty())
+ request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ user_agent);
+ spdy_stream_->SetDelegate(this);
+ was_ever_used_ = spdy_stream_->WasEverUsed();
+}
+
+SpdyProxyClientSocket::~SpdyProxyClientSocket() {
+ Disconnect();
+}
+
+const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const {
+ return response_.headers ? &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() {
+ DCHECK(response_stream_.get());
+ return response_stream_.release();
+}
+
+// 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_.clear();
+ user_buffer_ = NULL;
+ read_callback_.Reset();
+
+ write_buffer_len_ = 0;
+ write_bytes_outstanding_ = 0;
+ write_callback_.Reset();
+
+ next_state_ = STATE_DISCONNECTED;
+
+ if (spdy_stream_)
+ // This will cause OnClose to be invoked, which takes care of
+ // cleaning up all the internal state.
+ spdy_stream_->Cancel();
+}
+
+bool SpdyProxyClientSocket::IsConnected() const {
+ return next_state_ == STATE_OPEN;
+}
+
+bool SpdyProxyClientSocket::IsConnectedAndIdle() const {
+ return IsConnected() && read_buffer_.empty() && spdy_stream_->is_idle();
+}
+
+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_ && spdy_stream_->WasEverUsed());
+}
+
+bool SpdyProxyClientSocket::UsingTCPFastOpen() const {
+ return false;
+}
+
+int64 SpdyProxyClientSocket::NumBytesRead() const {
+ return -1;
+}
+
+base::TimeDelta SpdyProxyClientSocket::GetConnectTimeMicros() const {
+ return base::TimeDelta::FromMicroseconds(-1);
+}
+
+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_);
+
+ if (next_state_ == STATE_DISCONNECTED)
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ if (next_state_ == STATE_CLOSED && read_buffer_.empty()) {
+ return 0;
+ }
+
+ DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED);
+ DCHECK(buf);
+ user_buffer_ = new DrainableIOBuffer(buf, buf_len);
+ int result = PopulateUserReadBuffer();
+ if (result == 0) {
+ DCHECK(!callback.is_null());
+ read_callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+ user_buffer_ = NULL;
+ return result;
+}
+
+int SpdyProxyClientSocket::PopulateUserReadBuffer() {
+ if (!user_buffer_)
+ return ERR_IO_PENDING;
+
+ int bytes_read = 0;
+ while (!read_buffer_.empty() && user_buffer_->BytesRemaining() > 0) {
+ scoped_refptr<DrainableIOBuffer> data = read_buffer_.front();
+ const int bytes_to_copy = std::min(user_buffer_->BytesRemaining(),
+ data->BytesRemaining());
+ memcpy(user_buffer_->data(), data->data(), bytes_to_copy);
+ user_buffer_->DidConsume(bytes_to_copy);
+ bytes_read += bytes_to_copy;
+ if (data->BytesRemaining() == bytes_to_copy) {
+ // Consumed all data from this buffer
+ read_buffer_.pop_front();
+ } else {
+ data->DidConsume(bytes_to_copy);
+ }
+ }
+
+ if (bytes_read > 0 && spdy_stream_)
+ spdy_stream_->IncreaseRecvWindowSize(bytes_read);
+
+ return user_buffer_->BytesConsumed();
+}
+
+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_);
+ write_bytes_outstanding_= buf_len;
+ if (buf_len <= kMaxSpdyFrameChunkSize) {
+ int rv = spdy_stream_->WriteStreamData(buf, buf_len, DATA_FLAG_NONE);
+ if (rv == ERR_IO_PENDING) {
+ write_callback_ = callback;
+ write_buffer_len_ = buf_len;
+ }
+ return rv;
+ }
+
+ // Since a SPDY Data frame can only include kMaxSpdyFrameChunkSize bytes
+ // we need to send multiple data frames
+ for (int i = 0; i < buf_len; i += kMaxSpdyFrameChunkSize) {
+ int len = std::min(kMaxSpdyFrameChunkSize, buf_len - i);
+ scoped_refptr<DrainableIOBuffer> iobuf(new DrainableIOBuffer(buf, i + len));
+ iobuf->SetOffset(i);
+ int rv = spdy_stream_->WriteStreamData(iobuf, len, DATA_FLAG_NONE);
+ if (rv > 0) {
+ write_bytes_outstanding_ -= rv;
+ } else if (rv != ERR_IO_PENDING) {
+ return rv;
+ }
+ }
+ if (write_bytes_outstanding_ > 0) {
+ write_callback_ = callback;
+ write_buffer_len_ = buf_len;
+ return ERR_IO_PENDING;
+ } else {
+ return buf_len;
+ }
+}
+
+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);
+ 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, base::Unretained(this)),
+ 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");
+ }
+ spdy_stream_->set_spdy_headers(headers.Pass());
+
+ return spdy_stream_->SendRequest(true);
+}
+
+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)) {
+ // Immediately hand off our SpdyStream to a newly created
+ // SpdyHttpStream so that any subsequent SpdyFrames are processed in
+ // the context of the HttpStream, not the socket.
+ DCHECK(spdy_stream_);
+ SpdyStream* stream = spdy_stream_;
+ spdy_stream_ = NULL;
+ response_stream_.reset(new SpdyHttpStream(NULL, false));
+ response_stream_->InitializeWithExistingStream(stream);
+ 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_, &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.
+bool SpdyProxyClientSocket::OnSendHeadersComplete(int status) {
+ DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE);
+
+ OnIOComplete(status);
+
+ // We return true here so that we send |spdy_stream_| into
+ // STATE_OPEN (ala WebSockets).
+ return true;
+}
+
+int SpdyProxyClientSocket::OnSendBody() {
+ // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
+ // OnSendBody() should never be called.
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+}
+
+int SpdyProxyClientSocket::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
+ // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
+ // OnSendBodyComplete() should never be called.
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+}
+
+int SpdyProxyClientSocket::OnResponseReceived(
+ const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) {
+ // 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 OK;
+
+ // Save the response
+ if (!SpdyHeadersToHttpResponse(
+ response, spdy_stream_->GetProtocolVersion(), &response_))
+ return ERR_INCOMPLETE_SPDY_HEADERS;
+
+ OnIOComplete(status);
+ return OK;
+}
+
+void SpdyProxyClientSocket::OnHeadersSent() {
+ // Proxy client sockets don't send any HEADERS frame.
+ NOTREACHED();
+}
+
+// Called when data is received.
+int SpdyProxyClientSocket::OnDataReceived(const char* data, int length) {
+ if (length > 0) {
+ // Save the received data.
+ scoped_refptr<IOBuffer> io_buffer(new IOBuffer(length));
+ memcpy(io_buffer->data(), data, length);
+ read_buffer_.push_back(
+ make_scoped_refptr(new DrainableIOBuffer(io_buffer, length)));
+ }
+
+ if (!read_callback_.is_null()) {
+ int rv = PopulateUserReadBuffer();
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ user_buffer_ = NULL;
+ c.Run(rv);
+ }
+ return OK;
+}
+
+void SpdyProxyClientSocket::OnDataSent(int length) {
+ DCHECK(!write_callback_.is_null());
+
+ write_bytes_outstanding_ -= length;
+
+ DCHECK_GE(write_bytes_outstanding_, 0);
+
+ if (write_bytes_outstanding_ == 0) {
+ int rv = write_buffer_len_;
+ write_buffer_len_ = 0;
+ write_bytes_outstanding_ = 0;
+ CompletionCallback c = write_callback_;
+ write_callback_.Reset();
+ c.Run(rv);
+ }
+}
+
+void SpdyProxyClientSocket::OnClose(int status) {
+ DCHECK(spdy_stream_);
+ was_ever_used_ = spdy_stream_->WasEverUsed();
+ spdy_stream_ = NULL;
+
+ 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;
+ write_bytes_outstanding_ = 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(NULL, 0);
+ }
+ // This may have been deleted by read_callback_, so check first.
+ if (weak_ptr && !write_callback.is_null())
+ write_callback.Run(ERR_CONNECTION_CLOSED);
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_proxy_client_socket.h b/src/net/spdy/spdy_proxy_client_socket.h
new file mode 100644
index 0000000..a5da281
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket.h
@@ -0,0 +1,177 @@
+// 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 <string>
+#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/host_port_pair.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_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.
+ SpdyProxyClientSocket(SpdyStream* spdy_stream,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ const GURL& url,
+ const HostPortPair& proxy_server,
+ 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 int64 NumBytesRead() const OVERRIDE;
+ virtual base::TimeDelta GetConnectTimeMicros() 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 bool OnSendHeadersComplete(int status) OVERRIDE;
+ virtual int OnSendBody() OVERRIDE;
+ virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE;
+ virtual void OnHeadersSent() OVERRIDE;
+ virtual int OnDataReceived(const char* data, int length) OVERRIDE;
+ virtual void OnDataSent(int length) 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.
+ int PopulateUserReadBuffer();
+
+ State next_state_;
+
+ // Pointer to the SPDY Stream that this sits on top of.
+ scoped_refptr<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.
+ std::list<scoped_refptr<DrainableIOBuffer> > read_buffer_;
+
+ // User provided buffer for the Read() response.
+ scoped_refptr<DrainableIOBuffer> user_buffer_;
+
+ // User specified number of bytes to be written.
+ int write_buffer_len_;
+ // Number of bytes written which have not been confirmed
+ int write_bytes_outstanding_;
+
+ // True if the transport socket has ever sent data.
+ bool was_ever_used_;
+
+ scoped_ptr<SpdyHttpStream> response_stream_;
+
+ 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/src/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc b/src/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc
new file mode 100644
index 0000000..5216e4a
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc
@@ -0,0 +1,1357 @@
+// 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/utf_string_conversions.h"
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/tcp_client_socket.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_pool.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/platform_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+//-----------------------------------------------------------------------------
+
+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 SpdyProxyClientSocketSpdy2Test : public PlatformTest {
+ public:
+ SpdyProxyClientSocketSpdy2Test();
+
+ 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 string16 kFoo(ASCIIToUTF16("foo"));
+ const 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();
+ }
+
+ scoped_ptr<SpdyProxyClientSocket> sock_;
+ TestCompletionCallback read_callback_;
+ TestCompletionCallback write_callback_;
+ scoped_ptr<DeterministicSocketData> data_;
+
+ private:
+ scoped_refptr<HttpNetworkSession> session_;
+ scoped_refptr<IOBuffer> read_buf_;
+ SpdySessionDependencies session_deps_;
+ MockConnect connect_data_;
+ scoped_refptr<SpdySession> spdy_session_;
+ scoped_refptr<SpdyStream> spdy_stream_;
+ BufferedSpdyFramer framer_;
+
+ std::string user_agent_;
+ GURL url_;
+ HostPortPair proxy_host_port_;
+ HostPortPair endpoint_host_port_pair_;
+ ProxyServer proxy_;
+ HostPortProxyPair endpoint_host_port_proxy_pair_;
+ scoped_refptr<TransportSocketParams> transport_params_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketSpdy2Test);
+};
+
+SpdyProxyClientSocketSpdy2Test::SpdyProxyClientSocketSpdy2Test()
+ : sock_(NULL),
+ data_(NULL),
+ session_(NULL),
+ read_buf_(NULL),
+ session_deps_(),
+ connect_data_(SYNCHRONOUS, OK),
+ spdy_session_(NULL),
+ spdy_stream_(NULL),
+ framer_(2, 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_host_port_proxy_pair_(endpoint_host_port_pair_, proxy_),
+ transport_params_(new TransportSocketParams(proxy_host_port_,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback())) {
+}
+
+void SpdyProxyClientSocketSpdy2Test::TearDown() {
+ sock_.reset(NULL);
+ if (session_ != NULL)
+ session_->spdy_session_pool()->CloseAllSessions();
+
+ // Empty the current queue.
+ MessageLoop::current()->RunUntilIdle();
+ PlatformTest::TearDown();
+}
+
+void SpdyProxyClientSocketSpdy2Test::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 a new spdy session
+ spdy_session_ =
+ session_->spdy_session_pool()->Get(endpoint_host_port_proxy_pair_,
+ BoundNetLog());
+
+ // Perform the TCP connect
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK,
+ connection->Init(endpoint_host_port_pair_.ToString(),
+ transport_params_, LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ spdy_session_->InitializeWithSocket(connection.release(), false, OK);
+
+ // Create the SPDY Stream
+ ASSERT_EQ(
+ OK,
+ spdy_session_->CreateStream(url_, LOWEST, &spdy_stream_, BoundNetLog(),
+ CompletionCallback()));
+
+ // Create the SpdyProxyClientSocket
+ sock_.reset(
+ new SpdyProxyClientSocket(spdy_stream_, user_agent_,
+ endpoint_host_port_pair_, url_,
+ proxy_host_port_, session_->http_auth_cache(),
+ session_->http_auth_handler_factory()));
+}
+
+scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketSpdy2Test::CreateBuffer(
+ const char* data, int size) {
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size));
+ memcpy(buf->data(), data, size);
+ return buf;
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertConnectSucceeds() {
+ ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+ data_->Run();
+ ASSERT_EQ(OK, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertConnectFails(int result) {
+ ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+ data_->Run();
+ ASSERT_EQ(result, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy2Test::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 SpdyProxyClientSocketSpdy2Test::AssertSyncReadEquals(const char* data,
+ int len) {
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ ASSERT_EQ(len, sock_->Read(buf, len, CompletionCallback()));
+ ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+ ASSERT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy2Test::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, 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 SpdyProxyClientSocketSpdy2Test::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_, len, read_callback_.callback()));
+ EXPECT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy2Test::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 SpdyProxyClientSocketSpdy2Test::AssertAsyncWriteSucceeds(const char* data,
+ int len) {
+ AssertWriteReturns(data, len, ERR_IO_PENDING);
+ data_->RunFor(1);
+ AssertWriteLength(len);
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertWriteReturns(const char* data,
+ int len,
+ int rv) {
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+ EXPECT_EQ(rv, sock_->Write(buf, buf->size(), write_callback_.callback()));
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertWriteLength(int len) {
+ EXPECT_EQ(len, write_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertAsyncWriteWithReadsSucceeds(
+ const char* data, int len, int num_reads) {
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+
+ EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, 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*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectRequestFrame() {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ kStreamId,
+ 0,
+ net::ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ const char* const kConnectHeaders[] = {
+ "method", "CONNECT",
+ "url", kOriginHostPort,
+ "host", kOriginHost,
+ "user-agent", kUserAgent,
+ "version", "HTTP/1.1",
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes
+// Proxy-Authorization headers.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectAuthRequestFrame() {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ kStreamId,
+ 0,
+ net::ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ const char* const kConnectHeaders[] = {
+ "method", "CONNECT",
+ "url", kOriginHostPort,
+ "host", kOriginHost,
+ "user-agent", kUserAgent,
+ "version", "HTTP/1.1",
+ "proxy-authorization", "Basic Zm9vOmJhcg==",
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame* SpdyProxyClientSocketSpdy2Test::ConstructConnectReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ "status", "200 Connection Established",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectAuthReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ "status", "407 Proxy Authentication Required",
+ "version", "HTTP/1.1",
+ "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+ };
+
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 302 redirect.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectRedirectReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ "status", "302 Found",
+ "version", "HTTP/1.1",
+ "location", kRedirectUrl,
+ "set-cookie", "foo=bar"
+ };
+
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectErrorReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ "status", "500 Internal Server Error",
+ "version", "HTTP/1.1",
+ };
+
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+SpdyFrame* SpdyProxyClientSocketSpdy2Test::ConstructBodyFrame(
+ const char* data, int length) {
+ return framer_.CreateDataFrame(kStreamId, data, length, DATA_FLAG_NONE);
+}
+
+// ----------- Connect
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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;
+ 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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, buf->size(),
+ write_callback_.callback()));
+ data_->RunFor(3);
+
+ EXPECT_EQ(buf->size(), write_callback_.WaitForResult());
+}
+
+// ----------- Read
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test,
+ 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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, kLen33, read_callback_.callback()));
+ ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3));
+ ASSERT_TRUE(sock_->IsConnected());
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test, 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_F(SpdyProxyClientSocketSpdy2Test,
+ 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_F(SpdyProxyClientSocketSpdy2Test, 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, 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_F(SpdyProxyClientSocketSpdy2Test, 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, buf->size(), CompletionCallback()));
+}
+
+// Calling Write() on a disconnected socket is an error
+TEST_F(SpdyProxyClientSocketSpdy2Test, 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, buf->size(), CompletionCallback()));
+}
+
+// If the socket is closed with a pending Write(), the callback
+// should be called with ERR_CONNECTION_CLOSED.
+TEST_F(SpdyProxyClientSocketSpdy2Test, WritePendingOnClose) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_IO_PENDING, 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, buf->size(), write_callback_.callback()));
+
+ Run(1);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult());
+}
+
+// If the socket is Disconnected with a pending Write(), the callback
+// should not be called.
+TEST_F(SpdyProxyClientSocketSpdy2Test, 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, 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_F(SpdyProxyClientSocketSpdy2Test, 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, 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_F(SpdyProxyClientSocketSpdy2Test, RstWithReadAndWritePending) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_IO_PENDING, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*rst, 3, ASYNC),
+ };
+
+ 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, kLen1, read_callback_.callback()));
+
+ scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_IO_PENDING,
+ sock_->Write(write_buf, write_buf->size(),
+ write_callback_.callback()));
+
+ Run(2);
+
+ EXPECT_TRUE(sock_.get());
+ EXPECT_TRUE(read_callback_.have_result());
+ EXPECT_TRUE(write_callback_.have_result());
+}
+
+// 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),
+ ALLOW_THIS_IN_INITIALIZER_LIST(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_F(SpdyProxyClientSocketSpdy2Test, RstWithReadAndWritePendingDelete) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_IO_PENDING, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*rst, 3, ASYNC),
+ };
+
+ 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, kLen1, read_callback.callback()));
+
+ scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf, write_buf->size(),
+ write_callback_.callback()));
+
+ Run(2);
+
+ EXPECT_FALSE(sock_.get());
+ EXPECT_TRUE(read_callback.have_result());
+ EXPECT_FALSE(write_callback_.have_result());
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc b/src/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc
new file mode 100644
index 0000000..627568f
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc
@@ -0,0 +1,1360 @@
+// 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/utf_string_conversions.h"
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/tcp_client_socket.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_pool.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+//-----------------------------------------------------------------------------
+
+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 SpdyProxyClientSocketSpdy3Test : public PlatformTest {
+ public:
+ SpdyProxyClientSocketSpdy3Test();
+
+ 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 string16 kFoo(ASCIIToUTF16("foo"));
+ const 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();
+ }
+
+ scoped_ptr<SpdyProxyClientSocket> sock_;
+ TestCompletionCallback read_callback_;
+ TestCompletionCallback write_callback_;
+ scoped_ptr<DeterministicSocketData> data_;
+
+ private:
+ scoped_refptr<HttpNetworkSession> session_;
+ scoped_refptr<IOBuffer> read_buf_;
+ SpdySessionDependencies session_deps_;
+ MockConnect connect_data_;
+ scoped_refptr<SpdySession> spdy_session_;
+ scoped_refptr<SpdyStream> spdy_stream_;
+ BufferedSpdyFramer framer_;
+
+ std::string user_agent_;
+ GURL url_;
+ HostPortPair proxy_host_port_;
+ HostPortPair endpoint_host_port_pair_;
+ ProxyServer proxy_;
+ HostPortProxyPair endpoint_host_port_proxy_pair_;
+ scoped_refptr<TransportSocketParams> transport_params_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketSpdy3Test);
+};
+
+SpdyProxyClientSocketSpdy3Test::SpdyProxyClientSocketSpdy3Test()
+ : sock_(NULL),
+ data_(NULL),
+ session_(NULL),
+ read_buf_(NULL),
+ session_deps_(),
+ connect_data_(SYNCHRONOUS, OK),
+ spdy_session_(NULL),
+ spdy_stream_(NULL),
+ framer_(3, 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_host_port_proxy_pair_(endpoint_host_port_pair_, proxy_),
+ transport_params_(new TransportSocketParams(proxy_host_port_,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback())) {
+}
+
+void SpdyProxyClientSocketSpdy3Test::TearDown() {
+ sock_.reset(NULL);
+ if (session_ != NULL)
+ session_->spdy_session_pool()->CloseAllSessions();
+
+ // Empty the current queue.
+ MessageLoop::current()->RunUntilIdle();
+ PlatformTest::TearDown();
+}
+
+void SpdyProxyClientSocketSpdy3Test::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 a new spdy session
+ spdy_session_ =
+ session_->spdy_session_pool()->Get(endpoint_host_port_proxy_pair_,
+ BoundNetLog());
+
+ // Perform the TCP connect
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK,
+ connection->Init(endpoint_host_port_pair_.ToString(),
+ transport_params_, LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ spdy_session_->InitializeWithSocket(connection.release(), false, OK);
+
+ // Create the SPDY Stream
+ ASSERT_EQ(
+ OK,
+ spdy_session_->CreateStream(url_, LOWEST, &spdy_stream_, BoundNetLog(),
+ CompletionCallback()));
+
+ // Create the SpdyProxyClientSocket
+ sock_.reset(
+ new SpdyProxyClientSocket(spdy_stream_, user_agent_,
+ endpoint_host_port_pair_, url_,
+ proxy_host_port_, session_->http_auth_cache(),
+ session_->http_auth_handler_factory()));
+}
+
+scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketSpdy3Test::CreateBuffer(
+ const char* data, int size) {
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size));
+ memcpy(buf->data(), data, size);
+ return buf;
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertConnectSucceeds() {
+ ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+ data_->Run();
+ ASSERT_EQ(OK, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertConnectFails(int result) {
+ ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+ data_->Run();
+ ASSERT_EQ(result, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy3Test::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 SpdyProxyClientSocketSpdy3Test::AssertSyncReadEquals(const char* data,
+ int len) {
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ ASSERT_EQ(len, sock_->Read(buf, len, CompletionCallback()));
+ ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+ ASSERT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy3Test::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, 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 SpdyProxyClientSocketSpdy3Test::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_, len, read_callback_.callback()));
+ EXPECT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy3Test::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 SpdyProxyClientSocketSpdy3Test::AssertAsyncWriteSucceeds(const char* data,
+ int len) {
+ AssertWriteReturns(data, len, ERR_IO_PENDING);
+ data_->RunFor(1);
+ AssertWriteLength(len);
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertWriteReturns(const char* data,
+ int len,
+ int rv) {
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+ EXPECT_EQ(rv, sock_->Write(buf, buf->size(), write_callback_.callback()));
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertWriteLength(int len) {
+ EXPECT_EQ(len, write_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertAsyncWriteWithReadsSucceeds(
+ const char* data, int len, int num_reads) {
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+
+ EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, 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*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectRequestFrame() {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ kStreamId,
+ 0,
+ net::ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+ 0,
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ const char* const kConnectHeaders[] = {
+ ":method", "CONNECT",
+ ":path", kOriginHostPort,
+ ":host", kOriginHost,
+ "user-agent", kUserAgent,
+ ":version", "HTTP/1.1",
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes
+// Proxy-Authorization headers.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectAuthRequestFrame() {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ kStreamId,
+ 0,
+ net::ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+ 0,
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ const char* const kConnectHeaders[] = {
+ ":method", "CONNECT",
+ ":path", kOriginHostPort,
+ ":host", kOriginHost,
+ "user-agent", kUserAgent,
+ ":version", "HTTP/1.1",
+ "proxy-authorization", "Basic Zm9vOmJhcg==",
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame* SpdyProxyClientSocketSpdy3Test::ConstructConnectReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ ":status", "200 Connection Established",
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectAuthReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ ":status", "407 Proxy Authentication Required",
+ ":version", "HTTP/1.1",
+ "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+ };
+
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 302 redirect.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectRedirectReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ ":status", "302 Found",
+ ":version", "HTTP/1.1",
+ "location", kRedirectUrl,
+ "set-cookie", "foo=bar"
+ };
+
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectErrorReplyFrame() {
+ const char* const kStandardReplyHeaders[] = {
+ ":status", "500 Internal Server Error",
+ ":version", "HTTP/1.1",
+ };
+
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders));
+}
+
+SpdyFrame* SpdyProxyClientSocketSpdy3Test::ConstructBodyFrame(
+ const char* data,
+ int length) {
+ return framer_.CreateDataFrame(kStreamId, data, length, DATA_FLAG_NONE);
+}
+
+// ----------- Connect
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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;
+ 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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, buf->size(),
+ write_callback_.callback()));
+ data_->RunFor(3);
+
+ EXPECT_EQ(buf->size(), write_callback_.WaitForResult());
+}
+
+// ----------- Read
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test,
+ 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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, kLen33, read_callback_.callback()));
+ ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3));
+ ASSERT_TRUE(sock_->IsConnected());
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test, 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_F(SpdyProxyClientSocketSpdy3Test,
+ 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_F(SpdyProxyClientSocketSpdy3Test, 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, 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_F(SpdyProxyClientSocketSpdy3Test, 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, buf->size(), CompletionCallback()));
+}
+
+// Calling Write() on a disconnected socket is an error
+TEST_F(SpdyProxyClientSocketSpdy3Test, 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, buf->size(), CompletionCallback()));
+}
+
+// If the socket is closed with a pending Write(), the callback
+// should be called with ERR_CONNECTION_CLOSED.
+TEST_F(SpdyProxyClientSocketSpdy3Test, WritePendingOnClose) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_IO_PENDING, 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, buf->size(), write_callback_.callback()));
+
+ Run(1);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult());
+}
+
+// If the socket is Disconnected with a pending Write(), the callback
+// should not be called.
+TEST_F(SpdyProxyClientSocketSpdy3Test, 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, 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_F(SpdyProxyClientSocketSpdy3Test, 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, 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_F(SpdyProxyClientSocketSpdy3Test, RstWithReadAndWritePending) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_IO_PENDING, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*rst, 3, ASYNC),
+ };
+
+ 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, kLen1, read_callback_.callback()));
+
+ scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_IO_PENDING,
+ sock_->Write(write_buf, write_buf->size(),
+ write_callback_.callback()));
+
+ Run(2);
+
+ EXPECT_TRUE(sock_.get());
+ EXPECT_TRUE(read_callback_.have_result());
+ EXPECT_TRUE(write_callback_.have_result());
+}
+
+// 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),
+ ALLOW_THIS_IN_INITIALIZER_LIST(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_F(SpdyProxyClientSocketSpdy3Test, RstWithReadAndWritePendingDelete) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_IO_PENDING, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*rst, 3, ASYNC),
+ };
+
+ 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, kLen1, read_callback.callback()));
+
+ scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf, write_buf->size(),
+ write_callback_.callback()));
+
+ Run(2);
+
+ EXPECT_FALSE(sock_.get());
+ EXPECT_TRUE(read_callback.have_result());
+ EXPECT_FALSE(write_callback_.have_result());
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_session.cc b/src/net/spdy/spdy_session.cc
new file mode 100644
index 0000000..9982a59
--- /dev/null
+++ b/src/net/spdy/spdy_session.cc
@@ -0,0 +1,1976 @@
+// 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 <map>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.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"
+
+namespace net {
+
+namespace {
+
+const int kReadBufferSize = 8 * 1024;
+const int kDefaultConnectionAtRiskOfLossSeconds = 10;
+const int kHungIntervalSeconds = 10;
+
+// Minimum seconds that unclaimed pushed streams will be kept in memory.
+const int kMinPushedStreamLifetimeSeconds = 300;
+
+Value* NetLogSpdySynCallback(const SpdyHeaderBlock* headers,
+ bool fin,
+ bool unidirectional,
+ SpdyStreamId stream_id,
+ SpdyStreamId associated_stream,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ ListValue* headers_list = new ListValue();
+ for (SpdyHeaderBlock::const_iterator it = headers->begin();
+ it != headers->end(); ++it) {
+ headers_list->Append(new StringValue(base::StringPrintf(
+ "%s: %s", it->first.c_str(), it->second.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;
+}
+
+Value* NetLogSpdyCredentialCallback(size_t slot,
+ const std::string* origin,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("slot", slot);
+ dict->SetString("origin", *origin);
+ return dict;
+}
+
+Value* NetLogSpdySessionCloseCallback(int net_error,
+ const std::string* description,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("net_error", net_error);
+ dict->SetString("description", *description);
+ return dict;
+}
+
+Value* NetLogSpdySessionCallback(const HostPortProxyPair* host_pair,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString("host", host_pair->first.ToString());
+ dict->SetString("proxy", host_pair->second.ToPacString());
+ return dict;
+}
+
+Value* NetLogSpdySettingCallback(SpdySettingsIds id,
+ SpdySettingsFlags flags,
+ uint32 value,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("id", id);
+ dict->SetInteger("flags", flags);
+ dict->SetInteger("value", value);
+ return dict;
+}
+
+Value* NetLogSpdySettingsCallback(const SettingsMap* settings,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ ListValue* settings_list = new 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 StringValue(
+ base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value)));
+ }
+ dict->Set("settings", settings_list);
+ return dict;
+}
+
+Value* NetLogSpdyWindowUpdateCallback(SpdyStreamId stream_id,
+ int32 delta,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("delta", delta);
+ return dict;
+}
+
+Value* NetLogSpdyDataCallback(SpdyStreamId stream_id,
+ int size,
+ SpdyDataFlags flags,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("size", size);
+ dict->SetInteger("flags", static_cast<int>(flags));
+ return dict;
+}
+
+Value* NetLogSpdyRstCallback(SpdyStreamId stream_id,
+ int status,
+ const std::string* description,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("status", status);
+ dict->SetString("description", *description);
+ return dict;
+}
+
+Value* NetLogSpdyPingCallback(uint32 unique_id,
+ const char* type,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("unique_id", unique_id);
+ dict->SetString("type", type);
+ return dict;
+}
+
+Value* NetLogSpdyGoAwayCallback(SpdyStreamId last_stream_id,
+ int active_streams,
+ int unclaimed_streams,
+ SpdyGoAwayStatus status,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new 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;
+}
+
+// Maximum number of concurrent streams we will create, unless the server
+// sends a SETTINGS frame with a different value.
+const size_t kInitialMaxConcurrentStreams = 100;
+// 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;
+const size_t kDefaultInitialRecvWindowSize = 10 * 1024 * 1024; // 10MB
+
+} // namespace
+
+// static
+void SpdySession::SpdyIOBufferProducer::ActivateStream(
+ SpdySession* spdy_session,
+ SpdyStream* spdy_stream) {
+ spdy_session->ActivateStream(spdy_stream);
+}
+
+// static
+SpdyIOBuffer* SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
+ SpdyFrame* frame,
+ RequestPriority priority,
+ SpdyStream* stream) {
+ size_t size = frame->length() + SpdyFrame::kHeaderSize;
+ DCHECK_GT(size, 0u);
+
+ // TODO(mbelshe): We have too much copying of data here.
+ IOBufferWithSize* buffer = new IOBufferWithSize(size);
+ memcpy(buffer->data(), frame->data(), size);
+
+ return new SpdyIOBuffer(buffer, size, priority, stream);
+}
+
+SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
+ SpdySessionPool* spdy_session_pool,
+ HttpServerProperties* http_server_properties,
+ bool verify_domain_authentication,
+ bool enable_sending_initial_settings,
+ bool enable_credential_frames,
+ bool enable_compression,
+ bool enable_ping_based_connection_checking,
+ NextProto default_protocol,
+ size_t 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)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+ host_port_proxy_pair_(host_port_proxy_pair),
+ spdy_session_pool_(spdy_session_pool),
+ http_server_properties_(http_server_properties),
+ connection_(new ClientSocketHandle),
+ read_buffer_(new IOBuffer(kReadBufferSize)),
+ read_pending_(false),
+ stream_hi_water_mark_(1), // Always start at 1 for the first stream id.
+ write_pending_(false),
+ delayed_write_pending_(false),
+ is_secure_(false),
+ certificate_error_code_(OK),
+ error_(OK),
+ state_(IDLE),
+ 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),
+ bytes_received_(0),
+ sent_settings_(false),
+ received_settings_(false),
+ stalled_streams_(0),
+ pings_in_flight_(0),
+ next_ping_id_(1),
+ last_activity_time_(base::TimeTicks::Now()),
+ check_ping_status_pending_(false),
+ flow_control_(false),
+ initial_send_window_size_(kSpdyStreamInitialWindowSize),
+ initial_recv_window_size_(initial_recv_window_size == 0 ?
+ kDefaultInitialRecvWindowSize :
+ initial_recv_window_size),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)),
+ verify_domain_authentication_(verify_domain_authentication),
+ enable_sending_initial_settings_(enable_sending_initial_settings),
+ enable_credential_frames_(enable_credential_frames),
+ enable_compression_(enable_compression),
+ enable_ping_based_connection_checking_(
+ enable_ping_based_connection_checking),
+ default_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) {
+ 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::PendingCreateStream::PendingCreateStream(
+ const GURL& url, RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback)
+ : url(&url),
+ priority(priority),
+ spdy_stream(spdy_stream),
+ stream_net_log(&stream_net_log),
+ callback(callback) {
+}
+
+SpdySession::PendingCreateStream::~PendingCreateStream() {}
+
+SpdySession::CallbackResultPair::CallbackResultPair(
+ const CompletionCallback& callback_in, int result_in)
+ : callback(callback_in),
+ result(result_in) {
+}
+
+SpdySession::CallbackResultPair::~CallbackResultPair() {}
+
+SpdySession::~SpdySession() {
+ if (state_ != CLOSED) {
+ state_ = CLOSED;
+
+ // Cleanup all the streams.
+ CloseAllStreams(net::ERR_ABORTED);
+ }
+
+ if (connection_->is_initialized()) {
+ // With SPDY we can't recycle sockets.
+ connection_->socket()->Disconnect();
+ }
+
+ // Streams should all be gone now.
+ DCHECK_EQ(0u, num_active_streams());
+ DCHECK_EQ(0u, num_unclaimed_pushed_streams());
+
+ DCHECK(pending_callback_map_.empty());
+
+ RecordHistograms();
+
+ net_log_.EndEvent(NetLog::TYPE_SPDY_SESSION);
+}
+
+net::Error SpdySession::InitializeWithSocket(
+ ClientSocketHandle* connection,
+ bool is_secure,
+ int certificate_error_code) {
+ base::StatsCounter spdy_sessions("spdy.sessions");
+ spdy_sessions.Increment();
+
+ state_ = CONNECTED;
+ connection_.reset(connection);
+ is_secure_ = is_secure;
+ certificate_error_code_ = certificate_error_code;
+
+ NextProto protocol = default_protocol_;
+ NextProto protocol_negotiated = connection->socket()->GetNegotiatedProtocol();
+ if (protocol_negotiated != kProtoUnknown) {
+ protocol = protocol_negotiated;
+ }
+
+ 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()));
+ }
+
+ DCHECK(protocol >= kProtoSPDY2);
+ DCHECK(protocol <= kProtoSPDY3);
+ int version = (protocol == kProtoSPDY3) ? 3 : 2;
+ flow_control_ = (protocol >= kProtoSPDY3);
+
+ buffered_spdy_framer_.reset(new BufferedSpdyFramer(version,
+ enable_compression_));
+ buffered_spdy_framer_->set_visitor(this);
+ SendInitialSettings();
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol, kProtoMaximumVersion);
+
+ // Write out any data that we might have to send, such as the settings frame.
+ WriteSocketLater();
+ net::Error error = ReadSocket();
+ if (error == ERR_IO_PENDING)
+ return OK;
+ return error;
+}
+
+bool SpdySession::VerifyDomainAuthentication(const std::string& domain) {
+ if (!verify_domain_authentication_)
+ return true;
+
+ if (state_ != CONNECTED)
+ 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_proxy_pair_.first.host())) &&
+ ssl_info.cert->VerifyNameMatch(domain);
+}
+
+void SpdySession::SetStreamHasWriteAvailable(SpdyStream* stream,
+ SpdyIOBufferProducer* producer) {
+ write_queue_.push(producer);
+ stream_producers_[producer] = stream;
+ WriteSocketLater();
+}
+
+int SpdySession::GetPushStream(
+ const GURL& url,
+ scoped_refptr<SpdyStream>* stream,
+ const BoundNetLog& stream_net_log) {
+ CHECK_NE(state_, CLOSED);
+
+ *stream = NULL;
+
+ // Don't allow access to secure push streams over an unauthenticated, but
+ // encrypted SSL socket.
+ if (is_secure_ && certificate_error_code_ != OK &&
+ (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+ RecordProtocolErrorHistogram(
+ PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION);
+ CloseSessionOnError(
+ static_cast<net::Error>(certificate_error_code_),
+ true,
+ "Tried to get SPDY stream for secure content over an unauthenticated "
+ "session.");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ *stream = GetActivePushStream(url.spec());
+ if (stream->get()) {
+ DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_);
+ streams_pushed_and_claimed_count_++;
+ return OK;
+ }
+ return 0;
+}
+
+int SpdySession::CreateStream(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback) {
+ if (!max_concurrent_streams_ ||
+ (active_streams_.size() + created_streams_.size() <
+ max_concurrent_streams_)) {
+ return CreateStreamImpl(url, priority, spdy_stream, stream_net_log);
+ }
+
+ stalled_streams_++;
+ net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_STALLED_MAX_STREAMS);
+ pending_create_stream_queues_[priority].push(
+ PendingCreateStream(url, priority, spdy_stream,
+ stream_net_log, callback));
+ return ERR_IO_PENDING;
+}
+
+void SpdySession::ProcessPendingCreateStreams() {
+ while (!max_concurrent_streams_ ||
+ (active_streams_.size() + created_streams_.size() <
+ max_concurrent_streams_)) {
+ bool no_pending_create_streams = true;
+ for (int i = NUM_PRIORITIES - 1; i >= MINIMUM_PRIORITY; --i) {
+ if (!pending_create_stream_queues_[i].empty()) {
+ PendingCreateStream pending_create =
+ pending_create_stream_queues_[i].front();
+ pending_create_stream_queues_[i].pop();
+ no_pending_create_streams = false;
+ int error = CreateStreamImpl(*pending_create.url,
+ pending_create.priority,
+ pending_create.spdy_stream,
+ *pending_create.stream_net_log);
+ scoped_refptr<SpdyStream>* stream = pending_create.spdy_stream;
+ DCHECK(!ContainsKey(pending_callback_map_, stream));
+ pending_callback_map_.insert(std::make_pair(stream,
+ CallbackResultPair(pending_create.callback, error)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::InvokeUserStreamCreationCallback,
+ weak_factory_.GetWeakPtr(), stream));
+ break;
+ }
+ }
+ if (no_pending_create_streams)
+ return; // there were no streams in any queue
+ }
+}
+
+void SpdySession::CancelPendingCreateStreams(
+ const scoped_refptr<SpdyStream>* spdy_stream) {
+ PendingCallbackMap::iterator it = pending_callback_map_.find(spdy_stream);
+ if (it != pending_callback_map_.end()) {
+ pending_callback_map_.erase(it);
+ return;
+ }
+
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ PendingCreateStreamQueue tmp;
+ // Make a copy removing this trans
+ while (!pending_create_stream_queues_[i].empty()) {
+ PendingCreateStream pending_create =
+ pending_create_stream_queues_[i].front();
+ pending_create_stream_queues_[i].pop();
+ if (pending_create.spdy_stream != spdy_stream)
+ tmp.push(pending_create);
+ }
+ // Now copy it back
+ while (!tmp.empty()) {
+ pending_create_stream_queues_[i].push(tmp.front());
+ tmp.pop();
+ }
+ }
+}
+
+int SpdySession::CreateStreamImpl(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log) {
+ DCHECK_GE(priority, MINIMUM_PRIORITY);
+ DCHECK_LT(priority, NUM_PRIORITIES);
+
+ // Make sure that we don't try to send https/wss over an unauthenticated, but
+ // encrypted SSL socket.
+ if (is_secure_ && certificate_error_code_ != OK &&
+ (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+ RecordProtocolErrorHistogram(
+ PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION);
+ CloseSessionOnError(
+ static_cast<net::Error>(certificate_error_code_),
+ true,
+ "Tried to create SPDY stream for secure content over an "
+ "unauthenticated session.");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ const std::string& path = url.PathForRequest();
+
+ *spdy_stream = new SpdyStream(this,
+ false,
+ stream_net_log);
+ const scoped_refptr<SpdyStream>& stream = *spdy_stream;
+
+ stream->set_priority(priority);
+ stream->set_path(path);
+ stream->set_send_window_size(initial_send_window_size_);
+ stream->set_recv_window_size(initial_recv_window_size_);
+ created_streams_.insert(stream);
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount",
+ static_cast<int>(priority), 0, 10, 11);
+
+ // TODO(mbelshe): Optimize memory allocations
+
+ return OK;
+}
+
+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 HostPortProxyPair& alias) {
+ pooled_aliases_.insert(alias);
+}
+
+int SpdySession::GetProtocolVersion() const {
+ DCHECK(buffered_spdy_framer_.get());
+ return buffered_spdy_framer_->protocol_version();
+}
+
+SpdySynStreamControlFrame* SpdySession::CreateSynStream(
+ SpdyStreamId stream_id,
+ RequestPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ const SpdyHeaderBlock& headers) {
+ CHECK(IsStreamActive(stream_id));
+ const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ SendPrefacePingIfNoneInFlight();
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdySynStreamControlFrame> syn_frame(
+ buffered_spdy_framer_->CreateSynStream(
+ stream_id, 0,
+ ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion()),
+ credential_slot, flags, true, &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.release();
+}
+
+SpdyCredentialControlFrame* SpdySession::CreateCredentialFrame(
+ const std::string& origin,
+ SSLClientCertType type,
+ const std::string& key,
+ const std::string& cert,
+ RequestPriority priority) {
+ 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, type, key, cert, slot,
+ &credential);
+ DCHECK_EQ(OK, rv);
+ if (rv != OK)
+ return NULL;
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyCredentialControlFrame> credential_frame(
+ 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 credential_frame.release();
+}
+
+SpdyHeadersControlFrame* SpdySession::CreateHeadersFrame(
+ SpdyStreamId stream_id,
+ const SpdyHeaderBlock& headers,
+ SpdyControlFlags flags) {
+ // Find our stream
+ CHECK(IsStreamActive(stream_id));
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ // Create a HEADER frame.
+ scoped_ptr<SpdyHeadersControlFrame> frame(
+ buffered_spdy_framer_->CreateHeaders(stream_id, flags, true, &headers));
+
+ if (net_log().IsLoggingAllEvents()) {
+ bool fin = flags & CONTROL_FLAG_FIN;
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_HEADERS,
+ base::Bind(&NetLogSpdySynCallback,
+ &headers, fin, /*unidirectional=*/false,
+ stream_id, 0));
+ }
+ return frame.release();
+}
+
+SpdyDataFrame* SpdySession::CreateDataFrame(SpdyStreamId stream_id,
+ net::IOBuffer* data, int len,
+ SpdyDataFlags flags) {
+ // Find our stream
+ CHECK(IsStreamActive(stream_id));
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ if (len > kMaxSpdyFrameChunkSize) {
+ len = kMaxSpdyFrameChunkSize;
+ flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN);
+ }
+
+ // Obey send window size of the stream if flow control is enabled.
+ if (flow_control_) {
+ if (stream->send_window_size() <= 0) {
+ // Because we queue frames onto the session, it is possible that
+ // a stream was not flow controlled at the time it attempted the
+ // write, but when we go to fulfill the write, it is now flow
+ // controlled. This is why we need the session to mark the stream
+ // as stalled - because only the session knows for sure when the
+ // stall occurs.
+ stream->set_stalled_by_flow_control(true);
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_STALLED_ON_SEND_WINDOW,
+ NetLog::IntegerCallback("stream_id", stream_id));
+ return NULL;
+ }
+ int new_len = std::min(len, stream->send_window_size());
+ if (new_len < len) {
+ len = new_len;
+ flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN);
+ }
+ stream->DecreaseSendWindowSize(len);
+ }
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_DATA,
+ base::Bind(&NetLogSpdyDataCallback, stream_id, len, flags));
+ }
+
+ // Send PrefacePing for DATA_FRAMEs with nonzero payload size.
+ if (len > 0)
+ SendPrefacePingIfNoneInFlight();
+
+ // TODO(mbelshe): reduce memory copies here.
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyDataFrame> frame(
+ buffered_spdy_framer_->CreateDataFrame(
+ stream_id, data->data(), len, flags));
+
+ return frame.release();
+}
+
+void SpdySession::CloseStream(SpdyStreamId stream_id, int status) {
+ DCHECK_NE(0u, stream_id);
+ // TODO(mbelshe): We should send a RST_STREAM control frame here
+ // so that the server can cancel a large send.
+
+ DeleteStream(stream_id, status);
+}
+
+void SpdySession::CloseCreatedStream(SpdyStream* stream, int status) {
+ DCHECK_EQ(0u, stream->stream_id());
+ created_streams_.erase(scoped_refptr<SpdyStream>(stream));
+ ProcessPendingCreateStreams();
+}
+
+void SpdySession::ResetStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status,
+ const std::string& description) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_RST_STREAM,
+ base::Bind(&NetLogSpdyRstCallback, stream_id, status, &description));
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyRstStreamControlFrame> rst_frame(
+ buffered_spdy_framer_->CreateRstStream(stream_id, status));
+
+ // Default to lowest priority unless we know otherwise.
+ RequestPriority priority = net::IDLE;
+ if(IsStreamActive(stream_id)) {
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ priority = stream->priority();
+ }
+ QueueFrame(rst_frame.release(), priority);
+ RecordProtocolErrorHistogram(
+ static_cast<SpdyProtocolErrorDetails>(status + STATUS_CODE_INVALID));
+ DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
+}
+
+bool SpdySession::IsStreamActive(SpdyStreamId stream_id) const {
+ return ContainsKey(active_streams_, stream_id);
+}
+
+LoadState SpdySession::GetLoadState() const {
+ // NOTE: The application only queries the LoadState via the
+ // SpdyNetworkTransaction, and details are only needed when
+ // we're in the process of connecting.
+
+ // If we're connecting, defer to the connection to give us the actual
+ // LoadState.
+ if (state_ == CONNECTING)
+ return connection_->GetLoadState();
+
+ // Just report that we're idle since the session could be doing
+ // many things concurrently.
+ return LOAD_STATE_IDLE;
+}
+
+void SpdySession::OnReadComplete(int bytes_read) {
+ // Parse a frame. For now this code requires that the frame fit into our
+ // buffer (32KB).
+ // TODO(mbelshe): support arbitrarily large frames!
+
+ read_pending_ = false;
+
+ if (bytes_read <= 0) {
+ // Session is tearing down.
+ net::Error error = static_cast<net::Error>(bytes_read);
+ if (bytes_read == 0)
+ error = ERR_CONNECTION_CLOSED;
+ CloseSessionOnError(error, true, "bytes_read is <= 0.");
+ return;
+ }
+
+ bytes_received_ += bytes_read;
+
+ last_activity_time_ = base::TimeTicks::Now();
+
+ // The SpdyFramer will use callbacks onto |this| as it parses frames.
+ // When errors occur, those callbacks can lead to teardown of all references
+ // to |this|, so maintain a reference to self during this call for safe
+ // cleanup.
+ scoped_refptr<SpdySession> self(this);
+
+ DCHECK(buffered_spdy_framer_.get());
+ char *data = read_buffer_->data();
+ while (bytes_read &&
+ buffered_spdy_framer_->error_code() ==
+ SpdyFramer::SPDY_NO_ERROR) {
+ uint32 bytes_processed =
+ buffered_spdy_framer_->ProcessInput(data, bytes_read);
+ bytes_read -= bytes_processed;
+ data += bytes_processed;
+ if (buffered_spdy_framer_->state() == SpdyFramer::SPDY_DONE)
+ buffered_spdy_framer_->Reset();
+ }
+
+ if (state_ != CLOSED)
+ ReadSocket();
+}
+
+void SpdySession::OnWriteComplete(int result) {
+ DCHECK(write_pending_);
+ DCHECK(in_flight_write_.size());
+
+ last_activity_time_ = base::TimeTicks::Now();
+ write_pending_ = false;
+
+ scoped_refptr<SpdyStream> stream = in_flight_write_.stream();
+
+ if (result >= 0) {
+ // It should not be possible to have written more bytes than our
+ // in_flight_write_.
+ DCHECK_LE(result, in_flight_write_.buffer()->BytesRemaining());
+
+ in_flight_write_.buffer()->DidConsume(result);
+
+ // We only notify the stream when we've fully written the pending frame.
+ if (!in_flight_write_.buffer()->BytesRemaining()) {
+ if (stream) {
+ // Report the number of bytes written to the caller, but exclude the
+ // frame size overhead. NOTE: if this frame was compressed the
+ // reported bytes written is the compressed size, not the original
+ // size.
+ if (result > 0) {
+ result = in_flight_write_.buffer()->size();
+ DCHECK_GE(result, static_cast<int>(SpdyFrame::kHeaderSize));
+ result -= static_cast<int>(SpdyFrame::kHeaderSize);
+ }
+
+ // It is possible that the stream was cancelled while we were writing
+ // to the socket.
+ if (!stream->cancelled())
+ stream->OnWriteComplete(result);
+ }
+
+ // Cleanup the write which just completed.
+ in_flight_write_.release();
+ }
+
+ // Write more data. We're already in a continuation, so we can
+ // go ahead and write it immediately (without going back to the
+ // message loop).
+ WriteSocketLater();
+ } else {
+ in_flight_write_.release();
+
+ // The stream is now errored. Close it down.
+ CloseSessionOnError(
+ static_cast<net::Error>(result), true, "The stream has errored.");
+ }
+}
+
+net::Error SpdySession::ReadSocket() {
+ if (read_pending_)
+ return OK;
+
+ if (state_ == CLOSED) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ CHECK(connection_.get());
+ CHECK(connection_->socket());
+ int bytes_read = connection_->socket()->Read(
+ read_buffer_.get(),
+ kReadBufferSize,
+ base::Bind(&SpdySession::OnReadComplete, base::Unretained(this)));
+ switch (bytes_read) {
+ case 0:
+ // Socket is closed!
+ CloseSessionOnError(ERR_CONNECTION_CLOSED, true, "bytes_read is 0.");
+ return ERR_CONNECTION_CLOSED;
+ case net::ERR_IO_PENDING:
+ // Waiting for data. Nothing to do now.
+ read_pending_ = true;
+ return ERR_IO_PENDING;
+ default:
+ // Data was read, process it.
+ // Schedule the work through the message loop to avoid recursive
+ // callbacks.
+ read_pending_ = true;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::OnReadComplete,
+ weak_factory_.GetWeakPtr(), bytes_read));
+ break;
+ }
+ return OK;
+}
+
+void SpdySession::WriteSocketLater() {
+ if (delayed_write_pending_)
+ return;
+
+ if (state_ < CONNECTED)
+ return;
+
+ delayed_write_pending_ = true;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::WriteSocket, weak_factory_.GetWeakPtr()));
+}
+
+void SpdySession::WriteSocket() {
+ // This function should only be called via WriteSocketLater.
+ DCHECK(delayed_write_pending_);
+ delayed_write_pending_ = false;
+
+ // If the socket isn't connected yet, just wait; we'll get called
+ // again when the socket connection completes. If the socket is
+ // closed, just return.
+ if (state_ < CONNECTED || state_ == CLOSED)
+ return;
+
+ if (write_pending_) // Another write is in progress still.
+ return;
+
+ // Loop sending frames until we've sent everything or until the write
+ // returns error (or ERR_IO_PENDING).
+ DCHECK(buffered_spdy_framer_.get());
+ while (in_flight_write_.buffer() || !write_queue_.empty()) {
+ if (!in_flight_write_.buffer()) {
+ // Grab the next SpdyBuffer to send.
+ scoped_ptr<SpdyIOBufferProducer> producer(write_queue_.top());
+ write_queue_.pop();
+ scoped_ptr<SpdyIOBuffer> buffer(producer->ProduceNextBuffer(this));
+ stream_producers_.erase(producer.get());
+ // It is possible that a stream had data to write, but a
+ // WINDOW_UPDATE frame has been received which made that
+ // stream no longer writable.
+ // TODO(rch): consider handling that case by removing the
+ // stream from the writable queue?
+ if (buffer == NULL)
+ continue;
+
+ in_flight_write_ = *buffer;
+ } else {
+ DCHECK(in_flight_write_.buffer()->BytesRemaining());
+ }
+
+ write_pending_ = true;
+ int rv = connection_->socket()->Write(
+ in_flight_write_.buffer(),
+ in_flight_write_.buffer()->BytesRemaining(),
+ base::Bind(&SpdySession::OnWriteComplete, base::Unretained(this)));
+ if (rv == net::ERR_IO_PENDING)
+ break;
+
+ // We sent the frame successfully.
+ OnWriteComplete(rv);
+
+ // TODO(mbelshe): Test this error case. Maybe we should mark the socket
+ // as in an error state.
+ if (rv < 0)
+ break;
+ }
+}
+
+void SpdySession::CloseAllStreams(net::Error status) {
+ base::StatsCounter abandoned_streams("spdy.abandoned_streams");
+ base::StatsCounter abandoned_push_streams(
+ "spdy.abandoned_push_streams");
+
+ if (!active_streams_.empty())
+ abandoned_streams.Add(active_streams_.size());
+ if (!unclaimed_pushed_streams_.empty()) {
+ streams_abandoned_count_ += unclaimed_pushed_streams_.size();
+ abandoned_push_streams.Add(unclaimed_pushed_streams_.size());
+ unclaimed_pushed_streams_.clear();
+ }
+
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ while (!pending_create_stream_queues_[i].empty()) {
+ PendingCreateStream pending_create =
+ pending_create_stream_queues_[i].front();
+ pending_create_stream_queues_[i].pop();
+ pending_create.callback.Run(ERR_ABORTED);
+ }
+ }
+
+ while (!active_streams_.empty()) {
+ ActiveStreamMap::iterator it = active_streams_.begin();
+ const scoped_refptr<SpdyStream>& stream = it->second;
+ LogAbandonedStream(stream, status);
+ DeleteStream(stream->stream_id(), status);
+ }
+
+ while (!created_streams_.empty()) {
+ CreatedStreamSet::iterator it = created_streams_.begin();
+ const scoped_refptr<SpdyStream> stream = *it;
+ created_streams_.erase(it);
+ LogAbandonedStream(stream, status);
+ stream->OnClose(status);
+ }
+
+ // We also need to drain the queue.
+ while (!write_queue_.empty()) {
+ scoped_ptr<SpdyIOBufferProducer> producer(write_queue_.top());
+ write_queue_.pop();
+ stream_producers_.erase(producer.get());
+ }
+}
+
+void SpdySession::LogAbandonedStream(const scoped_refptr<SpdyStream>& stream,
+ net::Error status) {
+ DCHECK(stream);
+ std::string description = base::StringPrintf(
+ "ABANDONED (stream_id=%d): ", stream->stream_id()) + stream->path();
+ stream->LogStreamError(status, description);
+}
+
+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(net::Error err,
+ bool remove_from_pool,
+ const std::string& description) {
+ // Closing all streams can have a side-effect of dropping the last reference
+ // to |this|. Hold a reference through this function.
+ scoped_refptr<SpdySession> self(this);
+
+ DCHECK_LT(err, OK);
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_CLOSE,
+ base::Bind(&NetLogSpdySessionCloseCallback, err, &description));
+
+ // Don't close twice. This can occur because we can have both
+ // a read and a write outstanding, and each can complete with
+ // an error.
+ if (state_ != CLOSED) {
+ state_ = CLOSED;
+ error_ = err;
+ if (remove_from_pool)
+ RemoveFromPool();
+ CloseAllStreams(err);
+ }
+}
+
+Value* SpdySession::GetInfoAsValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+
+ dict->SetInteger("source_id", net_log_.source().id);
+
+ dict->SetString("host_port_pair", host_port_proxy_pair_.first.ToString());
+ if (!pooled_aliases_.empty()) {
+ ListValue* alias_list = new ListValue();
+ for (std::set<HostPortProxyPair>::const_iterator it =
+ pooled_aliases_.begin();
+ it != pooled_aliases_.end(); it++) {
+ alias_list->Append(Value::CreateStringValue(it->first.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_);
+ 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_);
+ return dict;
+}
+
+bool SpdySession::IsReused() const {
+ return buffered_spdy_framer_->frames_received() > 0;
+}
+
+int SpdySession::GetPeerAddress(IPEndPoint* address) const {
+ if (!connection_->socket())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ return connection_->socket()->GetPeerAddress(address);
+}
+
+int SpdySession::GetLocalAddress(IPEndPoint* address) const {
+ if (!connection_->socket())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ return connection_->socket()->GetLocalAddress(address);
+}
+
+class SimpleSpdyIOBufferProducer : public SpdySession::SpdyIOBufferProducer {
+ public:
+ SimpleSpdyIOBufferProducer(SpdyFrame* frame,
+ RequestPriority priority)
+ : frame_(frame),
+ priority_(priority) {
+ }
+
+ virtual RequestPriority GetPriority() const OVERRIDE {
+ return priority_;
+ }
+
+ virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) OVERRIDE {
+ return SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
+ frame_.get(), priority_, NULL);
+ }
+
+ private:
+ scoped_ptr<SpdyFrame> frame_;
+ RequestPriority priority_;
+};
+
+void SpdySession::QueueFrame(SpdyFrame* frame,
+ RequestPriority priority) {
+ SimpleSpdyIOBufferProducer* producer =
+ new SimpleSpdyIOBufferProducer(frame, priority);
+ write_queue_.push(producer);
+ WriteSocketLater();
+}
+
+void SpdySession::ActivateStream(SpdyStream* stream) {
+ if (stream->stream_id() == 0) {
+ stream->set_stream_id(GetNewStreamId());
+ created_streams_.erase(scoped_refptr<SpdyStream>(stream));
+ }
+ const SpdyStreamId id = stream->stream_id();
+ DCHECK(!IsStreamActive(id));
+
+ active_streams_[id] = stream;
+}
+
+void SpdySession::DeleteStream(SpdyStreamId id, int status) {
+ // For push streams, if they are being deleted normally, we leave
+ // the stream in the unclaimed_pushed_streams_ list. However, if
+ // the stream is errored out, clean it up entirely.
+ if (status != OK) {
+ PushedStreamMap::iterator it;
+ for (it = unclaimed_pushed_streams_.begin();
+ it != unclaimed_pushed_streams_.end(); ++it) {
+ scoped_refptr<SpdyStream> curr = it->second.first;
+ if (id == curr->stream_id()) {
+ unclaimed_pushed_streams_.erase(it);
+ break;
+ }
+ }
+ }
+
+ // The stream might have been deleted.
+ ActiveStreamMap::iterator it2 = active_streams_.find(id);
+ if (it2 == active_streams_.end())
+ return;
+
+ // Possibly remove from the write queue.
+ WriteQueue old = write_queue_;
+ write_queue_ = WriteQueue();
+ while (!old.empty()) {
+ scoped_ptr<SpdyIOBufferProducer> producer(old.top());
+ old.pop();
+ StreamProducerMap::iterator it = stream_producers_.find(producer.get());
+ if (it == stream_producers_.end() || it->second->stream_id() != id) {
+ write_queue_.push(producer.release());
+ } else {
+ stream_producers_.erase(producer.get());
+ producer.reset(NULL);
+ }
+ }
+
+ // If this is an active stream, call the callback.
+ const scoped_refptr<SpdyStream> stream(it2->second);
+ active_streams_.erase(it2);
+ if (stream)
+ stream->OnClose(status);
+ ProcessPendingCreateStreams();
+}
+
+void SpdySession::RemoveFromPool() {
+ if (spdy_session_pool_) {
+ SpdySessionPool* pool = spdy_session_pool_;
+ spdy_session_pool_ = NULL;
+ pool->Remove(make_scoped_refptr(this));
+ }
+}
+
+scoped_refptr<SpdyStream> SpdySession::GetActivePushStream(
+ const std::string& path) {
+ base::StatsCounter used_push_streams("spdy.claimed_push_streams");
+
+ PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path);
+ if (it != unclaimed_pushed_streams_.end()) {
+ net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM);
+ scoped_refptr<SpdyStream> stream = it->second.first;
+ unclaimed_pushed_streams_.erase(it);
+ used_push_streams.Increment();
+ return stream;
+ }
+ return NULL;
+}
+
+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) {
+ RecordProtocolErrorHistogram(
+ static_cast<SpdyProtocolErrorDetails>(error_code));
+ std::string description = base::StringPrintf(
+ "SPDY_ERROR error_code: %d.", error_code);
+ CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR, true, description);
+}
+
+void SpdySession::OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) {
+ if (IsStreamActive(stream_id))
+ ResetStream(stream_id, PROTOCOL_ERROR, description);
+}
+
+void SpdySession::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ SpdyDataFlags flags) {
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_DATA,
+ base::Bind(&NetLogSpdyDataCallback, stream_id, len, flags));
+ }
+
+ if (!IsStreamActive(stream_id)) {
+ // NOTE: it may just be that the stream was cancelled.
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ stream->OnDataReceived(data, len);
+}
+
+void SpdySession::OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) {
+ 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::OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) {
+ if (uncompressed_frame.type() == SYN_STREAM) {
+ int uncompressed_size = uncompressed_frame.length();
+ int compressed_size = compressed_frame.length();
+ // Make sure we avoid early decimal truncation.
+ int compression_pct = 100 - (100 * compressed_size) / uncompressed_size;
+ UMA_HISTOGRAM_PERCENTAGE("Net.SpdySynStreamCompressionPercentage",
+ compression_pct);
+ }
+}
+
+
+bool SpdySession::Respond(const SpdyHeaderBlock& headers,
+ const scoped_refptr<SpdyStream> stream) {
+ int rv = OK;
+ rv = stream->OnResponseReceived(headers);
+ if (rv < 0) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ const SpdyStreamId stream_id = stream->stream_id();
+ DeleteStream(stream_id, rv);
+ return false;
+ }
+ return true;
+}
+
+void SpdySession::OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) {
+ 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;
+ }
+
+ 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);
+ ResetStream(stream_id, 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()) {
+ ResetStream(stream_id, PROTOCOL_ERROR,
+ "Pushed stream url was invalid: " + gurl.spec());
+ return;
+ }
+ const std::string& url = gurl.spec();
+
+ // Verify we have a valid stream association.
+ if (!IsStreamActive(associated_stream_id)) {
+ ResetStream(stream_id, 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")) {
+ ResetStream(stream_id, REFUSED_STREAM,
+ base::StringPrintf(
+ "Rejected push of Cross Origin HTTPS content %d",
+ associated_stream_id));
+ }
+ } else {
+ scoped_refptr<SpdyStream> associated_stream =
+ active_streams_[associated_stream_id];
+ GURL associated_url(associated_stream->GetUrl());
+ if (associated_url.GetOrigin() != gurl.GetOrigin()) {
+ ResetStream(stream_id, 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 it = unclaimed_pushed_streams_.find(url);
+ if (it != unclaimed_pushed_streams_.end()) {
+ ResetStream(stream_id, PROTOCOL_ERROR,
+ "Received duplicate pushed stream with url: " + url);
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream(new SpdyStream(this, true, net_log_));
+ stream->set_stream_id(stream_id);
+
+ stream->set_path(gurl.PathForRequest());
+ stream->set_send_window_size(initial_send_window_size_);
+ stream->set_recv_window_size(initial_recv_window_size_);
+
+ DeleteExpiredPushedStreams();
+ unclaimed_pushed_streams_[url] =
+ std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> (
+ stream, time_func_());
+
+
+ ActivateStream(stream);
+ stream->set_response_received();
+
+ // Parse the headers.
+ if (!Respond(headers, stream))
+ 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;
+
+ // Delete old streams.
+ base::TimeTicks minimum_freshness = time_func_() -
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+ PushedStreamMap::iterator it;
+ for (it = unclaimed_pushed_streams_.begin();
+ it != unclaimed_pushed_streams_.end(); ) {
+ const scoped_refptr<SpdyStream>& stream = it->second.first;
+ base::TimeTicks creation_time = it->second.second;
+ // DeleteStream() will invalidate the current iterator, so move to next.
+ ++it;
+ if (minimum_freshness > creation_time) {
+ DeleteStream(stream->stream_id(), ERR_INVALID_SPDY_STREAM);
+ base::StatsCounter abandoned_push_streams(
+ "spdy.abandoned_push_streams");
+ base::StatsCounter abandoned_streams("spdy.abandoned_streams");
+ abandoned_push_streams.Increment();
+ abandoned_streams.Increment();
+ streams_abandoned_count_++;
+ }
+ }
+ next_unclaimed_push_stream_sweep_time_ = time_func_() +
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+}
+
+void SpdySession::OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SYN_REPLY,
+ base::Bind(&NetLogSpdySynCallback,
+ &headers, fin, false,// not unidirectional
+ stream_id, 0));
+ }
+
+ if (!IsStreamActive(stream_id)) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received SYN_REPLY for invalid stream " << stream_id;
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ if (stream->response_received()) {
+ stream->LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED,
+ "Received duplicate SYN_REPLY for stream.");
+ RecordProtocolErrorHistogram(PROTOCOL_ERROR_SYN_REPLY_NOT_RECEIVED);
+ CloseStream(stream->stream_id(), ERR_SPDY_PROTOCOL_ERROR);
+ return;
+ }
+ stream->set_response_received();
+
+ Respond(headers, stream);
+}
+
+void SpdySession::OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_HEADERS,
+ base::Bind(&NetLogSpdySynCallback,
+ &headers, fin, /*unidirectional=*/false,
+ stream_id, 0));
+ }
+
+ if (!IsStreamActive(stream_id)) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received HEADERS for invalid stream " << stream_id;
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ int rv = stream->OnHeaders(headers);
+ if (rv < 0) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ const SpdyStreamId stream_id = stream->stream_id();
+ DeleteStream(stream_id, rv);
+ }
+}
+
+void SpdySession::OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
+ std::string description;
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RST_STREAM,
+ base::Bind(&NetLogSpdyRstCallback,
+ stream_id, status, &description));
+
+ if (!IsStreamActive(stream_id)) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received RST for invalid stream" << stream_id;
+ return;
+ }
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ if (status == 0) {
+ stream->OnDataReceived(NULL, 0);
+ } else if (status == REFUSED_STREAM) {
+ DeleteStream(stream_id, ERR_SPDY_SERVER_REFUSED_STREAM);
+ } else {
+ RecordProtocolErrorHistogram(
+ PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM);
+ stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+ base::StringPrintf("SPDY stream closed: %d",
+ status));
+ // TODO(mbelshe): Map from Spdy-protocol errors to something sensical.
+ // For now, it doesn't matter much - it is a protocol error.
+ DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
+ }
+}
+
+void SpdySession::OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {
+ net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_GOAWAY,
+ base::Bind(&NetLogSpdyGoAwayCallback,
+ last_accepted_stream_id,
+ active_streams_.size(),
+ unclaimed_pushed_streams_.size(),
+ status));
+ RemoveFromPool();
+ CloseAllStreams(net::ERR_ABORTED);
+
+ // TODO(willchan): Cancel any streams that are past the GoAway frame's
+ // |last_accepted_stream_id|.
+
+ // Don't bother killing any streams that are still reading. They'll either
+ // complete successfully or get an ERR_CONNECTION_CLOSED when the socket is
+ // closed.
+}
+
+void SpdySession::OnPing(uint32 unique_id) {
+ 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);
+ CloseSessionOnError(
+ net::ERR_SPDY_PROTOCOL_ERROR, true, "pings_in_flight_ is < 0.");
+ 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(base::TimeTicks::Now() - last_ping_sent_time_);
+}
+
+void SpdySession::OnWindowUpdate(SpdyStreamId stream_id,
+ int delta_window_size) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECEIVED_WINDOW_UPDATE,
+ base::Bind(&NetLogSpdyWindowUpdateCallback,
+ stream_id, delta_window_size));
+
+ if (!IsStreamActive(stream_id)) {
+ LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id;
+ return;
+ }
+
+ if (delta_window_size < 1) {
+ ResetStream(stream_id, FLOW_CONTROL_ERROR,
+ base::StringPrintf(
+ "Received WINDOW_UPDATE with an invalid "
+ "delta_window_size %d", delta_window_size));
+ return;
+ }
+
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+ CHECK(!stream->cancelled());
+
+ if (flow_control_)
+ stream->IncreaseSendWindowSize(delta_window_size);
+}
+
+void SpdySession::SendWindowUpdate(SpdyStreamId stream_id,
+ int32 delta_window_size) {
+ CHECK(IsStreamActive(stream_id));
+ scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SENT_WINDOW_UPDATE,
+ base::Bind(&NetLogSpdyWindowUpdateCallback,
+ stream_id, delta_window_size));
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
+ buffered_spdy_framer_->CreateWindowUpdate(stream_id, delta_window_size));
+ QueueFrame(window_update_frame.release(), stream->priority());
+}
+
+// Given a cwnd that we would have sent to the server, modify it based on the
+// field trial policy.
+uint32 ApplyCwndFieldTrialPolicy(int cwnd) {
+#if defined(COBALT)
+ // Cobalt doesn't support field trials.
+ return cwnd;
+#else
+ base::FieldTrial* trial = base::FieldTrialList::Find("SpdyCwnd");
+ if (!trial) {
+ LOG(WARNING) << "Could not find \"SpdyCwnd\" in FieldTrialList";
+ return cwnd;
+ }
+ if (trial->group_name() == "cwnd10")
+ return 10;
+ else if (trial->group_name() == "cwnd16")
+ return 16;
+ else if (trial->group_name() == "cwndMin16")
+ return std::max(cwnd, 16);
+ else if (trial->group_name() == "cwndMin10")
+ return std::max(cwnd, 10);
+ else if (trial->group_name() == "cwndDynamic")
+ return cwnd;
+ NOTREACHED();
+ return cwnd;
+#endif
+}
+
+void SpdySession::SendInitialSettings() {
+ // First notify the server about the settings they should use when
+ // communicating with us.
+ if (GetProtocolVersion() >= 2 && enable_sending_initial_settings_) {
+ SettingsMap settings_map;
+ // Create a new settings frame notifying the sever of our
+ // max_concurrent_streams_ and initial window size.
+ settings_map[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+ if (GetProtocolVersion() > 2 &&
+ initial_recv_window_size_ != kSpdyStreamInitialWindowSize) {
+ settings_map[SETTINGS_INITIAL_WINDOW_SIZE] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, initial_recv_window_size_);
+ }
+ SendSettings(settings_map);
+ }
+
+ // Next notify the server about the settings they have previously
+ // told us to use when communicating with them.
+ const SettingsMap& settings_map =
+ http_server_properties_->GetSpdySettings(host_port_pair());
+ if (settings_map.empty())
+ return;
+
+ // Record Histogram Data and Apply the SpdyCwnd FieldTrial if applicable.
+ const SpdySettingsIds id = SETTINGS_CURRENT_CWND;
+ SettingsMap::const_iterator it = settings_map.find(id);
+ uint32 value = 0;
+ if (it != settings_map.end())
+ value = it->second.second;
+ uint32 cwnd = ApplyCwndFieldTrialPolicy(value);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
+ if (cwnd != value) {
+ http_server_properties_->SetSpdySetting(
+ host_port_pair(), id, SETTINGS_FLAG_PLEASE_PERSIST, cwnd);
+ }
+
+ const SettingsMap& settings_map_new =
+ http_server_properties_->GetSpdySettings(host_port_pair());
+ for (SettingsMap::const_iterator i = settings_map_new.begin(),
+ end = settings_map_new.end(); i != end; ++i) {
+ const SpdySettingsIds new_id = i->first;
+ const uint32 new_val = i->second.second;
+ HandleSetting(new_id, new_val);
+ }
+
+ SendSettings(settings_map_new);
+}
+
+
+void SpdySession::SendSettings(const SettingsMap& settings) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS,
+ base::Bind(&NetLogSpdySettingsCallback, &settings));
+
+ // Create the SETTINGS frame and send it.
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdySettingsControlFrame> settings_frame(
+ buffered_spdy_framer_->CreateSettings(settings));
+ sent_settings_ = true;
+ QueueFrame(settings_frame.release(), HIGHEST);
+}
+
+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);
+ ProcessPendingCreateStreams();
+ break;
+ case SETTINGS_INITIAL_WINDOW_SIZE:
+ if (static_cast<int32>(value) < 0) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_NEGATIVE_INITIAL_WINDOW_SIZE,
+ NetLog::IntegerCallback("initial_window_size", value));
+ } else {
+ // SETTINGS_INITIAL_WINDOW_SIZE updates initial_send_window_size_ only.
+ int32 delta_window_size = value - initial_send_window_size_;
+ initial_send_window_size_ = 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) {
+ ActiveStreamMap::iterator it;
+ for (it = active_streams_.begin(); it != active_streams_.end(); ++it) {
+ const scoped_refptr<SpdyStream>& stream = it->second;
+ DCHECK(stream);
+ stream->AdjustSendWindowSize(delta_window_size);
+ }
+
+ CreatedStreamSet::iterator i;
+ for (i = created_streams_.begin(); i != created_streams_.end(); i++) {
+ const scoped_refptr<SpdyStream>& stream = *i;
+ stream->AdjustSendWindowSize(delta_window_size);
+ }
+}
+
+void SpdySession::SendPrefacePingIfNoneInFlight() {
+ if (pings_in_flight_ || !enable_ping_based_connection_checking_)
+ return;
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ // 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::WritePingFrame(uint32 unique_id) {
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyPingControlFrame> ping_frame(
+ buffered_spdy_framer_->CreatePingFrame(unique_id));
+ QueueFrame(ping_frame.release(), HIGHEST);
+
+ 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_ = base::TimeTicks::Now();
+ }
+}
+
+void SpdySession::PlanToCheckPingStatus() {
+ if (check_ping_status_pending_)
+ return;
+
+ check_ping_status_pending_ = true;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(),
+ base::TimeTicks::Now()), hung_interval_);
+}
+
+void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) {
+ // 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 = base::TimeTicks::Now();
+ base::TimeDelta delay = hung_interval_ - (now - last_activity_time_);
+
+ if (delay.InMilliseconds() < 0 || last_activity_time_ < last_check_time) {
+ CloseSessionOnError(net::ERR_SPDY_PING_FAILED, true, "Failed ping.");
+ // Track all failed PING messages in a separate bucket.
+ const base::TimeDelta kFailedPing =
+ base::TimeDelta::FromInternalValue(INT_MAX);
+ RecordPingRTTHistogram(kFailedPing);
+ return;
+ }
+
+ // Check the status of connection after a delay.
+ 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.SpdySessionErrorDetails", details,
+ NUM_SPDY_PROTOCOL_ERROR_DETAILS);
+ if (EndsWith(host_port_pair().host(), "google.com", false)) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails_Google", 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 (bytes_received_ > 10 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd10K",
+ val, 1, 200, 100);
+ if (bytes_received_ > 25 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd25K",
+ val, 1, 200, 100);
+ if (bytes_received_ > 50 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd50K",
+ val, 1, 200, 100);
+ if (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::InvokeUserStreamCreationCallback(
+ scoped_refptr<SpdyStream>* stream) {
+ PendingCallbackMap::iterator it = pending_callback_map_.find(stream);
+
+ // Exit if the request has already been cancelled.
+ if (it == pending_callback_map_.end())
+ return;
+
+ CompletionCallback callback = it->second.callback;
+ int result = it->second.result;
+ pending_callback_map_.erase(it);
+ callback.Run(result);
+}
+
+SSLClientSocket* SpdySession::GetSSLClientSocket() const {
+ if (!is_secure_)
+ return NULL;
+ SSLClientSocket* ssl_socket =
+ reinterpret_cast<SSLClientSocket*>(connection_->socket());
+ DCHECK(ssl_socket);
+ return ssl_socket;
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_session.h b/src/net/spdy/spdy_session.h
new file mode 100644
index 0000000..abb5e92
--- /dev/null
+++ b/src/net/spdy/spdy_session.h
@@ -0,0 +1,746 @@
+// 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 <algorithm>
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_client_cert_type.h"
+#include "net/base/ssl_config_service.h"
+#include "net/socket/client_socket_handle.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_credential_state.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.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;
+const int kMaxSpdyFrameChunkSize = (2 * kMss) - SpdyFrame::kHeaderSize;
+
+// Specifies the maxiumum concurrent streams server could send (via push).
+const int kMaxConcurrentPushedStreams = 1000;
+
+class BoundNetLog;
+class SpdyStream;
+class SSLInfo;
+
+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,
+
+ // SpdyStatusCodes
+ 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,
+ 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>(NUM_STATUS_CODES +
+ STATUS_CODE_INVALID),
+ SpdyProtocolErrorDetails_SpdyErrors_mismatch);
+
+class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
+ public BufferedSpdyFramerVisitorInterface {
+ public:
+ typedef base::TimeTicks (*TimeFunc)(void);
+
+ // Defines an interface for producing SpdyIOBuffers.
+ class NET_EXPORT_PRIVATE SpdyIOBufferProducer {
+ public:
+ SpdyIOBufferProducer() {}
+
+ // Returns a newly created SpdyIOBuffer, owned by the caller, or NULL
+ // if not buffer is ready to be produced.
+ virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) = 0;
+
+ virtual RequestPriority GetPriority() const = 0;
+
+ virtual ~SpdyIOBufferProducer() {}
+
+ protected:
+ // Activates |spdy_stream| in |spdy_session|.
+ static void ActivateStream(SpdySession* spdy_session,
+ SpdyStream* spdy_stream);
+
+ static SpdyIOBuffer* CreateIOBuffer(SpdyFrame* frame,
+ RequestPriority priority,
+ SpdyStream* spdy_stream);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyIOBufferProducer);
+ };
+
+ // Create a new SpdySession.
+ // |host_port_proxy_pair| is the host/port that this session connects to, and
+ // the proxy configuration settings that it's using.
+ // |spdy_session_pool| is the SpdySessionPool that owns us. Its lifetime must
+ // strictly be greater than |this|.
+ // |session| is the HttpNetworkSession. |net_log| is the NetLog that we log
+ // network events to.
+ SpdySession(const HostPortProxyPair& host_port_proxy_pair,
+ SpdySessionPool* spdy_session_pool,
+ HttpServerProperties* http_server_properties,
+ bool verify_domain_authentication,
+ bool enable_sending_initial_settings,
+ bool enable_credential_frames,
+ bool enable_compression,
+ bool enable_ping_based_connection_checking,
+ NextProto default_protocol_,
+ size_t 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);
+
+ const HostPortPair& host_port_pair() const {
+ return host_port_proxy_pair_.first;
+ }
+ const HostPortProxyPair& host_port_proxy_pair() const {
+ return host_port_proxy_pair_;
+ }
+
+ // 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. Writes the stream out to |spdy_stream|.
+ // Returns a net error code.
+ int GetPushStream(
+ const GURL& url,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log);
+
+ // Create a new stream for a given |url|. Writes it out to |spdy_stream|.
+ // Returns a net error code, possibly ERR_IO_PENDING.
+ int CreateStream(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback);
+
+ // Remove PendingCreateStream objects on transaction deletion
+ void CancelPendingCreateStreams(const scoped_refptr<SpdyStream>* spdy_stream);
+
+ // Used by SpdySessionPool to initialize with a pre-existing SSL socket. For
+ // testing, setting is_secure to false allows initialization with a
+ // pre-existing TCP socket.
+ // Returns OK on success, or an error on failure.
+ net::Error InitializeWithSocket(ClientSocketHandle* connection,
+ bool is_secure,
+ int certificate_error_code);
+
+ // 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);
+
+ // Records that |stream| has a write available from |producer|.
+ // |producer| will be owned by this SpdySession.
+ void SetStreamHasWriteAvailable(SpdyStream* stream,
+ SpdyIOBufferProducer* producer);
+
+ // Send the SYN frame for |stream_id|. This also sends PING message to check
+ // the status of the connection.
+ SpdySynStreamControlFrame* CreateSynStream(
+ SpdyStreamId stream_id,
+ RequestPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ const SpdyHeaderBlock& headers);
+
+ // Write a CREDENTIAL frame to the session.
+ SpdyCredentialControlFrame* CreateCredentialFrame(const std::string& origin,
+ SSLClientCertType type,
+ const std::string& key,
+ const std::string& cert,
+ RequestPriority priority);
+
+ // Write a HEADERS frame to the stream.
+ SpdyHeadersControlFrame* CreateHeadersFrame(SpdyStreamId stream_id,
+ const SpdyHeaderBlock& headers,
+ SpdyControlFlags flags);
+
+ // Write a data frame to the stream.
+ // Used to create and queue a data frame for the given stream.
+ SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id,
+ net::IOBuffer* data, int len,
+ SpdyDataFlags flags);
+
+ // Close a stream.
+ void CloseStream(SpdyStreamId stream_id, int status);
+
+ // Close a stream that has been created but is not yet active.
+ void CloseCreatedStream(SpdyStream* stream, int status);
+
+ // Reset a stream by sending a RST_STREAM frame with given status code.
+ // Also closes the stream. Was not piggybacked to CloseStream since not
+ // all of the calls to CloseStream necessitate sending a RST_STREAM.
+ void ResetStream(SpdyStreamId stream_id,
+ SpdyStatusCodes 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 WINDOW_UPDATE frame, called by a stream whenever receive window
+ // size is increased.
+ void SendWindowUpdate(SpdyStreamId stream_id, int32 delta_window_size);
+
+ // If session is closed, no new streams/transactions should be created.
+ bool IsClosed() const { return state_ == CLOSED; }
+
+ // Closes this session. This will close all active streams and mark
+ // the session as permanently closed.
+ // |err| should not be OK; this function is intended to be called on
+ // error.
+ // |remove_from_pool| indicates whether to also remove the session from the
+ // session pool.
+ // |description| indicates the reason for the error.
+ void CloseSessionOnError(net::Error err,
+ bool remove_from_pool,
+ 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();
+ }
+
+ void set_spdy_session_pool(SpdySessionPool* pool) {
+ spdy_session_pool_ = NULL;
+ }
+
+ // 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_queues(int priority) {
+ DCHECK_LT(priority, NUM_PRIORITIES);
+ return pending_create_stream_queues_[priority].size();
+ }
+
+ // Returns true if flow control is enabled for the session.
+ bool is_flow_control_enabled() const {
+ return flow_control_;
+ }
+
+ // Returns the current |initial_send_window_size_|.
+ int32 initial_send_window_size() const {
+ return initial_send_window_size_;
+ }
+
+ // Returns the current |initial_recv_window_size_|.
+ int32 initial_recv_window_size() const { return initial_recv_window_size_; }
+
+ // Sets |initial_recv_window_size_| used by unittests.
+ void set_initial_recv_window_size(int32 window_size) {
+ initial_recv_window_size_ = window_size;
+ }
+
+ 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 HostPortProxyPair& alias);
+
+ // Returns the set of aliases associated with this session.
+ const std::set<HostPortProxyPair>& pooled_aliases() const {
+ return pooled_aliases_;
+ }
+
+ int GetProtocolVersion() const;
+
+ private:
+ friend class base::RefCounted<SpdySession>;
+
+ // Allow tests to access our innards for testing purposes.
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, ClientPing);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, FailedPing);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, GetActivePushStream);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, DeleteExpiredPushStreams);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, ClientPing);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, FailedPing);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, GetActivePushStream);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, DeleteExpiredPushStreams);
+
+ struct PendingCreateStream {
+ PendingCreateStream(const GURL& url, RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback);
+
+ ~PendingCreateStream();
+
+ const GURL* url;
+ RequestPriority priority;
+ scoped_refptr<SpdyStream>* spdy_stream;
+ const BoundNetLog* stream_net_log;
+ CompletionCallback callback;
+ };
+ typedef std::queue<PendingCreateStream, std::list<PendingCreateStream> >
+ PendingCreateStreamQueue;
+ typedef std::map<int, scoped_refptr<SpdyStream> > ActiveStreamMap;
+ typedef std::map<std::string,
+ std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> > PushedStreamMap;
+ typedef std::priority_queue<SpdyIOBuffer> OutputQueue;
+
+ typedef std::set<scoped_refptr<SpdyStream> > CreatedStreamSet;
+ typedef std::map<SpdyIOBufferProducer*, SpdyStream*> StreamProducerMap;
+ class SpdyIOBufferProducerCompare {
+ public:
+ bool operator() (const SpdyIOBufferProducer* lhs,
+ const SpdyIOBufferProducer* rhs) const {
+ return lhs->GetPriority() < rhs->GetPriority();
+ }
+ };
+ typedef std::priority_queue<SpdyIOBufferProducer*,
+ std::vector<SpdyIOBufferProducer*>,
+ SpdyIOBufferProducerCompare> WriteQueue;
+
+ struct CallbackResultPair {
+ CallbackResultPair(const CompletionCallback& callback_in, int result_in);
+ ~CallbackResultPair();
+
+ CompletionCallback callback;
+ int result;
+ };
+
+ typedef std::map<const scoped_refptr<SpdyStream>*, CallbackResultPair>
+ PendingCallbackMap;
+
+ enum State {
+ IDLE,
+ CONNECTING,
+ CONNECTED,
+ CLOSED
+ };
+
+ virtual ~SpdySession();
+
+ void ProcessPendingCreateStreams();
+ int CreateStreamImpl(
+ const GURL& url,
+ RequestPriority priority,
+ scoped_refptr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log);
+
+ // IO Callbacks
+ void OnReadComplete(int result);
+ void OnWriteComplete(int result);
+
+ // Send relevant SETTINGS. This is generally called on connection setup.
+ void SendInitialSettings();
+
+ // Helper method to send SETTINGS a 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 PendingCreateStreams.
+ 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 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);
+
+ // Start reading from the socket.
+ // Returns OK on success, or an error on failure.
+ net::Error ReadSocket();
+
+ // Write current data to the socket.
+ void WriteSocketLater();
+ void WriteSocket();
+
+ // Get a new stream id.
+ int GetNewStreamId();
+
+ // Queue a frame for sending.
+ // |frame| is the frame to send.
+ // |priority| is the priority for insertion into the queue.
+ void QueueFrame(SpdyFrame* frame, RequestPriority priority);
+
+ // Track active streams in the active stream list.
+ void ActivateStream(SpdyStream* stream);
+ void DeleteStream(SpdyStreamId id, int status);
+
+ // Removes this session from the session pool.
+ void RemoveFromPool();
+
+ // 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.
+ scoped_refptr<SpdyStream> GetActivePushStream(const std::string& url);
+
+ // Calls OnResponseReceived().
+ // Returns true if successful.
+ bool Respond(const SpdyHeaderBlock& headers,
+ const scoped_refptr<SpdyStream> stream);
+
+ void RecordPingRTTHistogram(base::TimeDelta duration);
+ void RecordHistograms();
+ void RecordProtocolErrorHistogram(SpdyProtocolErrorDetails details);
+
+ // Closes all streams. Used as part of shutdown.
+ void CloseAllStreams(net::Error status);
+
+ void LogAbandonedStream(const scoped_refptr<SpdyStream>& stream,
+ net::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 InvokeUserStreamCreationCallback(scoped_refptr<SpdyStream>* stream);
+
+ // 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,
+ SpdyStatusCodes 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,
+ SpdyDataFlags flags) OVERRIDE;
+ virtual void OnSetting(
+ SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ int delta_window_size) OVERRIDE;
+ virtual void OnControlFrameCompressed(
+ const SpdyControlFrame& uncompressed_frame,
+ const SpdyControlFrame& compressed_frame) 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;
+
+ // --------------------------
+ // 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_; }
+
+ // 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_;
+
+ // Map of the SpdyStreams for which we have a pending Task to invoke a
+ // callback. This is necessary since, before we invoke said callback, it's
+ // possible that the request is cancelled.
+ PendingCallbackMap pending_callback_map_;
+
+ // The domain this session is connected to.
+ const HostPortProxyPair host_port_proxy_pair_;
+
+ // Set set of HostPortProxyPairs for which this session has serviced
+ // requests.
+ std::set<HostPortProxyPair> pooled_aliases_;
+
+ // |spdy_session_pool_| owns us, therefore its lifetime must exceed ours. We
+ // set this to NULL after we are removed from the pool.
+ SpdySessionPool* spdy_session_pool_;
+ HttpServerProperties* const 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_;
+ bool read_pending_;
+
+ int stream_hi_water_mark_; // The next stream id to use.
+
+ // Queue, for each priority, of pending Create Streams that have not
+ // yet been satisfied
+ PendingCreateStreamQueue pending_create_stream_queues_[NUM_PRIORITIES];
+
+ // 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).
+ // 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_;
+ // Map of all the streams that have already started to be pushed by the
+ // server, but do not have consumers yet.
+ PushedStreamMap unclaimed_pushed_streams_;
+
+ // Set of all created streams but that have not yet sent any frames.
+ CreatedStreamSet created_streams_;
+
+ // As streams have data to be sent, we put them into the write queue.
+ WriteQueue write_queue_;
+
+ // Mapping from SpdyIOBufferProducers to their corresponding SpdyStream
+ // so that when a stream is destroyed, we can remove the corresponding
+ // producer from |write_queue_|.
+ StreamProducerMap stream_producers_;
+
+ // The packet we are currently sending.
+ bool write_pending_; // Will be true when a write is in progress.
+ SpdyIOBuffer in_flight_write_; // This is the write buffer in progress.
+
+ // Flag if we have a pending message scheduled for WriteSocket.
+ bool delayed_write_pending_;
+
+ // 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_;
+
+ // If an error has occurred on the session, the session is effectively
+ // dead. Record this error here. When no error has occurred, |error_| will
+ // be OK.
+ net::Error error_;
+ State state_;
+
+ // 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_;
+ int 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_;
+
+ // Indicate if flow control is enabled or not.
+ bool flow_control_;
+
+ // Initial send window size for the session; can be changed by an
+ // arriving SETTINGS frame; newly created streams use this value for the
+ // initial send window size.
+ int32 initial_send_window_size_;
+
+ // Initial receive window size for the session; 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 initial_recv_window_size_;
+
+ BoundNetLog net_log_;
+
+ // Outside of tests, these should always be true.
+ bool verify_domain_authentication_;
+ bool enable_sending_initial_settings_;
+ bool enable_credential_frames_;
+ bool enable_compression_;
+ bool enable_ping_based_connection_checking_;
+ NextProto default_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/src/net/spdy/spdy_session_pool.cc b/src/net/spdy/spdy_session_pool.cc
new file mode 100644
index 0000000..b967a46
--- /dev/null
+++ b/src/net/spdy/spdy_session_pool.cc
@@ -0,0 +1,497 @@
+// 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/callback.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
+};
+
+bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a,
+ const HostPortProxyPair& b) {
+ return a.first.Equals(b.first) && a.second == b.second;
+}
+
+}
+
+// The maximum number of sessions to open to a single domain.
+static const size_t kMaxSessionsPerDomain = 1;
+
+SpdySessionPool::SpdySessionPool(
+ HostResolver* resolver,
+ SSLConfigService* ssl_config_service,
+ HttpServerProperties* http_server_properties,
+ size_t max_sessions_per_domain,
+ 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 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_settings_(true),
+ max_sessions_per_domain_(max_sessions_per_domain == 0 ?
+ kMaxSessionsPerDomain :
+ max_sessions_per_domain),
+ 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),
+ default_protocol_(default_protocol),
+ initial_recv_window_size_(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)) {
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ if (ssl_config_service_)
+ ssl_config_service_->AddObserver(this);
+ CertDatabase::GetInstance()->AddObserver(this);
+}
+
+SpdySessionPool::~SpdySessionPool() {
+ CloseAllSessions();
+
+ if (ssl_config_service_)
+ ssl_config_service_->RemoveObserver(this);
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::Get(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log) {
+ return GetInternal(host_port_proxy_pair, net_log, false);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetIfExists(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log) {
+ return GetInternal(host_port_proxy_pair, net_log, true);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetInternal(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log,
+ bool only_use_existing_sessions) {
+ scoped_refptr<SpdySession> spdy_session;
+ SpdySessionList* list = GetSessionList(host_port_proxy_pair);
+ if (!list) {
+ // Check if we have a Session through a domain alias.
+ spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true);
+ if (spdy_session) {
+ 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,
+ spdy_session->net_log().source().ToEventParametersCallback());
+ // Add this session to the map so that we can find it next time.
+ list = AddSessionList(host_port_proxy_pair);
+ list->push_back(spdy_session);
+ spdy_session->AddPooledAlias(host_port_proxy_pair);
+ return spdy_session;
+ } else if (only_use_existing_sessions) {
+ return NULL;
+ }
+ list = AddSessionList(host_port_proxy_pair);
+ }
+
+ DCHECK(list);
+ if (list->size() && list->size() == max_sessions_per_domain_) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+ FOUND_EXISTING,
+ SPDY_SESSION_GET_MAX);
+ spdy_session = GetExistingSession(list, net_log);
+ net_log.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION,
+ spdy_session->net_log().source().ToEventParametersCallback());
+ return spdy_session;
+ }
+
+ DCHECK(!only_use_existing_sessions);
+
+ spdy_session = new SpdySession(host_port_proxy_pair, this,
+ http_server_properties_,
+ verify_domain_authentication_,
+ enable_sending_initial_settings_,
+ enable_credential_frames_,
+ enable_compression_,
+ enable_ping_based_connection_checking_,
+ default_protocol_,
+ initial_recv_window_size_,
+ initial_max_concurrent_streams_,
+ max_concurrent_streams_limit_,
+ time_func_,
+ trusted_spdy_proxy_,
+ net_log.net_log());
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+ CREATED_NEW,
+ SPDY_SESSION_GET_MAX);
+ list->push_back(spdy_session);
+ net_log.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION,
+ spdy_session->net_log().source().ToEventParametersCallback());
+ DCHECK_LE(list->size(), max_sessions_per_domain_);
+ return spdy_session;
+}
+
+net::Error SpdySessionPool::GetSpdySessionFromSocket(
+ const HostPortProxyPair& host_port_proxy_pair,
+ ClientSocketHandle* connection,
+ const BoundNetLog& net_log,
+ int certificate_error_code,
+ scoped_refptr<SpdySession>* spdy_session,
+ bool is_secure) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+ IMPORTED_FROM_SOCKET,
+ SPDY_SESSION_GET_MAX);
+ // Create the SPDY session and add it to the pool.
+ *spdy_session = new SpdySession(host_port_proxy_pair, this,
+ http_server_properties_,
+ verify_domain_authentication_,
+ enable_sending_initial_settings_,
+ enable_credential_frames_,
+ enable_compression_,
+ enable_ping_based_connection_checking_,
+ default_protocol_,
+ initial_recv_window_size_,
+ initial_max_concurrent_streams_,
+ max_concurrent_streams_limit_,
+ time_func_,
+ trusted_spdy_proxy_,
+ net_log.net_log());
+ SpdySessionList* list = GetSessionList(host_port_proxy_pair);
+ if (!list)
+ list = AddSessionList(host_port_proxy_pair);
+ DCHECK(list->empty());
+ list->push_back(*spdy_session);
+
+ net_log.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
+ (*spdy_session)->net_log().source().ToEventParametersCallback());
+
+ // We have a new session. Lookup 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_ && host_port_proxy_pair.second.is_direct()) {
+ IPEndPoint address;
+ if (connection->socket()->GetPeerAddress(&address) == OK)
+ AddAlias(address, host_port_proxy_pair);
+ }
+
+ // Now we can initialize the session with the SSL socket.
+ return (*spdy_session)->InitializeWithSocket(connection, is_secure,
+ certificate_error_code);
+}
+
+bool SpdySessionPool::HasSession(
+ const HostPortProxyPair& host_port_proxy_pair) const {
+ if (GetSessionList(host_port_proxy_pair))
+ return true;
+
+ // Check if we have a session via an alias.
+ scoped_refptr<SpdySession> spdy_session =
+ GetFromAlias(host_port_proxy_pair, BoundNetLog(), false);
+ return spdy_session.get() != NULL;
+}
+
+void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) {
+ bool ok = RemoveFromSessionList(session, session->host_port_proxy_pair());
+ DCHECK(ok);
+ session->net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
+ session->net_log().source().ToEventParametersCallback());
+
+ const std::set<HostPortProxyPair>& aliases = session->pooled_aliases();
+ for (std::set<HostPortProxyPair>::const_iterator it = aliases.begin();
+ it != aliases.end(); ++it) {
+ ok = RemoveFromSessionList(session, *it);
+ DCHECK(ok);
+ }
+}
+
+bool SpdySessionPool::RemoveFromSessionList(
+ const scoped_refptr<SpdySession>& session,
+ const HostPortProxyPair& pair) {
+ SpdySessionList* list = GetSessionList(pair);
+ if (!list)
+ return false;
+ list->remove(session);
+ if (list->empty())
+ RemoveSessionList(pair);
+ return true;
+}
+
+Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
+ ListValue* list = new ListValue();
+
+ for (SpdySessionsMap::const_iterator it = sessions_.begin();
+ it != sessions_.end(); ++it) {
+ SpdySessionList* sessions = it->second;
+ for (SpdySessionList::const_iterator session = sessions->begin();
+ session != sessions->end(); ++session) {
+ // Only add the session if the key in the map matches the main
+ // host_port_proxy_pair (not an alias).
+ const HostPortProxyPair& key = it->first;
+ const HostPortProxyPair& pair = session->get()->host_port_proxy_pair();
+ if (key.first.Equals(pair.first) && key.second == pair.second)
+ list->Append(session->get()->GetInfoAsValue());
+ }
+ }
+ return list;
+}
+
+void SpdySessionPool::OnIPAddressChanged() {
+ CloseCurrentSessions(ERR_NETWORK_CHANGED);
+ http_server_properties_->ClearSpdySettings();
+}
+
+void SpdySessionPool::OnSSLConfigChanged() {
+ CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession(
+ SpdySessionList* list,
+ const BoundNetLog& net_log) const {
+ DCHECK(list);
+ DCHECK_LT(0u, list->size());
+ scoped_refptr<SpdySession> spdy_session = list->front();
+ if (list->size() > 1) {
+ list->pop_front(); // Rotate the list.
+ list->push_back(spdy_session);
+ }
+
+ return spdy_session;
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetFromAlias(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log,
+ bool record_histograms) const {
+ // We should only be checking aliases when there is no direct session.
+ DCHECK(!GetSessionList(host_port_proxy_pair));
+
+ if (!enable_ip_pooling_)
+ return NULL;
+
+ AddressList addresses;
+ if (!LookupAddresses(host_port_proxy_pair, net_log, &addresses))
+ return NULL;
+ for (AddressList::const_iterator iter = addresses.begin();
+ iter != addresses.end();
+ ++iter) {
+ SpdyAliasMap::const_iterator alias_iter = aliases_.find(*iter);
+ if (alias_iter == aliases_.end())
+ continue;
+
+ // We found an alias.
+ const HostPortProxyPair& alias_pair = alias_iter->second;
+
+ // If the proxy settings match, we can reuse this session.
+ if (!(alias_pair.second == host_port_proxy_pair.second))
+ continue;
+
+ SpdySessionList* list = GetSessionList(alias_pair);
+ if (!list) {
+ NOTREACHED(); // It shouldn't be in the aliases table if we can't get it!
+ continue;
+ }
+
+ scoped_refptr<SpdySession> spdy_session = GetExistingSession(list, net_log);
+ // If the SPDY session is a secure one, we need to verify that the server
+ // is authenticated to serve traffic for |host_port_proxy_pair| too.
+ if (!spdy_session->VerifyDomainAuthentication(
+ host_port_proxy_pair.first.host())) {
+ if (record_histograms)
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
+ continue;
+ }
+ if (record_histograms)
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
+ return spdy_session;
+ }
+ return NULL;
+}
+
+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);
+}
+
+const HostPortProxyPair& SpdySessionPool::NormalizeListPair(
+ const HostPortProxyPair& host_port_proxy_pair) const {
+ if (!force_single_domain_)
+ return host_port_proxy_pair;
+
+ static HostPortProxyPair* single_domain_pair = NULL;
+ if (!single_domain_pair) {
+ HostPortPair single_domain = HostPortPair("singledomain.com", 80);
+ single_domain_pair = new HostPortProxyPair(single_domain,
+ ProxyServer::Direct());
+ }
+ return *single_domain_pair;
+}
+
+SpdySessionPool::SpdySessionList*
+ SpdySessionPool::AddSessionList(
+ const HostPortProxyPair& host_port_proxy_pair) {
+ const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
+ DCHECK(sessions_.find(pair) == sessions_.end());
+ SpdySessionPool::SpdySessionList* list = new SpdySessionList();
+ sessions_[pair] = list;
+ return list;
+}
+
+SpdySessionPool::SpdySessionList*
+ SpdySessionPool::GetSessionList(
+ const HostPortProxyPair& host_port_proxy_pair) const {
+ const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
+ SpdySessionsMap::const_iterator it = sessions_.find(pair);
+ if (it != sessions_.end())
+ return it->second;
+ return NULL;
+}
+
+void SpdySessionPool::RemoveSessionList(
+ const HostPortProxyPair& host_port_proxy_pair) {
+ const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
+ SpdySessionList* list = GetSessionList(pair);
+ if (list) {
+ delete list;
+ sessions_.erase(pair);
+ } else {
+ DCHECK(false) << "removing orphaned session list";
+ }
+ RemoveAliases(host_port_proxy_pair);
+}
+
+bool SpdySessionPool::LookupAddresses(const HostPortProxyPair& pair,
+ const BoundNetLog& net_log,
+ AddressList* addresses) const {
+ net::HostResolver::RequestInfo resolve_info(pair.first);
+ int rv = resolver_->ResolveFromCache(resolve_info, addresses, net_log);
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ return rv == OK;
+}
+
+void SpdySessionPool::AddAlias(const IPEndPoint& endpoint,
+ const HostPortProxyPair& pair) {
+ DCHECK(enable_ip_pooling_);
+ aliases_[endpoint] = pair;
+}
+
+void SpdySessionPool::RemoveAliases(const HostPortProxyPair& pair) {
+ // Walk the aliases map, find references to this pair.
+ // TODO(mbelshe): Figure out if this is too expensive.
+ SpdyAliasMap::iterator alias_it = aliases_.begin();
+ while (alias_it != aliases_.end()) {
+ if (HostPortProxyPairsAreEqual(alias_it->second, pair)) {
+ aliases_.erase(alias_it);
+ alias_it = aliases_.begin(); // Iterator was invalidated.
+ continue;
+ }
+ ++alias_it;
+ }
+}
+
+void SpdySessionPool::CloseAllSessions() {
+ while (!sessions_.empty()) {
+ SpdySessionList* list = sessions_.begin()->second;
+ CHECK(list);
+ const scoped_refptr<SpdySession>& session = list->front();
+ CHECK(session);
+ // This call takes care of removing the session from the pool, as well as
+ // removing the session list if the list is empty.
+ session->CloseSessionOnError(
+ net::ERR_ABORTED, true, "Closing all sessions.");
+ }
+}
+
+void SpdySessionPool::CloseCurrentSessions(net::Error error) {
+ SpdySessionsMap old_map;
+ old_map.swap(sessions_);
+ for (SpdySessionsMap::const_iterator it = old_map.begin();
+ it != old_map.end(); ++it) {
+ SpdySessionList* list = it->second;
+ CHECK(list);
+ const scoped_refptr<SpdySession>& session = list->front();
+ CHECK(session);
+ session->set_spdy_session_pool(NULL);
+ }
+
+ while (!old_map.empty()) {
+ SpdySessionList* list = old_map.begin()->second;
+ CHECK(list);
+ const scoped_refptr<SpdySession>& session = list->front();
+ CHECK(session);
+ session->CloseSessionOnError(error, false, "Closing current sessions.");
+ list->pop_front();
+ if (list->empty()) {
+ delete list;
+ RemoveAliases(old_map.begin()->first);
+ old_map.erase(old_map.begin()->first);
+ }
+ }
+ DCHECK(sessions_.empty());
+ DCHECK(aliases_.empty());
+}
+
+void SpdySessionPool::CloseIdleSessions() {
+ SpdySessionsMap::const_iterator map_it = sessions_.begin();
+ while (map_it != sessions_.end()) {
+ SpdySessionList* list = map_it->second;
+ ++map_it;
+ CHECK(list);
+
+ // Assumes there is only 1 element in the list
+ SpdySessionList::iterator session_it = list->begin();
+ const scoped_refptr<SpdySession>& session = *session_it;
+ CHECK(session);
+ if (!session->is_active()) {
+ session->CloseSessionOnError(
+ net::ERR_ABORTED, true, "Closing idle sessions.");
+ }
+ }
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_session_pool.h b/src/net/spdy/spdy_session_pool.h
new file mode 100644
index 0000000..0467583
--- /dev/null
+++ b/src/net/spdy/spdy_session_pool.h
@@ -0,0 +1,230 @@
+// 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 <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/cert_database.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/base/ssl_config_service.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/next_proto.h"
+
+namespace net {
+
+class AddressList;
+class BoundNetLog;
+class ClientSocketHandle;
+class HostResolver;
+class HttpServerProperties;
+class SpdySession;
+
+namespace test_spdy2 {
+class SpdySessionPoolPeer;
+} // namespace test_spdy
+
+namespace test_spdy3 {
+class SpdySessionPoolPeer;
+} // namespace test_spdy
+
+// 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);
+
+ SpdySessionPool(HostResolver* host_resolver,
+ SSLConfigService* ssl_config_service,
+ HttpServerProperties* http_server_properties,
+ size_t max_sessions_per_domain,
+ 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 default_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();
+
+ // Either returns an existing SpdySession or creates a new SpdySession for
+ // use.
+ scoped_refptr<SpdySession> Get(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log);
+
+ // Only returns a SpdySession if it already exists.
+ scoped_refptr<SpdySession> GetIfExists(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log);
+
+ // Builds a SpdySession from an existing SSL socket. Users should try
+ // calling Get() first to use an existing SpdySession so we don't get
+ // multiple SpdySessions per domain. Note that ownership of |connection| is
+ // transferred from the caller to the SpdySession.
+ // |certificate_error_code| is used to indicate the certificate error
+ // encountered when connecting the SSL socket. OK means there was no error.
+ // For testing, setting is_secure to false allows Spdy to connect with a
+ // pre-existing TCP socket.
+ // Returns OK on success, and the |spdy_session| will be provided.
+ // Returns an error on failure, and |spdy_session| will be NULL.
+ net::Error GetSpdySessionFromSocket(
+ const HostPortProxyPair& host_port_proxy_pair,
+ ClientSocketHandle* connection,
+ const BoundNetLog& net_log,
+ int certificate_error_code,
+ scoped_refptr<SpdySession>* spdy_session,
+ bool is_secure);
+
+ // TODO(willchan): Consider renaming to HasReusableSession, since perhaps we
+ // should be creating a new session. WARNING: Because of IP connection pooling
+ // using the HostCache, if HasSession() returns true at one point, it does not
+ // imply the SpdySessionPool will still have a matching session in the near
+ // future, since the HostCache's entry may have expired.
+ bool HasSession(const HostPortProxyPair& host_port_proxy_pair) const;
+
+ // Close all SpdySessions, including any new ones created in the process of
+ // closing the current ones.
+ void CloseAllSessions();
+ // Close only the currently existing SpdySessions with |error|.
+ // Let any new ones created continue to live.
+ void CloseCurrentSessions(net::Error error);
+ // Close only the idle SpdySessions.
+ void CloseIdleSessions();
+
+ // Removes a SpdySession from the SpdySessionPool. This should only be called
+ // by SpdySession, because otherwise session->state_ is not set to CLOSED.
+ void Remove(const scoped_refptr<SpdySession>& session);
+
+ // Creates a Value summary of the state of the spdy session pool. The caller
+ // responsible for deleting the returned value.
+ base::Value* SpdySessionPoolInfoToValue() const;
+
+ 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 test_spdy2::SpdySessionPoolPeer; // For testing.
+ friend class test_spdy3::SpdySessionPoolPeer; // For testing.
+ friend class SpdyNetworkTransactionSpdy2Test; // For testing.
+ friend class SpdyNetworkTransactionSpdy3Test; // For testing.
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test,
+ WindowUpdateOverflow);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test,
+ WindowUpdateOverflow);
+
+ typedef std::list<scoped_refptr<SpdySession> > SpdySessionList;
+ typedef std::map<HostPortProxyPair, SpdySessionList*> SpdySessionsMap;
+ typedef std::map<IPEndPoint, HostPortProxyPair> SpdyAliasMap;
+
+
+ scoped_refptr<SpdySession> GetInternal(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log,
+ bool only_use_existing_sessions);
+ scoped_refptr<SpdySession> GetExistingSession(
+ SpdySessionList* list,
+ const BoundNetLog& net_log) const;
+ scoped_refptr<SpdySession> GetFromAlias(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log,
+ bool record_histograms) const;
+
+ // Helper functions for manipulating the lists.
+ const HostPortProxyPair& NormalizeListPair(
+ const HostPortProxyPair& host_port_proxy_pair) const;
+ SpdySessionList* AddSessionList(
+ const HostPortProxyPair& host_port_proxy_pair);
+ SpdySessionList* GetSessionList(
+ const HostPortProxyPair& host_port_proxy_pair) const;
+ void RemoveSessionList(const HostPortProxyPair& host_port_proxy_pair);
+
+ // Does a DNS cache lookup for |pair|, and returns the |addresses| found.
+ // Returns true if addresses found, false otherwise.
+ bool LookupAddresses(const HostPortProxyPair& pair,
+ const BoundNetLog& net_log,
+ AddressList* addresses) const;
+
+ // Add |address| as an IP-equivalent address for |pair|.
+ void AddAlias(const IPEndPoint& address, const HostPortProxyPair& pair);
+
+ // Remove all aliases for |pair| from the aliases table.
+ void RemoveAliases(const HostPortProxyPair& pair);
+
+ // Removes |session| from the session list associated with |pair|.
+ // Returns true if the session was removed, false otherwise.
+ bool RemoveFromSessionList(const scoped_refptr<SpdySession>& session,
+ const HostPortProxyPair& pair);
+
+ HttpServerProperties* const http_server_properties_;
+
+ // This is our weak session pool - one session per domain.
+ SpdySessionsMap sessions_;
+ // A map of IPEndPoint aliases for sessions.
+ SpdyAliasMap 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_settings_;
+ size_t max_sessions_per_domain_;
+ 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 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/src/net/spdy/spdy_session_spdy2_unittest.cc b/src/net/spdy/spdy_session_spdy2_unittest.cc
new file mode 100644
index 0000000..655676d
--- /dev/null
+++ b/src/net/spdy/spdy_session_spdy2_unittest.cc
@@ -0,0 +1,1871 @@
+// 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 "net/base/host_cache.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log_unittest.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+namespace {
+
+static int g_delta_seconds = 0;
+base::TimeTicks TheNearFuture() {
+ return base::TimeTicks::Now() + base::TimeDelta::FromSeconds(g_delta_seconds);
+}
+
+class ClosingDelegate : public SpdyStream::Delegate {
+ public:
+ ClosingDelegate(SpdyStream* stream) : stream_(stream) {}
+
+ // SpdyStream::Delegate implementation:
+ virtual bool OnSendHeadersComplete(int status) OVERRIDE {
+ return true;
+ }
+ virtual int OnSendBody() OVERRIDE {
+ return OK;
+ }
+ virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE {
+ return OK;
+ }
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE {
+ return OK;
+ }
+ virtual void OnHeadersSent() OVERRIDE {}
+ virtual int OnDataReceived(const char* data, int length) OVERRIDE {
+ return OK;
+ }
+ virtual void OnDataSent(int length) OVERRIDE {}
+ virtual void OnClose(int status) OVERRIDE {
+ stream_->Close();
+ }
+ private:
+ SpdyStream* stream_;
+};
+
+} // namespace
+
+class SpdySessionSpdy2Test : public PlatformTest {
+ protected:
+ void SetUp() {
+ g_delta_seconds = 0;
+ }
+};
+
+class TestSpdyStreamDelegate : public net::SpdyStream::Delegate {
+ public:
+ explicit TestSpdyStreamDelegate(const CompletionCallback& callback)
+ : callback_(callback) {}
+ virtual ~TestSpdyStreamDelegate() {}
+
+ virtual bool OnSendHeadersComplete(int status) OVERRIDE { return true; }
+
+ virtual int OnSendBody() OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE {
+ return status;
+ }
+
+ virtual void OnHeadersSent() OVERRIDE {}
+
+ virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnDataSent(int length) OVERRIDE {}
+
+ virtual void OnClose(int status) OVERRIDE {
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(OK);
+ }
+
+ private:
+ CompletionCallback callback_;
+};
+
+// Test the SpdyIOBuffer class.
+TEST_F(SpdySessionSpdy2Test, SpdyIOBuffer) {
+ std::priority_queue<SpdyIOBuffer> queue_;
+ const size_t kQueueSize = 100;
+
+ // Insert items with random priority and increasing buffer size.
+ for (size_t index = 0; index < kQueueSize; ++index) {
+ queue_.push(SpdyIOBuffer(
+ new IOBufferWithSize(index + 1),
+ index + 1,
+ static_cast<RequestPriority>(rand() % NUM_PRIORITIES),
+ NULL));
+ }
+
+ EXPECT_EQ(kQueueSize, queue_.size());
+
+ // Verify items come out with decreasing priority or FIFO order.
+ RequestPriority last_priority = NUM_PRIORITIES;
+ size_t last_size = 0;
+ for (size_t index = 0; index < kQueueSize; ++index) {
+ SpdyIOBuffer buffer = queue_.top();
+ EXPECT_LE(buffer.priority(), last_priority);
+ if (buffer.priority() < last_priority)
+ last_size = 0;
+ EXPECT_LT(last_size, buffer.size());
+ last_priority = buffer.priority();
+ last_size = buffer.size();
+ queue_.pop();
+ }
+
+ EXPECT_EQ(0u, queue_.size());
+}
+
+TEST_F(SpdySessionSpdy2Test, GoAway) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(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);
+ ssl.SetNextProto(kProtoSPDY2);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ EXPECT_EQ(2, session->GetProtocolVersion());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(pair, BoundNetLog());
+
+ // Delete the first session.
+ session = NULL;
+
+ // Delete the second session.
+ spdy_session_pool->Remove(session2);
+ session2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, ClientPing) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(callback1.callback()));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ base::TimeTicks before_ping_time = base::TimeTicks::Now();
+
+ session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+ session->set_hung_interval(base::TimeDelta::FromMilliseconds(50));
+
+ session->SendPrefacePingIfNoneInFlight();
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+
+ 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);
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the first session.
+ session = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, ServerPing) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(2));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(callback1.callback()));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the session.
+ session = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, DeleteExpiredPushStreams) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps.time_func = TheNearFuture;
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ // Give the session a SPDY2 framer.
+ session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(2, false));
+
+ // Create the associated stream and add to active streams.
+ scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
+ (*request_headers)["scheme"] = "http";
+ (*request_headers)["host"] = "www.google.com";
+ (*request_headers)["url"] = "/";
+
+ scoped_refptr<SpdyStream> stream(
+ new SpdyStream(session, false, session->net_log_));
+ stream->set_spdy_headers(request_headers.Pass());
+ session->ActivateStream(stream);
+
+ SpdyHeaderBlock headers;
+ headers["url"] = "http://www.google.com/a.dat";
+ session->OnSynStream(2, 1, 0, 0, true, false, headers);
+
+ // 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("http://www.google.com/a.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Shift time.
+ g_delta_seconds = 301;
+
+ headers["url"] = "http://www.google.com/b.dat";
+ session->OnSynStream(4, 1, 0, 0, true, false, headers);
+
+ // 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("http://www.google.com/b.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Delete the session.
+ session = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, FailedPing) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.gmail.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.gmail.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(callback1.callback()));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ 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(spdy_session_pool->HasSession(pair));
+
+ // 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->IsClosed());
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(0u, session->num_unclaimed_pushed_streams());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the first session.
+ session = NULL;
+}
+
+class StreamReleaserCallback : public TestCompletionCallbackBase {
+ public:
+ StreamReleaserCallback(SpdySession* session,
+ SpdyStream* first_stream)
+ : session_(session),
+ first_stream_(first_stream),
+ ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+ base::Bind(&StreamReleaserCallback::OnComplete,
+ base::Unretained(this)))) {
+ }
+
+ virtual ~StreamReleaserCallback() {}
+
+ scoped_refptr<SpdyStream>* stream() { return &stream_; }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ session_->CloseSessionOnError(ERR_FAILED, false, "On complete.");
+ session_ = NULL;
+ first_stream_->Cancel();
+ first_stream_ = NULL;
+ stream_->Cancel();
+ stream_ = NULL;
+ SetResult(result);
+ }
+
+ scoped_refptr<SpdySession> session_;
+ scoped_refptr<SpdyStream> first_stream_;
+ scoped_refptr<SpdyStream> stream_;
+ CompletionCallback callback_;
+};
+
+// TODO(kristianm): Could also test with more sessions where some are idle,
+// and more than one session to a HostPortPair.
+TEST_F(SpdySessionSpdy2Test, CloseIdleSessions) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Set up session 1
+ const std::string kTestHost1("http://www.a.com");
+ HostPortPair test_host_port_pair1(kTestHost1, 80);
+ HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session1 =
+ spdy_session_pool->Get(pair1, BoundNetLog());
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1(kTestHost1);
+ EXPECT_EQ(OK, session1->CreateStream(url1,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+
+ // Set up session 2
+ const std::string kTestHost2("http://www.b.com");
+ HostPortPair test_host_port_pair2(kTestHost2, 80);
+ HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(pair2, BoundNetLog());
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2(kTestHost2);
+ EXPECT_EQ(OK, session2->CreateStream(
+ url2, MEDIUM, /* priority, not important */
+ &spdy_stream2, BoundNetLog(), callback2.callback()));
+
+ // Set up session 3
+ const std::string kTestHost3("http://www.c.com");
+ HostPortPair test_host_port_pair3(kTestHost3, 80);
+ HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session3 =
+ spdy_session_pool->Get(pair3, BoundNetLog());
+ scoped_refptr<SpdyStream> spdy_stream3;
+ TestCompletionCallback callback3;
+ GURL url3(kTestHost3);
+ EXPECT_EQ(OK, session3->CreateStream(
+ url3, MEDIUM, /* priority, not important */
+ &spdy_stream3, BoundNetLog(), callback3.callback()));
+
+ // 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->CloseIdleSessions();
+ 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);
+ session3->CloseCreatedStream(spdy_stream3, OK);
+ 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->CloseIdleSessions();
+ EXPECT_FALSE(session1->is_active());
+ EXPECT_TRUE(session1->IsClosed());
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+ EXPECT_FALSE(session3->is_active());
+ EXPECT_TRUE(session3->IsClosed());
+
+ // Should not do anything
+ spdy_session_pool->CloseIdleSessions();
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // Make 2 not active
+ session2->CloseCreatedStream(spdy_stream2, OK);
+ EXPECT_FALSE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // This should close session 2
+ spdy_session_pool->CloseIdleSessions();
+ EXPECT_FALSE(session2->is_active());
+ EXPECT_TRUE(session2->IsClosed());
+}
+
+// Start with max concurrent streams set to 1. Request two streams. Receive a
+// settings frame setting max concurrent streams to 2. Have the callback
+// release the stream, which releases its reference (the last) to the session.
+// Make sure nothing blows up.
+// http://crbug.com/57331
+TEST_F(SpdySessionSpdy2Test, OnSettings) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ SettingsMap new_settings;
+ const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+ const size_t max_concurrent_streams = 2;
+ new_settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+ // Set up the socket so we read a SETTINGS frame that raises max concurrent
+ // streams to 2.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame),
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ // Initialize the SpdySetting with 1 max concurrent streams.
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ test_host_port_pair,
+ kSpdySettingsIds1,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ 1);
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // Create 2 streams. First will succeed. Second will be pending.
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url("http://www.google.com");
+ EXPECT_EQ(OK,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+
+ StreamReleaserCallback stream_releaser(session, spdy_stream1);
+
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ stream_releaser.stream(),
+ BoundNetLog(),
+ stream_releaser.callback()));
+
+ // Make sure |stream_releaser| holds the last refs.
+ session = NULL;
+ spdy_stream1 = NULL;
+
+ EXPECT_EQ(OK, stream_releaser.WaitForResult());
+}
+
+// Start with max concurrent streams set to 1. Request two streams. When the
+// first completes, have the callback close itself, which should trigger the
+// second stream creation. Then cancel that one immediately. Don't crash.
+// http://crbug.com/63532
+TEST_F(SpdySessionSpdy2Test, CancelPendingCreateStream) {
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ // Initialize the SpdySetting with 1 max concurrent streams.
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ test_host_port_pair,
+ SETTINGS_MAX_CONCURRENT_STREAMS,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ 1);
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // 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);
+
+ // Create 2 streams. First will succeed. Second will be pending.
+ scoped_refptr<SpdyStream> spdy_stream1;
+ GURL url("http://www.google.com");
+ ASSERT_EQ(OK,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback->callback()));
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream2,
+ BoundNetLog(),
+ callback->callback()));
+
+ // Release the first one, this will allow the second to be created.
+ spdy_stream1->Cancel();
+ spdy_stream1 = NULL;
+
+ session->CancelPendingCreateStreams(&spdy_stream2);
+ callback.reset();
+
+ // Should not crash when running the pending callback.
+ MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(SpdySessionSpdy2Test, SendInitialSettingsOnNewSession) {
+ SpdySessionDependencies session_deps;
+ 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;
+ settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(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);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ SpdySessionPoolPeer pool_peer(spdy_session_pool);
+ pool_peer.EnableSendingInitialSettings(true);
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ // Create the bogus setting that we want to verify is sent out.
+ // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But
+ // to persist it into the HttpServerProperties, we need to mark as
+ // SETTINGS_FLAG_PLEASE_PERSIST.
+ SettingsMap settings;
+ const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_UPLOAD_BANDWIDTH;
+ const uint32 kBogusSettingValue = 0xCDCD;
+ settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED, kBogusSettingValue);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(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);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ test_host_port_pair,
+ kSpdySettingsIds1,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ kBogusSettingValue);
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+namespace {
+// This test has two variants, one for each style of closing the connection.
+// If |clean_via_close_current_sessions| is false, the sessions are closed
+// manually, calling SpdySessionPool::Remove() directly. If it is true,
+// sessions are closed with SpdySessionPool::CloseCurrentSessions().
+void IPPoolingTest(bool clean_via_close_current_sessions) {
+ const int kTestPort = 80;
+ struct TestHosts {
+ std::string name;
+ std::string iplist;
+ HostPortProxyPair pair;
+ AddressList addresses;
+ } test_hosts[] = {
+ { "www.foo.com", "192.0.2.33,192.168.0.1,192.168.0.5" },
+ { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" },
+ { "js.foo.com", "192.168.0.4,192.168.0.3" },
+ };
+
+ SpdySessionDependencies session_deps;
+ 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, "");
+
+ // 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 HostPortProxyPair
+ test_hosts[i].pair = HostPortProxyPair(
+ HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct());
+ }
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Setup the first session to the first host.
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+
+ HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port
+ // number. Fix it to return port 80 and then use GetPeerAddress to AddAlias.
+ SpdySessionPoolPeer pool_peer(spdy_session_pool);
+ pool_peer.AddAlias(test_hosts[0].addresses.front(), test_hosts[0].pair);
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ // The third host has no overlap with the first, so it can't pool IPs.
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+ // The second host overlaps with the first, and should IP pool.
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+ // Verify that the second host, through a proxy, won't share the IP.
+ HostPortProxyPair proxy_pair(test_hosts[1].pair.first,
+ ProxyServer::FromPacString("HTTP http://proxy.foo.com/"));
+ EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair));
+
+ // Overlap between 2 and 3 does is not transitive to 1.
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+ // Create a new session to host 2.
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog());
+
+ // Verify that we have sessions for everything.
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+ // Grab the session to host 1 and verify that it is the same session
+ // we got with host 0, and that is a different than host 2's session.
+ scoped_refptr<SpdySession> session1 =
+ spdy_session_pool->Get(test_hosts[1].pair, 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.
+ pool_peer.RemoveAliases(test_hosts[0].pair);
+ pool_peer.RemoveAliases(test_hosts[1].pair);
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+ // Expire the host cache
+ session_deps.host_resolver->GetHostCache()->clear();
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+ // Cleanup the sessions.
+ if (!clean_via_close_current_sessions) {
+ spdy_session_pool->Remove(session);
+ session = NULL;
+ spdy_session_pool->Remove(session2);
+ session2 = NULL;
+ } else {
+ spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED);
+ }
+
+ // Verify that the map is all cleaned up.
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair));
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+}
+
+} // namespace
+
+TEST_F(SpdySessionSpdy2Test, IPPooling) {
+ IPPoolingTest(false);
+}
+
+TEST_F(SpdySessionSpdy2Test, IPPoolingCloseCurrentSessions) {
+ IPPoolingTest(true);
+}
+
+TEST_F(SpdySessionSpdy2Test, ClearSettingsStorageOnIPAddressChanged) {
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ 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_F(SpdySessionSpdy2Test, NeedsCredentials) {
+ SpdySessionDependencies session_deps;
+
+ 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 = kProtoSPDY2;
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ SSLConfig ssl_config;
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ 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,
+ test_host_port_pair,
+ ssl_config,
+ 0,
+ false,
+ false));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ ssl_params, MEDIUM, CompletionCallback(),
+ http_session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
+
+ EXPECT_FALSE(session->NeedsCredentials());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ spdy_session_pool->Remove(session);
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+}
+
+TEST_F(SpdySessionSpdy2Test, CloseSessionOnError) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*goaway),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+
+ CapturingBoundNetLog log;
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, log.bound());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ log.bound()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // 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);
+
+ CapturingNetLog::CapturedEntry entry = entries[pos];
+ int error_code = 0;
+ ASSERT_TRUE(entry.GetNetErrorCode(&error_code));
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code);
+}
+
+TEST_F(SpdySessionSpdy2Test, OutOfOrderSynStreams) {
+ // Construct the request.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, HIGHEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 2),
+ CreateMockWrite(*req2, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 3),
+ CreateMockRead(*body1, 4),
+ CreateMockRead(*resp2, 5),
+ CreateMockRead(*body2, 6),
+ MockRead(ASYNC, 0, 7) // EOF
+ };
+
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1, LOWEST, &spdy_stream1,
+ BoundNetLog(), callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url2, HIGHEST, &spdy_stream2,
+ BoundNetLog(), callback2.callback()));
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url1.scheme();
+ (*headers)["host"] = url1.host();
+ (*headers)["url"] = url1.path();
+ (*headers)["version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+
+ spdy_stream1->SendRequest(false);
+ spdy_stream2->SendRequest(false);
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(3u, spdy_stream1->stream_id());
+ EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+ spdy_stream1->Cancel();
+ spdy_stream1 = NULL;
+
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req2, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp2, 1),
+ CreateMockRead(*body2, 2),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+ BoundNetLog(), callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+ BoundNetLog(), callback2.callback()));
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url1.scheme();
+ (*headers)["host"] = url1.host();
+ (*headers)["url"] = url1.path();
+ (*headers)["version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+
+ spdy_stream1->SendRequest(false);
+ spdy_stream2->SendRequest(false);
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ spdy_stream1->Cancel();
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+ spdy_stream1 = NULL;
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, CloseSessionWithTwoCreatedStreams) {
+ // Test that if a sesion is closed with two created streams pending,
+ // it does not crash. http://crbug.com/139518
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ // 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+ BoundNetLog(), callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+ BoundNetLog(), callback2.callback()));
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url1.scheme();
+ (*headers)["host"] = url1.host();
+ (*headers)["url"] = url1.path();
+ (*headers)["version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+ ClosingDelegate delegate1(spdy_stream1.get());
+ spdy_stream1->SetDelegate(&delegate1);
+
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+ ClosingDelegate delegate2(spdy_stream2.get());
+ spdy_stream2->SetDelegate(&delegate2);
+
+ spdy_stream1->SendRequest(false);
+ spdy_stream2->SendRequest(false);
+
+ // 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, true, "");
+
+ EXPECT_TRUE(spdy_stream1->closed());
+ EXPECT_TRUE(spdy_stream2->closed());
+
+ spdy_stream1 = NULL;
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ 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(ConstructSpdySettings(new_settings));
+
+ scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(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
+ };
+
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, LOWEST, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // Read the settings frame.
+ data.RunFor(1);
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1,
+ LOWEST,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url2,
+ LOWEST,
+ &spdy_stream2,
+ BoundNetLog(),
+ callback2.callback()));
+
+ scoped_refptr<SpdyStream> spdy_stream3;
+ TestCompletionCallback callback3;
+ GURL url3("http://www.google.com");
+ EXPECT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url3,
+ LOWEST,
+ &spdy_stream3,
+ BoundNetLog(),
+ callback3.callback()));
+
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url1.scheme();
+ (*headers)["host"] = url1.host();
+ (*headers)["url"] = url1.path();
+ (*headers)["version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+ scoped_ptr<SpdyHeaderBlock> headers3(new SpdyHeaderBlock);
+ *headers3 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+ spdy_stream1->SendRequest(false);
+
+ // Run until 1st stream is closed.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+ EXPECT_TRUE(spdy_stream2.get() != NULL);
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+ spdy_stream2->SendRequest(false);
+
+ // Run until 2nd stream is closed.
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+ EXPECT_TRUE(spdy_stream3.get() != NULL);
+ spdy_stream3->set_spdy_headers(headers3.Pass());
+ EXPECT_TRUE(spdy_stream3->HasUrl());
+ spdy_stream3->SendRequest(false);
+
+ EXPECT_EQ(0u, spdy_stream3->stream_id());
+ data.RunFor(4);
+ EXPECT_EQ(5u, spdy_stream3->stream_id());
+ EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+TEST_F(SpdySessionSpdy2Test, CancelTwoStalledCreateStream) {
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // 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);
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params,
+ LOWEST,
+ CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ ASSERT_EQ(OK,
+ session->CreateStream(url1,
+ LOWEST,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url2,
+ LOWEST,
+ &spdy_stream2,
+ BoundNetLog(),
+ callback2.callback()));
+
+ scoped_refptr<SpdyStream> spdy_stream3;
+ TestCompletionCallback callback3;
+ GURL url3("http://www.google.com");
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url3,
+ LOWEST,
+ &spdy_stream3,
+ BoundNetLog(),
+ callback3.callback()));
+
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+ // Cancel the first stream, this will allow the second stream to be created.
+ EXPECT_TRUE(spdy_stream1.get() != NULL);
+ spdy_stream1->Cancel();
+ spdy_stream1 = NULL;
+ session->CancelPendingCreateStreams(&spdy_stream1);
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+ // Cancel the second stream, this will allow the third stream to be created.
+ EXPECT_TRUE(spdy_stream2.get() != NULL);
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+ session->CancelPendingCreateStreams(&spdy_stream2);
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+ // Cancel the third stream.
+ EXPECT_TRUE(spdy_stream3.get() != NULL);
+ spdy_stream3->Cancel();
+ spdy_stream3 = NULL;
+ session->CancelPendingCreateStreams(&spdy_stream3);
+ EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_session_spdy3_unittest.cc b/src/net/spdy/spdy_session_spdy3_unittest.cc
new file mode 100644
index 0000000..3065e64
--- /dev/null
+++ b/src/net/spdy/spdy_session_spdy3_unittest.cc
@@ -0,0 +1,2205 @@
+// 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 "net/base/cert_test_util.h"
+#include "net/base/host_cache.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_data_directory.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+namespace {
+
+static int g_delta_seconds = 0;
+base::TimeTicks TheNearFuture() {
+ return base::TimeTicks::Now() + base::TimeDelta::FromSeconds(g_delta_seconds);
+}
+
+class ClosingDelegate : public SpdyStream::Delegate {
+ public:
+ ClosingDelegate(SpdyStream* stream) : stream_(stream) {}
+
+ // SpdyStream::Delegate implementation:
+ virtual bool OnSendHeadersComplete(int status) OVERRIDE {
+ return true;
+ }
+ virtual int OnSendBody() OVERRIDE {
+ return OK;
+ }
+ virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE {
+ return OK;
+ }
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE {
+ return OK;
+ }
+ virtual void OnHeadersSent() OVERRIDE {}
+ virtual int OnDataReceived(const char* data, int length) OVERRIDE {
+ return OK;
+ }
+ virtual void OnDataSent(int length) OVERRIDE {}
+ virtual void OnClose(int status) OVERRIDE {
+ stream_->Close();
+ }
+ private:
+ SpdyStream* stream_;
+};
+
+
+class TestSpdyStreamDelegate : public SpdyStream::Delegate {
+ public:
+ explicit TestSpdyStreamDelegate(const CompletionCallback& callback)
+ : callback_(callback) {}
+ virtual ~TestSpdyStreamDelegate() {}
+
+ virtual bool OnSendHeadersComplete(int status) OVERRIDE { return true; }
+
+ virtual int OnSendBody() OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE {
+ return status;
+ }
+
+ virtual void OnHeadersSent() OVERRIDE {}
+ virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnDataSent(int length) OVERRIDE {}
+
+ virtual void OnClose(int status) OVERRIDE {
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(OK);
+ }
+
+ private:
+ CompletionCallback callback_;
+};
+
+} // namespace
+
+class SpdySessionSpdy3Test : public PlatformTest {
+ protected:
+ void SetUp() {
+ g_delta_seconds = 0;
+ }
+};
+
+// Test the SpdyIOBuffer class.
+TEST_F(SpdySessionSpdy3Test, SpdyIOBuffer) {
+ std::priority_queue<SpdyIOBuffer> queue_;
+ const size_t kQueueSize = 100;
+
+ // Insert items with random priority and increasing buffer size
+ for (size_t index = 0; index < kQueueSize; ++index) {
+ queue_.push(SpdyIOBuffer(
+ new IOBufferWithSize(index + 1),
+ index + 1,
+ static_cast<RequestPriority>(rand() % NUM_PRIORITIES),
+ NULL));
+ }
+
+ EXPECT_EQ(kQueueSize, queue_.size());
+
+ // Verify items come out with decreasing priority or FIFO order.
+ RequestPriority last_priority = NUM_PRIORITIES;
+ size_t last_size = 0;
+ for (size_t index = 0; index < kQueueSize; ++index) {
+ SpdyIOBuffer buffer = queue_.top();
+ EXPECT_LE(buffer.priority(), last_priority);
+ if (buffer.priority() < last_priority)
+ last_size = 0;
+ EXPECT_LT(last_size, buffer.size());
+ last_priority = buffer.priority();
+ last_size = buffer.size();
+ queue_.pop();
+ }
+
+ EXPECT_EQ(0u, queue_.size());
+}
+
+TEST_F(SpdySessionSpdy3Test, GoAway) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(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);
+ ssl.SetNextProto(kProtoSPDY3);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ EXPECT_EQ(3, session->GetProtocolVersion());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(pair, BoundNetLog());
+
+ // Delete the first session.
+ session = NULL;
+
+ // Delete the second session.
+ spdy_session_pool->Remove(session2);
+ session2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, ClientPing) {
+ SpdySessionDependencies session_deps;
+ session_deps.enable_ping = true;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(callback1.callback()));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ base::TimeTicks before_ping_time = base::TimeTicks::Now();
+
+ session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+ session->set_hung_interval(base::TimeDelta::FromMilliseconds(50));
+
+ session->SendPrefacePingIfNoneInFlight();
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+
+ 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);
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the first session.
+ session = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, ServerPing) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(2));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(callback1.callback()));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the session.
+ session = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, DeleteExpiredPushStreams) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps.time_func = TheNearFuture;
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ // Give the session a SPDY3 framer.
+ session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(3, false));
+
+ // Create the associated stream and add to active streams.
+ scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
+ (*request_headers)[":scheme"] = "http";
+ (*request_headers)[":host"] = "www.google.com";
+ (*request_headers)[":path"] = "/";
+
+ scoped_refptr<SpdyStream> stream(
+ new SpdyStream(session, false, session->net_log_));
+ stream->set_spdy_headers(request_headers.Pass());
+ session->ActivateStream(stream);
+
+ SpdyHeaderBlock headers;
+ headers[":scheme"] = "http";
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/a.dat";
+ session->OnSynStream(2, 1, 0, 0, true, false, headers);
+
+ // 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("http://www.google.com/a.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Shift time.
+ g_delta_seconds = 301;
+
+ headers[":scheme"] = "http";
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/b.dat";
+ session->OnSynStream(4, 1, 0, 0, true, false, headers);
+
+ // 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("http://www.google.com/b.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Delete the session.
+ session = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, FailedPing) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.gmail.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.gmail.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(callback1.callback()));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ 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(spdy_session_pool->HasSession(pair));
+
+ // 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->IsClosed());
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(0u, session->num_unclaimed_pushed_streams());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the first session.
+ session = NULL;
+}
+
+class StreamReleaserCallback : public TestCompletionCallbackBase {
+ public:
+ StreamReleaserCallback(SpdySession* session,
+ SpdyStream* first_stream)
+ : session_(session),
+ first_stream_(first_stream),
+ ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+ base::Bind(&StreamReleaserCallback::OnComplete,
+ base::Unretained(this)))) {
+ }
+
+ virtual ~StreamReleaserCallback() {}
+
+ scoped_refptr<SpdyStream>* stream() { return &stream_; }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ session_->CloseSessionOnError(ERR_FAILED, false, "On complete.");
+ session_ = NULL;
+ first_stream_->Cancel();
+ first_stream_ = NULL;
+ stream_->Cancel();
+ stream_ = NULL;
+ SetResult(result);
+ }
+
+ scoped_refptr<SpdySession> session_;
+ scoped_refptr<SpdyStream> first_stream_;
+ scoped_refptr<SpdyStream> stream_;
+ CompletionCallback callback_;
+};
+
+// TODO(kristianm): Could also test with more sessions where some are idle,
+// and more than one session to a HostPortPair.
+TEST_F(SpdySessionSpdy3Test, CloseIdleSessions) {
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Set up session 1
+ const std::string kTestHost1("http://www.a.com");
+ HostPortPair test_host_port_pair1(kTestHost1, 80);
+ HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session1 =
+ spdy_session_pool->Get(pair1, BoundNetLog());
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1(kTestHost1);
+ EXPECT_EQ(OK, session1->CreateStream(url1,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+
+ // Set up session 2
+ const std::string kTestHost2("http://www.b.com");
+ HostPortPair test_host_port_pair2(kTestHost2, 80);
+ HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(pair2, BoundNetLog());
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2(kTestHost2);
+ EXPECT_EQ(OK, session2->CreateStream(
+ url2, MEDIUM, /* priority, not important */
+ &spdy_stream2, BoundNetLog(), callback2.callback()));
+
+ // Set up session 3
+ const std::string kTestHost3("http://www.c.com");
+ HostPortPair test_host_port_pair3(kTestHost3, 80);
+ HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session3 =
+ spdy_session_pool->Get(pair3, BoundNetLog());
+ scoped_refptr<SpdyStream> spdy_stream3;
+ TestCompletionCallback callback3;
+ GURL url3(kTestHost3);
+ EXPECT_EQ(OK, session3->CreateStream(
+ url3, MEDIUM, /* priority, not important */
+ &spdy_stream3, BoundNetLog(), callback3.callback()));
+
+ // 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->CloseIdleSessions();
+ 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);
+ session3->CloseCreatedStream(spdy_stream3, OK);
+ 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->CloseIdleSessions();
+ EXPECT_FALSE(session1->is_active());
+ EXPECT_TRUE(session1->IsClosed());
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+ EXPECT_FALSE(session3->is_active());
+ EXPECT_TRUE(session3->IsClosed());
+
+ // Should not do anything
+ spdy_session_pool->CloseIdleSessions();
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // Make 2 not active
+ session2->CloseCreatedStream(spdy_stream2, OK);
+ EXPECT_FALSE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // This should close session 2
+ spdy_session_pool->CloseIdleSessions();
+ EXPECT_FALSE(session2->is_active());
+ EXPECT_TRUE(session2->IsClosed());
+}
+
+// Start with max concurrent streams set to 1. Request two streams. Receive a
+// settings frame setting max concurrent streams to 2. Have the callback
+// release the stream, which releases its reference (the last) to the session.
+// Make sure nothing blows up.
+// http://crbug.com/57331
+TEST_F(SpdySessionSpdy3Test, OnSettings) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ SettingsMap new_settings;
+ const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+ const size_t max_concurrent_streams = 2;
+ new_settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+ // Set up the socket so we read a SETTINGS frame that raises max concurrent
+ // streams to 2.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame),
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ // Initialize the SpdySetting with 1 max concurrent streams.
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ test_host_port_pair,
+ kSpdySettingsIds1,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ 1);
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // Create 2 streams. First will succeed. Second will be pending.
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url("http://www.google.com");
+ EXPECT_EQ(OK,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+
+ StreamReleaserCallback stream_releaser(session, spdy_stream1);
+
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ stream_releaser.stream(),
+ BoundNetLog(),
+ stream_releaser.callback()));
+
+ // Make sure |stream_releaser| holds the last refs.
+ session = NULL;
+ spdy_stream1 = NULL;
+
+ EXPECT_EQ(OK, stream_releaser.WaitForResult());
+}
+
+// Start with max concurrent streams set to 1. Request two streams. When the
+// first completes, have the callback close itself, which should trigger the
+// second stream creation. Then cancel that one immediately. Don't crash.
+// http://crbug.com/63532
+TEST_F(SpdySessionSpdy3Test, CancelPendingCreateStream) {
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ // Initialize the SpdySetting with 1 max concurrent streams.
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ test_host_port_pair,
+ SETTINGS_MAX_CONCURRENT_STREAMS,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ 1);
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // 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);
+
+ // Create 2 streams. First will succeed. Second will be pending.
+ scoped_refptr<SpdyStream> spdy_stream1;
+ GURL url("http://www.google.com");
+ ASSERT_EQ(OK,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback->callback()));
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream2,
+ BoundNetLog(),
+ callback->callback()));
+
+ // Release the first one, this will allow the second to be created.
+ spdy_stream1->Cancel();
+ spdy_stream1 = NULL;
+
+ session->CancelPendingCreateStreams(&spdy_stream2);
+ callback.reset();
+
+ // Should not crash when running the pending callback.
+ MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(SpdySessionSpdy3Test, SendInitialSettingsOnNewSession) {
+ SpdySessionDependencies session_deps;
+ 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);
+ settings[kSpdySettingsIds2] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kInitialRecvWindowSize);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+ MockWrite writes[] = {
+ CreateMockWrite(*settings_frame),
+ };
+ session_deps.initial_recv_window_size = kInitialRecvWindowSize;
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ SpdySessionPoolPeer pool_peer(spdy_session_pool);
+ pool_peer.EnableSendingInitialSettings(true);
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_F(SpdySessionSpdy3Test, SendSettingsOnNewSession) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ // Create the bogus setting that we want to verify is sent out.
+ // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But
+ // to persist it into the HttpServerProperties, we need to mark as
+ // SETTINGS_FLAG_PLEASE_PERSIST.
+ SettingsMap settings;
+ const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_UPLOAD_BANDWIDTH;
+ const uint32 kBogusSettingValue = 0xCDCD;
+ settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED, kBogusSettingValue);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(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);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ test_host_port_pair,
+ kSpdySettingsIds1,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ kBogusSettingValue);
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+namespace {
+// This test has two variants, one for each style of closing the connection.
+// If |clean_via_close_current_sessions| is false, the sessions are closed
+// manually, calling SpdySessionPool::Remove() directly. If it is true,
+// sessions are closed with SpdySessionPool::CloseCurrentSessions().
+void IPPoolingTest(bool clean_via_close_current_sessions) {
+ const int kTestPort = 80;
+ struct TestHosts {
+ std::string name;
+ std::string iplist;
+ HostPortProxyPair pair;
+ AddressList addresses;
+ } test_hosts[] = {
+ { "www.foo.com", "192.0.2.33,192.168.0.1,192.168.0.5" },
+ { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" },
+ { "js.foo.com", "192.168.0.4,192.168.0.3" },
+ };
+
+ SpdySessionDependencies session_deps;
+ 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, "");
+
+ // 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 HostPortProxyPair
+ test_hosts[i].pair = HostPortProxyPair(
+ HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct());
+ }
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Setup the first session to the first host.
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+
+ HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port
+ // number. Fix it to return port 80 and then use GetPeerAddress to AddAlias.
+ SpdySessionPoolPeer pool_peer(spdy_session_pool);
+ pool_peer.AddAlias(test_hosts[0].addresses.front(), test_hosts[0].pair);
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ // The third host has no overlap with the first, so it can't pool IPs.
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+ // The second host overlaps with the first, and should IP pool.
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+ // Verify that the second host, through a proxy, won't share the IP.
+ HostPortProxyPair proxy_pair(test_hosts[1].pair.first,
+ ProxyServer::FromPacString("HTTP http://proxy.foo.com/"));
+ EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair));
+
+ // Overlap between 2 and 3 does is not transitive to 1.
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+ // Create a new session to host 2.
+ scoped_refptr<SpdySession> session2 =
+ spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog());
+
+ // Verify that we have sessions for everything.
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+ // Grab the session to host 1 and verify that it is the same session
+ // we got with host 0, and that is a different than host 2's session.
+ scoped_refptr<SpdySession> session1 =
+ spdy_session_pool->Get(test_hosts[1].pair, 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.
+ pool_peer.RemoveAliases(test_hosts[0].pair);
+ pool_peer.RemoveAliases(test_hosts[1].pair);
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+ // Expire the host cache
+ session_deps.host_resolver->GetHostCache()->clear();
+ EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+ // Cleanup the sessions.
+ if (!clean_via_close_current_sessions) {
+ spdy_session_pool->Remove(session);
+ session = NULL;
+ spdy_session_pool->Remove(session2);
+ session2 = NULL;
+ } else {
+ spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED);
+ }
+
+ // Verify that the map is all cleaned up.
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair));
+ EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+}
+
+} // namespace
+
+TEST_F(SpdySessionSpdy3Test, IPPooling) {
+ IPPoolingTest(false);
+}
+
+TEST_F(SpdySessionSpdy3Test, IPPoolingCloseCurrentSessions) {
+ IPPoolingTest(true);
+}
+
+TEST_F(SpdySessionSpdy3Test, ClearSettingsStorageOnIPAddressChanged) {
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+
+ SpdySessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ 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_F(SpdySessionSpdy3Test, NeedsCredentials) {
+ SpdySessionDependencies session_deps;
+
+ 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 = kProtoSPDY3;
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const GURL url("https:/www.foo.com");
+ HostPortPair test_host_port_pair(url.host(), 443);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ SSLConfig ssl_config;
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ 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,
+ test_host_port_pair,
+ ssl_config,
+ 0,
+ false,
+ false));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ ssl_params, MEDIUM, CompletionCallback(),
+ http_session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
+
+ EXPECT_TRUE(session->NeedsCredentials());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ spdy_session_pool->Remove(session);
+}
+
+TEST_F(SpdySessionSpdy3Test, SendCredentials) {
+ SpdySessionDependencies session_deps;
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+ SettingsMap settings;
+ scoped_ptr<SpdyFrame> settings_frame(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 = kProtoSPDY3;
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const GURL kTestUrl("https://www.foo.com");
+ HostPortPair test_host_port_pair(kTestUrl.host(), 443);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ SSLConfig ssl_config;
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ 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,
+ test_host_port_pair,
+ ssl_config,
+ 0,
+ false,
+ false));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ ssl_params, MEDIUM, CompletionCallback(),
+ http_session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
+ EXPECT_TRUE(session->NeedsCredentials());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ spdy_session_pool->Remove(session);
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+}
+
+TEST_F(SpdySessionSpdy3Test, CloseSessionOnError) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*goaway),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+
+ CapturingBoundNetLog log;
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, log.bound());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ log.bound()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // Flush the SpdySession::OnReadComplete() task.
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // 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);
+
+ CapturingNetLog::CapturedEntry entry = entries[pos];
+ int error_code = 0;
+ ASSERT_TRUE(entry.GetNetErrorCode(&error_code));
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code);
+}
+
+TEST_F(SpdySessionSpdy3Test, UpdateStreamsSendWindowSize) {
+ // 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(ConstructSpdySettings(new_settings));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ MockRead(ASYNC, 0, 1) // EOF
+ };
+
+ SpdySessionDependencies session_deps;
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url("http://www.google.com");
+ EXPECT_EQ(OK,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ EXPECT_NE(spdy_stream1->send_window_size(), window_size);
+
+ data->RunFor(1); // Process the SETTINGS frame, but not the EOF
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(session->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();
+ spdy_stream1 = NULL;
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ EXPECT_EQ(OK,
+ session->CreateStream(url,
+ MEDIUM, /* priority, not important */
+ &spdy_stream2,
+ BoundNetLog(),
+ callback1.callback()));
+
+ EXPECT_EQ(spdy_stream2->send_window_size(), window_size);
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, OutOfOrderSynStreams) {
+ // Construct the request.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, HIGHEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 2),
+ CreateMockWrite(*req2, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(3, true));
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(5, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 3),
+ CreateMockRead(*body1, 4),
+ CreateMockRead(*resp2, 5),
+ CreateMockRead(*body2, 6),
+ MockRead(ASYNC, 0, 7) // EOF
+ };
+
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1, LOWEST, &spdy_stream1,
+ BoundNetLog(), callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url2, HIGHEST, &spdy_stream2,
+ BoundNetLog(), callback2.callback()));
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":method"] = "GET";
+ (*headers)[":scheme"] = url1.scheme();
+ (*headers)[":host"] = url1.host();
+ (*headers)[":path"] = url1.path();
+ (*headers)[":version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+
+ spdy_stream1->SendRequest(false);
+ spdy_stream2->SendRequest(false);
+ MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(3u, spdy_stream1->stream_id());
+ EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+ spdy_stream1->Cancel();
+ spdy_stream1 = NULL;
+
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req2, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp2, 1),
+ CreateMockRead(*body2, 2),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+ BoundNetLog(), callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+ BoundNetLog(), callback2.callback()));
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":method"] = "GET";
+ (*headers)[":scheme"] = url1.scheme();
+ (*headers)[":host"] = url1.host();
+ (*headers)[":path"] = url1.path();
+ (*headers)[":version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+
+ spdy_stream1->SendRequest(false);
+ spdy_stream2->SendRequest(false);
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ spdy_stream1->Cancel();
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+ spdy_stream1 = NULL;
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, CloseSessionWithTwoCreatedStreams) {
+ // Test that if a sesion is closed with two created streams pending,
+ // it does not crash. http://crbug.com/139518
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ // 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, MEDIUM, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+ BoundNetLog(), callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+ BoundNetLog(), callback2.callback()));
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url1.scheme();
+ (*headers)["host"] = url1.host();
+ (*headers)["url"] = url1.path();
+ (*headers)["version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+ ClosingDelegate delegate1(spdy_stream1.get());
+ spdy_stream1->SetDelegate(&delegate1);
+
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+ ClosingDelegate delegate2(spdy_stream2.get());
+ spdy_stream2->SetDelegate(&delegate2);
+
+ spdy_stream1->SendRequest(false);
+ spdy_stream2->SendRequest(false);
+
+ // 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, true, "");
+
+ EXPECT_TRUE(spdy_stream1->closed());
+ EXPECT_TRUE(spdy_stream2->closed());
+
+ spdy_stream1 = NULL;
+ spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, VerifyDomainAuthentication) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ // 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
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.example.org");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ SSLConfig ssl_config;
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ 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,
+ test_host_port_pair,
+ ssl_config,
+ 0,
+ false,
+ false));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ ssl_params, MEDIUM, CompletionCallback(),
+ http_session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ 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_F(SpdySessionSpdy3Test, ConnectionPooledWithTlsChannelId) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ // 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
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.example.org");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ SSLConfig ssl_config;
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ 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,
+ test_host_port_pair,
+ ssl_config,
+ 0,
+ false,
+ false));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ ssl_params, MEDIUM, CompletionCallback(),
+ http_session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+ 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_F(SpdySessionSpdy3Test, 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(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+ scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+ 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(ConstructSpdySettings(new_settings));
+
+ scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(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
+ };
+
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params, LOWEST, CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ // Read the settings frame.
+ data.RunFor(1);
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ EXPECT_EQ(OK, session->CreateStream(url1,
+ LOWEST,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ EXPECT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url2,
+ LOWEST,
+ &spdy_stream2,
+ BoundNetLog(),
+ callback2.callback()));
+
+ scoped_refptr<SpdyStream> spdy_stream3;
+ TestCompletionCallback callback3;
+ GURL url3("http://www.google.com");
+ EXPECT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url3,
+ LOWEST,
+ &spdy_stream3,
+ BoundNetLog(),
+ callback3.callback()));
+
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":method"] = "GET";
+ (*headers)[":scheme"] = url1.scheme();
+ (*headers)[":host"] = url1.host();
+ (*headers)[":path"] = url1.path();
+ (*headers)[":version"] = "HTTP/1.1";
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+ *headers2 = *headers;
+ scoped_ptr<SpdyHeaderBlock> headers3(new SpdyHeaderBlock);
+ *headers3 = *headers;
+
+ spdy_stream1->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(spdy_stream1->HasUrl());
+ spdy_stream1->SendRequest(false);
+
+ // Run until 1st stream is closed.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+ EXPECT_TRUE(spdy_stream2.get() != NULL);
+ spdy_stream2->set_spdy_headers(headers2.Pass());
+ EXPECT_TRUE(spdy_stream2->HasUrl());
+ spdy_stream2->SendRequest(false);
+
+ // Run until 2nd stream is closed.
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+ EXPECT_TRUE(spdy_stream3.get() != NULL);
+ spdy_stream3->set_spdy_headers(headers3.Pass());
+ EXPECT_TRUE(spdy_stream3->HasUrl());
+ spdy_stream3->SendRequest(false);
+
+ EXPECT_EQ(0u, spdy_stream3->stream_id());
+ data.RunFor(4);
+ EXPECT_EQ(5u, spdy_stream3->stream_id());
+ EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+TEST_F(SpdySessionSpdy3Test, CancelTwoStalledCreateStream) {
+ SpdySessionDependencies session_deps;
+ 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);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.foo.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+ // 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);
+
+ // Create a session.
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+ transport_params,
+ LOWEST,
+ CompletionCallback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestCompletionCallback callback1;
+ GURL url1("http://www.google.com");
+ ASSERT_EQ(OK,
+ session->CreateStream(url1,
+ LOWEST,
+ &spdy_stream1,
+ BoundNetLog(),
+ callback1.callback()));
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_refptr<SpdyStream> spdy_stream2;
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url2,
+ LOWEST,
+ &spdy_stream2,
+ BoundNetLog(),
+ callback2.callback()));
+
+ scoped_refptr<SpdyStream> spdy_stream3;
+ TestCompletionCallback callback3;
+ GURL url3("http://www.google.com");
+ ASSERT_EQ(ERR_IO_PENDING,
+ session->CreateStream(url3,
+ LOWEST,
+ &spdy_stream3,
+ BoundNetLog(),
+ callback3.callback()));
+
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+ // Cancel the first stream, this will allow the second stream to be created.
+ EXPECT_TRUE(spdy_stream1.get() != NULL);
+ spdy_stream1->Cancel();
+ spdy_stream1 = NULL;
+ session->CancelPendingCreateStreams(&spdy_stream1);
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+ // Cancel the second stream, this will allow the third stream to be created.
+ EXPECT_TRUE(spdy_stream2.get() != NULL);
+ spdy_stream2->Cancel();
+ spdy_stream2 = NULL;
+ session->CancelPendingCreateStreams(&spdy_stream2);
+ EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+ // Cancel the third stream.
+ EXPECT_TRUE(spdy_stream3.get() != NULL);
+ spdy_stream3->Cancel();
+ spdy_stream3 = NULL;
+ session->CancelPendingCreateStreams(&spdy_stream3);
+ EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_stream.cc b/src/net/spdy/spdy_stream.cc
new file mode 100644
index 0000000..aeb370e
--- /dev/null
+++ b/src/net/spdy/spdy_stream.cc
@@ -0,0 +1,867 @@
+// 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/logging.h"
+#include "base/message_loop.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+namespace {
+
+Value* NetLogSpdyStreamErrorCallback(SpdyStreamId stream_id,
+ int status,
+ const std::string* description,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("status", status);
+ dict->SetString("description", *description);
+ return dict;
+}
+
+Value* NetLogSpdyStreamWindowUpdateCallback(SpdyStreamId stream_id,
+ int32 delta,
+ int32 window_size,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("stream_id", stream_id);
+ dict->SetInteger("delta", delta);
+ dict->SetInteger("window_size", window_size);
+ return dict;
+}
+
+bool ContainsUpperAscii(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
+
+SpdyStream::SpdyStream(SpdySession* session,
+ bool pushed,
+ const BoundNetLog& net_log)
+ : continue_buffering_data_(true),
+ stream_id_(0),
+ priority_(HIGHEST),
+ slot_(0),
+ stalled_by_flow_control_(false),
+ send_window_size_(kSpdyStreamInitialWindowSize),
+ recv_window_size_(kSpdyStreamInitialWindowSize),
+ unacked_recv_window_bytes_(0),
+ pushed_(pushed),
+ response_received_(false),
+ session_(session),
+ delegate_(NULL),
+ request_time_(base::Time::Now()),
+ response_(new SpdyHeaderBlock),
+ io_state_(STATE_NONE),
+ response_status_(OK),
+ cancelled_(false),
+ has_upload_data_(false),
+ net_log_(net_log),
+ send_bytes_(0),
+ recv_bytes_(0),
+ domain_bound_cert_type_(CLIENT_CERT_INVALID_TYPE),
+ domain_bound_cert_request_handle_(NULL) {
+}
+
+class SpdyStream::SpdyStreamIOBufferProducer
+ : public SpdySession::SpdyIOBufferProducer {
+ public:
+ SpdyStreamIOBufferProducer(SpdyStream* stream) : stream_(stream) {}
+
+ // SpdyFrameProducer
+ virtual RequestPriority GetPriority() const OVERRIDE {
+ return stream_->priority();
+ }
+
+ virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) OVERRIDE {
+ if (stream_->cancelled())
+ return NULL;
+ if (stream_->stream_id() == 0)
+ SpdySession::SpdyIOBufferProducer::ActivateStream(session, stream_);
+ frame_.reset(stream_->ProduceNextFrame());
+ return frame_ == NULL ? NULL :
+ SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
+ frame_.get(), GetPriority(), stream_);
+ }
+
+ private:
+ scoped_refptr<SpdyStream> stream_;
+ scoped_ptr<SpdyFrame> frame_;
+};
+
+void SpdyStream::SetHasWriteAvailable() {
+ session_->SetStreamHasWriteAvailable(this,
+ new SpdyStreamIOBufferProducer(this));
+}
+
+SpdyFrame* SpdyStream::ProduceNextFrame() {
+ if (io_state_ == STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE) {
+ CHECK(request_.get());
+ CHECK_GT(stream_id_, 0u);
+
+ std::string origin = GetUrl().GetOrigin().spec();
+ DCHECK(origin[origin.length() - 1] == '/');
+ origin.erase(origin.length() - 1); // Trim trailing slash.
+ SpdyCredentialControlFrame* frame = session_->CreateCredentialFrame(
+ origin, domain_bound_cert_type_, domain_bound_private_key_,
+ domain_bound_cert_, priority_);
+ return frame;
+ } else if (io_state_ == STATE_SEND_HEADERS_COMPLETE) {
+ CHECK(request_.get());
+ CHECK_GT(stream_id_, 0u);
+
+ SpdyControlFlags flags =
+ has_upload_data_ ? CONTROL_FLAG_NONE : CONTROL_FLAG_FIN;
+ SpdySynStreamControlFrame* frame = session_->CreateSynStream(
+ stream_id_, priority_, slot_, flags, *request_);
+ send_time_ = base::TimeTicks::Now();
+ return frame;
+ } else {
+ CHECK(!cancelled());
+ // We must need to write stream data.
+ // Until the headers have been completely sent, we can not be sure
+ // that our stream_id is correct.
+ DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
+ DCHECK_GT(stream_id_, 0u);
+ DCHECK(!pending_frames_.empty());
+
+ PendingFrame frame = pending_frames_.front();
+ pending_frames_.pop_front();
+
+ waiting_completions_.push_back(frame.type);
+
+ if (frame.type == TYPE_DATA) {
+ // Send queued data frame.
+ return frame.data_frame;
+ } else {
+ DCHECK(frame.type == TYPE_HEADERS);
+ // Create actual HEADERS frame just in time because it depends on
+ // compression context and should not be reordered after the creation.
+ SpdyFrame* header_frame = session_->CreateHeadersFrame(
+ stream_id_, *frame.header_block, SpdyControlFlags());
+ delete frame.header_block;
+ return header_frame;
+ }
+ }
+ NOTREACHED();
+}
+
+SpdyStream::~SpdyStream() {
+ UpdateHistograms();
+ while (!pending_frames_.empty()) {
+ PendingFrame frame = pending_frames_.back();
+ pending_frames_.pop_back();
+ if (frame.type == TYPE_DATA)
+ delete frame.data_frame;
+ else
+ delete frame.header_block;
+ }
+}
+
+void SpdyStream::SetDelegate(Delegate* delegate) {
+ CHECK(delegate);
+ delegate_ = delegate;
+
+ if (pushed_) {
+ CHECK(response_received());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SpdyStream::PushedStreamReplayData, this));
+ } else {
+ continue_buffering_data_ = false;
+ }
+}
+
+void SpdyStream::PushedStreamReplayData() {
+ if (cancelled_ || !delegate_)
+ return;
+
+ continue_buffering_data_ = false;
+
+ int rv = delegate_->OnResponseReceived(*response_, response_time_, OK);
+ if (rv == ERR_INCOMPLETE_SPDY_HEADERS) {
+ // We don't have complete headers. Assume we're waiting for another
+ // HEADERS frame. Since we don't have headers, we had better not have
+ // any pending data frames.
+ if (pending_buffers_.size() != 0U) {
+ LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+ "HEADERS incomplete headers, but pending data frames.");
+ session_->CloseStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+ }
+ return;
+ }
+
+ std::vector<scoped_refptr<IOBufferWithSize> > buffers;
+ buffers.swap(pending_buffers_);
+ for (size_t i = 0; i < buffers.size(); ++i) {
+ // It is always possible that a callback to the delegate results in
+ // the delegate no longer being available.
+ if (!delegate_)
+ break;
+ if (buffers[i]) {
+ delegate_->OnDataReceived(buffers[i]->data(), buffers[i]->size());
+ } else {
+ delegate_->OnDataReceived(NULL, 0);
+ session_->CloseStream(stream_id_, net::OK);
+ // Note: |this| may be deleted after calling CloseStream.
+ DCHECK_EQ(buffers.size() - 1, i);
+ }
+ }
+}
+
+void SpdyStream::DetachDelegate() {
+ delegate_ = NULL;
+ if (!closed())
+ Cancel();
+}
+
+const SpdyHeaderBlock& SpdyStream::spdy_headers() const {
+ DCHECK(request_ != NULL);
+ return *request_.get();
+}
+
+void SpdyStream::set_spdy_headers(scoped_ptr<SpdyHeaderBlock> headers) {
+ request_.reset(headers.release());
+}
+
+void SpdyStream::set_initial_recv_window_size(int32 window_size) {
+ session_->set_initial_recv_window_size(window_size);
+}
+
+void SpdyStream::PossiblyResumeIfStalled() {
+ if (send_window_size_ > 0 && stalled_by_flow_control_) {
+ stalled_by_flow_control_ = false;
+ io_state_ = STATE_SEND_BODY;
+ DoLoop(OK);
+ }
+}
+
+void SpdyStream::AdjustSendWindowSize(int32 delta_window_size) {
+ send_window_size_ += delta_window_size;
+ PossiblyResumeIfStalled();
+}
+
+void SpdyStream::IncreaseSendWindowSize(int32 delta_window_size) {
+ DCHECK(session_->is_flow_control_enabled());
+ DCHECK_GE(delta_window_size, 1);
+
+ // Ignore late WINDOW_UPDATEs.
+ if (closed())
+ return;
+
+ int32 new_window_size = send_window_size_ + delta_window_size;
+
+ // It's valid for send_window_size_ to become negative (via an incoming
+ // SETTINGS), in which case incoming WINDOW_UPDATEs will eventually make
+ // it positive; however, if send_window_size_ is positive and incoming
+ // WINDOW_UPDATE makes it negative, we have an overflow.
+ if (send_window_size_ > 0 && new_window_size < 0) {
+ 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_, FLOW_CONTROL_ERROR, desc);
+ return;
+ }
+
+ send_window_size_ = new_window_size;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
+ base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+ stream_id_, delta_window_size, send_window_size_));
+
+ PossiblyResumeIfStalled();
+}
+
+void SpdyStream::DecreaseSendWindowSize(int32 delta_window_size) {
+ // we only call this method when sending a frame, therefore
+ // |delta_window_size| should be within the valid frame size range.
+ DCHECK(session_->is_flow_control_enabled());
+ 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::IncreaseRecvWindowSize(int32 delta_window_size) {
+ DCHECK_GE(delta_window_size, 1);
+ // By the time a read is isued, stream may become inactive.
+ if (!session_->IsStreamActive(stream_id_))
+ return;
+
+ if (!session_->is_flow_control_enabled())
+ return;
+
+ int32 new_window_size = recv_window_size_ + delta_window_size;
+ if (recv_window_size_ > 0)
+ DCHECK(new_window_size > 0);
+
+ recv_window_size_ = new_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_->initial_recv_window_size() / 2) {
+ session_->SendWindowUpdate(stream_id_, unacked_recv_window_bytes_);
+ unacked_recv_window_bytes_ = 0;
+ }
+}
+
+void SpdyStream::DecreaseRecvWindowSize(int32 delta_window_size) {
+ DCHECK_GE(delta_window_size, 1);
+
+ if (!session_->is_flow_control_enabled())
+ 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_));
+
+ // Since we never decrease the initial window size, we should never hit
+ // a negative |recv_window_size_|, if we do, it's a client side bug, so we use
+ // PROTOCOL_ERROR for lack of a better error code.
+ if (recv_window_size_ < 0) {
+ session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+ "Negative recv window size");
+ NOTREACHED();
+ }
+}
+
+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::OnResponseReceived(const SpdyHeaderBlock& response) {
+ int rv = OK;
+
+ metrics_.StartStream();
+
+ DCHECK(response_->empty());
+ *response_ = response; // TODO(ukai): avoid copy.
+
+ recv_first_byte_time_ = base::TimeTicks::Now();
+ response_time_ = base::Time::Now();
+
+ // If we receive a response before we are in STATE_WAITING_FOR_RESPONSE, then
+ // the server has sent the SYN_REPLY too early.
+ if (!pushed_ && io_state_ != STATE_WAITING_FOR_RESPONSE)
+ return ERR_SPDY_PROTOCOL_ERROR;
+ if (pushed_)
+ CHECK(io_state_ == STATE_NONE);
+ io_state_ = STATE_OPEN;
+
+ // Append all the headers into the response header block.
+ for (SpdyHeaderBlock::const_iterator it = response.begin();
+ it != response.end(); ++it) {
+ // Disallow uppercase headers.
+ if (ContainsUpperAscii(it->first)) {
+ session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+ "Upper case characters in header: " + it->first);
+ response_status_ = ERR_SPDY_PROTOCOL_ERROR;
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+ }
+
+ if ((*response_).find("transfer-encoding") != (*response_).end()) {
+ session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+ "Received transfer-encoding header");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ if (delegate_)
+ rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
+ // If delegate_ is not yet attached, we'll call OnResponseReceived after the
+ // delegate gets attached to the stream.
+
+ return rv;
+}
+
+int SpdyStream::OnHeaders(const SpdyHeaderBlock& headers) {
+ DCHECK(!response_->empty());
+
+ // Append all the headers into the response header block.
+ for (SpdyHeaderBlock::const_iterator it = headers.begin();
+ it != headers.end(); ++it) {
+ // Disallow duplicate headers. This is just to be conservative.
+ if ((*response_).find(it->first) != (*response_).end()) {
+ LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "HEADERS duplicate header");
+ response_status_ = ERR_SPDY_PROTOCOL_ERROR;
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ // Disallow uppercase headers.
+ if (ContainsUpperAscii(it->first)) {
+ session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+ "Upper case characters in header: " + it->first);
+ response_status_ = ERR_SPDY_PROTOCOL_ERROR;
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ (*response_)[it->first] = it->second;
+ }
+
+ if ((*response_).find("transfer-encoding") != (*response_).end()) {
+ session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+ "Received transfer-encoding header");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ int rv = OK;
+ if (delegate_) {
+ rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
+ // ERR_INCOMPLETE_SPDY_HEADERS means that we are waiting for more
+ // headers before the response header block is complete.
+ if (rv == ERR_INCOMPLETE_SPDY_HEADERS)
+ rv = OK;
+ }
+ return rv;
+}
+
+void SpdyStream::OnDataReceived(const char* data, int length) {
+ DCHECK_GE(length, 0);
+
+ // If we don't have a response, then the SYN_REPLY did not come through.
+ // We cannot pass data up to the caller unless the reply headers have been
+ // received.
+ if (!response_received()) {
+ LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED, "Didn't receive a response.");
+ session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED);
+ return;
+ }
+
+ if (!delegate_ || continue_buffering_data_) {
+ // 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 (length > 0) {
+ IOBufferWithSize* buf = new IOBufferWithSize(length);
+ memcpy(buf->data(), data, length);
+ pending_buffers_.push_back(make_scoped_refptr(buf));
+ } else {
+ pending_buffers_.push_back(NULL);
+ metrics_.StopStream();
+ // Note: we leave the stream open in the session until the stream
+ // is claimed.
+ }
+ return;
+ }
+
+ CHECK(!closed());
+
+ // A zero-length read means that the stream is being closed.
+ if (!length) {
+ metrics_.StopStream();
+ session_->CloseStream(stream_id_, net::OK);
+ // Note: |this| may be deleted after calling CloseStream.
+ return;
+ }
+
+ DecreaseRecvWindowSize(length);
+
+ // Track our bandwidth.
+ metrics_.RecordBytes(length);
+ recv_bytes_ += length;
+ recv_last_byte_time_ = base::TimeTicks::Now();
+
+ if (!delegate_) {
+ // 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.
+ IOBufferWithSize* buf = new IOBufferWithSize(length);
+ memcpy(buf->data(), data, length);
+ pending_buffers_.push_back(make_scoped_refptr(buf));
+ return;
+ }
+
+ if (delegate_->OnDataReceived(data, length) != net::OK) {
+ // |delegate_| rejected the data.
+ LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "Delegate rejected the data");
+ session_->CloseStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+ return;
+ }
+}
+
+// This function is only called when an entire frame is written.
+void SpdyStream::OnWriteComplete(int bytes) {
+ DCHECK_LE(0, bytes);
+ send_bytes_ += bytes;
+ if (cancelled() || closed())
+ return;
+ DoLoop(bytes);
+}
+
+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) {
+ io_state_ = STATE_DONE;
+ response_status_ = status;
+ Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ if (delegate)
+ delegate->OnClose(status);
+}
+
+void SpdyStream::Cancel() {
+ if (cancelled())
+ return;
+
+ cancelled_ = true;
+ if (session_->IsStreamActive(stream_id_))
+ session_->ResetStream(stream_id_, CANCEL, "");
+ else if (stream_id_ == 0)
+ session_->CloseCreatedStream(this, CANCEL);
+}
+
+void SpdyStream::Close() {
+ if (stream_id_ != 0)
+ session_->CloseStream(stream_id_, net::OK);
+ else
+ session_->CloseCreatedStream(this, OK);
+}
+
+int SpdyStream::SendRequest(bool has_upload_data) {
+ // Pushed streams do not send any data, and should always be in STATE_OPEN or
+ // STATE_DONE. However, we still want to return IO_PENDING to mimic non-push
+ // behavior.
+ has_upload_data_ = has_upload_data;
+ if (pushed_) {
+ send_time_ = base::TimeTicks::Now();
+ DCHECK(!has_upload_data_);
+ DCHECK(response_received());
+ return ERR_IO_PENDING;
+ }
+ CHECK_EQ(STATE_NONE, io_state_);
+ io_state_ = STATE_GET_DOMAIN_BOUND_CERT;
+ return DoLoop(OK);
+}
+
+int SpdyStream::WriteHeaders(SpdyHeaderBlock* headers) {
+ // Until the first headers by SYN_STREAM have been completely sent, we can
+ // not be sure that our stream_id is correct.
+ DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
+ CHECK_GT(stream_id_, 0u);
+
+ PendingFrame frame;
+ frame.type = TYPE_HEADERS;
+ frame.header_block = headers;
+ pending_frames_.push_back(frame);
+
+ SetHasWriteAvailable();
+ return ERR_IO_PENDING;
+}
+
+int SpdyStream::WriteStreamData(IOBuffer* data,
+ int length,
+ SpdyDataFlags flags) {
+ // Until the headers have been completely sent, we can not be sure
+ // that our stream_id is correct.
+ DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
+ CHECK_GT(stream_id_, 0u);
+
+ SpdyDataFrame* data_frame = session_->CreateDataFrame(
+ stream_id_, data, length, flags);
+ if (!data_frame)
+ return ERR_IO_PENDING;
+
+ PendingFrame frame;
+ frame.type = TYPE_DATA;
+ frame.data_frame = data_frame;
+ pending_frames_.push_back(frame);
+
+ SetHasWriteAvailable();
+ return ERR_IO_PENDING;
+}
+
+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);
+}
+
+bool SpdyStream::HasUrl() const {
+ if (pushed_)
+ return response_received();
+ return request_.get() != NULL;
+}
+
+GURL SpdyStream::GetUrl() const {
+ DCHECK(HasUrl());
+
+ const SpdyHeaderBlock& headers = (pushed_) ? *response_ : *request_;
+ return GetUrlFromHeaderBlock(headers, GetProtocolVersion(), pushed_);
+}
+
+void SpdyStream::OnGetDomainBoundCertComplete(int result) {
+ DCHECK_EQ(STATE_GET_DOMAIN_BOUND_CERT_COMPLETE, io_state_);
+ DoLoop(result);
+}
+
+int SpdyStream::DoLoop(int result) {
+ do {
+ State state = io_state_;
+ io_state_ = STATE_NONE;
+ switch (state) {
+ // State machine 1: Send headers and body.
+ case STATE_GET_DOMAIN_BOUND_CERT:
+ CHECK_EQ(OK, result);
+ result = DoGetDomainBoundCert();
+ break;
+ case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
+ result = DoGetDomainBoundCertComplete(result);
+ break;
+ case STATE_SEND_DOMAIN_BOUND_CERT:
+ CHECK_EQ(OK, result);
+ result = DoSendDomainBoundCert();
+ break;
+ case STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE:
+ result = DoSendDomainBoundCertComplete(result);
+ break;
+ case STATE_SEND_HEADERS:
+ CHECK_EQ(OK, result);
+ result = DoSendHeaders();
+ break;
+ case STATE_SEND_HEADERS_COMPLETE:
+ result = DoSendHeadersComplete(result);
+ break;
+ case STATE_SEND_BODY:
+ CHECK_EQ(OK, result);
+ result = DoSendBody();
+ break;
+ case STATE_SEND_BODY_COMPLETE:
+ result = DoSendBodyComplete(result);
+ break;
+ // This is an intermediary waiting state. This state is reached when all
+ // data has been sent, but no data has been received.
+ case STATE_WAITING_FOR_RESPONSE:
+ io_state_ = STATE_WAITING_FOR_RESPONSE;
+ result = ERR_IO_PENDING;
+ break;
+ // State machine 2: connection is established.
+ // In STATE_OPEN, OnResponseReceived has already been called.
+ // OnDataReceived, OnClose and OnWriteCompelte can be called.
+ // Only OnWriteComplete calls DoLoop(().
+ //
+ // For HTTP streams, no data is sent from the client while in the OPEN
+ // state, so OnWriteComplete is never called here. The HTTP body is
+ // handled in the OnDataReceived callback, which does not call into
+ // DoLoop.
+ //
+ // For WebSocket streams, which are bi-directional, we'll send and
+ // receive data once the connection is established. Received data is
+ // handled in OnDataReceived. Sent data is handled in OnWriteComplete,
+ // which calls DoOpen().
+ case STATE_OPEN:
+ result = DoOpen(result);
+ break;
+
+ case STATE_DONE:
+ DCHECK(result != ERR_IO_PENDING);
+ break;
+ default:
+ NOTREACHED() << io_state_;
+ break;
+ }
+ } while (result != ERR_IO_PENDING && io_state_ != STATE_NONE &&
+ io_state_ != STATE_OPEN);
+
+ return result;
+}
+
+int SpdyStream::DoGetDomainBoundCert() {
+ CHECK(request_.get());
+ if (!session_->NeedsCredentials()) {
+ // Proceed directly to sending headers
+ io_state_ = STATE_SEND_HEADERS;
+ return OK;
+ }
+
+ slot_ = session_->credential_state()->FindCredentialSlot(GetUrl());
+ if (slot_ != SpdyCredentialState::kNoEntry) {
+ // Proceed directly to sending headers
+ io_state_ = STATE_SEND_HEADERS;
+ return OK;
+ }
+
+ io_state_ = STATE_GET_DOMAIN_BOUND_CERT_COMPLETE;
+ ServerBoundCertService* sbc_service = session_->GetServerBoundCertService();
+ DCHECK(sbc_service != NULL);
+ std::vector<uint8> requested_cert_types;
+ requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
+ int rv = sbc_service->GetDomainBoundCert(
+ GetUrl().GetOrigin().spec(), requested_cert_types,
+ &domain_bound_cert_type_, &domain_bound_private_key_, &domain_bound_cert_,
+ base::Bind(&SpdyStream::OnGetDomainBoundCertComplete,
+ base::Unretained(this)),
+ &domain_bound_cert_request_handle_);
+ return rv;
+}
+
+int SpdyStream::DoGetDomainBoundCertComplete(int result) {
+ if (result != OK)
+ return result;
+
+ io_state_ = STATE_SEND_DOMAIN_BOUND_CERT;
+ slot_ = session_->credential_state()->SetHasCredential(GetUrl());
+ return OK;
+}
+
+int SpdyStream::DoSendDomainBoundCert() {
+ io_state_ = STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE;
+ CHECK(request_.get());
+ SetHasWriteAvailable();
+ return ERR_IO_PENDING;
+}
+
+int SpdyStream::DoSendDomainBoundCertComplete(int result) {
+ if (result < 0)
+ return result;
+
+ io_state_ = STATE_SEND_HEADERS;
+ return OK;
+}
+
+int SpdyStream::DoSendHeaders() {
+ CHECK(!cancelled_);
+
+ SetHasWriteAvailable();
+ io_state_ = STATE_SEND_HEADERS_COMPLETE;
+ return ERR_IO_PENDING;
+}
+
+int SpdyStream::DoSendHeadersComplete(int result) {
+ if (result < 0)
+ return result;
+
+ CHECK_GT(result, 0);
+
+ if (!delegate_)
+ return ERR_UNEXPECTED;
+
+ // There is no body, skip that state.
+ if (delegate_->OnSendHeadersComplete(result)) {
+ io_state_ = STATE_WAITING_FOR_RESPONSE;
+ return OK;
+ }
+
+ io_state_ = STATE_SEND_BODY;
+ return OK;
+}
+
+// DoSendBody is called to send the optional body for the request. This call
+// will also be called as each write of a chunk of the body completes.
+int SpdyStream::DoSendBody() {
+ // If we're already in the STATE_SEND_BODY state, then we've already
+ // sent a portion of the body. In that case, we need to first consume
+ // the bytes written in the body stream. Note that the bytes written is
+ // the number of bytes in the frame that were written, only consume the
+ // data portion, of course.
+ io_state_ = STATE_SEND_BODY_COMPLETE;
+ if (!delegate_)
+ return ERR_UNEXPECTED;
+ return delegate_->OnSendBody();
+}
+
+int SpdyStream::DoSendBodyComplete(int result) {
+ if (result < 0)
+ return result;
+
+ if (!delegate_)
+ return ERR_UNEXPECTED;
+
+ bool eof = false;
+ result = delegate_->OnSendBodyComplete(result, &eof);
+ if (!eof)
+ io_state_ = STATE_SEND_BODY;
+ else
+ io_state_ = STATE_WAITING_FOR_RESPONSE;
+
+ return result;
+}
+
+int SpdyStream::DoOpen(int result) {
+ if (delegate_) {
+ FrameType type = waiting_completions_.front();
+ waiting_completions_.pop_front();
+ if (type == TYPE_DATA) {
+ delegate_->OnDataSent(result);
+ } else {
+ DCHECK(type == TYPE_HEADERS);
+ delegate_->OnHeadersSent();
+ }
+ }
+ io_state_ = STATE_OPEN;
+ return result;
+}
+
+void SpdyStream::UpdateHistograms() {
+ // We need all timers to be filled in, otherwise metrics can be bogus.
+ if (send_time_.is_null() || recv_first_byte_time_.is_null() ||
+ recv_last_byte_time_.is_null())
+ return;
+
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamTimeToFirstByte",
+ recv_first_byte_time_ - send_time_);
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamDownloadTime",
+ recv_last_byte_time_ - recv_first_byte_time_);
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamTime",
+ recv_last_byte_time_ - send_time_);
+
+ UMA_HISTOGRAM_COUNTS("Net.SpdySendBytes", send_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SpdyRecvBytes", recv_bytes_);
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_stream.h b/src/net/spdy/spdy_stream.h
new file mode 100644
index 0000000..5621564
--- /dev/null
+++ b/src/net/spdy/spdy_stream.h
@@ -0,0 +1,410 @@
+// 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 <list>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "googleurl/src/gurl.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/base/server_bound_cert_service.h"
+#include "net/base/ssl_client_cert_type.h"
+#include "net/socket/ssl_client_socket.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.h"
+
+namespace net {
+
+class AddressList;
+class IPEndPoint;
+class SSLCertRequestInfo;
+class SSLInfo;
+
+// 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 base::RefCounted<SpdyStream> {
+ public:
+ // Delegate handles protocol specific behavior of spdy stream.
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ Delegate() {}
+
+ // Called when SYN frame has been sent.
+ // Returns true if no more data to be sent after SYN frame.
+ virtual bool OnSendHeadersComplete(int status) = 0;
+
+ // Called when stream is ready to send data.
+ // Returns network error code. OK when it successfully sent data.
+ // ERR_IO_PENDING when performing operation asynchronously.
+ virtual int OnSendBody() = 0;
+
+ // Called when data has been sent. |status| indicates network error
+ // or number of bytes that has been sent. On return, |eof| is set to true
+ // if no more data is available to send in the request body.
+ // Returns network error code. OK when it successfully sent data.
+ virtual int OnSendBodyComplete(int status, bool* eof) = 0;
+
+ // Called when the SYN_STREAM, SYN_REPLY, or HEADERS frames are received.
+ // Normal streams will receive a SYN_REPLY and optional HEADERS frames.
+ // Pushed streams will receive a SYN_STREAM and optional HEADERS frames.
+ // Because a stream may have a SYN_* frame and multiple HEADERS frames,
+ // this callback may be called multiple times.
+ // |status| indicates network error. Returns network error code.
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) = 0;
+
+ // Called when a HEADERS frame is sent.
+ virtual void OnHeadersSent() = 0;
+
+ // Called when data is received.
+ // Returns network error code. OK when it successfully receives data.
+ virtual int OnDataReceived(const char* data, int length) = 0;
+
+ // Called when data is sent.
+ virtual void OnDataSent(int length) = 0;
+
+ // Called when SpdyStream is closed.
+ virtual void OnClose(int status) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // Indicates pending frame type.
+ enum FrameType {
+ TYPE_HEADERS,
+ TYPE_DATA
+ };
+
+ // Structure to contains pending frame information.
+ typedef struct {
+ FrameType type;
+ union {
+ SpdyHeaderBlock* header_block;
+ SpdyDataFrame* data_frame;
+ };
+ } PendingFrame;
+
+ // SpdyStream constructor
+ SpdyStream(SpdySession* session,
+ bool pushed,
+ const BoundNetLog& net_log);
+
+ // Set new |delegate|. |delegate| must not be NULL.
+ // If it already received SYN_REPLY or data, OnResponseReceived() or
+ // OnDataReceived() will be called.
+ void SetDelegate(Delegate* delegate);
+ Delegate* GetDelegate() { return delegate_; }
+
+ // Detach delegate from the stream. It will cancel the stream if it was not
+ // cancelled yet. It is safe to call multiple times.
+ void DetachDelegate();
+
+ // Is this stream a pushed stream from the server.
+ bool pushed() const { return pushed_; }
+
+ SpdyStreamId stream_id() const { return stream_id_; }
+ void set_stream_id(SpdyStreamId stream_id) { stream_id_ = stream_id; }
+
+ bool response_received() const { return response_received_; }
+ void set_response_received() { response_received_ = true; }
+
+ // For pushed streams, we track a path to identify them.
+ const std::string& path() const { return path_; }
+ void set_path(const std::string& path) { path_ = path; }
+
+ RequestPriority priority() const { return priority_; }
+ void set_priority(RequestPriority priority) { priority_ = priority; }
+
+ int32 send_window_size() const { return send_window_size_; }
+ void set_send_window_size(int32 window_size) {
+ send_window_size_ = window_size;
+ }
+
+ int32 recv_window_size() const { return recv_window_size_; }
+ void set_recv_window_size(int32 window_size) {
+ recv_window_size_ = window_size;
+ }
+
+ // Set session_'s initial_recv_window_size. Used by unittests.
+ void set_initial_recv_window_size(int32 window_size);
+
+ bool stalled_by_flow_control() { return stalled_by_flow_control_; }
+
+ void set_stalled_by_flow_control(bool stalled) {
+ stalled_by_flow_control_ = stalled;
+ }
+
+ // Adjusts the |send_window_size_| by |delta_window_size|. |delta_window_size|
+ // is the difference between the SETTINGS_INITIAL_WINDOW_SIZE in SETTINGS
+ // frame and the previous initial_send_window_size.
+ void AdjustSendWindowSize(int32 delta_window_size);
+
+ // Increases |send_window_size_| with delta extracted from a WINDOW_UPDATE
+ // frame; sends a RST_STREAM if delta overflows |send_window_size_| and
+ // removes the stream from the session.
+ void IncreaseSendWindowSize(int32 delta_window_size);
+
+ // Decreases |send_window_size_| by the given number of bytes.
+ void DecreaseSendWindowSize(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;
+
+ // Increases |recv_window_size_| by the given number of bytes, also sends
+ // a WINDOW_UPDATE frame.
+ void IncreaseRecvWindowSize(int32 delta_window_size);
+
+ // Decreases |recv_window_size_| by the given number of bytes, called
+ // whenever data is read. May also send a RST_STREAM and remove the
+ // stream from the session if the resultant |recv_window_size_| is
+ // negative, since that would be a flow control violation.
+ void DecreaseRecvWindowSize(int32 delta_window_size);
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ const SpdyHeaderBlock& spdy_headers() const;
+ void set_spdy_headers(scoped_ptr<SpdyHeaderBlock> headers);
+ base::Time GetRequestTime() const;
+ void SetRequestTime(base::Time t);
+
+ // Called by the SpdySession when a response (e.g. a SYN_STREAM or SYN_REPLY)
+ // has been received for this stream. Returns a status code.
+ int OnResponseReceived(const SpdyHeaderBlock& response);
+
+ // Called by the SpdySession when late-bound headers are received for a
+ // stream. Returns a status code.
+ int OnHeaders(const SpdyHeaderBlock& 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 OnResponseReceived.
+ // |buffer| contains the data received. The stream must copy any data
+ // from this buffer before returning from this callback.
+ // |length| is the number of bytes received or an error.
+ // A zero-length count does not indicate end-of-stream.
+ void OnDataReceived(const char* buffer, int bytes);
+
+ // Called by the SpdySession when a write has completed. This callback
+ // will be called multiple times for each write which completes. Writes
+ // include the SYN_STREAM write and also DATA frame writes.
+ // |result| is the number of bytes written or a net error code.
+ void OnWriteComplete(int bytes);
+
+ // 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);
+
+ void Cancel();
+ void Close();
+ bool cancelled() const { return cancelled_; }
+ bool closed() const { return io_state_ == STATE_DONE; }
+ // TODO(satorux): This is only for testing. We should be able to remove
+ // this once crbug.com/113107 is addressed.
+ bool body_sent() const { return io_state_ > STATE_SEND_BODY_COMPLETE; }
+
+ // Interface for Spdy[Http|WebSocket]Stream to use.
+
+ // Sends the request.
+ // For non push stream, it will send SYN_STREAM frame.
+ int SendRequest(bool has_upload_data);
+
+ // Sends a HEADERS frame. SpdyStream owns |headers| and will release it after
+ // the HEADERS frame is actually sent.
+ int WriteHeaders(SpdyHeaderBlock* headers);
+
+ // Sends DATA frame.
+ int WriteStreamData(IOBuffer* data, int length,
+ SpdyDataFlags flags);
+
+ // 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);
+
+ bool is_idle() const {
+ return io_state_ == STATE_OPEN || io_state_ == STATE_DONE;
+ }
+
+ int response_status() const { return response_status_; }
+
+ // Returns true if the URL for this stream is known.
+ bool HasUrl() const;
+
+ // Get the URL associated with this stream. Only valid when has_url() is
+ // true.
+ GURL GetUrl() const;
+
+ int GetProtocolVersion() const;
+
+ private:
+ class SpdyStreamIOBufferProducer;
+
+ 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_HEADERS,
+ STATE_SEND_HEADERS_COMPLETE,
+ STATE_SEND_BODY,
+ STATE_SEND_BODY_COMPLETE,
+ STATE_WAITING_FOR_RESPONSE,
+ STATE_OPEN,
+ STATE_DONE
+ };
+
+ friend class base::RefCounted<SpdyStream>;
+
+ virtual ~SpdyStream();
+
+ // If the stream is stalled and if |send_window_size_| is positive, then set
+ // |stalled_by_flow_control_| to false and unstall the stream.
+ void PossiblyResumeIfStalled();
+
+ 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 DoSendHeaders();
+ int DoSendHeadersComplete(int result);
+ int DoSendBody();
+ int DoSendBodyComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoOpen(int result);
+
+ // 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 MessageLoop to replay all the data that the server has already sent.
+ void PushedStreamReplayData();
+
+ // Informs the SpdySession that this stream has a write available.
+ void SetHasWriteAvailable();
+
+ // Returns a newly created SPDY frame owned by the called that contains
+ // the next frame to be sent by this frame. May return NULL if this
+ // stream has become stalled on flow control.
+ SpdyFrame* ProduceNextFrame();
+
+ // 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_;
+ std::string path_;
+ RequestPriority priority_;
+ size_t slot_;
+
+ // Flow control variables.
+ bool stalled_by_flow_control_;
+ int32 send_window_size_;
+ int32 recv_window_size_;
+ int32 unacked_recv_window_bytes_;
+
+ const bool pushed_;
+ ScopedBandwidthMetrics metrics_;
+ bool response_received_;
+
+ scoped_refptr<SpdySession> session_;
+
+ // The transaction should own the delegate.
+ SpdyStream::Delegate* delegate_;
+
+ // The request to send.
+ scoped_ptr<SpdyHeaderBlock> request_;
+
+ // 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_;
+
+ scoped_ptr<SpdyHeaderBlock> response_;
+ base::Time response_time_;
+
+ // An in order list of pending frame data that are going to be sent. HEADERS
+ // frames are queued as SpdyHeaderBlock structures because these must be
+ // compressed just before sending. Data frames are queued as SpdyDataFrame.
+ std::list<PendingFrame> pending_frames_;
+
+ // An in order list of sending frame types. It will be used to know which type
+ // of frame is sent and which callback should be invoked in OnOpen().
+ std::list<FrameType> waiting_completions_;
+
+ State io_state_;
+
+ // Since we buffer the response, we also buffer the response status.
+ // Not valid until the stream is closed.
+ int response_status_;
+
+ bool cancelled_;
+ bool has_upload_data_;
+
+ BoundNetLog net_log_;
+
+ base::TimeTicks send_time_;
+ base::TimeTicks recv_first_byte_time_;
+ base::TimeTicks recv_last_byte_time_;
+ int send_bytes_;
+ int recv_bytes_;
+ // Data received before delegate is attached.
+ std::vector<scoped_refptr<IOBufferWithSize> > pending_buffers_;
+
+ SSLClientCertType domain_bound_cert_type_;
+ std::string domain_bound_private_key_;
+ std::string domain_bound_cert_;
+ ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_H_
diff --git a/src/net/spdy/spdy_stream_spdy2_unittest.cc b/src/net/spdy/spdy_stream_spdy2_unittest.cc
new file mode 100644
index 0000000..602ff31
--- /dev/null
+++ b/src/net/spdy/spdy_stream_spdy2_unittest.cc
@@ -0,0 +1,462 @@
+// 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 "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log_unittest.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "net/spdy/spdy_websocket_test_util_spdy2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+//
+namespace net {
+
+namespace {
+
+SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateDataFrame(1, data, length, DATA_FLAG_NONE);
+}
+
+} // anonymous namespace
+
+namespace test {
+
+class SpdyStreamSpdy2Test : public testing::Test {
+ protected:
+ SpdyStreamSpdy2Test() {
+ }
+
+ scoped_refptr<SpdySession> CreateSpdySession() {
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session(
+ session_->spdy_session_pool()->Get(pair, BoundNetLog()));
+ return session;
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ scoped_refptr<HttpNetworkSession> session_;
+};
+
+TEST_F(SpdyStreamSpdy2Test, SendDataAfterOpen) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ 1,
+ 0,
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ static const char* const kGetHeaders[] = {
+ "method",
+ "GET",
+ "scheme",
+ "http",
+ "host",
+ "www.google.com",
+ "url",
+ "/",
+ "version",
+ "HTTP/1.1",
+ };
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> msg(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*msg),
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*echo),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 3;
+ reads[2].sequence_number = 4;
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ const char* kStreamUrl = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ session->InitializeWithSocket(connection.release(), false, OK);
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, LOWEST, &stream, BoundNetLog(),
+ CompletionCallback()));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+ memcpy(buf->data(), "\0hello!\xff", 8);
+ TestCompletionCallback callback;
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(
+ stream.get(), NULL, buf.get(), callback.callback()));
+ stream->SetDelegate(delegate.get());
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url.scheme();
+ (*headers)["host"] = url.host();
+ (*headers)["url"] = url.path();
+ (*headers)["version"] = "HTTP/1.1";
+ stream->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("200", (*delegate->response())["status"]);
+ EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]);
+ EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+ EXPECT_EQ(8, delegate->data_sent());
+ EXPECT_TRUE(delegate->closed());
+}
+
+TEST_F(SpdyStreamSpdy2Test, SendHeaderAndDataAfterOpen) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ scoped_ptr<SpdyFrame> expected_request(ConstructSpdyWebSocketSynStream(
+ 1,
+ "/chat",
+ "server.example.com",
+ "http://example.com"));
+ scoped_ptr<SpdyFrame> expected_headers(ConstructSpdyWebSocketHeadersFrame(
+ 1, "6", true));
+ scoped_ptr<SpdyFrame> expected_message(ConstructSpdyBodyFrame("hello!", 6));
+ MockWrite writes[] = {
+ CreateMockWrite(*expected_request),
+ CreateMockWrite(*expected_headers),
+ CreateMockWrite(*expected_message)
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+ writes[1].sequence_number = 3;
+
+ scoped_ptr<SpdyFrame> response(
+ ConstructSpdyWebSocketSynReply(1));
+ MockRead reads[] = {
+ CreateMockRead(*response),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 4;
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ const char* kStreamUrl = "ws://server.example.com/chat";
+ GURL url(kStreamUrl);
+
+ HostPortPair host_port_pair("server.example.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ session->InitializeWithSocket(connection.release(), false, OK);
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, HIGHEST, &stream, BoundNetLog(),
+ CompletionCallback()));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(6));
+ memcpy(buf->data(), "hello!", 6);
+ TestCompletionCallback callback;
+ scoped_ptr<SpdyHeaderBlock> message_headers(new SpdyHeaderBlock);
+ (*message_headers)["opcode"] = "1";
+ (*message_headers)["length"] = "6";
+ (*message_headers)["fin"] = "1";
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(stream.get(),
+ message_headers.release(),
+ buf.get(),
+ callback.callback()));
+ stream->SetDelegate(delegate.get());
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["path"] = url.path();
+ (*headers)["host"] = url.host();
+ (*headers)["version"] = "WebSocket/13";
+ (*headers)["scheme"] = url.scheme();
+ (*headers)["origin"] = "http://example.com";
+ stream->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(stream->HasUrl());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("101", (*delegate->response())["status"]);
+ EXPECT_EQ(1, delegate->headers_sent());
+ EXPECT_EQ(std::string(), delegate->received_data());
+ EXPECT_EQ(6, delegate->data_sent());
+}
+
+TEST_F(SpdyStreamSpdy2Test, PushedStream) {
+ const char kStreamUrl[] = "http://www.google.com/";
+
+ SpdySessionDependencies session_deps;
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+ scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads), NULL, 0);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ spdy_session->InitializeWithSocket(connection.release(), false, OK);
+ BoundNetLog net_log;
+
+ // Conjure up a stream.
+ scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session,
+ true,
+ net_log);
+ stream->set_stream_id(2);
+ EXPECT_FALSE(stream->response_received());
+ EXPECT_FALSE(stream->HasUrl());
+
+ // Set a couple of headers.
+ SpdyHeaderBlock response;
+ response["url"] = kStreamUrl;
+ stream->OnResponseReceived(response);
+
+ // Send some basic headers.
+ SpdyHeaderBlock headers;
+ response["status"] = "200";
+ response["version"] = "OK";
+ stream->OnHeaders(headers);
+
+ stream->set_response_received();
+ EXPECT_TRUE(stream->response_received());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+}
+
+TEST_F(SpdyStreamSpdy2Test, StreamError) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ 1,
+ 0,
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ static const char* const kGetHeaders[] = {
+ "method",
+ "GET",
+ "scheme",
+ "http",
+ "host",
+ "www.google.com",
+ "url",
+ "/",
+ "version",
+ "HTTP/1.1",
+ };
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> msg(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*msg),
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*echo),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 3;
+ reads[2].sequence_number = 4;
+
+ CapturingBoundNetLog log;
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ const char* kStreamUrl = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ log.bound()));
+ session->InitializeWithSocket(connection.release(), false, OK);
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, LOWEST, &stream, log.bound(),
+ CompletionCallback()));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+ memcpy(buf->data(), "\0hello!\xff", 8);
+ TestCompletionCallback callback;
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(
+ stream.get(), NULL, buf.get(), callback.callback()));
+ stream->SetDelegate(delegate.get());
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["method"] = "GET";
+ (*headers)["scheme"] = url.scheme();
+ (*headers)["host"] = url.host();
+ (*headers)["url"] = url.path();
+ (*headers)["version"] = "HTTP/1.1";
+ stream->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const SpdyStreamId stream_id = stream->stream_id();
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("200", (*delegate->response())["status"]);
+ EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]);
+ EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+ EXPECT_EQ(8, delegate->data_sent());
+ EXPECT_TRUE(delegate->closed());
+
+ // 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);
+}
+
+} //namespace test
+
+} // namespace net
diff --git a/src/net/spdy/spdy_stream_spdy3_unittest.cc b/src/net/spdy/spdy_stream_spdy3_unittest.cc
new file mode 100644
index 0000000..3b07ea4
--- /dev/null
+++ b/src/net/spdy/spdy_stream_spdy3_unittest.cc
@@ -0,0 +1,467 @@
+// 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 "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log_unittest.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "net/spdy/spdy_websocket_test_util_spdy3.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+//
+namespace net {
+
+namespace {
+
+SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateDataFrame(1, data, length, DATA_FLAG_NONE);
+}
+
+} // anonymous namespace
+
+namespace test {
+
+class SpdyStreamSpdy3Test : public testing::Test {
+ protected:
+ SpdyStreamSpdy3Test() {
+ }
+
+ scoped_refptr<SpdySession> CreateSpdySession() {
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ scoped_refptr<SpdySession> session(
+ session_->spdy_session_pool()->Get(pair, BoundNetLog()));
+ return session;
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ scoped_refptr<HttpNetworkSession> session_;
+};
+
+TEST_F(SpdyStreamSpdy3Test, SendDataAfterOpen) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ 1,
+ 0,
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+ 0,
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ static const char* const kGetHeaders[] = {
+ ":method",
+ "GET",
+ ":scheme",
+ "http",
+ ":host",
+ "www.google.com",
+ ":path",
+ "/",
+ ":version",
+ "HTTP/1.1",
+ };
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> msg(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*msg),
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*echo),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 3;
+ reads[2].sequence_number = 4;
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ const char* kStreamUrl = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ session->InitializeWithSocket(connection.release(), false, OK);
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, LOWEST, &stream, BoundNetLog(),
+ CompletionCallback()));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+ memcpy(buf->data(), "\0hello!\xff", 8);
+ TestCompletionCallback callback;
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(
+ stream.get(), NULL, buf.get(), callback.callback()));
+ stream->SetDelegate(delegate.get());
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":method"] = "GET";
+ (*headers)[":scheme"] = url.scheme();
+ (*headers)[":host"] = url.host();
+ (*headers)[":path"] = url.path();
+ (*headers)[":version"] = "HTTP/1.1";
+ stream->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("200", (*delegate->response())[":status"]);
+ EXPECT_EQ("HTTP/1.1", (*delegate->response())[":version"]);
+ EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+ EXPECT_EQ(8, delegate->data_sent());
+ EXPECT_TRUE(delegate->closed());
+}
+
+TEST_F(SpdyStreamSpdy3Test, SendHeaderAndDataAfterOpen) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ scoped_ptr<SpdyFrame> expected_request(ConstructSpdyWebSocketSynStream(
+ 1,
+ "/chat",
+ "server.example.com",
+ "http://example.com"));
+ scoped_ptr<SpdyFrame> expected_headers(ConstructSpdyWebSocketHeadersFrame(
+ 1, "6", true));
+ scoped_ptr<SpdyFrame> expected_message(ConstructSpdyBodyFrame("hello!", 6));
+ MockWrite writes[] = {
+ CreateMockWrite(*expected_request),
+ CreateMockWrite(*expected_headers),
+ CreateMockWrite(*expected_message)
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+ writes[1].sequence_number = 3;
+
+ scoped_ptr<SpdyFrame> response(
+ ConstructSpdyWebSocketSynReply(1));
+ MockRead reads[] = {
+ CreateMockRead(*response),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 4;
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ const char* kStreamUrl = "ws://server.example.com/chat";
+ GURL url(kStreamUrl);
+
+ HostPortPair host_port_pair("server.example.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ session->InitializeWithSocket(connection.release(), false, OK);
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, HIGHEST, &stream, BoundNetLog(),
+ CompletionCallback()));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(6));
+ memcpy(buf->data(), "hello!", 6);
+ TestCompletionCallback callback;
+ scoped_ptr<SpdyHeaderBlock> message_headers(new SpdyHeaderBlock);
+ (*message_headers)[":opcode"] = "1";
+ (*message_headers)[":length"] = "6";
+ (*message_headers)[":fin"] = "1";
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(stream.get(),
+ message_headers.release(),
+ buf.get(),
+ callback.callback()));
+ stream->SetDelegate(delegate.get());
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":path"] = url.path();
+ (*headers)[":host"] = url.host();
+ (*headers)[":version"] = "WebSocket/13";
+ (*headers)[":scheme"] = url.scheme();
+ (*headers)[":origin"] = "http://example.com";
+ stream->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(stream->HasUrl());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("101", (*delegate->response())[":status"]);
+ EXPECT_EQ(1, delegate->headers_sent());
+ EXPECT_EQ(std::string(), delegate->received_data());
+ EXPECT_EQ(6, delegate->data_sent());
+}
+
+TEST_F(SpdyStreamSpdy3Test, PushedStream) {
+ const char kStreamUrl[] = "http://www.google.com/";
+
+ SpdySessionDependencies session_deps;
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+ scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads), NULL, 0);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ spdy_session->InitializeWithSocket(connection.release(), false, OK);
+ BoundNetLog net_log;
+
+ // Conjure up a stream.
+ scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session,
+ true,
+ net_log);
+ stream->set_stream_id(2);
+ EXPECT_FALSE(stream->response_received());
+ EXPECT_FALSE(stream->HasUrl());
+
+ // Set a couple of headers.
+ SpdyHeaderBlock response;
+ GURL url(kStreamUrl);
+ response[":host"] = url.host();
+ response[":scheme"] = url.scheme();
+ response[":path"] = url.path();
+ stream->OnResponseReceived(response);
+
+ // Send some basic headers.
+ SpdyHeaderBlock headers;
+ response[":status"] = "200";
+ response[":version"] = "OK";
+ stream->OnHeaders(headers);
+
+ stream->set_response_received();
+ EXPECT_TRUE(stream->response_received());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+}
+
+TEST_F(SpdyStreamSpdy3Test, StreamError) {
+ SpdySessionDependencies session_deps;
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ 1,
+ 0,
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+ 0,
+ CONTROL_FLAG_NONE,
+ false,
+ INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ static const char* const kGetHeaders[] = {
+ ":method",
+ "GET",
+ ":scheme",
+ "http",
+ ":host",
+ "www.google.com",
+ ":path",
+ "/",
+ ":version",
+ "HTTP/1.1",
+ };
+ scoped_ptr<SpdyFrame> req(
+ ConstructSpdyPacket(
+ kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+ scoped_ptr<SpdyFrame> msg(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*msg),
+ };
+ writes[0].sequence_number = 0;
+ writes[1].sequence_number = 2;
+
+ scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ ConstructSpdyBodyFrame("\0hello!\xff", 8));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*echo),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ reads[0].sequence_number = 1;
+ reads[1].sequence_number = 3;
+ reads[2].sequence_number = 4;
+
+ CapturingBoundNetLog log;
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+ const char* kStreamUrl = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(host_port_pair, LOWEST, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+ LOWEST, CompletionCallback(),
+ session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ log.bound()));
+ session->InitializeWithSocket(connection.release(), false, OK);
+
+ scoped_refptr<SpdyStream> stream;
+ ASSERT_EQ(
+ OK,
+ session->CreateStream(url, LOWEST, &stream, log.bound(),
+ CompletionCallback()));
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+ memcpy(buf->data(), "\0hello!\xff", 8);
+ TestCompletionCallback callback;
+
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(
+ stream.get(), NULL, buf.get(), callback.callback()));
+ stream->SetDelegate(delegate.get());
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":method"] = "GET";
+ (*headers)[":scheme"] = url.scheme();
+ (*headers)[":host"] = url.host();
+ (*headers)[":path"] = url.path();
+ (*headers)[":version"] = "HTTP/1.1";
+ stream->set_spdy_headers(headers.Pass());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const SpdyStreamId stream_id = stream->stream_id();
+
+ EXPECT_TRUE(delegate->send_headers_completed());
+ EXPECT_EQ("200", (*delegate->response())[":status"]);
+ EXPECT_EQ("HTTP/1.1", (*delegate->response())[":version"]);
+ EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+ EXPECT_EQ(8, delegate->data_sent());
+ EXPECT_TRUE(delegate->closed());
+
+ // 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);
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/src/net/spdy/spdy_stream_test_util.cc b/src/net/spdy/spdy_stream_test_util.cc
new file mode 100644
index 0000000..e2144ae
--- /dev/null
+++ b/src/net/spdy/spdy_stream_test_util.cc
@@ -0,0 +1,87 @@
+// 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 "net/base/completion_callback.h"
+#include "net/spdy/spdy_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace test {
+
+TestSpdyStreamDelegate::TestSpdyStreamDelegate(
+ SpdyStream* stream,
+ SpdyHeaderBlock* headers,
+ IOBufferWithSize* buf,
+ const CompletionCallback& callback)
+ : stream_(stream),
+ headers_(headers),
+ buf_(buf),
+ callback_(callback),
+ send_headers_completed_(false),
+ response_(new SpdyHeaderBlock),
+ headers_sent_(0),
+ data_sent_(0),
+ closed_(false) {
+}
+
+TestSpdyStreamDelegate::~TestSpdyStreamDelegate() {
+}
+
+bool TestSpdyStreamDelegate::OnSendHeadersComplete(int status) {
+ send_headers_completed_ = true;
+ return true;
+}
+
+int TestSpdyStreamDelegate::OnSendBody() {
+ ADD_FAILURE() << "OnSendBody should not be called";
+ return ERR_UNEXPECTED;
+}
+int TestSpdyStreamDelegate::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
+ ADD_FAILURE() << "OnSendBodyComplete should not be called";
+ return ERR_UNEXPECTED;
+}
+
+int TestSpdyStreamDelegate::OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) {
+ EXPECT_TRUE(send_headers_completed_);
+ *response_ = response;
+ if (headers_.get()) {
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream_->WriteHeaders(headers_.release()));
+ }
+ if (buf_) {
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream_->WriteStreamData(buf_.get(), buf_->size(),
+ DATA_FLAG_NONE));
+ }
+ return status;
+}
+
+void TestSpdyStreamDelegate::OnHeadersSent() {
+ headers_sent_++;
+}
+
+int TestSpdyStreamDelegate::OnDataReceived(const char* buffer, int bytes) {
+ received_data_ += std::string(buffer, bytes);
+ return OK;
+}
+
+void TestSpdyStreamDelegate::OnDataSent(int length) {
+ data_sent_ += length;
+}
+
+void TestSpdyStreamDelegate::OnClose(int status) {
+ closed_ = true;
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(OK);
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/src/net/spdy/spdy_stream_test_util.h b/src/net/spdy/spdy_stream_test_util.h
new file mode 100644
index 0000000..6059014
--- /dev/null
+++ b/src/net/spdy/spdy_stream_test_util.h
@@ -0,0 +1,63 @@
+// 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/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+namespace test {
+
+class TestSpdyStreamDelegate : public SpdyStream::Delegate {
+ public:
+ TestSpdyStreamDelegate(SpdyStream* stream,
+ SpdyHeaderBlock* headers,
+ IOBufferWithSize* buf,
+ const CompletionCallback& callback);
+ virtual ~TestSpdyStreamDelegate();
+
+ virtual bool OnSendHeadersComplete(int status) OVERRIDE;
+ virtual int OnSendBody() OVERRIDE;
+ virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE;
+ virtual void OnHeadersSent() OVERRIDE;
+ virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE;
+ virtual void OnDataSent(int length) OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ bool send_headers_completed() const { return send_headers_completed_; }
+ const linked_ptr<SpdyHeaderBlock>& response() const {
+ return response_;
+ }
+ const std::string& received_data() const { return received_data_; }
+ int headers_sent() const { return headers_sent_; }
+ int data_sent() const { return data_sent_; }
+ bool closed() const { return closed_; }
+
+ private:
+ SpdyStream* stream_;
+ scoped_ptr<SpdyHeaderBlock> headers_;
+ scoped_refptr<IOBufferWithSize> buf_;
+ CompletionCallback callback_;
+ bool send_headers_completed_;
+ linked_ptr<SpdyHeaderBlock> response_;
+ std::string received_data_;
+ int headers_sent_;
+ int data_sent_;
+ bool closed_;
+
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_test_util_spdy2.cc b/src/net/spdy/spdy_test_util_spdy2.cc
new file mode 100644
index 0000000..5f5e017
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy2.cc
@@ -0,0 +1,987 @@
+// 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_util_spdy2.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "net/base/mock_cert_verifier.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/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+namespace test_spdy2 {
+
+namespace {
+
+// Parses a URL into the scheme, host, and path components required for a
+// SPDY request.
+void ParseUrl(const char* const url, std::string* scheme, std::string* host,
+ std::string* path) {
+ GURL gurl(url);
+ path->assign(gurl.PathForRequest());
+ scheme->assign(gurl.scheme());
+ host->assign(gurl.host());
+ if (gurl.has_port()) {
+ host->append(":");
+ host->append(gurl.port());
+ }
+}
+
+} // namespace
+
+// 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.length() + SpdyFrame::kHeaderSize,
+ 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.length() + SpdyFrame::kHeaderSize,
+ 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 AppendHeadersToSpdyFrame(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;
+ }
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+ int len,
+ unsigned char** buffer_handle,
+ int* buffer_len_remaining) {
+ if (len <= 0)
+ return 0;
+ DCHECK((size_t) len <= sizeof(len)) << "Data length too long for data type";
+ DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+ DCHECK(NULL != *buffer_handle) << "NULL pointer";
+ DCHECK(NULL != buffer_len_remaining)
+ << "NULL buffer remainder length pointer";
+ DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+ for (int i = 0; i < len; i++) {
+ int shift = (8 * (len - (i + 1)));
+ unsigned char val_chunk = (val >> shift) & 0x0FF;
+ *(*buffer_handle)++ = val_chunk;
+ *buffer_len_remaining += 1;
+ }
+ return len;
+}
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail[],
+ int tail_header_count) {
+ BufferedSpdyFramer framer(2, header_info.compressed);
+ SpdyHeaderBlock headers;
+ // Copy in the extra headers to our map.
+ AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+ // Copy in the tail headers to our map.
+ if (tail && tail_header_count)
+ AppendHeadersToSpdyFrame(tail, tail_header_count, &headers);
+ SpdyFrame* frame = NULL;
+ switch (header_info.kind) {
+ case SYN_STREAM:
+ frame = framer.CreateSynStream(header_info.id, header_info.assoc_id,
+ header_info.priority, 0,
+ header_info.control_flags,
+ header_info.compressed, &headers);
+ break;
+ case SYN_REPLY:
+ frame = framer.CreateSynReply(header_info.id, header_info.control_flags,
+ header_info.compressed, &headers);
+ 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);
+ break;
+ default:
+ frame = framer.CreateDataFrame(header_info.id, header_info.data,
+ header_info.data_length,
+ header_info.data_flags);
+ break;
+ }
+ return frame;
+}
+
+// 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) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateSettings(settings);
+}
+
+// Construct an expected SPDY CREDENTIAL frame.
+// |credential| is the credential to sen.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyCredential(
+ const SpdyCredential& credential) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateCredentialFrame(credential);
+}
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreatePingFrame(ping_id);
+}
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway() {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateGoAway(0, GOAWAY_OK);
+}
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(
+ const SpdyStreamId stream_id, uint32 delta_window_size) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateRstStream(stream_id, status);
+}
+
+// Construct a single SPDY header entry, for validation.
+// |extra_headers| are the extra header-value pairs.
+// |buffer| is the buffer we're filling in.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length,
+ int index) {
+ const char* this_header = NULL;
+ const char* this_value = NULL;
+ if (!buffer || !buffer_length)
+ return 0;
+ *buffer = '\0';
+ // Sanity check: Non-empty header list.
+ DCHECK(NULL != extra_headers) << "NULL extra headers pointer";
+ // Sanity check: Index out of range.
+ DCHECK((index >= 0) && (index < extra_header_count))
+ << "Index " << index
+ << " out of range [0, " << extra_header_count << ")";
+ this_header = extra_headers[index * 2];
+ // Sanity check: Non-empty header.
+ if (!*this_header)
+ return 0;
+ std::string::size_type header_len = strlen(this_header);
+ if (!header_len)
+ return 0;
+ this_value = extra_headers[1 + (index * 2)];
+ // Sanity check: Non-empty value.
+ if (!*this_value)
+ this_value = "";
+ int n = base::snprintf(buffer,
+ buffer_length,
+ "%s: %s\r\n",
+ this_header,
+ this_value);
+ return n;
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize) {
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ compressed,
+ stream_id,
+ request_priority,
+ type,
+ flags,
+ kHeaders,
+ kHeadersSize,
+ 0);
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize,
+ int associated_stream_id) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ type, // Kind = Syn
+ stream_id, // Stream ID
+ associated_stream_id, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+ // Priority
+ flags, // Control Flags
+ compressed, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ return ConstructSpdyPacket(kSynStartHeader,
+ extra_headers,
+ extra_header_count,
+ kHeaders,
+ kHeadersSize / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, 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,
+ int stream_id,
+ RequestPriority request_priority) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM, // Kind = Syn
+ stream_id, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+ // Priority
+ CONTROL_FLAG_FIN, // Control Flags
+ compressed, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+
+ std::string scheme, host, path;
+ ParseUrl(url, &scheme, &host, &path);
+ const char* const headers[] = {
+ "method", "GET",
+ "url", path.c_str(),
+ "host", host.c_str(),
+ "scheme", scheme.c_str(),
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader,
+ NULL,
+ 0,
+ headers,
+ arraysize(headers) / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority) {
+ return ConstructSpdyGet(extra_headers, extra_header_count, compressed,
+ stream_id, request_priority, true);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// 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 char* const kStandardGetHeaders[] = {
+ "method", "GET",
+ "url", (direct ? "/" : "http://www.google.com/"),
+ "host", "www.google.com",
+ "scheme", "http",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ compressed,
+ stream_id,
+ request_priority,
+ SYN_STREAM,
+ CONTROL_FLAG_FIN,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// 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 char* const kConnectHeaders[] = {
+ "method", "CONNECT",
+ "url", "www.google.com:443",
+ "host", "www.google.com",
+ "version", "HTTP/1.1",
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ /*compressed*/ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kConnectHeaders,
+ arraysize(kConnectHeaders));
+}
+
+// Constructs a standard SPDY push SYN packet.
+// |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* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ "status", "200",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id,
+ int associated_stream_id,
+ const char* url) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ "status", "200 OK",
+ "url", url,
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ associated_stream_id);
+
+}
+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) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ "status", status,
+ "location", location,
+ "url", url,
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPush(int stream_id,
+ int associated_stream_id,
+ const char* url) {
+ const char* const kStandardGetHeaders[] = {
+ "url", url
+ };
+ return ConstructSpdyControlFrame(0,
+ 0,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+ const char* const extra_headers[],
+ int extra_header_count) {
+ const char* const kStandardGetHeaders[] = {
+ "status", "200 OK",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ "status", status,
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ static const char* const kExtraHeaders[] = {
+ "location", "http://www.foo.com/index.php",
+ };
+ return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders,
+ arraysize(kExtraHeaders)/2, stream_id);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id) {
+ return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ static const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ "status", "200",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY POST SYN packet.
+// |content_length| is the size of post data.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+ const char* const extra_headers[],
+ int extra_header_count) {
+ std::string length_str = base::Int64ToString(content_length);
+ const char* post_headers[] = {
+ "method", "POST",
+ "url", "/",
+ "host", "www.google.com",
+ "scheme", "http",
+ "version", "HTTP/1.1",
+ "content-length", length_str.c_str()
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ post_headers,
+ arraysize(post_headers));
+}
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |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) {
+ const char* post_headers[] = {
+ "method", "POST",
+ "url", "/",
+ "host", "www.google.com",
+ "scheme", "http",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ post_headers,
+ arraysize(post_headers));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ static const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ "status", "200",
+ "url", "/index.php",
+ "version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a single SPDY data frame with the default contents.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, bool fin) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateDataFrame(
+ stream_id, kUploadData, kUploadDataSize,
+ fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Constructs a single SPDY data frame with the given content.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+ uint32 len, bool fin) {
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateDataFrame(
+ stream_id, data, len, fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Wraps |frame| in the payload of a data frame in stream |stream_id|.
+SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+ int stream_id) {
+ return ConstructSpdyBodyFrame(stream_id, frame->data(),
+ frame->length() + SpdyFrame::kHeaderSize,
+ false);
+}
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length) {
+ int packet_size = 0;
+ char* buffer_write = buffer;
+ int buffer_left = buffer_length;
+ SpdyHeaderBlock headers;
+ if (!buffer || !buffer_length)
+ return 0;
+ // Copy in the extra headers.
+ AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+ // The iterator gets us the list of header/value pairs in sorted order.
+ SpdyHeaderBlock::iterator next = headers.begin();
+ SpdyHeaderBlock::iterator last = headers.end();
+ for ( ; next != last; ++next) {
+ // Write the header.
+ int value_len, current_len, offset;
+ const char* header_string = next->first.c_str();
+ packet_size += AppendToBuffer(header_string,
+ next->first.length(),
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer(": ",
+ strlen(": "),
+ &buffer_write,
+ &buffer_left);
+ // Write the value(s).
+ const char* value_string = next->second.c_str();
+ // Check if it's split among two or more values.
+ value_len = next->second.length();
+ current_len = strlen(value_string);
+ offset = 0;
+ // Handle the first N-1 values.
+ while (current_len < value_len) {
+ // Finish this line -- write the current value.
+ packet_size += AppendToBuffer(value_string + offset,
+ current_len - offset,
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer("\n",
+ strlen("\n"),
+ &buffer_write,
+ &buffer_left);
+ // Advance to next value.
+ offset = current_len + 1;
+ current_len += 1 + strlen(value_string + offset);
+ // Start another line -- add the header again.
+ packet_size += AppendToBuffer(header_string,
+ next->first.length(),
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer(": ",
+ strlen(": "),
+ &buffer_write,
+ &buffer_left);
+ }
+ EXPECT_EQ(value_len, current_len);
+ // Copy the last (or only) value.
+ packet_size += AppendToBuffer(value_string + offset,
+ value_len - offset,
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer("\n",
+ strlen("\n"),
+ &buffer_write,
+ &buffer_left);
+ }
+ return packet_size;
+}
+
+// Create a MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req) {
+ return MockWrite(
+ ASYNC, req.data(), req.length() + SpdyFrame::kHeaderSize);
+}
+
+// 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.length() + SpdyFrame::kHeaderSize, seq);
+}
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp) {
+ return MockRead(
+ ASYNC, resp.data(), resp.length() + SpdyFrame::kHeaderSize);
+}
+
+// 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.length() + SpdyFrame::kHeaderSize, 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]->length() + SpdyFrame::kHeaderSize;
+ }
+ DCHECK_LE(total_len, buff_len);
+ char* ptr = buff;
+ for (int i = 0; i < num_frames; ++i) {
+ int len = frames[i]->length() + SpdyFrame::kHeaderSize;
+ memcpy(ptr, frames[i]->data(), len);
+ ptr += len;
+ }
+ return total_len;
+}
+
+SpdySessionDependencies::SpdySessionDependencies()
+ : host_resolver(new MockCachingHostResolver),
+ cert_verifier(new MockCertVerifier),
+ 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),
+ time_func(&base::TimeTicks::Now),
+ net_log(NULL) {
+ // 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(ProxyService* proxy_service)
+ : host_resolver(new MockHostResolver),
+ cert_verifier(new MockCertVerifier),
+ 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),
+ time_func(&base::TimeTicks::Now),
+ net_log(NULL) {}
+
+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.EnableSendingInitialSettings(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.EnableSendingInitialSettings(false);
+ return http_session;
+}
+
+// static
+net::HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams(
+ SpdySessionDependencies* session_deps) {
+ net::HttpNetworkSession::Params params;
+ params.host_resolver = session_deps->host_resolver.get();
+ params.cert_verifier = session_deps->cert_verifier.get();
+ params.proxy_service = session_deps->proxy_service.get();
+ params.ssl_config_service = session_deps->ssl_config_service;
+ params.http_auth_handler_factory =
+ session_deps->http_auth_handler_factory.get();
+ params.http_server_properties = &session_deps->http_server_properties;
+ params.enable_spdy_ip_pooling = session_deps->enable_ip_pooling;
+ params.enable_spdy_compression = session_deps->enable_compression;
+ params.enable_spdy_ping_based_connection_checking = session_deps->enable_ping;
+ params.spdy_default_protocol = kProtoSPDY2;
+ 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()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) {
+ storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
+ storage_.set_cert_verifier(new MockCertVerifier);
+ 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(new HttpServerPropertiesImpl);
+ net::HttpNetworkSession::Params params;
+ params.client_socket_factory = &socket_factory_;
+ params.host_resolver = host_resolver();
+ params.cert_verifier = cert_verifier();
+ 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 = kProtoSPDY2;
+ 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.EnableSendingInitialSettings(false);
+ storage_.set_http_transaction_factory(new HttpCache(
+ network_session,
+ HttpCache::DefaultBackend::InMemory(0)));
+}
+
+SpdyURLRequestContext::~SpdyURLRequestContext() {
+}
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type) {
+ const SpdyHeaderInfo kHeader = {
+ type, // Kind = Syn
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 2), // Priority
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ return kHeader;
+}
+
+} // namespace test_spdy2
+} // namespace net
diff --git a/src/net/spdy/spdy_test_util_spdy2.h b/src/net/spdy/spdy_test_util_spdy2.h
new file mode 100644
index 0000000..1a9be94
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy2.h
@@ -0,0 +1,425 @@
+// 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_TEST_UTIL_H_
+#define NET_SPDY_SPDY_TEST_UTIL_H_
+
+#include "base/basictypes.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+
+namespace net {
+
+namespace test_spdy2 {
+
+// 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;
+
+// NOTE: In GCC, on a Mac, this can't be in an anonymous namespace!
+// This struct holds information used to construct spdy control and data frames.
+struct SpdyHeaderInfo {
+ SpdyControlType kind;
+ SpdyStreamId id;
+ SpdyStreamId assoc_id;
+ SpdyPriority priority;
+ SpdyControlFlags control_flags;
+ bool compressed;
+ SpdyStatusCodes status;
+ const char* data;
+ uint32 data_length;
+ SpdyDataFlags data_flags;
+};
+
+// 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 AppendHeadersToSpdyFrame(const char* const extra_headers[],
+ int extra_header_count,
+ SpdyHeaderBlock* headers);
+
+// Writes |str| of the given |len| to the buffer pointed to by |buffer_handle|.
+// Uses a template so buffer_handle can be a char* or an unsigned char*.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written into *|buffer_handle|
+template<class T>
+int AppendToBuffer(const char* str,
+ int len,
+ T** buffer_handle,
+ int* buffer_len_remaining) {
+ DCHECK_GT(len, 0);
+ DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+ DCHECK(NULL != *buffer_handle) << "NULL pointer";
+ DCHECK(NULL != buffer_len_remaining)
+ << "NULL buffer remainder length pointer";
+ DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+ memcpy(*buffer_handle, str, len);
+ *buffer_handle += len;
+ *buffer_len_remaining -= len;
+ return len;
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+ int len,
+ unsigned char** buffer_handle,
+ int* buffer_len_remaining);
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail[],
+ int tail_header_count);
+
+// Construct a generic SpdyControlFrame.
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize);
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize,
+ int associated_stream_id);
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length);
+
+// 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);
+
+// 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);
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id);
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway();
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(SpdyStreamId, uint32 delta_window_size);
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status);
+
+// Construct a single SPDY header entry, for validation.
+// |extra_headers| are the extra header-value pairs.
+// |buffer| is the buffer we're filling in.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length,
+ int index);
+
+// Constructs a standard SPDY GET SYN packet, 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,
+ int stream_id,
+ RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, 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);
+
+// 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);
+
+// Constructs a standard SPDY push SYN packet.
+// |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);
+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* ConstructSpdyPush(int stream_id,
+ int associated_stream_id,
+ const char* url);
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+ const char* const extra_headers[],
+ int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet 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 packet 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 packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet 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 packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+ const char* const extra_headers[],
+ int extra_header_count);
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |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 packet 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);
+
+// 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);
+
+// Helper to manage the lifetimes of the dependencies for a
+// HttpNetworkTransaction.
+struct SpdySessionDependencies {
+ // Default set of dependencies -- "null" proxy service.
+ SpdySessionDependencies();
+
+ // Custom proxy service dependency.
+ explicit SpdySessionDependencies(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<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;
+ SpdySession::TimeFunc time_func;
+ std::string trusted_spdy_proxy;
+ NetLog* net_log;
+};
+
+class SpdyURLRequestContext : public URLRequestContext {
+ public:
+ SpdyURLRequestContext();
+ virtual ~SpdyURLRequestContext();
+
+ MockClientSocketFactory& socket_factory() { return socket_factory_; }
+
+ private:
+ MockClientSocketFactory socket_factory_;
+ net::URLRequestContextStorage storage_;
+};
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type);
+
+class SpdySessionPoolPeer {
+ public:
+ explicit SpdySessionPoolPeer(SpdySessionPool* pool)
+ : pool_(pool) {}
+
+ void AddAlias(const IPEndPoint& address, const HostPortProxyPair& pair) {
+ pool_->AddAlias(address, pair);
+ }
+
+ void RemoveAliases(const HostPortProxyPair& pair) {
+ pool_->RemoveAliases(pair);
+ }
+
+ void RemoveSpdySession(const scoped_refptr<SpdySession>& session) {
+ pool_->Remove(session);
+ }
+
+ void DisableDomainAuthenticationVerification() {
+ pool_->verify_domain_authentication_ = false;
+ }
+
+ void EnableSendingInitialSettings(bool enabled) {
+ pool_->enable_sending_initial_settings_ = enabled;
+ }
+
+ private:
+ SpdySessionPool* const pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer);
+};
+
+} // namespace test_spdy2
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_test_util_spdy3.cc b/src/net/spdy/spdy_test_util_spdy3.cc
new file mode 100644
index 0000000..79d8100
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy3.cc
@@ -0,0 +1,1033 @@
+// 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_util_spdy3.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "net/base/mock_cert_verifier.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/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+namespace test_spdy3 {
+
+namespace {
+
+// Parses a URL into the scheme, host, and path components required for a
+// SPDY request.
+void ParseUrl(const char* const url, std::string* scheme, std::string* host,
+ std::string* path) {
+ GURL gurl(url);
+ path->assign(gurl.PathForRequest());
+ scheme->assign(gurl.scheme());
+ host->assign(gurl.host());
+ if (gurl.has_port()) {
+ host->append(":");
+ host->append(gurl.port());
+ }
+}
+
+} // namespace
+
+
+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);
+}
+
+// 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.length() + SpdyFrame::kHeaderSize,
+ 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.length() + SpdyFrame::kHeaderSize,
+ 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 AppendHeadersToSpdyFrame(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;
+ }
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+ int len,
+ unsigned char** buffer_handle,
+ int* buffer_len_remaining) {
+ if (len <= 0)
+ return 0;
+ DCHECK((size_t) len <= sizeof(len)) << "Data length too long for data type";
+ DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+ DCHECK(NULL != *buffer_handle) << "NULL pointer";
+ DCHECK(NULL != buffer_len_remaining)
+ << "NULL buffer remainder length pointer";
+ DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+ for (int i = 0; i < len; i++) {
+ int shift = (8 * (len - (i + 1)));
+ unsigned char val_chunk = (val >> shift) & 0x0FF;
+ *(*buffer_handle)++ = val_chunk;
+ *buffer_len_remaining += 1;
+ }
+ return len;
+}
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail[],
+ int tail_header_count) {
+ BufferedSpdyFramer framer(3, header_info.compressed);
+ SpdyHeaderBlock headers;
+ // Copy in the extra headers to our map.
+ AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+ // Copy in the tail headers to our map.
+ if (tail && tail_header_count)
+ AppendHeadersToSpdyFrame(tail, tail_header_count, &headers);
+ SpdyFrame* frame = NULL;
+ switch (header_info.kind) {
+ case SYN_STREAM:
+ frame = framer.CreateSynStream(header_info.id, header_info.assoc_id,
+ header_info.priority,
+ header_info.credential_slot,
+ header_info.control_flags,
+ header_info.compressed, &headers);
+ break;
+ case SYN_REPLY:
+ frame = framer.CreateSynReply(header_info.id, header_info.control_flags,
+ header_info.compressed, &headers);
+ 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);
+ break;
+ default:
+ frame = framer.CreateDataFrame(header_info.id, header_info.data,
+ header_info.data_length,
+ header_info.data_flags);
+ break;
+ }
+ return frame;
+}
+
+// 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) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateSettings(settings);
+}
+
+// Construct an expected SPDY CREDENTIAL frame.
+// |credential| is the credential to sen.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyCredential(
+ const SpdyCredential& credential) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateCredentialFrame(credential);
+}
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreatePingFrame(ping_id);
+}
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway() {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateGoAway(0, GOAWAY_OK);
+}
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(
+ const SpdyStreamId stream_id, uint32 delta_window_size) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateRstStream(stream_id, status);
+}
+
+// Construct a single SPDY header entry, for validation.
+// |extra_headers| are the extra header-value pairs.
+// |buffer| is the buffer we're filling in.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length,
+ int index) {
+ const char* this_header = NULL;
+ const char* this_value = NULL;
+ if (!buffer || !buffer_length)
+ return 0;
+ *buffer = '\0';
+ // Sanity check: Non-empty header list.
+ DCHECK(NULL != extra_headers) << "NULL extra headers pointer";
+ // Sanity check: Index out of range.
+ DCHECK((index >= 0) && (index < extra_header_count))
+ << "Index " << index
+ << " out of range [0, " << extra_header_count << ")";
+ this_header = extra_headers[index * 2];
+ // Sanity check: Non-empty header.
+ if (!*this_header)
+ return 0;
+ std::string::size_type header_len = strlen(this_header);
+ if (!header_len)
+ return 0;
+ this_value = extra_headers[1 + (index * 2)];
+ // Sanity check: Non-empty value.
+ if (!*this_value)
+ this_value = "";
+ int n = base::snprintf(buffer,
+ buffer_length,
+ "%s: %s\r\n",
+ this_header,
+ this_value);
+ return n;
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize) {
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ compressed,
+ stream_id,
+ request_priority,
+ type,
+ flags,
+ kHeaders,
+ kHeadersSize,
+ 0);
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize,
+ int associated_stream_id) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ type, // Kind = Syn
+ stream_id, // Stream ID
+ associated_stream_id, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+ // Priority
+ 0, // Credential Slot
+ flags, // Control Flags
+ compressed, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ return ConstructSpdyPacket(kSynStartHeader,
+ extra_headers,
+ extra_header_count,
+ kHeaders,
+ kHeadersSize / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, 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,
+ int stream_id,
+ RequestPriority request_priority) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM, // Kind = Syn
+ stream_id, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+ // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_FIN, // Control Flags
+ compressed, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+
+ std::string scheme, host, path;
+ ParseUrl(url, &scheme, &host, &path);
+ const char* const headers[] = {
+ ":method", "GET",
+ ":path", path.c_str(),
+ ":host", host.c_str(),
+ ":scheme", scheme.c_str(),
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyPacket(
+ kSynStartHeader,
+ NULL,
+ 0,
+ headers,
+ arraysize(headers) / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority) {
+ return ConstructSpdyGet(extra_headers, extra_header_count, compressed,
+ stream_id, request_priority, true);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// 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 char* const kStandardGetHeaders[] = {
+ ":method", "GET",
+ ":host", "www.google.com",
+ ":scheme", "http",
+ ":version", "HTTP/1.1",
+ ":path", (direct ? "/" : "/")
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ compressed,
+ stream_id,
+ request_priority,
+ SYN_STREAM,
+ CONTROL_FLAG_FIN,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// 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 char* const kConnectHeaders[] = {
+ ":method", "CONNECT",
+ ":path", "www.google.com:443",
+ ":host", "www.google.com",
+ ":version", "HTTP/1.1",
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ /*compressed*/ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kConnectHeaders,
+ arraysize(kConnectHeaders));
+}
+
+// Constructs a standard SPDY push SYN packet.
+// |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* const kStandardPushHeaders[] = {
+ "hello", "bye",
+ ":status", "200",
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kStandardPushHeaders,
+ arraysize(kStandardPushHeaders),
+ associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id,
+ int associated_stream_id,
+ const char* url) {
+ std::string scheme, host, path;
+ ParseUrl(url, &scheme, &host, &path);
+ const char* const headers[] = {
+ "hello", "bye",
+ ":status", "200 OK",
+ ":version", "HTTP/1.1",
+ ":path", path.c_str(),
+ ":host", host.c_str(),
+ ":scheme", scheme.c_str(),
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ headers,
+ arraysize(headers),
+ associated_stream_id);
+
+}
+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) {
+ std::string scheme, host, path;
+ ParseUrl(url, &scheme, &host, &path);
+ const char* const headers[] = {
+ "hello", "bye",
+ ":status", status,
+ "location", location,
+ ":path", path.c_str(),
+ ":host", host.c_str(),
+ ":scheme", scheme.c_str(),
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ headers,
+ arraysize(headers),
+ associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+ const char* const extra_headers[],
+ int extra_header_count) {
+ const char* const kStandardGetHeaders[] = {
+ ":status", "200 OK",
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ ":status", status,
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ static const char* const kExtraHeaders[] = {
+ "location", "http://www.foo.com/index.php",
+ };
+ return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders,
+ arraysize(kExtraHeaders)/2, stream_id);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id) {
+ return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1);
+}
+
+
+
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ static const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ ":status", "200",
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY POST SYN packet.
+// |content_length| is the size of post data.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+ const char* const extra_headers[],
+ int extra_header_count) {
+ std::string length_str = base::Int64ToString(content_length);
+ const char* post_headers[] = {
+ ":method", "POST",
+ ":path", "/",
+ ":host", "www.google.com",
+ ":scheme", "http",
+ ":version", "HTTP/1.1",
+ "content-length", length_str.c_str()
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ post_headers,
+ arraysize(post_headers));
+}
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |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) {
+ const char* post_headers[] = {
+ ":method", "POST",
+ ":path", "/",
+ ":host", "www.google.com",
+ ":scheme", "http",
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ post_headers,
+ arraysize(post_headers));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet 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) {
+ static const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ ":status", "200",
+ "url", "/index.php",
+ ":version", "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders));
+}
+
+// Constructs a single SPDY data frame with the default contents.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, bool fin) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateDataFrame(
+ stream_id, kUploadData, kUploadDataSize,
+ fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Constructs a single SPDY data frame with the given content.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+ uint32 len, bool fin) {
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateDataFrame(
+ stream_id, data, len, fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Wraps |frame| in the payload of a data frame in stream |stream_id|.
+SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+ int stream_id) {
+ return ConstructSpdyBodyFrame(stream_id, frame->data(),
+ frame->length() + SpdyFrame::kHeaderSize,
+ false);
+}
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length) {
+ int packet_size = 0;
+ char* buffer_write = buffer;
+ int buffer_left = buffer_length;
+ SpdyHeaderBlock headers;
+ if (!buffer || !buffer_length)
+ return 0;
+ // Copy in the extra headers.
+ AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+ // The iterator gets us the list of header/value pairs in sorted order.
+ SpdyHeaderBlock::iterator next = headers.begin();
+ SpdyHeaderBlock::iterator last = headers.end();
+ for ( ; next != last; ++next) {
+ // Write the header.
+ int value_len, current_len, offset;
+ const char* header_string = next->first.c_str();
+ if (header_string && header_string[0] == ':')
+ header_string++;
+ packet_size += AppendToBuffer(header_string,
+ strlen(header_string),
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer(": ",
+ strlen(": "),
+ &buffer_write,
+ &buffer_left);
+ // Write the value(s).
+ const char* value_string = next->second.c_str();
+ // Check if it's split among two or more values.
+ value_len = next->second.length();
+ current_len = strlen(value_string);
+ offset = 0;
+ // Handle the first N-1 values.
+ while (current_len < value_len) {
+ // Finish this line -- write the current value.
+ packet_size += AppendToBuffer(value_string + offset,
+ current_len - offset,
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer("\n",
+ strlen("\n"),
+ &buffer_write,
+ &buffer_left);
+ // Advance to next value.
+ offset = current_len + 1;
+ current_len += 1 + strlen(value_string + offset);
+ // Start another line -- add the header again.
+ packet_size += AppendToBuffer(header_string,
+ next->first.length(),
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer(": ",
+ strlen(": "),
+ &buffer_write,
+ &buffer_left);
+ }
+ EXPECT_EQ(value_len, current_len);
+ // Copy the last (or only) value.
+ packet_size += AppendToBuffer(value_string + offset,
+ value_len - offset,
+ &buffer_write,
+ &buffer_left);
+ packet_size += AppendToBuffer("\n",
+ strlen("\n"),
+ &buffer_write,
+ &buffer_left);
+ }
+ return packet_size;
+}
+
+// Create a MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req) {
+ return MockWrite(
+ ASYNC, req.data(), req.length() + SpdyFrame::kHeaderSize);
+}
+
+// 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.length() + SpdyFrame::kHeaderSize, seq);
+}
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp) {
+ return MockRead(
+ ASYNC, resp.data(), resp.length() + SpdyFrame::kHeaderSize);
+}
+
+// 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.length() + SpdyFrame::kHeaderSize, 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]->length() + SpdyFrame::kHeaderSize;
+ }
+ DCHECK_LE(total_len, buff_len);
+ char* ptr = buff;
+ for (int i = 0; i < num_frames; ++i) {
+ int len = frames[i]->length() + SpdyFrame::kHeaderSize;
+ memcpy(ptr, frames[i]->data(), len);
+ ptr += len;
+ }
+ return total_len;
+}
+
+SpdySessionDependencies::SpdySessionDependencies()
+ : host_resolver(new MockCachingHostResolver),
+ cert_verifier(new MockCertVerifier),
+ 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),
+ initial_recv_window_size(kSpdyStreamInitialWindowSize),
+ time_func(&base::TimeTicks::Now),
+ net_log(NULL) {
+ // 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(ProxyService* proxy_service)
+ : host_resolver(new MockHostResolver),
+ cert_verifier(new MockCertVerifier),
+ 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),
+ initial_recv_window_size(kSpdyStreamInitialWindowSize),
+ time_func(&base::TimeTicks::Now),
+ net_log(NULL) {}
+
+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.EnableSendingInitialSettings(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.EnableSendingInitialSettings(false);
+ return http_session;
+}
+
+// static
+net::HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams(
+ SpdySessionDependencies* session_deps) {
+ net::HttpNetworkSession::Params params;
+ params.host_resolver = session_deps->host_resolver.get();
+ params.cert_verifier = session_deps->cert_verifier.get();
+ params.proxy_service = session_deps->proxy_service.get();
+ params.ssl_config_service = session_deps->ssl_config_service;
+ params.http_auth_handler_factory =
+ session_deps->http_auth_handler_factory.get();
+ params.http_server_properties = &session_deps->http_server_properties;
+ params.enable_spdy_compression = session_deps->enable_compression;
+ params.enable_spdy_ping_based_connection_checking = session_deps->enable_ping;
+ params.spdy_default_protocol = kProtoSPDY3;
+ params.spdy_initial_recv_window_size = session_deps->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()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) {
+ storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
+ storage_.set_cert_verifier(new MockCertVerifier);
+ 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(new HttpServerPropertiesImpl);
+ net::HttpNetworkSession::Params params;
+ params.client_socket_factory = &socket_factory_;
+ params.host_resolver = host_resolver();
+ params.cert_verifier = cert_verifier();
+ 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 = kProtoSPDY3;
+ 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.EnableSendingInitialSettings(false);
+ storage_.set_http_transaction_factory(new HttpCache(
+ network_session,
+ HttpCache::DefaultBackend::InMemory(0)));
+}
+
+SpdyURLRequestContext::~SpdyURLRequestContext() {
+}
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type) {
+ const SpdyHeaderInfo kHeader = {
+ type, // Kind = Syn
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3), // Priority
+ 0, // Credential Slot
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ return kHeader;
+}
+
+} // namespace test_spdy3
+
+} // namespace net
diff --git a/src/net/spdy/spdy_test_util_spdy3.h b/src/net/spdy/spdy_test_util_spdy3.h
new file mode 100644
index 0000000..5103c89
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy3.h
@@ -0,0 +1,466 @@
+// 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_TEST_UTIL_H_
+#define NET_SPDY_SPDY_TEST_UTIL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+
+namespace crypto {
+class ECSignatureCreatorFactory;
+}
+
+namespace net {
+
+namespace test_spdy3 {
+
+// 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;
+
+// NOTE: In GCC, on a Mac, this can't be in an anonymous namespace!
+// This struct holds information used to construct spdy control and data frames.
+struct SpdyHeaderInfo {
+ SpdyControlType kind;
+ SpdyStreamId id;
+ SpdyStreamId assoc_id;
+ SpdyPriority priority;
+ size_t credential_slot;
+ SpdyControlFlags control_flags;
+ bool compressed;
+ SpdyStatusCodes 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);
+};
+
+// 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 AppendHeadersToSpdyFrame(const char* const extra_headers[],
+ int extra_header_count,
+ SpdyHeaderBlock* headers);
+
+// Writes |str| of the given |len| to the buffer pointed to by |buffer_handle|.
+// Uses a template so buffer_handle can be a char* or an unsigned char*.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written into *|buffer_handle|
+template<class T>
+int AppendToBuffer(const char* str,
+ int len,
+ T** buffer_handle,
+ int* buffer_len_remaining) {
+ DCHECK_GT(len, 0);
+ DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+ DCHECK(NULL != *buffer_handle) << "NULL pointer";
+ DCHECK(NULL != buffer_len_remaining)
+ << "NULL buffer remainder length pointer";
+ DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+ memcpy(*buffer_handle, str, len);
+ *buffer_handle += len;
+ *buffer_len_remaining -= len;
+ return len;
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+ int len,
+ unsigned char** buffer_handle,
+ int* buffer_len_remaining);
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail[],
+ int tail_header_count);
+
+// Construct a generic SpdyControlFrame.
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize);
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ SpdyControlType type,
+ SpdyControlFlags flags,
+ const char* const* kHeaders,
+ int kHeadersSize,
+ int associated_stream_id);
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length);
+
+// 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);
+
+// 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);
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id);
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway();
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(SpdyStreamId, uint32 delta_window_size);
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+ SpdyStatusCodes status);
+
+// Construct a single SPDY header entry, for validation.
+// |extra_headers| are the extra header-value pairs.
+// |buffer| is the buffer we're filling in.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+ int extra_header_count,
+ char* buffer,
+ int buffer_length,
+ int index);
+
+// Constructs a standard SPDY GET SYN packet, 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,
+ int stream_id,
+ RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, 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);
+
+// 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);
+
+// Constructs a standard SPDY push SYN packet.
+// |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);
+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* ConstructSpdyPush(int stream_id,
+ int associated_stream_id,
+ const char* url);
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+ const char* const extra_headers[],
+ int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet 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 packet 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 packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet 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 packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+ const char* const extra_headers[],
+ int extra_header_count);
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |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 packet 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);
+
+// 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);
+
+// Helper to manage the lifetimes of the dependencies for a
+// HttpNetworkTransaction.
+struct SpdySessionDependencies {
+ // Default set of dependencies -- "null" proxy service.
+ SpdySessionDependencies();
+
+ // Custom proxy service dependency.
+ explicit SpdySessionDependencies(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<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;
+ size_t initial_recv_window_size;
+ SpdySession::TimeFunc time_func;
+ std::string trusted_spdy_proxy;
+ NetLog* net_log;
+};
+
+class SpdyURLRequestContext : public URLRequestContext {
+ public:
+ SpdyURLRequestContext();
+ virtual ~SpdyURLRequestContext();
+
+ MockClientSocketFactory& socket_factory() { return socket_factory_; }
+
+ private:
+ MockClientSocketFactory socket_factory_;
+ net::URLRequestContextStorage storage_;
+};
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type);
+
+class SpdySessionPoolPeer {
+ public:
+ explicit SpdySessionPoolPeer(SpdySessionPool* pool)
+ : pool_(pool) {}
+
+ void AddAlias(const IPEndPoint& address, const HostPortProxyPair& pair) {
+ pool_->AddAlias(address, pair);
+ }
+
+ void RemoveAliases(const HostPortProxyPair& pair) {
+ pool_->RemoveAliases(pair);
+ }
+
+ void RemoveSpdySession(const scoped_refptr<SpdySession>& session) {
+ pool_->Remove(session);
+ }
+
+ void DisableDomainAuthenticationVerification() {
+ pool_->verify_domain_authentication_ = false;
+ }
+
+ void EnableSendingInitialSettings(bool enabled) {
+ pool_->enable_sending_initial_settings_ = enabled;
+ }
+
+ private:
+ SpdySessionPool* const pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer);
+};
+
+} // namespace test_spdy3
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_websocket_stream.cc b/src/net/spdy/spdy_websocket_stream.cc
new file mode 100644
index 0000000..6323b71
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream.cc
@@ -0,0 +1,147 @@
+// 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 "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/base/io_buffer.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"
+
+namespace net {
+
+SpdyWebSocketStream::SpdyWebSocketStream(
+ SpdySession* spdy_session, Delegate* delegate)
+ : stream_(NULL),
+ spdy_session_(spdy_session),
+ delegate_(delegate) {
+ DCHECK(spdy_session_);
+ DCHECK(delegate_);
+}
+
+SpdyWebSocketStream::~SpdyWebSocketStream() {
+ if (stream_) {
+ // If Close() has not already been called, DetachDelegate() will send a
+ // SPDY RST_STREAM. Deleting SpdyWebSocketStream is good enough to initiate
+ // graceful shutdown, so we call Close() to avoid sending a RST_STREAM.
+ // For safe, we should eliminate |delegate_| for OnClose() calback.
+ delegate_ = NULL;
+ stream_->Close();
+ }
+}
+
+int SpdyWebSocketStream::InitializeStream(const GURL& url,
+ RequestPriority request_priority,
+ const BoundNetLog& net_log) {
+ if (spdy_session_->IsClosed())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ int result = spdy_session_->CreateStream(
+ url, request_priority, &stream_, net_log,
+ base::Bind(&SpdyWebSocketStream::OnSpdyStreamCreated,
+ base::Unretained(this)));
+
+ if (result == OK) {
+ DCHECK(stream_);
+ stream_->SetDelegate(this);
+ }
+ return result;
+}
+
+int SpdyWebSocketStream::SendRequest(scoped_ptr<SpdyHeaderBlock> headers) {
+ if (!stream_) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ stream_->set_spdy_headers(headers.Pass());
+ int result = stream_->SendRequest(true);
+ if (result < OK && result != ERR_IO_PENDING)
+ Close();
+ return result;
+}
+
+int SpdyWebSocketStream::SendData(const char* data, int length) {
+ if (!stream_) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ scoped_refptr<IOBuffer> buf(new IOBuffer(length));
+ memcpy(buf->data(), data, length);
+ return stream_->WriteStreamData(buf.get(), length, DATA_FLAG_NONE);
+}
+
+void SpdyWebSocketStream::Close() {
+ if (spdy_session_)
+ spdy_session_->CancelPendingCreateStreams(&stream_);
+ if (stream_)
+ stream_->Close();
+}
+
+bool SpdyWebSocketStream::OnSendHeadersComplete(int status) {
+ DCHECK(delegate_);
+ delegate_->OnSentSpdyHeaders(status);
+ return true;
+}
+
+int SpdyWebSocketStream::OnSendBody() {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+}
+
+int SpdyWebSocketStream::OnSendBodyComplete(int status, bool* eof) {
+ NOTREACHED();
+ *eof = true;
+ return ERR_UNEXPECTED;
+}
+
+int SpdyWebSocketStream::OnResponseReceived(
+ const SpdyHeaderBlock& response,
+ base::Time response_time, int status) {
+ DCHECK(delegate_);
+ return delegate_->OnReceivedSpdyResponseHeader(response, status);
+}
+
+void SpdyWebSocketStream::OnHeadersSent() {
+ // This will be called when WebSocket over SPDY supports new framing.
+ NOTREACHED();
+}
+
+int SpdyWebSocketStream::OnDataReceived(const char* data, int length) {
+ DCHECK(delegate_);
+ delegate_->OnReceivedSpdyData(data, length);
+ return OK;
+}
+
+void SpdyWebSocketStream::OnDataSent(int length) {
+ DCHECK(delegate_);
+ delegate_->OnSentSpdyData(length);
+}
+
+void SpdyWebSocketStream::OnClose(int status) {
+ stream_ = NULL;
+
+ // 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) {
+ DCHECK(stream_);
+ stream_->SetDelegate(this);
+ }
+ DCHECK(delegate_);
+ delegate_->OnCreatedSpdyStream(result);
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_websocket_stream.h b/src/net/spdy/spdy_websocket_stream.h
new file mode 100644
index 0000000..5ab4c48
--- /dev/null
+++ b/src/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/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_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(int status) = 0;
+
+ // Called on corresponding to OnResponseReceived() 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 int OnReceivedSpdyResponseHeader(
+ const SpdyHeaderBlock& headers,
+ int status) = 0;
+
+ // Called when data is sent.
+ virtual void OnSentSpdyData(int amount_sent) = 0;
+
+ // Called when data is received.
+ virtual void OnReceivedSpdyData(const char* data, int length) = 0;
+
+ // Called when SpdyStream is closed.
+ virtual void OnCloseSpdyStream() = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ SpdyWebSocketStream(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 bool OnSendHeadersComplete(int status) OVERRIDE;
+ virtual int OnSendBody() OVERRIDE;
+ virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+ virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) OVERRIDE;
+ virtual void OnHeadersSent() OVERRIDE;
+ virtual int OnDataReceived(const char* data, int length) OVERRIDE;
+ virtual void OnDataSent(int length) OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ private:
+ friend class SpdyWebSocketStreamSpdy2Test;
+ friend class SpdyWebSocketStreamSpdy3Test;
+ FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamSpdy2Test, Basic);
+ FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamSpdy3Test, Basic);
+
+ void OnSpdyStreamCreated(int status);
+
+ scoped_refptr<SpdyStream> stream_;
+ scoped_refptr<SpdySession> spdy_session_;
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
diff --git a/src/net/spdy/spdy_websocket_stream_spdy2_unittest.cc b/src/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
new file mode 100644
index 0000000..20b7217
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
@@ -0,0 +1,615 @@
+// 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 <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/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_test_util_spdy2.h"
+#include "net/spdy/spdy_websocket_test_util_spdy2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+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) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED,
+ SpdyHeaderBlock(),
+ result,
+ std::string()));
+ if (!on_created_.is_null())
+ on_created_.Run(&events_.back());
+ }
+ virtual void OnSentSpdyHeaders(int result) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ SpdyHeaderBlock(),
+ result,
+ std::string()));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual int OnReceivedSpdyResponseHeader(
+ const SpdyHeaderBlock& headers, int status) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ headers,
+ status,
+ std::string()));
+ if (!on_received_header_.is_null())
+ on_received_header_.Run(&events_.back());
+ return status;
+ }
+ virtual void OnSentSpdyData(int amount_sent) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ SpdyHeaderBlock(),
+ amount_sent,
+ std::string()));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual void OnReceivedSpdyData(const char* data, int length) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ SpdyHeaderBlock(),
+ length,
+ std::string(data, length)));
+ if (!on_received_data_.is_null())
+ on_received_data_.Run(&events_.back());
+ }
+ virtual void OnCloseSpdyStream() {
+ 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 SpdyWebSocketStreamSpdy2Test : public testing::Test {
+ 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:
+ SpdyWebSocketStreamSpdy2Test() {}
+ virtual ~SpdyWebSocketStreamSpdy2Test() {}
+
+ virtual void SetUp() {
+ host_port_pair_.set_host("example.com");
+ host_port_pair_.set_port(80);
+ host_port_proxy_pair_.first = host_port_pair_;
+ host_port_proxy_pair_.second = ProxyServer::Direct();
+
+ spdy_settings_id_to_set_ = SETTINGS_MAX_CONCURRENT_STREAMS;
+ spdy_settings_flags_to_set_ = SETTINGS_FLAG_PLEASE_PERSIST;
+ spdy_settings_value_to_set_ = 1;
+
+ spdy_settings_to_send_[spdy_settings_id_to_set_] =
+ SettingsFlagsAndValue(
+ SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_);
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ void Prepare(SpdyStreamId stream_id) {
+ stream_id_ = stream_id;
+
+ request_frame_.reset(ConstructSpdyWebSocketSynStream(
+ stream_id_,
+ "/echo",
+ "example.com",
+ "http://example.com/wsdemo"));
+
+ response_frame_.reset(ConstructSpdyWebSocketSynReply(stream_id_));
+
+ message_frame_.reset(ConstructSpdyWebSocketDataFrame(
+ kMessageFrame,
+ kMessageFrameLength,
+ stream_id_,
+ false));
+
+ closing_frame_.reset(ConstructSpdyWebSocketDataFrame(
+ kClosingFrame,
+ kClosingFrameLength,
+ stream_id_,
+ false));
+ }
+
+ int InitSession(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ bool throttling) {
+ data_.reset(new OrderedSocketData(reads, reads_count,
+ writes, writes_count));
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ SpdySessionPool* spdy_session_pool(http_session_->spdy_session_pool());
+
+ if (throttling) {
+ // Set max concurrent streams to 1.
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ host_port_pair_,
+ spdy_settings_id_to_set_,
+ spdy_settings_flags_to_set_,
+ spdy_settings_value_to_set_);
+ }
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+ session_ = spdy_session_pool->Get(host_port_proxy_pair_, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+ transport_params_ = new TransportSocketParams(host_port_pair_, MEDIUM,
+ false, false,
+ OnHostResolutionCallback());
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair_.ToString(), transport_params_,
+ MEDIUM, callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ return session_->InitializeWithSocket(connection.release(), false, OK);
+ }
+
+ void SendRequest() {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)["path"] = "/echo";
+ (*headers)["host"] = "example.com";
+ (*headers)["version"] = "WebSocket/13";
+ (*headers)["scheme"] = "ws";
+ (*headers)["origin"] = "http://example.com/wsdemo";
+
+ websocket_stream_->SendRequest(headers.Pass());
+ }
+
+ 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_;
+ scoped_refptr<SpdySession> session_;
+ scoped_refptr<TransportSocketParams> transport_params_;
+ 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_;
+ HostPortProxyPair host_port_proxy_pair_;
+ TestCompletionCallback completion_callback_;
+ TestCompletionCallback sync_callback_;
+
+ static const char kMessageFrame[];
+ static const char kClosingFrame[];
+ static const size_t kMessageFrameLength;
+ static const size_t kClosingFrameLength;
+};
+
+// TODO(toyoshim): Replace old framing data to new one, then use HEADERS and
+// data frames.
+const char SpdyWebSocketStreamSpdy2Test::kMessageFrame[] = "\x81\x05hello";
+const char SpdyWebSocketStreamSpdy2Test::kClosingFrame[] = "\x88\0";
+const size_t SpdyWebSocketStreamSpdy2Test::kMessageFrameLength =
+ arraysize(SpdyWebSocketStreamSpdy2Test::kMessageFrame) - 1;
+const size_t SpdyWebSocketStreamSpdy2Test::kClosingFrameLength =
+ arraysize(SpdyWebSocketStreamSpdy2Test::kClosingFrame) - 1;
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, 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.
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), false));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::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_);
+
+ 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_LT(0, 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_TRUE(!http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, 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)
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), false));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::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_LT(0, 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(http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, 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)
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), false));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::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_LT(0, 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(http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+}
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, IOPending) {
+ Prepare(1);
+ scoped_ptr<SpdyFrame> settings_frame(
+ ConstructSpdySettings(spdy_settings_to_send_));
+ MockWrite writes[] = {
+ // Setting throttling make SpdySession send settings frame automatically.
+ CreateMockWrite(*settings_frame.get(), 1),
+ CreateMockWrite(*request_frame_.get(), 3),
+ CreateMockWrite(*message_frame_.get(), 6),
+ CreateMockWrite(*closing_frame_.get(), 9)
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame.get(), 2),
+ CreateMockRead(*response_frame_.get(), 4),
+ // Skip sequence 5 (I/O Pending)
+ CreateMockRead(*message_frame_.get(), 7),
+ // Skip sequence 8 (I/O Pending)
+ CreateMockRead(*closing_frame_.get(), 10),
+ MockRead(SYNCHRONOUS, 0, 11) // EOF cause OnCloseSpdyStream event.
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), true));
+
+ // 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));
+
+ // Create a WebSocketStream under test.
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnCreated(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync,
+ base::Unretained(this)));
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy2Test::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();
+
+ 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_LT(0, 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_TRUE(!http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_websocket_stream_spdy3_unittest.cc b/src/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
new file mode 100644
index 0000000..932d0a0
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
@@ -0,0 +1,615 @@
+// 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 <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/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_test_util_spdy3.h"
+#include "net/spdy/spdy_websocket_test_util_spdy3.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+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) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED,
+ SpdyHeaderBlock(),
+ result,
+ std::string()));
+ if (!on_created_.is_null())
+ on_created_.Run(&events_.back());
+ }
+ virtual void OnSentSpdyHeaders(int result) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ SpdyHeaderBlock(),
+ result,
+ std::string()));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual int OnReceivedSpdyResponseHeader(
+ const SpdyHeaderBlock& headers, int status) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ headers,
+ status,
+ std::string()));
+ if (!on_received_header_.is_null())
+ on_received_header_.Run(&events_.back());
+ return status;
+ }
+ virtual void OnSentSpdyData(int amount_sent) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ SpdyHeaderBlock(),
+ amount_sent,
+ std::string()));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual void OnReceivedSpdyData(const char* data, int length) {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ SpdyHeaderBlock(),
+ length,
+ std::string(data, length)));
+ if (!on_received_data_.is_null())
+ on_received_data_.Run(&events_.back());
+ }
+ virtual void OnCloseSpdyStream() {
+ 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 SpdyWebSocketStreamSpdy3Test : public testing::Test {
+ 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:
+ SpdyWebSocketStreamSpdy3Test() {}
+ virtual ~SpdyWebSocketStreamSpdy3Test() {}
+
+ virtual void SetUp() {
+ host_port_pair_.set_host("example.com");
+ host_port_pair_.set_port(80);
+ host_port_proxy_pair_.first = host_port_pair_;
+ host_port_proxy_pair_.second = ProxyServer::Direct();
+
+ spdy_settings_id_to_set_ = SETTINGS_MAX_CONCURRENT_STREAMS;
+ spdy_settings_flags_to_set_ = SETTINGS_FLAG_PLEASE_PERSIST;
+ spdy_settings_value_to_set_ = 1;
+
+ spdy_settings_to_send_[spdy_settings_id_to_set_] =
+ SettingsFlagsAndValue(
+ SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_);
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunUntilIdle();
+ }
+
+ void Prepare(SpdyStreamId stream_id) {
+ stream_id_ = stream_id;
+
+ request_frame_.reset(ConstructSpdyWebSocketSynStream(
+ stream_id_,
+ "/echo",
+ "example.com",
+ "http://example.com/wsdemo"));
+
+ response_frame_.reset(ConstructSpdyWebSocketSynReply(stream_id_));
+
+ message_frame_.reset(ConstructSpdyWebSocketDataFrame(
+ kMessageFrame,
+ kMessageFrameLength,
+ stream_id_,
+ false));
+
+ closing_frame_.reset(ConstructSpdyWebSocketDataFrame(
+ kClosingFrame,
+ kClosingFrameLength,
+ stream_id_,
+ false));
+ }
+
+ int InitSession(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ bool throttling) {
+ data_.reset(new OrderedSocketData(reads, reads_count,
+ writes, writes_count));
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ SpdySessionPool* spdy_session_pool(http_session_->spdy_session_pool());
+
+ if (throttling) {
+ // Set max concurrent streams to 1.
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ host_port_pair_,
+ spdy_settings_id_to_set_,
+ spdy_settings_flags_to_set_,
+ spdy_settings_value_to_set_);
+ }
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+ session_ = spdy_session_pool->Get(host_port_proxy_pair_, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+ transport_params_ = new TransportSocketParams(host_port_pair_, MEDIUM,
+ false, false,
+ OnHostResolutionCallback());
+ TestCompletionCallback callback;
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection->Init(host_port_pair_.ToString(), transport_params_,
+ MEDIUM, callback.callback(),
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ return session_->InitializeWithSocket(connection.release(), false, OK);
+ }
+
+ void SendRequest() {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ (*headers)[":path"] = "/echo";
+ (*headers)[":host"] = "example.com";
+ (*headers)[":version"] = "WebSocket/13";
+ (*headers)[":scheme"] = "ws";
+ (*headers)[":origin"] = "http://example.com/wsdemo";
+
+ websocket_stream_->SendRequest(headers.Pass());
+ }
+
+ 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_;
+ scoped_refptr<SpdySession> session_;
+ scoped_refptr<TransportSocketParams> transport_params_;
+ 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_;
+ HostPortProxyPair host_port_proxy_pair_;
+ TestCompletionCallback completion_callback_;
+ TestCompletionCallback sync_callback_;
+
+ static const char kMessageFrame[];
+ static const char kClosingFrame[];
+ static const size_t kMessageFrameLength;
+ static const size_t kClosingFrameLength;
+};
+
+// TODO(toyoshim): Replace old framing data to new one, then use HEADERS and
+// data frames.
+const char SpdyWebSocketStreamSpdy3Test::kMessageFrame[] = "\x81\x05hello";
+const char SpdyWebSocketStreamSpdy3Test::kClosingFrame[] = "\x88\0";
+const size_t SpdyWebSocketStreamSpdy3Test::kMessageFrameLength =
+ arraysize(SpdyWebSocketStreamSpdy3Test::kMessageFrame) - 1;
+const size_t SpdyWebSocketStreamSpdy3Test::kClosingFrameLength =
+ arraysize(SpdyWebSocketStreamSpdy3Test::kClosingFrame) - 1;
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, 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.
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), false));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::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_);
+
+ 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_LT(0, 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_TRUE(!http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, 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)
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), false));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::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_LT(0, 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(http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, 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)
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), false));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::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_LT(0, 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(http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+}
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, IOPending) {
+ Prepare(1);
+ scoped_ptr<SpdyFrame> settings_frame(
+ ConstructSpdySettings(spdy_settings_to_send_));
+ MockWrite writes[] = {
+ // Setting throttling make SpdySession send settings frame automatically.
+ CreateMockWrite(*settings_frame.get(), 1),
+ CreateMockWrite(*request_frame_.get(), 3),
+ CreateMockWrite(*message_frame_.get(), 6),
+ CreateMockWrite(*closing_frame_.get(), 9)
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame.get(), 2),
+ CreateMockRead(*response_frame_.get(), 4),
+ // Skip sequence 5 (I/O Pending)
+ CreateMockRead(*message_frame_.get(), 7),
+ // Skip sequence 8 (I/O Pending)
+ CreateMockRead(*closing_frame_.get(), 10),
+ MockRead(SYNCHRONOUS, 0, 11) // EOF cause OnCloseSpdyStream event.
+ };
+
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+ writes, arraysize(writes), true));
+
+ // 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));
+
+ // Create a WebSocketStream under test.
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnCreated(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSync,
+ base::Unretained(this)));
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamSpdy3Test::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 first stream to allow create the second stream.
+ block_stream.reset();
+ ASSERT_EQ(OK, sync_callback_.WaitForResult());
+
+ SendRequest();
+
+ 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_LT(0, 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_TRUE(!http_session_->spdy_session_pool()->HasSession(
+ host_port_proxy_pair_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+} // namespace net
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy2.cc b/src/net/spdy/spdy_websocket_test_util_spdy2.cc
new file mode 100644
index 0000000..1a45949
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy2.cc
@@ -0,0 +1,162 @@
+// 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_test_util_spdy2.h"
+
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+
+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;
+
+namespace net {
+
+namespace test_spdy2 {
+
+SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+ const char* path,
+ const char* host,
+ const char* origin) {
+ const char* const kWebSocketHeaders[] = {
+ "path",
+ path,
+ "host",
+ host,
+ "version",
+ "WebSocket/13",
+ "scheme",
+ "ws",
+ "origin",
+ origin
+ };
+ return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+ /*extra_header_count*/ 0,
+ /*compressed*/ false,
+ stream_id,
+ HIGHEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kWebSocketHeaders,
+ arraysize(kWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id) {
+ static const char* const kStandardWebSocketHeaders[] = {
+ "status",
+ "101"
+ };
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardWebSocketHeaders,
+ arraysize(kStandardWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+ const char* const headers[],
+ int header_count,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) {
+
+ // SPDY SYN_STREAM control frame header.
+ const SpdyHeaderInfo kSynStreamHeader = {
+ SYN_STREAM,
+ stream_id,
+ kDefaultAssociatedStreamId,
+ ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+ CONTROL_FLAG_NONE,
+ kDefaultCompressed,
+ INVALID,
+ kDefaultDataPointer,
+ kDefaultDataLength,
+ DATA_FLAG_NONE
+ };
+
+ // Construct SPDY SYN_STREAM control frame.
+ return ConstructSpdyPacket(
+ kSynStreamHeader,
+ kDefaultExtraHeaders,
+ kDefaultExtraHeaderCount,
+ headers,
+ header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+ const char* const headers[],
+ int header_count,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) {
+
+ // SPDY SYN_REPLY control frame header.
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY,
+ stream_id,
+ kDefaultAssociatedStreamId,
+ ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+ CONTROL_FLAG_NONE,
+ kDefaultCompressed,
+ INVALID,
+ kDefaultDataPointer,
+ kDefaultDataLength,
+ DATA_FLAG_NONE
+ };
+
+ // Construct SPDY SYN_REPLY control frame.
+ return ConstructSpdyPacket(
+ kSynReplyHeader,
+ kDefaultExtraHeaders,
+ kDefaultExtraHeaderCount,
+ headers,
+ header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+ const char* length,
+ bool fin) {
+ static const char* const kHeaders[] = {
+ "opcode",
+ "1", // text frame
+ "length",
+ length,
+ "fin",
+ fin ? "1" : "0"
+ };
+ return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+ /*extra_header_count*/ 0,
+ /*compression*/ false,
+ stream_id,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ kHeaders,
+ arraysize(kHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketDataFrame(
+ const char* data,
+ int len,
+ SpdyStreamId stream_id,
+ bool fin) {
+
+ // Construct SPDY data frame.
+ BufferedSpdyFramer framer(2, false);
+ return framer.CreateDataFrame(
+ stream_id,
+ data,
+ len,
+ fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+} // namespace test_spdy2
+
+} // namespace net
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy2.h b/src/net/spdy/spdy_websocket_test_util_spdy2.h
new file mode 100644
index 0000000..3856bb6
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy2.h
@@ -0,0 +1,55 @@
+// 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_TEST_UTIL_H_
+#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace test_spdy2 {
+
+// 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(
+ const char* const headers[],
+ int header_count,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority);
+
+// Constructs a WebSocket over SPDY handshake response packet.
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+ const char* const headers[],
+ int header_count,
+ 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);
+
+} // namespace test_spdy2
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy3.cc b/src/net/spdy/spdy_websocket_test_util_spdy3.cc
new file mode 100644
index 0000000..4e2eec4
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy3.cc
@@ -0,0 +1,165 @@
+// 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_test_util_spdy3.h"
+
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+
+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;
+static const int kDefaultCredentialSlot = 0;
+
+namespace net {
+
+namespace test_spdy3 {
+
+SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+ const char* path,
+ const char* host,
+ const char* origin) {
+ const char* const kWebSocketHeaders[] = {
+ ":path",
+ path,
+ ":host",
+ host,
+ ":version",
+ "WebSocket/13",
+ ":scheme",
+ "ws",
+ ":origin",
+ origin
+ };
+ return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+ /*extra_header_count*/ 0,
+ /*compressed*/ false,
+ stream_id,
+ HIGHEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kWebSocketHeaders,
+ arraysize(kWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id) {
+ static const char* const kStandardWebSocketHeaders[] = {
+ ":status",
+ "101"
+ };
+ return ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardWebSocketHeaders,
+ arraysize(kStandardWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+ const char* const headers[],
+ int header_count,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) {
+
+ // SPDY SYN_STREAM control frame header.
+ const SpdyHeaderInfo kSynStreamHeader = {
+ SYN_STREAM,
+ stream_id,
+ kDefaultAssociatedStreamId,
+ ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+ kDefaultCredentialSlot,
+ CONTROL_FLAG_NONE,
+ kDefaultCompressed,
+ INVALID,
+ kDefaultDataPointer,
+ kDefaultDataLength,
+ DATA_FLAG_NONE
+ };
+
+ // Construct SPDY SYN_STREAM control frame.
+ return ConstructSpdyPacket(
+ kSynStreamHeader,
+ kDefaultExtraHeaders,
+ kDefaultExtraHeaderCount,
+ headers,
+ header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+ const char* const headers[],
+ int header_count,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) {
+
+ // SPDY SYN_REPLY control frame header.
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY,
+ stream_id,
+ kDefaultAssociatedStreamId,
+ ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+ kDefaultCredentialSlot,
+ CONTROL_FLAG_NONE,
+ kDefaultCompressed,
+ INVALID,
+ kDefaultDataPointer,
+ kDefaultDataLength,
+ DATA_FLAG_NONE
+ };
+
+ // Construct SPDY SYN_REPLY control frame.
+ return ConstructSpdyPacket(
+ kSynReplyHeader,
+ kDefaultExtraHeaders,
+ kDefaultExtraHeaderCount,
+ headers,
+ header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+ const char* length,
+ bool fin) {
+ static const char* const kHeaders[] = {
+ ":opcode",
+ "1", // text frame
+ ":length",
+ length,
+ ":fin",
+ fin ? "1" : "0"
+ };
+ return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+ /*extra_header_count*/ 0,
+ /*compression*/ false,
+ stream_id,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ kHeaders,
+ arraysize(kHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketDataFrame(
+ const char* data,
+ int len,
+ SpdyStreamId stream_id,
+ bool fin) {
+
+ // Construct SPDY data frame.
+ BufferedSpdyFramer framer(3, false);
+ return framer.CreateDataFrame(
+ stream_id,
+ data,
+ len,
+ fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+} // namespace test_spdy3
+
+} // namespace net
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy3.h b/src/net/spdy/spdy_websocket_test_util_spdy3.h
new file mode 100644
index 0000000..3446738
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy3.h
@@ -0,0 +1,55 @@
+// 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_TEST_UTIL_H_
+#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace test_spdy3 {
+
+// 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(
+ const char* const headers[],
+ int header_count,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority);
+
+// Constructs a WebSocket over SPDY handshake response packet.
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+ const char* const headers[],
+ int header_count,
+ 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);
+
+} // namespace test_spdy3
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_