summaryrefslogtreecommitdiffstats
path: root/net/spdy
diff options
context:
space:
mode:
authormbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-06 21:44:32 +0000
committermbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-06 21:44:32 +0000
commitdab9c7d4a7c5d4c5157043ee4db2d792b8a2f3b6 (patch)
tree2a91eee8aaf55502ed8e3a9b2b5f9e5909237a5d /net/spdy
parentfc524909b7165d44b8b4fc92d7ba59ae7f8cb2df (diff)
downloadchromium_src-dab9c7d4a7c5d4c5157043ee4db2d792b8a2f3b6.zip
chromium_src-dab9c7d4a7c5d4c5157043ee4db2d792b8a2f3b6.tar.gz
chromium_src-dab9c7d4a7c5d4c5157043ee4db2d792b8a2f3b6.tar.bz2
Rename all files from flip* to spdy*.
I haven't yet renamed the classes. BUG=30747 TEST=none Review URL: http://codereview.chromium.org/582001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38315 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/spdy')
-rw-r--r--net/spdy/spdy_bitmasks.h24
-rw-r--r--net/spdy/spdy_frame_builder.cc181
-rw-r--r--net/spdy/spdy_frame_builder.h163
-rw-r--r--net/spdy/spdy_framer.cc772
-rw-r--r--net/spdy/spdy_framer.h260
-rw-r--r--net/spdy/spdy_framer_test.cc407
-rw-r--r--net/spdy/spdy_io_buffer.cc29
-rw-r--r--net/spdy/spdy_io_buffer.h55
-rw-r--r--net/spdy/spdy_network_transaction.cc297
-rw-r--r--net/spdy/spdy_network_transaction.h120
-rw-r--r--net/spdy/spdy_network_transaction_unittest.cc1084
-rw-r--r--net/spdy/spdy_protocol.h387
-rw-r--r--net/spdy/spdy_protocol_test.cc178
-rw-r--r--net/spdy/spdy_session.cc1056
-rw-r--r--net/spdy/spdy_session.h237
-rw-r--r--net/spdy/spdy_session_pool.cc121
-rw-r--r--net/spdy/spdy_session_pool.h76
-rw-r--r--net/spdy/spdy_session_unittest.cc56
-rw-r--r--net/spdy/spdy_stream.cc456
-rw-r--r--net/spdy/spdy_stream.h211
-rw-r--r--net/spdy/spdy_stream_unittest.cc123
-rw-r--r--net/spdy/spdy_transaction_factory.h36
22 files changed, 6329 insertions, 0 deletions
diff --git a/net/spdy/spdy_bitmasks.h b/net/spdy/spdy_bitmasks.h
new file mode 100644
index 0000000..076a24d
--- /dev/null
+++ b/net/spdy/spdy_bitmasks.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2009 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 flip {
+
+// StreamId mask from the FlipHeader
+const unsigned int kStreamIdMask = 0x7fffffff;
+
+// Control flag mask from the FlipHeader
+const unsigned int kControlFlagMask = 0x8000;
+
+// Priority mask from the SYN_FRAME
+const unsigned int kPriorityMask = 0xc0;
+
+// Mask the lower 24 bits.
+const unsigned int kLengthMask = 0xffffff;
+
+} // flip
+
+#endif // NET_SPDY_SPDY_BITMASKS_H_
diff --git a/net/spdy/spdy_frame_builder.cc b/net/spdy/spdy_frame_builder.cc
new file mode 100644
index 0000000..7c6333d
--- /dev/null
+++ b/net/spdy/spdy_frame_builder.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 2009 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 flip {
+
+// We mark a read only FlipFrameBuilder with a special capacity_.
+static const size_t kCapacityReadOnly = std::numeric_limits<size_t>::max();
+
+FlipFrameBuilder::FlipFrameBuilder()
+ : buffer_(NULL),
+ capacity_(0),
+ length_(0),
+ variable_buffer_offset_(0) {
+ Resize(kInitialPayload);
+}
+
+FlipFrameBuilder::FlipFrameBuilder(const char* data, int data_len)
+ : buffer_(const_cast<char*>(data)),
+ capacity_(kCapacityReadOnly),
+ length_(data_len),
+ variable_buffer_offset_(0) {
+}
+
+FlipFrameBuilder::~FlipFrameBuilder() {
+ if (capacity_ != kCapacityReadOnly)
+ delete[] buffer_;
+}
+
+bool FlipFrameBuilder::ReadUInt16(void** iter, uint16* result) const {
+ DCHECK(iter);
+ if (!*iter)
+ *iter = const_cast<char*>(buffer_);
+
+ if (!IteratorHasRoomFor(*iter, sizeof(*result)))
+ return false;
+
+ *result = ntohs(*(reinterpret_cast<uint16*>(*iter)));
+
+ UpdateIter(iter, sizeof(*result));
+ return true;
+}
+
+bool FlipFrameBuilder::ReadUInt32(void** iter, uint32* result) const {
+ DCHECK(iter);
+ if (!*iter)
+ *iter = const_cast<char*>(buffer_);
+
+ if (!IteratorHasRoomFor(*iter, sizeof(*result)))
+ return false;
+
+ *result = ntohl(*(reinterpret_cast<uint32*>(*iter)));
+
+ UpdateIter(iter, sizeof(*result));
+ return true;
+}
+
+bool FlipFrameBuilder::ReadString(void** iter, std::string* result) const {
+ DCHECK(iter);
+
+ uint16 len;
+ if (!ReadUInt16(iter, &len))
+ return false;
+
+ if (!IteratorHasRoomFor(*iter, len))
+ return false;
+
+ char* chars = reinterpret_cast<char*>(*iter);
+ result->assign(chars, len);
+
+ UpdateIter(iter, len);
+ return true;
+}
+
+bool FlipFrameBuilder::ReadBytes(void** iter, const char** data,
+ uint16 length) const {
+ DCHECK(iter);
+ DCHECK(data);
+
+ if (!IteratorHasRoomFor(*iter, length))
+ return false;
+
+ *data = reinterpret_cast<const char*>(*iter);
+
+ UpdateIter(iter, length);
+ return true;
+}
+
+bool FlipFrameBuilder::ReadData(void** iter, const char** data,
+ uint16* length) const {
+ DCHECK(iter);
+ DCHECK(data);
+ DCHECK(length);
+
+ if (!ReadUInt16(iter, length))
+ return false;
+
+ return ReadBytes(iter, data, *length);
+}
+
+char* FlipFrameBuilder::BeginWrite(size_t length) {
+ size_t offset = length_;
+ size_t needed_size = length_ + length;
+ if (needed_size > capacity_ && !Resize(std::max(capacity_ * 2, needed_size)))
+ return NULL;
+
+#ifdef ARCH_CPU_64_BITS
+ DCHECK_LE(length, std::numeric_limits<uint32>::max());
+#endif
+
+ return buffer_ + offset;
+}
+
+void FlipFrameBuilder::EndWrite(char* dest, int length) {
+}
+
+bool FlipFrameBuilder::WriteBytes(const void* data, uint16 data_len) {
+ DCHECK(capacity_ != kCapacityReadOnly);
+
+ char* dest = BeginWrite(data_len);
+ if (!dest)
+ return false;
+
+ memcpy(dest, data, data_len);
+
+ EndWrite(dest, data_len);
+ length_ += data_len;
+ return true;
+}
+
+bool FlipFrameBuilder::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()));
+}
+
+char* FlipFrameBuilder::BeginWriteData(uint16 length) {
+ DCHECK_EQ(variable_buffer_offset_, 0U) <<
+ "There can only be one variable buffer in a FlipFrameBuilder";
+
+ if (!WriteUInt16(length))
+ return false;
+
+ char *data_ptr = BeginWrite(length);
+ if (!data_ptr)
+ return NULL;
+
+ variable_buffer_offset_ = data_ptr - buffer_ - sizeof(int);
+
+ // EndWrite doesn't necessarily have to be called after the write operation,
+ // so we call it here to pad out what the caller will eventually write.
+ EndWrite(data_ptr, length);
+ return data_ptr;
+}
+
+bool FlipFrameBuilder::Resize(size_t new_capacity) {
+ if (new_capacity < capacity_)
+ return true;
+
+ char* p = new char[new_capacity];
+ if (buffer_) {
+ memcpy(p, buffer_, capacity_);
+ delete[] buffer_;
+ }
+ if (!p && new_capacity > 0)
+ return false;
+ buffer_ = p;
+ capacity_ = new_capacity;
+ return true;
+}
+
+} // namespace flip
diff --git a/net/spdy/spdy_frame_builder.h b/net/spdy/spdy_frame_builder.h
new file mode 100644
index 0000000..240aae1f
--- /dev/null
+++ b/net/spdy/spdy_frame_builder.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2009 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_FLIP_FRAME_BUILDER_H_
+#define NET_FLIP_FRAME_BUILDER_H_
+
+#ifdef WIN32
+#include <winsock2.h> // for htonl() functions
+#else
+#include <arpa/inet.h>
+#endif
+
+#include <string>
+
+#include "base/logging.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace flip {
+
+// This class provides facilities for basic binary value packing and unpacking
+// into Flip frames.
+//
+// The FlipFrameBuilder supports appending primitive values (int, string, etc)
+// to a frame instance. The FlipFrameBuilder grows its internal memory buffer
+// dynamically to hold the sequence of primitive values. The internal memory
+// buffer is exposed as the "data" of the FlipFrameBuilder.
+//
+// When reading from a FlipFrameBuilder the consumer must know what value types
+// to read and in what order to read them as the FlipFrameBuilder does not keep
+// track of the type of data written to it.
+class FlipFrameBuilder {
+ public:
+ FlipFrameBuilder();
+ ~FlipFrameBuilder();
+
+ // Initializes a FlipFrameBuilder from a const block of data. The data is
+ // not copied; instead the data is merely referenced by this
+ // FlipFrameBuilder. Only const methods should be used when initialized
+ // this way.
+ FlipFrameBuilder(const char* data, int data_len);
+
+ // Returns the size of the FlipFrameBuilder's data.
+ int length() const { return length_; }
+
+ // Takes the buffer from the FlipFrameBuilder.
+ FlipFrame* take() {
+ FlipFrame* rv = new FlipFrame(buffer_, true);
+ buffer_ = NULL;
+ capacity_ = 0;
+ length_ = 0;
+ return rv;
+ }
+
+ // Methods for reading the payload of the FlipFrameBuilder. To read from the
+ // start of the FlipFrameBuilder, initialize *iter to NULL. If successful,
+ // these methods return true. Otherwise, false is returned to indicate that
+ // the result could not be extracted.
+ bool ReadUInt16(void** iter, uint16* result) const;
+ bool ReadUInt32(void** iter, uint32* result) const;
+ bool ReadString(void** iter, std::string* result) const;
+ bool ReadBytes(void** iter, const char** data, uint16 length) const;
+ bool ReadData(void** iter, const char** data, uint16* length) const;
+
+ // Methods for adding to the payload. These values are appended to the end
+ // of the FlipFrameBuilder payload. When reading values, you must read them
+ // in the order they were added. Note - binary integers are converted from
+ // host to network form.
+ bool WriteUInt16(uint16 value) {
+ value = htons(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteUInt32(uint32 value) {
+ value = htonl(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteString(const std::string& value);
+ bool WriteBytes(const void* data, uint16 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;
+ }
+
+ // Allows the caller to write data directly into the FlipFrameBuilder.
+ // This saves a copy when the data is not already available in a buffer.
+ // The caller must not write more than the length it declares it will.
+ // Use ReadData to get the data.
+ // Returns NULL on failure.
+ //
+ // The returned pointer will only be valid until the next write operation
+ // on this FlipFrameBuilder.
+ char* BeginWriteData(uint16 length);
+
+ // 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_; }
+
+ // Resizes the buffer for use when writing the specified amount of data. The
+ // location that the data should be written at is returned, or NULL if there
+ // was an error. Call EndWrite with the returned offset and the given length
+ // to pad out for the next write.
+ char* BeginWrite(size_t 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);
+
+ // Resize the capacity, note that the input value should include the size of
+ // the header: new_capacity = sizeof(Header) + desired_payload_capacity.
+ // A new failure will cause a Resize failure... and caller should check
+ // the return result for true (i.e., successful resizing).
+ bool Resize(size_t new_capacity);
+
+ // Moves the iterator by the given number of bytes.
+ static void UpdateIter(void** iter, int bytes) {
+ *iter = static_cast<char*>(*iter) + bytes;
+ }
+
+ // Initial size of the payload.
+ static const int kInitialPayload = 1024;
+
+ private:
+ char* buffer_;
+ size_t capacity_; // Allocation size of payload (or -1 if buffer is const).
+ size_t length_; // current length of the buffer
+ size_t variable_buffer_offset_; // IF non-zero, then offset to a buffer.
+};
+
+} // namespace flip
+
+#endif // NET_FLIP_FRAME_BUILDER_H_
+
diff --git a/net/spdy/spdy_framer.cc b/net/spdy/spdy_framer.cc
new file mode 100644
index 0000000..e78b42c
--- /dev/null
+++ b/net/spdy/spdy_framer.cc
@@ -0,0 +1,772 @@
+// Copyright (c) 2009 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/scoped_ptr.h"
+#include "base/stats_counters.h"
+
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+#if defined(USE_SYSTEM_ZLIB)
+#include <zlib.h>
+#else
+#include "third_party/zlib/zlib.h"
+#endif
+
+namespace flip {
+
+// The initial size of the control frame buffer; this is used internally
+// as we parse through control frames.
+static const size_t kControlFrameBufferInitialSize = 32 * 1024;
+// The maximum size of the control frame buffer that we support.
+// TODO(mbelshe): We should make this stream-based so there are no limits.
+static const size_t kControlFrameBufferMaxSize = 64 * 1024;
+
+// By default is compression on or off.
+bool FlipFramer::compression_default_ = true;
+
+#ifdef DEBUG_FLIP_STATE_CHANGES
+#define CHANGE_STATE(newstate) \
+{ \
+ do { \
+ LOG(INFO) << "Changing state from: " \
+ << StateToString(state_) \
+ << " to " << StateToString(newstate) << "\n"; \
+ state_ = newstate; \
+ } while (false); \
+}
+#else
+#define CHANGE_STATE(newstate) (state_ = newstate)
+#endif
+
+FlipFramer::FlipFramer()
+ : state_(FLIP_RESET),
+ error_code_(FLIP_NO_ERROR),
+ remaining_payload_(0),
+ remaining_control_payload_(0),
+ current_frame_buffer_(NULL),
+ current_frame_len_(0),
+ current_frame_capacity_(0),
+ enable_compression_(compression_default_),
+ visitor_(NULL) {
+}
+
+FlipFramer::~FlipFramer() {
+ if (compressor_.get()) {
+ deflateEnd(compressor_.get());
+ }
+ if (decompressor_.get()) {
+ inflateEnd(decompressor_.get());
+ }
+ delete [] current_frame_buffer_;
+}
+
+void FlipFramer::Reset() {
+ state_ = FLIP_RESET;
+ error_code_ = FLIP_NO_ERROR;
+ remaining_payload_ = 0;
+ remaining_control_payload_ = 0;
+ current_frame_len_ = 0;
+ if (current_frame_capacity_ != kControlFrameBufferInitialSize) {
+ delete [] current_frame_buffer_;
+ current_frame_buffer_ = 0;
+ current_frame_capacity_ = 0;
+ ExpandControlFrameBuffer(kControlFrameBufferInitialSize);
+ }
+}
+
+const char* FlipFramer::StateToString(int state) {
+ switch (state) {
+ case FLIP_ERROR:
+ return "ERROR";
+ case FLIP_DONE:
+ return "DONE";
+ case FLIP_AUTO_RESET:
+ return "AUTO_RESET";
+ case FLIP_RESET:
+ return "RESET";
+ case FLIP_READING_COMMON_HEADER:
+ return "READING_COMMON_HEADER";
+ case FLIP_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
+ return "INTERPRET_CONTROL_FRAME_COMMON_HEADER";
+ case FLIP_CONTROL_FRAME_PAYLOAD:
+ return "CONTROL_FRAME_PAYLOAD";
+ case FLIP_IGNORE_REMAINING_PAYLOAD:
+ return "IGNORE_REMAINING_PAYLOAD";
+ case FLIP_FORWARD_STREAM_FRAME:
+ return "FORWARD_STREAM_FRAME";
+ }
+ return "UNKNOWN_STATE";
+}
+
+size_t FlipFramer::BytesSafeToRead() const {
+ switch (state_) {
+ case FLIP_ERROR:
+ case FLIP_DONE:
+ case FLIP_AUTO_RESET:
+ case FLIP_RESET:
+ return 0;
+ case FLIP_READING_COMMON_HEADER:
+ DCHECK(current_frame_len_ < FlipFrame::size());
+ return FlipFrame::size() - current_frame_len_;
+ case FLIP_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
+ return 0;
+ case FLIP_CONTROL_FRAME_PAYLOAD:
+ case FLIP_IGNORE_REMAINING_PAYLOAD:
+ case FLIP_FORWARD_STREAM_FRAME:
+ return remaining_payload_;
+ }
+ // We should never get to here.
+ return 0;
+}
+
+void FlipFramer::set_error(FlipError error) {
+ DCHECK(visitor_);
+ error_code_ = error;
+ CHANGE_STATE(FLIP_ERROR);
+ visitor_->OnError(this);
+}
+
+const char* FlipFramer::ErrorCodeToString(int error_code) {
+ switch (error_code) {
+ case FLIP_NO_ERROR:
+ return "NO_ERROR";
+ case FLIP_UNKNOWN_CONTROL_TYPE:
+ return "UNKNOWN_CONTROL_TYPE";
+ case FLIP_INVALID_CONTROL_FRAME:
+ return "INVALID_CONTROL_FRAME";
+ case FLIP_CONTROL_PAYLOAD_TOO_LARGE:
+ return "CONTROL_PAYLOAD_TOO_LARGE";
+ case FLIP_ZLIB_INIT_FAILURE:
+ return "ZLIB_INIT_FAILURE";
+ case FLIP_UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case FLIP_DECOMPRESS_FAILURE:
+ return "DECOMPRESS_FAILURE";
+ }
+ return "UNKNOWN_STATE";
+}
+
+size_t FlipFramer::ProcessInput(const char* data, size_t len) {
+ DCHECK(visitor_);
+ DCHECK(data);
+
+ size_t original_len = len;
+ while (len != 0) {
+ switch (state_) {
+ case FLIP_ERROR:
+ case FLIP_DONE:
+ goto bottom;
+
+ case FLIP_AUTO_RESET:
+ case FLIP_RESET:
+ Reset();
+ CHANGE_STATE(FLIP_READING_COMMON_HEADER);
+ continue;
+
+ case FLIP_READING_COMMON_HEADER: {
+ int bytes_read = ProcessCommonHeader(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ continue;
+ }
+
+ // Arguably, this case is not necessary, as no bytes are consumed here.
+ // I felt it was a nice partitioning, however (which probably indicates
+ // that it should be refactored into its own function!)
+ case FLIP_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
+ ProcessControlFrameHeader();
+ continue;
+
+ case FLIP_CONTROL_FRAME_PAYLOAD: {
+ int bytes_read = ProcessControlFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ }
+ // intentional fallthrough
+ case FLIP_IGNORE_REMAINING_PAYLOAD:
+ // control frame has too-large payload
+ // intentional fallthrough
+ case FLIP_FORWARD_STREAM_FRAME: {
+ int bytes_read = ProcessDataFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ continue;
+ }
+ default:
+ break;
+ }
+ }
+ bottom:
+ return original_len - len;
+}
+
+size_t FlipFramer::ProcessCommonHeader(const char* data, size_t len) {
+ // This should only be called when we're in the FLIP_READING_COMMON_HEADER
+ // state.
+ DCHECK(state_ == FLIP_READING_COMMON_HEADER);
+
+ int original_len = len;
+ FlipFrame current_frame(current_frame_buffer_, false);
+
+ do {
+ if (current_frame_len_ < FlipFrame::size()) {
+ size_t bytes_desired = FlipFrame::size() - current_frame_len_;
+ size_t bytes_to_append = std::min(bytes_desired, len);
+ char* header_buffer = current_frame_buffer_;
+ memcpy(&header_buffer[current_frame_len_], data, bytes_to_append);
+ current_frame_len_ += bytes_to_append;
+ data += bytes_to_append;
+ len -= bytes_to_append;
+ // Check for an empty data frame.
+ if (current_frame_len_ == FlipFrame::size() &&
+ !current_frame.is_control_frame() &&
+ current_frame.length() == 0) {
+ if (current_frame.flags() & CONTROL_FLAG_FIN) {
+ FlipDataFrame data_frame(current_frame_buffer_, false);
+ visitor_->OnStreamFrameData(data_frame.stream_id(), NULL, 0);
+ }
+ CHANGE_STATE(FLIP_RESET);
+ }
+ break;
+ }
+ remaining_payload_ = current_frame.length();
+
+ // This is just a sanity check for help debugging early frame errors.
+ if (remaining_payload_ > 1000000u) {
+ LOG(ERROR) <<
+ "Unexpectedly large frame. Flip session is likely corrupt.";
+ }
+
+ // if we're here, then we have the common header all received.
+ if (!current_frame.is_control_frame())
+ CHANGE_STATE(FLIP_FORWARD_STREAM_FRAME);
+ else
+ CHANGE_STATE(FLIP_INTERPRET_CONTROL_FRAME_COMMON_HEADER);
+ } while (false);
+
+ return original_len - len;
+}
+
+void FlipFramer::ProcessControlFrameHeader() {
+ DCHECK_EQ(FLIP_NO_ERROR, error_code_);
+ DCHECK_LE(FlipFrame::size(), current_frame_len_);
+ FlipControlFrame current_control_frame(current_frame_buffer_, false);
+ // Do some sanity checking on the control frame sizes.
+ switch (current_control_frame.type()) {
+ case SYN_STREAM:
+ if (current_control_frame.length() <
+ FlipSynStreamControlFrame::size() - FlipControlFrame::size())
+ set_error(FLIP_INVALID_CONTROL_FRAME);
+ break;
+ case SYN_REPLY:
+ if (current_control_frame.length() <
+ FlipSynReplyControlFrame::size() - FlipControlFrame::size())
+ set_error(FLIP_INVALID_CONTROL_FRAME);
+ break;
+ case FIN_STREAM:
+ if (current_control_frame.length() !=
+ FlipFinStreamControlFrame::size() - FlipFrame::size())
+ set_error(FLIP_INVALID_CONTROL_FRAME);
+ break;
+ case NOOP:
+ // NOOP. Swallow it.
+ CHANGE_STATE(FLIP_AUTO_RESET);
+ return;
+ default:
+ set_error(FLIP_UNKNOWN_CONTROL_TYPE);
+ break;
+ }
+
+ // We only support version 1 of this protocol.
+ if (current_control_frame.version() != kFlipProtocolVersion)
+ set_error(FLIP_UNSUPPORTED_VERSION);
+
+ remaining_control_payload_ = current_control_frame.length();
+ if (remaining_control_payload_ > kControlFrameBufferMaxSize)
+ set_error(FLIP_CONTROL_PAYLOAD_TOO_LARGE);
+
+ if (error_code_)
+ return;
+
+ ExpandControlFrameBuffer(remaining_control_payload_);
+ CHANGE_STATE(FLIP_CONTROL_FRAME_PAYLOAD);
+}
+
+size_t FlipFramer::ProcessControlFramePayload(const char* data, size_t len) {
+ size_t original_len = len;
+ do {
+ if (remaining_control_payload_) {
+ size_t amount_to_consume = std::min(remaining_control_payload_, len);
+ memcpy(&current_frame_buffer_[current_frame_len_], data,
+ amount_to_consume);
+ current_frame_len_ += amount_to_consume;
+ data += amount_to_consume;
+ len -= amount_to_consume;
+ remaining_control_payload_ -= amount_to_consume;
+ remaining_payload_ -= amount_to_consume;
+ if (remaining_control_payload_)
+ break;
+ }
+ FlipControlFrame control_frame(current_frame_buffer_, false);
+ visitor_->OnControl(&control_frame);
+
+ // If this is a FIN, tell the caller.
+ if (control_frame.type() == SYN_REPLY &&
+ control_frame.flags() & CONTROL_FLAG_FIN)
+ visitor_->OnStreamFrameData(control_frame.stream_id(), NULL, 0);
+
+ CHANGE_STATE(FLIP_IGNORE_REMAINING_PAYLOAD);
+ } while (false);
+ return original_len - len;
+}
+
+size_t FlipFramer::ProcessDataFramePayload(const char* data, size_t len) {
+ size_t original_len = len;
+
+ FlipDataFrame current_data_frame(current_frame_buffer_, false);
+ if (remaining_payload_) {
+ size_t amount_to_forward = std::min(remaining_payload_, len);
+ if (amount_to_forward && state_ != FLIP_IGNORE_REMAINING_PAYLOAD) {
+ if (current_data_frame.flags() & DATA_FLAG_COMPRESSED) {
+ // TODO(mbelshe): Assert that the decompressor is init'ed.
+ if (!InitializeDecompressor())
+ return NULL;
+
+ size_t decompressed_max_size = amount_to_forward * 100;
+ scoped_ptr<char> decompressed(new char[decompressed_max_size]);
+ decompressor_->next_in = reinterpret_cast<Bytef*>(
+ const_cast<char*>(data));
+ decompressor_->avail_in = amount_to_forward;
+ decompressor_->next_out =
+ reinterpret_cast<Bytef*>(decompressed.get());
+ decompressor_->avail_out = decompressed_max_size;
+
+ int rv = inflate(decompressor_.get(), Z_SYNC_FLUSH);
+ if (rv != Z_OK) {
+ set_error(FLIP_DECOMPRESS_FAILURE);
+ return 0;
+ }
+ size_t decompressed_size = decompressed_max_size -
+ decompressor_->avail_out;
+ // Only inform the visitor if there is data.
+ if (decompressed_size)
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+ decompressed.get(),
+ decompressed_size);
+ amount_to_forward -= decompressor_->avail_in;
+ } else {
+ // The data frame was not compressed.
+ // Only inform the visitor if there is data.
+ if (amount_to_forward)
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+ data, amount_to_forward);
+ }
+ }
+ data += amount_to_forward;
+ len -= amount_to_forward;
+ remaining_payload_ -= 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_payload_ &&
+ current_data_frame.flags() & DATA_FLAG_FIN)
+ visitor_->OnStreamFrameData(current_data_frame.stream_id(), NULL,
+ 0);
+ } else {
+ CHANGE_STATE(FLIP_AUTO_RESET);
+ }
+ return original_len - len;
+}
+
+void FlipFramer::ExpandControlFrameBuffer(size_t size) {
+ DCHECK(size < kControlFrameBufferMaxSize);
+ if (size < current_frame_capacity_)
+ return;
+
+ int alloc_size = size + FlipFrame::size();
+ char* new_buffer = new char[alloc_size];
+ memcpy(new_buffer, current_frame_buffer_, current_frame_len_);
+ current_frame_capacity_ = alloc_size;
+ current_frame_buffer_ = new_buffer;
+}
+
+bool FlipFramer::ParseHeaderBlock(const FlipFrame* frame,
+ FlipHeaderBlock* block) {
+ FlipControlFrame control_frame(frame->data(), false);
+ uint32 type = control_frame.type();
+ if (type != SYN_STREAM && type != SYN_REPLY)
+ return false;
+
+ // Find the header data within the control frame.
+ scoped_ptr<FlipFrame> decompressed_frame(DecompressFrame(frame));
+ if (!decompressed_frame.get())
+ return false;
+ FlipSynStreamControlFrame syn_frame(decompressed_frame->data(), false);
+ const char *header_data = syn_frame.header_block();
+ int header_length = syn_frame.header_block_len();
+
+ FlipFrameBuilder builder(header_data, header_length);
+ void* iter = NULL;
+ uint16 num_headers;
+ if (builder.ReadUInt16(&iter, &num_headers)) {
+ for (int index = 0; index < num_headers; ++index) {
+ std::string name;
+ std::string value;
+ if (!builder.ReadString(&iter, &name))
+ break;
+ if (!builder.ReadString(&iter, &value))
+ break;
+ if (block->find(name) == block->end()) {
+ (*block)[name] = value;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+FlipSynStreamControlFrame* FlipFramer::CreateSynStream(
+ FlipStreamId stream_id, int priority, FlipControlFlags flags,
+ bool compressed, FlipHeaderBlock* headers) {
+ FlipFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | kFlipProtocolVersion);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length and flags
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt16(ntohs(priority) << 6); // Priority.
+
+ frame.WriteUInt16(headers->size()); // Number of headers.
+ FlipHeaderBlock::iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ frame.WriteString(it->first);
+ frame.WriteString(it->second);
+ }
+
+ // Write the length and flags.
+ size_t length = frame.length() - FlipFrame::size();
+ DCHECK(length < static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(static_cast<uint32>(length));
+ flags_length.flags_[0] = flags;
+ frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
+
+ scoped_ptr<FlipFrame> syn_frame(frame.take());
+ if (compressed) {
+ return reinterpret_cast<FlipSynStreamControlFrame*>(
+ CompressFrame(syn_frame.get()));
+ }
+ return reinterpret_cast<FlipSynStreamControlFrame*>(syn_frame.release());
+}
+
+/* static */
+FlipFinStreamControlFrame* FlipFramer::CreateFinStream(FlipStreamId stream_id,
+ int status) {
+ FlipFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kFlipProtocolVersion);
+ frame.WriteUInt16(FIN_STREAM);
+ frame.WriteUInt32(8);
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt32(status);
+ return reinterpret_cast<FlipFinStreamControlFrame*>(frame.take());
+}
+
+FlipSynReplyControlFrame* FlipFramer::CreateSynReply(FlipStreamId stream_id,
+ FlipControlFlags flags, bool compressed, FlipHeaderBlock* headers) {
+
+ FlipFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | kFlipProtocolVersion);
+ frame.WriteUInt16(SYN_REPLY);
+ frame.WriteUInt32(0); // Placeholder for the length and flags.
+ frame.WriteUInt32(stream_id);
+ frame.WriteUInt16(0); // Unused
+
+ frame.WriteUInt16(headers->size()); // Number of headers.
+ FlipHeaderBlock::iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ // TODO(mbelshe): Headers need to be sorted.
+ frame.WriteString(it->first);
+ frame.WriteString(it->second);
+ }
+
+ // Write the length
+ size_t length = frame.length() - FlipFrame::size();
+ DCHECK(length < static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(static_cast<uint32>(length));
+ flags_length.flags_[0] = flags;
+ frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
+
+ scoped_ptr<FlipFrame> reply_frame(frame.take());
+ if (compressed) {
+ return reinterpret_cast<FlipSynReplyControlFrame*>(
+ CompressFrame(reply_frame.get()));
+ }
+ return reinterpret_cast<FlipSynReplyControlFrame*>(reply_frame.release());
+}
+
+FlipDataFrame* FlipFramer::CreateDataFrame(FlipStreamId stream_id,
+ const char* data,
+ uint32 len, FlipDataFlags flags) {
+ FlipFrameBuilder frame;
+
+ frame.WriteUInt32(stream_id);
+
+ DCHECK(len < static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(len);
+ flags_length.flags_[0] = flags;
+ frame.WriteBytes(&flags_length, sizeof(flags_length));
+
+ frame.WriteBytes(data, len);
+ scoped_ptr<FlipFrame> data_frame(frame.take());
+ if (flags & DATA_FLAG_COMPRESSED)
+ return reinterpret_cast<FlipDataFrame*>(CompressFrame(data_frame.get()));
+ return reinterpret_cast<FlipDataFrame*>(data_frame.release());
+}
+
+/* static */
+FlipControlFrame* FlipFramer::CreateNopFrame() {
+ FlipFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kFlipProtocolVersion);
+ frame.WriteUInt16(NOOP);
+ frame.WriteUInt32(0);
+ return reinterpret_cast<FlipControlFrame*>(frame.take());
+}
+
+static const int kCompressorLevel = Z_DEFAULT_COMPRESSION;
+// This is just a hacked dictionary to use for shrinking HTTP-like headers.
+// TODO(mbelshe): Use a scientific methodology for computing the dictionary.
+static const char dictionary[] =
+ "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";
+static uLong dictionary_id = 0;
+
+bool FlipFramer::InitializeCompressor() {
+ if (compressor_.get())
+ return true; // Already initialized.
+
+ compressor_.reset(new z_stream);
+ memset(compressor_.get(), 0, sizeof(z_stream));
+
+ int success = deflateInit(compressor_.get(), kCompressorLevel);
+ if (success == Z_OK)
+ success = deflateSetDictionary(compressor_.get(),
+ reinterpret_cast<const Bytef*>(dictionary),
+ sizeof(dictionary));
+ if (success != Z_OK)
+ compressor_.reset(NULL);
+ return success == Z_OK;
+}
+
+bool FlipFramer::InitializeDecompressor() {
+ if (decompressor_.get())
+ return true; // Already initialized.
+
+ decompressor_.reset(new z_stream);
+ memset(decompressor_.get(), 0, sizeof(z_stream));
+
+ // Compute the id of our dictionary so that we know we're using the
+ // right one when asked for it.
+ if (dictionary_id == 0) {
+ dictionary_id = adler32(0L, Z_NULL, 0);
+ dictionary_id = adler32(dictionary_id,
+ reinterpret_cast<const Bytef*>(dictionary),
+ sizeof(dictionary));
+ }
+
+ int success = inflateInit(decompressor_.get());
+ if (success != Z_OK)
+ decompressor_.reset(NULL);
+ return success == Z_OK;
+}
+
+bool FlipFramer::GetFrameBoundaries(const FlipFrame* frame,
+ int* payload_length,
+ int* header_length,
+ const char** payload) const {
+ if (frame->is_control_frame()) {
+ const FlipControlFrame* control_frame =
+ reinterpret_cast<const FlipControlFrame*>(frame);
+ switch (control_frame->type()) {
+ case SYN_STREAM:
+ case SYN_REPLY:
+ {
+ const FlipSynStreamControlFrame *syn_frame =
+ reinterpret_cast<const FlipSynStreamControlFrame*>(frame);
+ *payload_length = syn_frame->header_block_len();
+ *header_length = syn_frame->size();
+ *payload = frame->data() + *header_length;
+ }
+ break;
+ default:
+ // TODO(mbelshe): set an error?
+ return false; // We can't compress this frame!
+ }
+ } else {
+ *header_length = FlipFrame::size();
+ *payload_length = frame->length();
+ *payload = frame->data() + FlipFrame::size();
+ }
+ DCHECK(static_cast<size_t>(*header_length) <=
+ FlipFrame::size() + *payload_length);
+ return true;
+}
+
+
+FlipFrame* FlipFramer::CompressFrame(const FlipFrame* frame) {
+ int payload_length;
+ int header_length;
+ const char* payload;
+
+ static StatsCounter pre_compress_bytes("flip.PreCompressSize");
+ static StatsCounter post_compress_bytes("flip.PostCompressSize");
+
+ if (!enable_compression_)
+ return DuplicateFrame(frame);
+
+ if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
+ return NULL;
+
+ if (!InitializeCompressor())
+ return NULL;
+
+ // TODO(mbelshe): Should we have a zlib header like what http servers do?
+
+ // Create an output frame.
+ int compressed_max_size = deflateBound(compressor_.get(), payload_length);
+ int new_frame_size = header_length + compressed_max_size;
+ FlipFrame* new_frame = new FlipFrame(new_frame_size);
+ memcpy(new_frame->data(), frame->data(), frame->length() + FlipFrame::size());
+
+ compressor_->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
+ compressor_->avail_in = payload_length;
+ compressor_->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
+ header_length;
+ compressor_->avail_out = compressed_max_size;
+
+ // Data packets have a 'compressed flag
+ if (!new_frame->is_control_frame()) {
+ FlipDataFrame* data_frame = reinterpret_cast<FlipDataFrame*>(new_frame);
+ data_frame->set_flags(data_frame->flags() | DATA_FLAG_COMPRESSED);
+ }
+
+ int rv = deflate(compressor_.get(), Z_SYNC_FLUSH);
+ if (rv != Z_OK) { // How can we know that it compressed everything?
+ // This shouldn't happen, right?
+ delete new_frame;
+ return NULL;
+ }
+
+ int compressed_size = compressed_max_size - compressor_->avail_out;
+ new_frame->set_length(header_length + compressed_size - FlipFrame::size());
+
+ pre_compress_bytes.Add(payload_length);
+ post_compress_bytes.Add(new_frame->length());
+
+ return new_frame;
+}
+
+FlipFrame* FlipFramer::DecompressFrame(const FlipFrame* frame) {
+ int payload_length;
+ int header_length;
+ const char* payload;
+
+ static StatsCounter pre_decompress_bytes("flip.PreDeCompressSize");
+ static StatsCounter post_decompress_bytes("flip.PostDeCompressSize");
+
+ if (!enable_compression_)
+ return DuplicateFrame(frame);
+
+ if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
+ return NULL;
+
+ if (!frame->is_control_frame()) {
+ const FlipDataFrame* data_frame =
+ reinterpret_cast<const FlipDataFrame*>(frame);
+ if ((data_frame->flags() & DATA_FLAG_COMPRESSED) == 0)
+ return DuplicateFrame(frame);
+ }
+
+ if (!InitializeDecompressor())
+ return NULL;
+
+ // TODO(mbelshe): Should we have a zlib header like what http servers do?
+
+ // Create an output frame. Assume it does not need to be longer than
+ // the input data.
+ int decompressed_max_size = kControlFrameBufferInitialSize;
+ int new_frame_size = header_length + decompressed_max_size;
+ FlipFrame* new_frame = new FlipFrame(new_frame_size);
+ memcpy(new_frame->data(), frame->data(), frame->length() + FlipFrame::size());
+
+ decompressor_->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
+ decompressor_->avail_in = payload_length;
+ decompressor_->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
+ header_length;
+ decompressor_->avail_out = decompressed_max_size;
+
+ int rv = inflate(decompressor_.get(), Z_SYNC_FLUSH);
+ if (rv == Z_NEED_DICT) {
+ // Need to try again with the right dictionary.
+ if (decompressor_->adler == dictionary_id) {
+ rv = inflateSetDictionary(decompressor_.get(), (const Bytef*)dictionary,
+ sizeof(dictionary));
+ if (rv == Z_OK)
+ rv = inflate(decompressor_.get(), Z_SYNC_FLUSH);
+ }
+ }
+ if (rv != Z_OK) { // How can we know that it decompressed everything?
+ delete new_frame;
+ return NULL;
+ }
+
+ // Unset the compressed flag for data frames.
+ if (!new_frame->is_control_frame()) {
+ FlipDataFrame* data_frame = reinterpret_cast<FlipDataFrame*>(new_frame);
+ data_frame->set_flags(data_frame->flags() & ~DATA_FLAG_COMPRESSED);
+ }
+
+ int decompressed_size = decompressed_max_size - decompressor_->avail_out;
+ new_frame->set_length(header_length + decompressed_size - FlipFrame::size());
+
+ pre_decompress_bytes.Add(frame->length());
+ post_decompress_bytes.Add(new_frame->length());
+
+ return new_frame;
+}
+
+FlipFrame* FlipFramer::DuplicateFrame(const FlipFrame* frame) {
+ int size = FlipFrame::size() + frame->length();
+ FlipFrame* new_frame = new FlipFrame(size);
+ memcpy(new_frame->data(), frame->data(), size);
+ return new_frame;
+}
+
+void FlipFramer::set_enable_compression(bool value) {
+ enable_compression_ = value;
+}
+
+void FlipFramer::set_enable_compression_default(bool value) {
+ compression_default_ = value;
+}
+
+} // namespace flip
+
diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h
new file mode 100644
index 0000000..668e1f3
--- /dev/null
+++ b/net/spdy/spdy_framer.h
@@ -0,0 +1,260 @@
+// Copyright (c) 2009 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_
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+typedef struct z_stream_s z_stream; // Forward declaration for zlib.
+
+namespace net {
+class FlipNetworkTransactionTest;
+class HttpNetworkLayer;
+}
+
+namespace flip {
+
+class FlipFramer;
+class FlipFramerTest;
+
+namespace test {
+class TestFlipVisitor;
+void FramerSetEnableCompressionHelper(FlipFramer* framer, bool compress);
+} // namespace test
+
+// A datastructure for holding a set of headers from either a
+// SYN_STREAM or SYN_REPLY frame.
+typedef std::map<std::string, std::string> FlipHeaderBlock;
+
+// FlipFramerVisitorInterface is a set of callbacks for the FlipFramer.
+// Implement this interface to receive event callbacks as frames are
+// decoded from the framer.
+class FlipFramerVisitorInterface {
+ public:
+ virtual ~FlipFramerVisitorInterface() {}
+
+ // Called if an error is detected in the FlipFrame protocol.
+ virtual void OnError(FlipFramer* framer) = 0;
+
+ // Called when a Control Frame is received.
+ virtual void OnControl(const FlipControlFrame* 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(flip::FlipStreamId stream_id,
+ const char* data,
+ size_t len) = 0;
+};
+
+class FlipFramer {
+ public:
+ // Flip states.
+ // TODO(mbelshe): Can we move these into the implementation
+ // and avoid exposing through the header. (Needed for test)
+ enum FlipState {
+ FLIP_ERROR,
+ FLIP_DONE,
+ FLIP_RESET,
+ FLIP_AUTO_RESET,
+ FLIP_READING_COMMON_HEADER,
+ FLIP_INTERPRET_CONTROL_FRAME_COMMON_HEADER,
+ FLIP_CONTROL_FRAME_PAYLOAD,
+ FLIP_IGNORE_REMAINING_PAYLOAD,
+ FLIP_FORWARD_STREAM_FRAME
+ };
+
+ // Flip error codes.
+ enum FlipError {
+ FLIP_NO_ERROR,
+ FLIP_UNKNOWN_CONTROL_TYPE, // Control frame is an unknown type.
+ FLIP_INVALID_CONTROL_FRAME, // Control frame is mal-formatted.
+ FLIP_CONTROL_PAYLOAD_TOO_LARGE, // Control frame payload was too large.
+ FLIP_ZLIB_INIT_FAILURE, // The Zlib library could not initialize.
+ FLIP_UNSUPPORTED_VERSION, // Control frame has unsupported version.
+ FLIP_DECOMPRESS_FAILURE, // There was an error decompressing.
+ };
+
+ // Create a new Framer.
+ FlipFramer();
+ virtual ~FlipFramer();
+
+ // 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(FlipFramerVisitorInterface* 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.
+ FlipError error_code() const { return error_code_; }
+ FlipState state() const { return state_; }
+
+ bool MessageFullyRead() {
+ return state_ == FLIP_DONE || state_ == FLIP_AUTO_RESET;
+ }
+ bool HasError() { return state_ == FLIP_ERROR; }
+
+ // Further parsing utilities.
+ // Given a control frame, parse out a FlipHeaderBlock. Only
+ // valid for SYN_STREAM and SYN_REPLY frames.
+ // Returns true if successfully parsed, false otherwise.
+ bool ParseHeaderBlock(const FlipFrame* frame, FlipHeaderBlock* block);
+
+ // Create a FlipSynStreamControlFrame.
+ // |stream_id| is the stream for this frame.
+ // |priority| is the priority (0-3) 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.
+ FlipSynStreamControlFrame* CreateSynStream(FlipStreamId stream_id,
+ int priority,
+ FlipControlFlags flags,
+ bool compressed,
+ FlipHeaderBlock* headers);
+
+ static FlipFinStreamControlFrame* CreateFinStream(FlipStreamId stream_id,
+ int status);
+
+ // Create a FlipSynReplyControlFrame.
+ // |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.
+ FlipSynReplyControlFrame* CreateSynReply(FlipStreamId stream_id,
+ FlipControlFlags flags,
+ bool compressed,
+ FlipHeaderBlock* headers);
+
+ // 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 create a compressed frame, enable DATA_FLAG_COMPRESSED.
+ // To mark this frame as the last data frame, enable DATA_FLAG_FIN.
+ FlipDataFrame* CreateDataFrame(FlipStreamId stream_id, const char* data,
+ uint32 len, FlipDataFlags flags);
+
+ static FlipControlFrame* CreateNopFrame();
+
+ // NOTES about frame compression.
+ // We want flip 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 FlipFrame.
+ // On success, returns a new FlipFrame with the payload compressed.
+ // Compression state is maintained as part of the FlipFramer.
+ // Returned frame must be freed with free().
+ // On failure, returns NULL.
+ FlipFrame* CompressFrame(const FlipFrame* frame);
+
+ // Decompresses a FlipFrame.
+ // On success, returns a new FlipFrame with the payload decompressed.
+ // Compression state is maintained as part of the FlipFramer.
+ // Returned frame must be freed with free().
+ // On failure, returns NULL.
+ FlipFrame* DecompressFrame(const FlipFrame* frame);
+
+ // Create a copy of a frame.
+ FlipFrame* DuplicateFrame(const FlipFrame* frame);
+
+ // For debugging.
+ static const char* StateToString(int state);
+ static const char* ErrorCodeToString(int error_code);
+
+ protected:
+ FRIEND_TEST(FlipFramerTest, HeaderBlockBarfsOnOutOfOrderHeaders);
+ friend class net::FlipNetworkTransactionTest;
+ friend class net::HttpNetworkLayer; // This is temporary for the server.
+ friend class test::TestFlipVisitor;
+ friend void test::FramerSetEnableCompressionHelper(FlipFramer* framer,
+ bool compress);
+
+ // For ease of testing we can tweak compression on/off.
+ void set_enable_compression(bool value);
+ static void set_enable_compression_default(bool value);
+
+ private:
+ // Internal breakout from ProcessInput. Returns the number of bytes
+ // consumed from the data.
+ size_t ProcessCommonHeader(const char* data, size_t len);
+ void ProcessControlFrameHeader();
+ size_t ProcessControlFramePayload(const char* data, size_t len);
+ size_t ProcessDataFramePayload(const char* data, size_t len);
+
+ // Initialize the ZLib state.
+ bool InitializeCompressor();
+ bool InitializeDecompressor();
+
+ // Not used (yet)
+ size_t BytesSafeToRead() const;
+
+ // Set the error code and moves the framer into the error state.
+ void set_error(FlipError error);
+
+ // Expands the control frame buffer to accomodate a particular payload size.
+ void ExpandControlFrameBuffer(size_t size);
+
+ // Given a frame, breakdown the variable payload length, the static header
+ // header length, and variable payload pointer.
+ bool GetFrameBoundaries(const FlipFrame* frame, int* payload_length,
+ int* header_length, const char** payload) const;
+
+ FlipState state_;
+ FlipError error_code_;
+ size_t remaining_payload_;
+ size_t remaining_control_payload_;
+
+ char* current_frame_buffer_;
+ size_t current_frame_len_; // Number of bytes read into the current_frame_.
+ size_t current_frame_capacity_;
+
+ bool enable_compression_;
+ scoped_ptr<z_stream> compressor_;
+ scoped_ptr<z_stream> decompressor_;
+ FlipFramerVisitorInterface* visitor_;
+
+ static bool compression_default_;
+};
+
+} // namespace flip
+
+#endif // NET_SPDY_SPDY_FRAMER_H_
+
diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_test.cc
new file mode 100644
index 0000000..21f2e68
--- /dev/null
+++ b/net/spdy/spdy_framer_test.cc
@@ -0,0 +1,407 @@
+// Copyright (c) 2009 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/scoped_ptr.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "testing/platform_test.h"
+
+namespace flip {
+
+namespace test {
+
+void FramerSetEnableCompressionHelper(FlipFramer* framer, bool compress) {
+ framer->set_enable_compression(compress);
+}
+
+class TestFlipVisitor : public FlipFramerVisitorInterface {
+ public:
+ TestFlipVisitor()
+ : error_count_(0),
+ syn_frame_count_(0),
+ syn_reply_frame_count_(0),
+ data_bytes_(0),
+ fin_frame_count_(0),
+ fin_flag_count_(0),
+ zero_length_data_frame_count_(0) {
+ }
+
+ void OnError(FlipFramer* f) {
+ error_count_++;
+ }
+
+ void OnStreamFrameData(FlipStreamId stream_id,
+ const char* data,
+ size_t len) {
+ 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";
+ }
+
+ void OnControl(const FlipControlFrame* frame) {
+ FlipHeaderBlock headers;
+ bool parsed_headers = false;
+ switch (frame->type()) {
+ case SYN_STREAM:
+ parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
+ DCHECK(parsed_headers);
+ syn_frame_count_++;
+ break;
+ case SYN_REPLY:
+ parsed_headers = framer_.ParseHeaderBlock(frame, &headers);
+ DCHECK(parsed_headers);
+ syn_reply_frame_count_++;
+ break;
+ case FIN_STREAM:
+ fin_frame_count_++;
+ break;
+ default:
+ DCHECK(false); // Error!
+ }
+ if (frame->flags() & CONTROL_FLAG_FIN)
+ ++fin_flag_count_;
+ }
+
+ // Convenience function which runs a framer simulation with particular input.
+ void SimulateInFramer(const unsigned char* input, size_t size) {
+ framer_.set_enable_compression(false);
+ 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() == FlipFramer::FLIP_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 = framer_.ProcessInput(input_ptr, bytes_read);
+ input_remaining -= bytes_processed;
+ input_ptr += bytes_processed;
+ if (framer_.state() == FlipFramer::FLIP_DONE)
+ framer_.Reset();
+ }
+ }
+
+ FlipFramer framer_;
+ // Counters from the visitor callbacks.
+ int error_count_;
+ int syn_frame_count_;
+ int syn_reply_frame_count_;
+ int data_bytes_;
+ int fin_frame_count_; // The count of FIN_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.
+};
+
+} // namespace test
+
+} // namespace flip
+
+using flip::FlipFrame;
+using flip::FlipFrameBuilder;
+using flip::FlipFramer;
+using flip::FlipHeaderBlock;
+using flip::FlipSynStreamControlFrame;
+using flip::kControlFlagMask;
+using flip::CONTROL_FLAG_NONE;
+using flip::SYN_STREAM;
+using flip::test::FramerSetEnableCompressionHelper;
+using flip::test::TestFlipVisitor;
+
+namespace {
+
+class FlipFramerTest : public PlatformTest {
+ public:
+ virtual void TearDown() {}
+};
+
+// Test that we can encode and decode a FlipHeaderBlock.
+TEST_F(FlipFramerTest, HeaderBlock) {
+ FlipHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "charlie";
+ FlipFramer framer;
+
+ // Encode the header block into a SynStream frame.
+ scoped_ptr<FlipSynStreamControlFrame> frame(
+ framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, true, &headers));
+ EXPECT_TRUE(frame.get() != NULL);
+
+ FlipHeaderBlock new_headers;
+ framer.ParseHeaderBlock(frame.get(), &new_headers);
+
+ EXPECT_EQ(headers.size(), new_headers.size());
+ EXPECT_EQ(headers["alpha"], new_headers["alpha"]);
+ EXPECT_EQ(headers["gamma"], new_headers["gamma"]);
+}
+
+TEST_F(FlipFramerTest, OutOfOrderHeaders) {
+ FlipFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | 1);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length.
+ frame.WriteUInt32(3); // stream_id
+ frame.WriteUInt16(0); // Priority.
+
+ frame.WriteUInt16(2); // Number of headers.
+ FlipHeaderBlock::iterator it;
+ frame.WriteString("gamma");
+ frame.WriteString("gamma");
+ frame.WriteString("alpha");
+ frame.WriteString("alpha");
+ // write the length
+ frame.WriteUInt32ToOffset(4, frame.length() - FlipFrame::size());
+
+ FlipHeaderBlock new_headers;
+ scoped_ptr<FlipFrame> control_frame(frame.take());
+ FlipFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
+}
+
+TEST_F(FlipFramerTest, DuplicateHeader) {
+ FlipFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | 1);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length.
+ frame.WriteUInt32(3); // stream_id
+ frame.WriteUInt16(0); // Priority.
+
+ frame.WriteUInt16(2); // Number of headers.
+ FlipHeaderBlock::iterator it;
+ frame.WriteString("name");
+ frame.WriteString("value1");
+ frame.WriteString("name");
+ frame.WriteString("value2");
+ // write the length
+ frame.WriteUInt32ToOffset(4, frame.length() - FlipFrame::size());
+
+ FlipHeaderBlock new_headers;
+ scoped_ptr<FlipFrame> control_frame(frame.take());
+ FlipFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ // This should fail because duplicate headers are verboten by the spec.
+ EXPECT_FALSE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
+}
+
+TEST_F(FlipFramerTest, MultiValueHeader) {
+ FlipFrameBuilder frame;
+
+ frame.WriteUInt16(kControlFlagMask | 1);
+ frame.WriteUInt16(SYN_STREAM);
+ frame.WriteUInt32(0); // Placeholder for the length.
+ frame.WriteUInt32(3); // stream_id
+ frame.WriteUInt16(0); // Priority.
+
+ frame.WriteUInt16(2); // Number of headers.
+ FlipHeaderBlock::iterator it;
+ frame.WriteString("name");
+ std::string value("value1\0value2");
+ frame.WriteString(value);
+ // write the length
+ frame.WriteUInt32ToOffset(4, frame.length() - FlipFrame::size());
+
+ FlipHeaderBlock new_headers;
+ scoped_ptr<FlipFrame> control_frame(frame.take());
+ FlipFramer framer;
+ FramerSetEnableCompressionHelper(&framer, false);
+ EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers));
+ EXPECT_TRUE(new_headers.find("name") != new_headers.end());
+ EXPECT_EQ(value, new_headers.find("name")->second);
+}
+
+TEST_F(FlipFramerTest, BasicCompression) {
+ FlipHeaderBlock headers;
+ headers["server"] = "FlipServer 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";
+
+ FlipFramer framer;
+ FramerSetEnableCompressionHelper(&framer, true);
+ scoped_ptr<FlipSynStreamControlFrame>
+ frame1(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, true, &headers));
+ scoped_ptr<FlipSynStreamControlFrame>
+ frame2(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, true, &headers));
+
+ // Expect the second frame to be more compact than the first.
+ EXPECT_LE(frame2->length(), frame1->length());
+
+ // Decompress the first frame
+ scoped_ptr<FlipFrame> frame3(framer.DecompressFrame(frame1.get()));
+
+ // Decompress the second frame
+ scoped_ptr<FlipFrame> frame4(framer.DecompressFrame(frame2.get()));
+
+ // Expect frames 3 & 4 to be the same.
+ EXPECT_EQ(0,
+ memcmp(frame3->data(), frame4->data(),
+ FlipFrame::size() + frame3->length()));
+}
+
+TEST_F(FlipFramerTest, DecompressUncompressedFrame) {
+ FlipHeaderBlock headers;
+ headers["server"] = "FlipServer 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";
+
+ FlipFramer framer;
+ FramerSetEnableCompressionHelper(&framer, true);
+ scoped_ptr<FlipSynStreamControlFrame>
+ frame1(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, false, &headers));
+
+ // Decompress the frame
+ scoped_ptr<FlipFrame> frame2(framer.DecompressFrame(frame1.get()));
+
+ EXPECT_EQ(NULL, frame2.get());
+}
+
+TEST_F(FlipFramerTest, Basic) {
+ const unsigned char input[] = {
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 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, 0x01, 0x00, 0x01, // SYN Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x03,
+ 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, 0x01, 0x00, 0x03, // FIN 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, 0x01, 0x00, 0x03, // FIN on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ TestFlipVisitor visitor;
+ visitor.SimulateInFramer(input, sizeof(input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_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_);
+}
+
+// Test that the FIN flag on a data frame signifies EOF.
+TEST_F(FlipFramerTest, FinOnDataFrame) {
+ const unsigned char input[] = {
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, 0x01, 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,
+ };
+
+ TestFlipVisitor visitor;
+ visitor.SimulateInFramer(input, sizeof(input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.syn_reply_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_);
+}
+
+// Test that the FIN flag on a SYN reply frame signifies EOF.
+TEST_F(FlipFramerTest, FinOnSynReplyFrame) {
+ const unsigned char input[] = {
+ 0x80, 0x01, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, 0x01, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x01, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'a', 'a',
+ 0x00, 0x02, 'b', 'b',
+ };
+
+ TestFlipVisitor visitor;
+ visitor.SimulateInFramer(input, sizeof(input));
+
+ 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.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_);
+}
+
+} // namespace
+
diff --git a/net/spdy/spdy_io_buffer.cc b/net/spdy/spdy_io_buffer.cc
new file mode 100644
index 0000000..d26e4c3
--- /dev/null
+++ b/net/spdy/spdy_io_buffer.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2009 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 FlipIOBuffer::order_ = 0;
+
+FlipIOBuffer::FlipIOBuffer(
+ IOBuffer* buffer, int size, int priority, FlipStream* stream)
+ : buffer_(new DrainableIOBuffer(buffer, size)),
+ priority_(priority),
+ position_(++order_),
+ stream_(stream) {}
+
+FlipIOBuffer::FlipIOBuffer() : priority_(0), position_(0), stream_(NULL) {}
+
+FlipIOBuffer::~FlipIOBuffer() {}
+
+void FlipIOBuffer::release() {
+ buffer_ = NULL;
+ stream_ = NULL;
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_io_buffer.h b/net/spdy/spdy_io_buffer.h
new file mode 100644
index 0000000..08e40bb
--- /dev/null
+++ b/net/spdy/spdy_io_buffer.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2009 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/ref_counted.h"
+#include "net/base/io_buffer.h"
+
+namespace net {
+
+class FlipStream;
+
+// A class for managing FLIP IO buffers. These buffers need to be prioritized
+// so that the FlipSession sends them in the right order. Further, they need
+// to track the FlipStream which they are associated with so that incremental
+// completion of the IO can notify the appropriate stream of completion.
+class FlipIOBuffer {
+ public:
+ // Constructor
+ // |buffer| is the actual data buffer.
+ // |size| is the size of the data buffer.
+ // |priority| is the priority of this buffer. Lower numbers are higher
+ // priority.
+ // |stream| is a pointer to the stream which is managing this buffer.
+ FlipIOBuffer(IOBuffer* buffer, int size, int priority, FlipStream* stream);
+ FlipIOBuffer();
+ ~FlipIOBuffer();
+
+ // Accessors.
+ DrainableIOBuffer* buffer() const { return buffer_; }
+ size_t size() const { return buffer_->size(); }
+ void release();
+ int priority() const { return priority_; }
+ const scoped_refptr<FlipStream>& stream() const { return stream_; }
+
+ // Comparison operator to support sorting.
+ bool operator<(const FlipIOBuffer& other) const {
+ if (priority_ != other.priority_)
+ return priority_ > other.priority_;
+ return position_ > other.position_;
+ }
+
+ private:
+ scoped_refptr<DrainableIOBuffer> buffer_;
+ int priority_;
+ uint64 position_;
+ scoped_refptr<FlipStream> stream_;
+ static uint64 order_; // Maintains a FIFO order for equal priorities.
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_IO_BUFFER_H_
diff --git a/net/spdy/spdy_network_transaction.cc b/net/spdy/spdy_network_transaction.cc
new file mode 100644
index 0000000..b578d38
--- /dev/null
+++ b/net/spdy/spdy_network_transaction.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 2009 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_network_transaction.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/stats_counters.h"
+#include "net/base/host_resolver.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_stream.h"
+
+using base::Time;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+FlipNetworkTransaction::FlipNetworkTransaction(HttpNetworkSession* session)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &FlipNetworkTransaction::OnIOComplete)),
+ user_callback_(NULL),
+ user_buffer_len_(0),
+ session_(session),
+ request_(NULL),
+ next_state_(STATE_NONE),
+ stream_(NULL) {
+}
+
+FlipNetworkTransaction::~FlipNetworkTransaction() {
+ LOG(INFO) << "FlipNetworkTransaction dead. " << this;
+ if (stream_.get())
+ stream_->Cancel();
+}
+
+int FlipNetworkTransaction::Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback,
+ LoadLog* load_log) {
+ CHECK(request_info);
+ CHECK(callback);
+
+ SIMPLE_STATS_COUNTER("FlipNetworkTransaction.Count");
+
+ load_log_ = load_log;
+ request_ = request_info;
+ start_time_ = base::TimeTicks::Now();
+
+ next_state_ = STATE_INIT_CONNECTION;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int FlipNetworkTransaction::RestartIgnoringLastError(
+ CompletionCallback* callback) {
+ // TODO(mbelshe): implement me.
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int FlipNetworkTransaction::RestartWithCertificate(
+ X509Certificate* client_cert, CompletionCallback* callback) {
+ // TODO(mbelshe): implement me.
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int FlipNetworkTransaction::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ // TODO(mbelshe): implement me.
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+int FlipNetworkTransaction::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+ DCHECK(callback);
+
+ user_buffer_ = buf;
+ user_buffer_len_ = buf_len;
+
+ next_state_ = STATE_READ_BODY;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+const HttpResponseInfo* FlipNetworkTransaction::GetResponseInfo() const {
+ return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
+}
+
+LoadState FlipNetworkTransaction::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_INIT_CONNECTION_COMPLETE:
+ if (flip_.get())
+ return flip_->GetLoadState();
+ return LOAD_STATE_CONNECTING;
+ case STATE_SEND_REQUEST_COMPLETE:
+ return LOAD_STATE_SENDING_REQUEST;
+ case STATE_READ_HEADERS_COMPLETE:
+ return LOAD_STATE_WAITING_FOR_RESPONSE;
+ case STATE_READ_BODY_COMPLETE:
+ return LOAD_STATE_READING_RESPONSE;
+ default:
+ return LOAD_STATE_IDLE;
+ }
+}
+
+uint64 FlipNetworkTransaction::GetUploadProgress() const {
+ if (!stream_.get())
+ return 0;
+
+ return stream_->GetUploadProgress();
+}
+
+void FlipNetworkTransaction::DoCallback(int rv) {
+ CHECK(rv != ERR_IO_PENDING);
+ CHECK(user_callback_);
+
+ // Since Run may result in Read being called, clear user_callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(rv);
+}
+
+void FlipNetworkTransaction::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int FlipNetworkTransaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+ DCHECK(request_);
+
+ if (!request_)
+ return 0;
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_INIT_CONNECTION:
+ DCHECK_EQ(OK, rv);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_INIT_CONNECTION);
+ rv = DoInitConnection();
+ break;
+ case STATE_INIT_CONNECTION_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_INIT_CONNECTION);
+ rv = DoInitConnectionComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST);
+ rv = DoSendRequestComplete(rv);
+ break;
+ case STATE_READ_HEADERS:
+ DCHECK_EQ(OK, rv);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS);
+ rv = DoReadHeadersComplete(rv);
+ break;
+ case STATE_READ_BODY:
+ DCHECK_EQ(OK, rv);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY);
+ rv = DoReadBody();
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY);
+ rv = DoReadBodyComplete(rv);
+ break;
+ case STATE_NONE:
+ rv = ERR_FAILED;
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int FlipNetworkTransaction::DoInitConnection() {
+ next_state_ = STATE_INIT_CONNECTION_COMPLETE;
+
+ std::string host = request_->url.HostNoBrackets();
+ int port = request_->url.EffectiveIntPort();
+
+ // Use the fixed testing ports if they've been provided. This is useful for
+ // debugging.
+ if (FlipSession::SSLMode()) {
+ if (session_->fixed_https_port() != 0)
+ port = session_->fixed_https_port();
+ } else if (session_->fixed_http_port() != 0) {
+ port = session_->fixed_http_port();
+ }
+
+ std::string connection_group = "flip.";
+ connection_group.append(host);
+
+ HostResolver::RequestInfo resolve_info(host, port);
+
+ flip_ = session_->flip_session_pool()->Get(resolve_info, session_);
+ DCHECK(flip_);
+
+ return flip_->Connect(
+ connection_group, resolve_info, request_->priority, load_log_);
+}
+
+int FlipNetworkTransaction::DoInitConnectionComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+}
+
+int FlipNetworkTransaction::DoSendRequest() {
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ CHECK(!stream_.get());
+ UploadDataStream* upload_data = request_->upload_data ?
+ new UploadDataStream(request_->upload_data) : NULL;
+ stream_ = flip_->GetOrCreateStream(*request_, upload_data, load_log_.get());
+ // Release the reference to |flip_| since we don't need it anymore.
+ flip_ = NULL;
+ return stream_->SendRequest(upload_data, &response_, &io_callback_);
+}
+
+int FlipNetworkTransaction::DoSendRequestComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int FlipNetworkTransaction::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+ return stream_->ReadResponseHeaders(&io_callback_);
+}
+
+int FlipNetworkTransaction::DoReadHeadersComplete(int result) {
+ // TODO(willchan): Flesh out the support for HTTP authentication here.
+ return result;
+}
+
+int FlipNetworkTransaction::DoReadBody() {
+ next_state_ = STATE_READ_BODY_COMPLETE;
+
+ return stream_->ReadResponseBody(
+ user_buffer_, user_buffer_len_, &io_callback_);
+}
+
+int FlipNetworkTransaction::DoReadBodyComplete(int result) {
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+
+ if (result <= 0)
+ stream_ = NULL;
+
+ return result;
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_network_transaction.h b/net/spdy/spdy_network_transaction.h
new file mode 100644
index 0000000..9379cfa
--- /dev/null
+++ b/net/spdy/spdy_network_transaction.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2009 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_FLIP_NETWORK_TRANSACTION_H_
+#define NET_FLIP_NETWORK_TRANSACTION_H_
+
+#include <string>
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+class FlipSession;
+class FlipStream;
+class HttpNetworkSession;
+class HttpResponseInfo;
+class IOBuffer;
+class UploadDataStream;
+
+// A FlipNetworkTransaction can be used to fetch HTTP conent.
+// The FlipDelegate is the consumer of events from the FlipSession.
+class FlipNetworkTransaction : public HttpTransaction {
+ public:
+ explicit FlipNetworkTransaction(HttpNetworkSession* session);
+ virtual ~FlipNetworkTransaction();
+
+ // HttpTransaction methods:
+ virtual int Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback,
+ LoadLog* load_log);
+ virtual int RestartIgnoringLastError(CompletionCallback* callback);
+ virtual int RestartWithCertificate(X509Certificate* client_cert,
+ CompletionCallback* callback);
+ virtual int RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback);
+ virtual bool IsReadyToRestartForAuth() { return false; }
+ virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
+ virtual const HttpResponseInfo* GetResponseInfo() const;
+ virtual LoadState GetLoadState() const;
+ virtual uint64 GetUploadProgress() const;
+
+ protected:
+ friend class FlipNetworkTransactionTest;
+
+ // Provide access to the session for testing.
+ FlipSession* GetFlipSession() { return flip_.get(); }
+
+ private:
+ enum State {
+ STATE_INIT_CONNECTION,
+ STATE_INIT_CONNECTION_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_NONE
+ };
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. Those with an input
+ // argument receive the result from the previous state. If a method returns
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
+ // next state method as the result arg.
+ int DoInitConnection();
+ int DoInitConnectionComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoReadBody();
+ int DoReadBodyComplete(int result);
+
+ scoped_refptr<LoadLog> load_log_;
+
+ scoped_refptr<FlipSession> flip_;
+
+ CompletionCallbackImpl<FlipNetworkTransaction> io_callback_;
+ CompletionCallback* user_callback_;
+
+ // Used to pass onto the FlipStream
+ scoped_refptr<IOBuffer> user_buffer_;
+ int user_buffer_len_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ const HttpRequestInfo* request_;
+ HttpResponseInfo response_;
+
+ // The time the Start method was called.
+ base::TimeTicks start_time_;
+
+ // The next state in the state machine.
+ State next_state_;
+
+ scoped_refptr<FlipStream> stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlipNetworkTransaction);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_NETWORK_TRANSACTION_H_
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc
new file mode 100644
index 0000000..0c879f7
--- /dev/null
+++ b/net/spdy/spdy_network_transaction_unittest.cc
@@ -0,0 +1,1084 @@
+// Copyright (c) 2010 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_network_transaction.h"
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_log_unittest.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_data.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+namespace {
+
+// Create a proxy service which fails on all requests (falls back to direct).
+ProxyService* CreateNullProxyService() {
+ return ProxyService::CreateNull();
+}
+
+// Helper to manage the lifetimes of the dependencies for a
+// FlipNetworkTransaction.
+class SessionDependencies {
+ public:
+ // Default set of dependencies -- "null" proxy service.
+ SessionDependencies()
+ : host_resolver(new MockHostResolver),
+ proxy_service(CreateNullProxyService()),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ flip_session_pool(new FlipSessionPool) {
+ // 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);
+ }
+
+ // Custom proxy service dependency.
+ explicit SessionDependencies(ProxyService* proxy_service)
+ : host_resolver(new MockHostResolver),
+ proxy_service(proxy_service),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ flip_session_pool(new FlipSessionPool) {}
+
+ scoped_refptr<MockHostResolverBase> host_resolver;
+ scoped_refptr<ProxyService> proxy_service;
+ scoped_refptr<SSLConfigService> ssl_config_service;
+ MockClientSocketFactory socket_factory;
+ scoped_refptr<FlipSessionPool> flip_session_pool;
+};
+
+ProxyService* CreateFixedProxyService(const std::string& proxy) {
+ ProxyConfig proxy_config;
+ proxy_config.proxy_rules.ParseFromString(proxy);
+ return ProxyService::CreateFixed(proxy_config);
+}
+
+
+HttpNetworkSession* CreateSession(SessionDependencies* session_deps) {
+ return new HttpNetworkSession(NULL,
+ session_deps->host_resolver,
+ session_deps->proxy_service,
+ &session_deps->socket_factory,
+ session_deps->ssl_config_service,
+ session_deps->flip_session_pool);
+}
+
+// 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* ChopFrame(const char* data, int length, int num_chunks) {
+ MockWrite* chunks = new MockWrite[num_chunks + 1];
+ 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(true, ptr, chunk_size);
+ }
+ chunks[num_chunks] = MockWrite(true, 0, 0);
+ return chunks;
+}
+
+// ----------------------------------------------------------------------------
+
+static const unsigned char kGetSyn[] = {
+ 0x80, 0x01, 0x00, 0x01, // header
+ 0x01, 0x00, 0x00, 0x45, // FIN, len
+ 0x00, 0x00, 0x00, 0x01, // stream id
+ 0xc0, 0x00, 0x00, 0x03, // 4 headers
+ 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd',
+ 0x00, 0x03, 'G', 'E', 'T',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w',
+ '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o',
+ 'm', '/',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+};
+
+static const unsigned char kGetSynCompressed[] = {
+ 0x80, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x43,
+ 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x78, 0xbb,
+ 0xdf, 0xa2, 0x51, 0xb2, 0x62, 0x60, 0x66, 0x60,
+ 0xcb, 0x05, 0xe6, 0xc3, 0xfc, 0x14, 0x06, 0x66,
+ 0x77, 0xd7, 0x10, 0x06, 0x66, 0x90, 0xa0, 0x58,
+ 0x46, 0x49, 0x49, 0x81, 0x95, 0xbe, 0x3e, 0x30,
+ 0xe2, 0xf5, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52,
+ 0xf5, 0x92, 0xf3, 0x73, 0xf5, 0x19, 0xd8, 0xa1,
+ 0x1a, 0x19, 0x38, 0x60, 0xe6, 0x01, 0x00, 0x00,
+ 0x00, 0xff, 0xff
+};
+
+static const unsigned char kGetSynReply[] = {
+ 0x80, 0x01, 0x00, 0x02, // header
+ 0x00, 0x00, 0x00, 0x45,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04, // 4 headers
+ 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', // "hello"
+ 0x00, 0x03, 'b', 'y', 'e', // "bye"
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', // "status"
+ 0x00, 0x03, '2', '0', '0', // "200"
+ 0x00, 0x03, 'u', 'r', 'l', // "url"
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', // "/index...
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // "version"
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', // "HTTP/1.1"
+};
+
+static const unsigned char kGetBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x01, // header
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'h', 'e', 'l', 'l', 'o', '!', // "hello"
+};
+
+static const unsigned char kPostSyn[] = {
+ 0x80, 0x01, 0x00, 0x01, // header
+ 0x00, 0x00, 0x00, 0x46, // flags, len
+ 0x00, 0x00, 0x00, 0x01, // stream id
+ 0xc0, 0x00, 0x00, 0x03, // 4 headers
+ 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd',
+ 0x00, 0x04, 'P', 'O', 'S', 'T',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w',
+ '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o',
+ 'm', '/',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+};
+
+static const unsigned char kPostUploadFrame[] = {
+ 0x00, 0x00, 0x00, 0x01, // header
+ 0x01, 0x00, 0x00, 0x0c, // FIN flag
+ 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'
+};
+
+// The response
+static const unsigned char kPostSynReply[] = {
+ 0x80, 0x01, 0x00, 0x02, // header
+ 0x00, 0x00, 0x00, 0x45,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04, // 4 headers
+ 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', // "hello"
+ 0x00, 0x03, 'b', 'y', 'e', // "bye"
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', // "status"
+ 0x00, 0x03, '2', '0', '0', // "200"
+ 0x00, 0x03, 'u', 'r', 'l', // "url"
+ // "/index.php"
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // "version"
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', // "HTTP/1.1"
+};
+
+static const unsigned char kPostBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x01, // header
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'h', 'e', 'l', 'l', 'o', '!', // "hello"
+};
+
+} // namespace
+
+// A DataProvider where the client must write a request before the reads (e.g.
+// the response) will complete.
+class DelayedSocketData : public StaticSocketDataProvider,
+ public base::RefCounted<DelayedSocketData> {
+ public:
+ // |reads| the list of MockRead completions.
+ // |write_delay| the number of MockWrites to complete before allowing
+ // a MockRead to complete.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: The MockRead and MockWrite lists musts end with a EOF
+ // e.g. a MockRead(true, 0, 0);
+ DelayedSocketData(MockRead* reads, int write_delay, MockWrite* writes)
+ : StaticSocketDataProvider(reads, writes),
+ write_delay_(write_delay),
+ ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
+ DCHECK_GE(write_delay_, 0);
+ }
+
+ // |connect| the result for the connect phase.
+ // |reads| the list of MockRead completions.
+ // |write_delay| the number of MockWrites to complete before allowing
+ // a MockRead to complete.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: The MockRead and MockWrite lists musts end with a EOF
+ // e.g. a MockRead(true, 0, 0);
+ DelayedSocketData(const MockConnect& connect, MockRead* reads,
+ int write_delay, MockWrite* writes)
+ : StaticSocketDataProvider(reads, writes),
+ write_delay_(write_delay),
+ ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) {
+ DCHECK_GE(write_delay_, 0);
+ set_connect_data(connect);
+ }
+
+ virtual MockRead GetNextRead() {
+ if (write_delay_)
+ return MockRead(true, ERR_IO_PENDING);
+ return StaticSocketDataProvider::GetNextRead();
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) {
+ MockWriteResult rv = StaticSocketDataProvider::OnWrite(data);
+ // Now that our write has completed, we can allow reads to continue.
+ if (!--write_delay_)
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ factory_.NewRunnableMethod(&DelayedSocketData::CompleteRead), 100);
+ return rv;
+ }
+
+ virtual void Reset() {
+ set_socket(NULL);
+ factory_.RevokeAll();
+ StaticSocketDataProvider::Reset();
+ }
+
+ void CompleteRead() {
+ if (socket())
+ socket()->OnReadComplete(GetNextRead());
+ }
+
+ private:
+ int write_delay_;
+ ScopedRunnableMethodFactory<DelayedSocketData> factory_;
+};
+
+class FlipNetworkTransactionTest : public PlatformTest {
+ protected:
+ virtual void SetUp() {
+ // By default, all tests turn off compression.
+ EnableCompression(false);
+ }
+
+ virtual void TearDown() {
+ // Empty the current queue.
+ MessageLoop::current()->RunAllPending();
+ PlatformTest::TearDown();
+ }
+
+ void KeepAliveConnectionResendRequestTest(const MockRead& read_failure);
+
+ struct TransactionHelperResult {
+ int rv;
+ std::string status_line;
+ std::string response_data;
+ HttpResponseInfo response_info;
+ };
+
+ void EnableCompression(bool enabled) {
+ flip::FlipFramer::set_enable_compression_default(enabled);
+ }
+
+ TransactionHelperResult TransactionHelper(const HttpRequestInfo& request,
+ DelayedSocketData* data,
+ LoadLog* log) {
+ TransactionHelperResult out;
+
+ // We disable SSL for this test.
+ FlipSession::SetSSLMode(false);
+
+ SessionDependencies session_deps;
+ scoped_ptr<FlipNetworkTransaction> trans(
+ new FlipNetworkTransaction(CreateSession(&session_deps)));
+
+ session_deps.socket_factory.AddSocketDataProvider(data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, log);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ out.rv = callback.WaitForResult();
+ if (out.rv != OK)
+ return out;
+
+ 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.
+
+ rv = ReadTransaction(trans.get(), &out.response_data);
+ EXPECT_EQ(OK, rv);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+
+ return out;
+ }
+
+ void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
+ int expected_status);
+
+ void ConnectStatusHelper(const MockRead& status);
+};
+
+//-----------------------------------------------------------------------------
+
+// Verify FlipNetworkTransaction constructor.
+TEST_F(FlipNetworkTransactionTest, Constructor) {
+ SessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session =
+ CreateSession(&session_deps);
+ scoped_ptr<HttpTransaction> trans(new FlipNetworkTransaction(session));
+}
+
+TEST_F(FlipNetworkTransactionTest, Get) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ 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_F(FlipNetworkTransactionTest, Post) {
+ static const char upload[] = { "hello world" };
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data = new UploadData();
+ request.upload_data->AppendBytes(upload, sizeof(upload));
+
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kPostSyn),
+ arraysize(kPostSyn)),
+ MockWrite(true, reinterpret_cast<const char*>(kPostUploadFrame),
+ arraysize(kPostUploadFrame)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kPostSynReply),
+ arraysize(kPostSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kPostBodyFrame),
+ arraysize(kPostBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 2, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ 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_F(FlipNetworkTransactionTest, EmptyPost) {
+static const unsigned char kEmptyPostSyn[] = {
+ 0x80, 0x01, 0x00, 0x01, // header
+ 0x01, 0x00, 0x00, 0x46, // flags, len
+ 0x00, 0x00, 0x00, 0x01, // stream id
+ 0xc0, 0x00, 0x00, 0x03, // 4 headers
+ 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd',
+ 0x00, 0x04, 'P', 'O', 'S', 'T',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w',
+ '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o',
+ 'm', '/',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+};
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ // Create an empty UploadData.
+ request.upload_data = new UploadData();
+
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kEmptyPostSyn),
+ arraysize(kEmptyPostSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kPostSynReply),
+ arraysize(kPostSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kPostBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+
+ TransactionHelperResult out = TransactionHelper(request, data, NULL);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that the transaction doesn't crash when we don't have a reply.
+TEST_F(FlipNetworkTransactionTest, ResponseWithoutSynReply) {
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kPostBodyFrame),
+ arraysize(kPostBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, NULL));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv);
+}
+
+TEST_F(FlipNetworkTransactionTest, CancelledTransaction) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ // This following read isn't used by the test, except during the
+ // RunAllPending() call at the end since the FlipSession survives the
+ // FlipNetworkTransaction and still tries to continue Read()'ing. Any
+ // MockRead will do here.
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // We disable SSL for this test.
+ FlipSession::SetSSLMode(false);
+
+ SessionDependencies session_deps;
+ scoped_ptr<FlipNetworkTransaction> trans(
+ new FlipNetworkTransaction(CreateSession(&session_deps)));
+
+ StaticSocketDataProvider data(reads, writes);
+ session_deps.socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, NULL);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ trans.reset(); // Cancel the transaction.
+
+ // Flush the MessageLoop while the SessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ MessageLoop::current()->RunAllPending();
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_F(FlipNetworkTransactionTest, SynReplyHeaders) {
+ // This uses a multi-valued cookie header.
+ static const unsigned char syn_reply1[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x4c,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e',
+ 0x00, 0x09, 'v', 'a', 'l', '1', '\0',
+ 'v', 'a', 'l', '2',
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+ };
+
+ // This is the minimalist set of headers.
+ static const unsigned char syn_reply2[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x39,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+ };
+
+ // Headers with a comma separated list.
+ static const unsigned char syn_reply3[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x4c,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e',
+ 0x00, 0x09, 'v', 'a', 'l', '1', ',', 'v', 'a', 'l', '2',
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+ };
+
+ struct SynReplyTests {
+ const unsigned char* syn_reply;
+ int syn_reply_length;
+ const char* expected_headers;
+ } 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_reply1, sizeof(syn_reply1),
+ "cookie: val1\n"
+ "cookie: val2\n"
+ "status: 200\n"
+ "url: /index.php\n"
+ "version: HTTP/1.1\n"
+ },
+ // This is the simplest set of headers possible.
+ { syn_reply2, sizeof(syn_reply2),
+ "status: 200\n"
+ "url: /index.php\n"
+ "version: HTTP/1.1\n"
+ },
+ // Test that a comma delimited list is NOT interpreted as a multi-value
+ // name/value pair. The comma-separated list is just a single value.
+ { syn_reply3, sizeof(syn_reply3),
+ "cookie: val1,val2\n"
+ "status: 200\n"
+ "url: /index.php\n"
+ "version: HTTP/1.1\n"
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(test_cases[i].syn_reply),
+ test_cases[i].syn_reply_length),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ 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 we don't crash on invalid SynReply responses.
+TEST_F(FlipNetworkTransactionTest, InvalidSynReply) {
+ static const unsigned char kSynReplyMissingStatus[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x3f,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e',
+ 0x00, 0x09, 'v', 'a', 'l', '1', '\0',
+ 'v', 'a', 'l', '2',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+ };
+
+ static const unsigned char kSynReplyMissingVersion[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x26,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ };
+
+ struct SynReplyTests {
+ const unsigned char* syn_reply;
+ int syn_reply_length;
+ } test_cases[] = {
+ { kSynReplyMissingStatus, arraysize(kSynReplyMissingStatus) },
+ { kSynReplyMissingVersion, arraysize(kSynReplyMissingVersion) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(test_cases[i].syn_reply),
+ test_cases[i].syn_reply_length),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(ERR_INVALID_RESPONSE, out.rv);
+ }
+}
+
+// Verify that we don't crash on some corrupt frames.
+TEST_F(FlipNetworkTransactionTest, CorruptFrameSessionError) {
+ static const unsigned char kSynReplyMassiveLength[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x0f, 0x11, 0x11, 0x26, // This is the length field with a big number
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ };
+
+ struct SynReplyTests {
+ const unsigned char* syn_reply;
+ int syn_reply_length;
+ } test_cases[] = {
+ { kSynReplyMassiveLength, arraysize(kSynReplyMassiveLength) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(test_cases[i].syn_reply),
+ test_cases[i].syn_reply_length),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(ERR_FLIP_PROTOCOL_ERROR, out.rv);
+ }
+}
+
+TEST_F(FlipNetworkTransactionTest, ServerPush) {
+ // Reply with the X-Associated-Content header.
+ static const unsigned char syn_reply[] = {
+ 0x80, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x71,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x14, 'X', '-', 'A', 's', 's', 'o', 'c', 'i', 'a', 't',
+ 'e', 'd', '-', 'C', 'o', 'n', 't', 'e', 'n', 't',
+ 0x00, 0x20, '1', '?', '?', 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w',
+ 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm',
+ '/', 'f', 'o', 'o', '.', 'd', 'a', 't',
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+ };
+
+ // Syn for the X-Associated-Content (foo.dat)
+ static const unsigned char syn_push[] = {
+ 0x80, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x47,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x04, 'p', 'a', 't', 'h',
+ 0x00, 0x08, '/', 'f', 'o', 'o', '.', 'd', 'a', 't',
+ 0x00, 0x06, 's', 't', 'a', 't', 'u', 's',
+ 0x00, 0x03, '2', '0', '0',
+ 0x00, 0x03, 'u', 'r', 'l',
+ 0x00, 0x08, '/', 'f', 'o', 'o', '.', 'd', 'a', 't',
+ 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1',
+ };
+
+ // Body for stream 2
+ static const unsigned char body_frame_2[] = {
+ 0x00, 0x00, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x07,
+ 'g', 'o', 'o', 'd', 'b', 'y', 'e',
+ };
+
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(syn_reply),
+ arraysize(syn_reply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, ERR_IO_PENDING), // Force a pause
+ MockRead(true, reinterpret_cast<const char*>(syn_push),
+ arraysize(syn_push)),
+ MockRead(true, reinterpret_cast<const char*>(body_frame_2),
+ arraysize(body_frame_2)),
+ MockRead(true, ERR_IO_PENDING), // Force a pause
+ MockRead(true, 0, 0) // EOF
+ };
+
+ // We disable SSL for this test.
+ FlipSession::SetSSLMode(false);
+
+ enum TestTypes {
+ // Simulate that the server sends the first request, notifying the client
+ // that it *will* push the second stream. But the client issues the
+ // request for the second stream before the push data arrives.
+ PUSH_AFTER_REQUEST,
+ // Simulate that the server is sending the pushed stream data before the
+ // client requests it. The FlipSession will buffer the response and then
+ // deliver the data when the client does make the request.
+ PUSH_BEFORE_REQUEST,
+ DONE
+ };
+
+ for (int test_type = PUSH_AFTER_REQUEST; test_type != DONE; ++test_type) {
+ // Setup a mock session.
+ SessionDependencies session_deps;
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ session_deps.socket_factory.AddSocketDataProvider(data.get());
+
+ // Issue the first request
+ {
+ FlipNetworkTransaction trans(session.get());
+
+ // Issue the first request.
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ TestCompletionCallback callback;
+ int rv = trans.Start(&request, &callback, NULL);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+
+ // Verify the SYN_REPLY.
+ const HttpResponseInfo* response = trans.GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ if (test_type == PUSH_BEFORE_REQUEST)
+ data->CompleteRead();
+
+ // Verify the body.
+ std::string response_data;
+ rv = ReadTransaction(&trans, &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello!", response_data);
+ }
+
+ // Issue a second request for the X-Associated-Content.
+ {
+ FlipNetworkTransaction trans(session.get());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/foo.dat");
+ request.load_flags = 0;
+ TestCompletionCallback callback;
+ int rv = trans.Start(&request, &callback, NULL);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // In the case where we are Complete the next read now.
+ if (test_type == PUSH_AFTER_REQUEST)
+ data->CompleteRead();
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+
+ // Verify the SYN_REPLY.
+ const HttpResponseInfo* response = trans.GetResponseInfo();
+ EXPECT_TRUE(response->headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ // Verify the body.
+ std::string response_data;
+ rv = ReadTransaction(&trans, &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("goodbye", response_data);
+ }
+
+ // Complete the next read now and teardown.
+ data->CompleteRead();
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+ }
+}
+
+// Test that we shutdown correctly on write errors.
+TEST_F(FlipNetworkTransactionTest, WriteError) {
+ MockWrite writes[] = {
+ // We'll write 10 bytes successfully
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn), 10),
+ // Followed by ERROR!
+ MockWrite(true, ERR_FAILED),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 2, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(ERR_FAILED, out.rv);
+ data->Reset();
+}
+
+// Test that partial writes work.
+TEST_F(FlipNetworkTransactionTest, PartialWrite) {
+ // Chop the SYN_STREAM frame into 5 chunks.
+ const int kChunks = 5;
+ scoped_array<MockWrite> writes(ChopFrame(
+ reinterpret_cast<const char*>(kGetSyn), arraysize(kGetSyn), kChunks));
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, kChunks, writes.get()));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+TEST_F(FlipNetworkTransactionTest, DISABLED_ConnectFailure) {
+ MockConnect connects[] = {
+ MockConnect(true, ERR_NAME_NOT_RESOLVED),
+ MockConnect(false, ERR_NAME_NOT_RESOLVED),
+ MockConnect(true, ERR_INTERNET_DISCONNECTED),
+ MockConnect(false, ERR_INTERNET_DISCONNECTED)
+ };
+
+ for (size_t index = 0; index < arraysize(connects); ++index) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(connects[index], reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(connects[index].result, out.rv);
+ }
+}
+
+// In this test, we enable compression, but get a uncompressed SynReply from
+// the server. Verify that teardown is all clean.
+TEST_F(FlipNetworkTransactionTest, DecompressFailureOnSynReply) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSynCompressed),
+ arraysize(kGetSynCompressed)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ // For this test, we turn on the normal compression.
+ EnableCompression(true);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(), NULL);
+ EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv);
+ data->Reset();
+
+ EnableCompression(false);
+}
+
+// Test that the LoadLog contains good data for a simple GET request.
+TEST_F(FlipNetworkTransactionTest, LoadLog) {
+ MockWrite writes[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kGetSyn),
+ arraysize(kGetSyn)),
+ MockWrite(true, 0, 0) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(true, reinterpret_cast<const char*>(kGetSynReply),
+ arraysize(kGetSynReply)),
+ MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame),
+ arraysize(kGetBodyFrame)),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(reads, 1, writes));
+ TransactionHelperResult out = TransactionHelper(request, data.get(),
+ log);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ // Check that the LoadLog 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.
+ EXPECT_LT(0u, log->entries().size());
+ int pos = 0;
+ // We know the first event at position 0.
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ *log, 0, net::LoadLog::TYPE_FLIP_TRANSACTION_INIT_CONNECTION));
+ // For the rest of the events, allow additional events in the middle,
+ // but expect these to be logged in order.
+ pos = net::ExpectLogContainsSomewhere(log, 0,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_INIT_CONNECTION,
+ net::LoadLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(log, pos + 1,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST,
+ net::LoadLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(log, pos + 1,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST,
+ net::LoadLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(log, pos + 1,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS,
+ net::LoadLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(log, pos + 1,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS,
+ net::LoadLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(log, pos + 1,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY,
+ net::LoadLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(log, pos + 1,
+ net::LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY,
+ net::LoadLog::PHASE_END);
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h
new file mode 100644
index 0000000..455e5aa
--- /dev/null
+++ b/net/spdy/spdy_protocol.h
@@ -0,0 +1,387 @@
+// Copyright (c) 2009 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 Flip.
+
+#ifndef NET_SPDY_SPDY_PROTOCOL_H_
+#define NET_SPDY_SPDY_PROTOCOL_H_
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "spdy_bitmasks.h" // cross-google3 directory naming.
+
+// 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) | >= 8
+// +----------------------------------+
+// |X| 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: FIN_STREAM
+// +----------------------------------+
+// |1|000000000000001|0000000000000011|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 4
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+// | Status (32 bits) |
+// +----------------------------------+
+//
+// Control Frame: SetMaxStreams
+// +----------------------------------+
+// |1|000000000000001|0000000000000100|
+// +----------------------------------+
+// | flags (8) | Length (24 bits) | >= 4
+// +----------------------------------+
+// |X| Stream-ID(31bits) |
+// +----------------------------------+
+
+// TODO(fenix): add ChangePriority support.
+
+namespace flip {
+
+// This implementation of Flip is version 1.
+const int kFlipProtocolVersion = 1;
+
+// 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 Flip Control Frames.
+enum FlipControlType {
+ SYN_STREAM = 1,
+ SYN_REPLY,
+ FIN_STREAM,
+ NOOP
+};
+
+// Flags on data packets
+enum FlipDataFlags {
+ DATA_FLAG_NONE = 0,
+ DATA_FLAG_FIN = 1,
+ DATA_FLAG_COMPRESSED = 2 // TODO(mbelshe): remove me.
+};
+
+// Flags on control packets
+enum FlipControlFlags {
+ CONTROL_FLAG_NONE = 0,
+ CONTROL_FLAG_FIN = 1
+};
+
+// A FLIP stream id is a 31 bit entity.
+typedef uint32 FlipStreamId;
+
+// FLIP Priorities. (there are only 2 bits)
+#define FLIP_PRIORITY_LOWEST 3
+#define FLIP_PRIORITY_HIGHEST 0
+
+// -------------------------------------------------------------------------
+// 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 FLIP Frame structure.
+struct FlipFrameBlock {
+ union {
+ struct {
+ uint16 version_;
+ uint16 type_;
+ } control_;
+ struct {
+ FlipStreamId stream_id_;
+ } data_;
+ };
+ FlagsAndLength flags_length_;
+};
+
+// A Control Frame structure.
+struct FlipControlFrameBlock : FlipFrameBlock {
+ FlipStreamId stream_id_;
+};
+
+// A SYN_STREAM Control Frame structure.
+struct FlipSynStreamControlFrameBlock : FlipControlFrameBlock {
+ uint8 priority_;
+ uint8 unused_;
+};
+
+// A SYN_REPLY Control Frame structure.
+struct FlipSynReplyControlFrameBlock : FlipControlFrameBlock {
+ uint16 unused_;
+};
+
+// A FNI_STREAM Control Frame structure.
+struct FlipFinStreamControlFrameBlock : FlipControlFrameBlock {
+ uint32 status_;
+};
+
+#pragma pack(pop)
+
+// -------------------------------------------------------------------------
+// Wrapper classes for various FLIP frames.
+
+// All Flip Frame types derive from this FlipFrame class.
+class FlipFrame {
+ public:
+ // Create a FlipFrame for a given sized buffer.
+ explicit FlipFrame(size_t size) : frame_(NULL), owns_buffer_(true) {
+ DCHECK_GE(size, sizeof(struct FlipFrameBlock));
+ char* buffer = new char[size];
+ memset(buffer, 0, size);
+ frame_ = reinterpret_cast<struct FlipFrameBlock*>(buffer);
+ }
+
+ // Create a FlipFrame 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.
+ FlipFrame(char* data, bool owns_buffer)
+ : frame_(reinterpret_cast<struct FlipFrameBlock*>(data)),
+ owns_buffer_(owns_buffer) {
+ DCHECK(frame_);
+ }
+
+ virtual ~FlipFrame() {
+ 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));
+ length = htonl(length & kLengthMask);
+ frame_->flags_length_.length_ = flags() | length;
+ }
+
+ bool is_control_frame() const {
+ return (ntohs(frame_->control_.version_) & kControlFlagMask) ==
+ kControlFlagMask;
+ }
+
+ // Returns the size of the FlipFrameBlock structure.
+ // Note: this is not the size of the FlipFrame class.
+ // Every FlipFrame* 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(FlipFrame).
+ static size_t size() { return sizeof(struct FlipFrameBlock); }
+
+ protected:
+ FlipFrameBlock* frame_;
+
+ private:
+ bool owns_buffer_;
+ DISALLOW_COPY_AND_ASSIGN(FlipFrame);
+};
+
+// A Data Frame.
+class FlipDataFrame : public FlipFrame {
+ public:
+ FlipDataFrame() : FlipFrame(size()) {}
+ FlipDataFrame(char* data, bool owns_buffer)
+ : FlipFrame(data, owns_buffer) {}
+ virtual ~FlipDataFrame() {}
+
+ FlipStreamId 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(FlipStreamId id) {
+ DCHECK_EQ(0u, (id & ~kStreamIdMask));
+ frame_->data_.stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ // Returns the size of the FlipFrameBlock structure.
+ // Note: this is not the size of the FlipDataFrame class.
+ static size_t size() { return FlipFrame::size(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FlipDataFrame);
+};
+
+// A Control Frame.
+class FlipControlFrame : public FlipFrame {
+ public:
+ explicit FlipControlFrame(size_t size) : FlipFrame(size) {}
+ FlipControlFrame(char* data, bool owns_buffer)
+ : FlipFrame(data, owns_buffer) {}
+ virtual ~FlipControlFrame() {}
+
+ uint16 version() const {
+ const int kVersionMask = 0x7fff;
+ return ntohs(block()->control_.version_) & kVersionMask;
+ }
+ FlipControlType type() const {
+ uint16 type = ntohs(block()->control_.type_);
+ DCHECK(type >= SYN_STREAM && type <= NOOP);
+ return static_cast<FlipControlType>(type);
+ }
+ FlipStreamId stream_id() const {
+ return ntohl(block()->stream_id_) & kStreamIdMask;
+ }
+
+ void set_stream_id(FlipStreamId id) {
+ block()->stream_id_ = htonl(id & kStreamIdMask);
+ }
+
+ // Returns the size of the FlipControlFrameBlock structure.
+ // Note: this is not the size of the FlipControlFrame class.
+ static size_t size() { return sizeof(FlipControlFrameBlock); }
+
+ private:
+ struct FlipControlFrameBlock* block() const {
+ return static_cast<FlipControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(FlipControlFrame);
+};
+
+// A SYN_STREAM frame.
+class FlipSynStreamControlFrame : public FlipControlFrame {
+ public:
+ FlipSynStreamControlFrame() : FlipControlFrame(size()) {}
+ FlipSynStreamControlFrame(char* data, bool owns_buffer)
+ : FlipControlFrame(data, owns_buffer) {}
+ virtual ~FlipSynStreamControlFrame() {}
+
+ uint8 priority() const { return (block()->priority_ & kPriorityMask) >> 6; }
+
+ // The number of bytes in the header block beyond the frame header length.
+ int header_block_len() const {
+ return length() - (size() - FlipFrame::size());
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the FlipSynStreamControlFrameBlock structure.
+ // Note: this is not the size of the FlipSynStreamControlFrame class.
+ static size_t size() { return sizeof(FlipSynStreamControlFrameBlock); }
+
+ private:
+ struct FlipSynStreamControlFrameBlock* block() const {
+ return static_cast<FlipSynStreamControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(FlipSynStreamControlFrame);
+};
+
+// A SYN_REPLY frame.
+class FlipSynReplyControlFrame : public FlipControlFrame {
+ public:
+ FlipSynReplyControlFrame() : FlipControlFrame(size()) {}
+ FlipSynReplyControlFrame(char* data, bool owns_buffer)
+ : FlipControlFrame(data, owns_buffer) {}
+ virtual ~FlipSynReplyControlFrame() {}
+
+ int header_block_len() const {
+ return length() - (size() - FlipFrame::size());
+ }
+
+ const char* header_block() const {
+ return reinterpret_cast<const char*>(block()) + size();
+ }
+
+ // Returns the size of the FlipSynReplyControlFrameBlock structure.
+ // Note: this is not the size of the FlipSynReplyControlFrame class.
+ static size_t size() { return sizeof(FlipSynReplyControlFrameBlock); }
+
+ private:
+ struct FlipSynReplyControlFrameBlock* block() const {
+ return static_cast<FlipSynReplyControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(FlipSynReplyControlFrame);
+};
+
+// A FIN_STREAM frame.
+class FlipFinStreamControlFrame : public FlipControlFrame {
+ public:
+ FlipFinStreamControlFrame() : FlipControlFrame(size()) {}
+ FlipFinStreamControlFrame(char* data, bool owns_buffer)
+ : FlipControlFrame(data, owns_buffer) {}
+ virtual ~FlipFinStreamControlFrame() {}
+
+ uint32 status() const { return ntohl(block()->status_); }
+ void set_status(uint32 status) { block()->status_ = htonl(status); }
+
+ // Returns the size of the FlipFinStreamControlFrameBlock structure.
+ // Note: this is not the size of the FlipFinStreamControlFrame class.
+ static size_t size() { return sizeof(FlipFinStreamControlFrameBlock); }
+
+ private:
+ struct FlipFinStreamControlFrameBlock* block() const {
+ return static_cast<FlipFinStreamControlFrameBlock*>(frame_);
+ }
+ DISALLOW_COPY_AND_ASSIGN(FlipFinStreamControlFrame);
+};
+
+} // namespace flip
+
+#endif // NET_SPDY_SPDY_PROTOCOL_H_
diff --git a/net/spdy/spdy_protocol_test.cc b/net/spdy/spdy_protocol_test.cc
new file mode 100644
index 0000000..d6e51e3
--- /dev/null
+++ b/net/spdy/spdy_protocol_test.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2009 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/scoped_ptr.h"
+#include "net/spdy/spdy_bitmasks.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/platform_test.h"
+
+using flip::FlipDataFrame;
+using flip::FlipFrame;
+using flip::FlipControlFrame;
+using flip::FlipSynStreamControlFrame;
+using flip::FlipSynReplyControlFrame;
+using flip::FlipFinStreamControlFrame;
+using flip::FlipFramer;
+using flip::FlipHeaderBlock;
+using flip::FlagsAndLength;
+using flip::kLengthMask;
+using flip::kStreamIdMask;
+using flip::kFlipProtocolVersion;
+using flip::SYN_STREAM;
+using flip::SYN_REPLY;
+using flip::FIN_STREAM;
+using flip::CONTROL_FLAG_FIN;
+using flip::CONTROL_FLAG_NONE;
+
+namespace {
+
+// Test our protocol constants
+TEST(FlipProtocolTest, ProtocolConstants) {
+ EXPECT_EQ(8u, FlipFrame::size());
+ EXPECT_EQ(8u, FlipDataFrame::size());
+ EXPECT_EQ(12u, FlipControlFrame::size());
+ EXPECT_EQ(14u, FlipSynStreamControlFrame::size());
+ EXPECT_EQ(14u, FlipSynReplyControlFrame::size());
+ EXPECT_EQ(16u, FlipFinStreamControlFrame::size());
+ EXPECT_EQ(4u, sizeof(FlagsAndLength));
+ EXPECT_EQ(1, SYN_STREAM);
+ EXPECT_EQ(2, SYN_REPLY);
+ EXPECT_EQ(3, FIN_STREAM);
+}
+
+// Test some of the protocol helper functions
+TEST(FlipProtocolTest, FrameStructs) {
+ FlipFrame frame(FlipFrame::size());
+ frame.set_length(12345);
+ frame.set_flags(10);
+ EXPECT_EQ(12345u, frame.length());
+ EXPECT_EQ(10u, frame.flags());
+ EXPECT_EQ(false, frame.is_control_frame());
+
+ frame.set_length(0);
+ frame.set_flags(10);
+ EXPECT_EQ(0u, frame.length());
+ EXPECT_EQ(10u, frame.flags());
+ EXPECT_EQ(false, frame.is_control_frame());
+}
+
+TEST(FlipProtocolTest, DataFrameStructs) {
+ FlipDataFrame data_frame;
+ data_frame.set_stream_id(12345);
+ EXPECT_EQ(12345u, data_frame.stream_id());
+}
+
+TEST(FlipProtocolTest, ControlFrameStructs) {
+ FlipFramer framer;
+ FlipHeaderBlock headers;
+
+ scoped_ptr<FlipSynStreamControlFrame> syn_frame(
+ framer.CreateSynStream(123, 2, CONTROL_FLAG_FIN, false, &headers));
+ EXPECT_EQ(kFlipProtocolVersion, syn_frame->version());
+ EXPECT_EQ(true, syn_frame->is_control_frame());
+ EXPECT_EQ(SYN_STREAM, syn_frame->type());
+ EXPECT_EQ(123u, syn_frame->stream_id());
+ EXPECT_EQ(2u, syn_frame->priority());
+ EXPECT_EQ(2, syn_frame->header_block_len());
+ EXPECT_EQ(1u, syn_frame->flags());
+
+ scoped_ptr<FlipSynReplyControlFrame> syn_reply(
+ framer.CreateSynReply(123, CONTROL_FLAG_NONE, false, &headers));
+ EXPECT_EQ(kFlipProtocolVersion, syn_reply->version());
+ EXPECT_EQ(true, syn_reply->is_control_frame());
+ EXPECT_EQ(SYN_REPLY, syn_reply->type());
+ EXPECT_EQ(123u, syn_reply->stream_id());
+ EXPECT_EQ(2, syn_reply->header_block_len());
+ EXPECT_EQ(0, syn_reply->flags());
+
+ scoped_ptr<FlipFinStreamControlFrame> fin_frame(
+ framer.CreateFinStream(123, 444));
+ EXPECT_EQ(kFlipProtocolVersion, fin_frame->version());
+ EXPECT_EQ(true, fin_frame->is_control_frame());
+ EXPECT_EQ(FIN_STREAM, fin_frame->type());
+ EXPECT_EQ(123u, fin_frame->stream_id());
+ EXPECT_EQ(444u, fin_frame->status());
+ fin_frame->set_status(555);
+ EXPECT_EQ(555u, fin_frame->status());
+ EXPECT_EQ(0, fin_frame->flags());
+}
+
+TEST(FlipProtocolTest, TestDataFrame) {
+ FlipDataFrame 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', FlipDataFrame::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', FlipDataFrame::size());
+ uint32 length = frame.length();
+ frame.set_flags(0);
+ 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());
+}
+
+// Make sure that overflows both die in debug mode, and do not cause problems
+// in opt mode. Note: Chrome doesn't die on DCHECK failures, so the
+// EXPECT_DEBUG_DEATH doesn't work.
+TEST(FlipProtocolDeathTest, TestDataFrame) {
+ FlipDataFrame frame;
+
+ frame.set_stream_id(0);
+#ifndef WIN32
+ EXPECT_DEBUG_DEATH(frame.set_stream_id(~0), "");
+#endif
+ EXPECT_FALSE(frame.is_control_frame());
+
+ frame.set_flags(0);
+#ifndef WIN32
+ EXPECT_DEBUG_DEATH(frame.set_length(~0), "");
+#endif
+ EXPECT_EQ(0, frame.flags());
+}
+
+TEST(FlipProtocolDeathTest, TestFlipControlFrame) {
+ FlipControlFrame frame(FlipControlFrame::size());
+ memset(frame.data(), '1', FlipControlFrame::size());
+
+ // 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());
+}
+
+} // namespace
+
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
new file mode 100644
index 0000000..4a837c5
--- /dev/null
+++ b/net/spdy/spdy_session.cc
@@ -0,0 +1,1056 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/spdy/spdy_session.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/rand_util.h"
+#include "base/stats_counters.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/client_socket.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+namespace {
+
+// Diagnostics function to dump the headers of a request.
+// TODO(mbelshe): Remove this function.
+void DumpFlipHeaders(const flip::FlipHeaderBlock& headers) {
+ // Because this function gets called on every request,
+ // take extra care to optimize it away if logging is turned off.
+ if (logging::LOG_INFO < logging::GetMinLogLevel())
+ return;
+
+ flip::FlipHeaderBlock::const_iterator it = headers.begin();
+ while (it != headers.end()) {
+ std::string val = (*it).second;
+ std::string::size_type pos = 0;
+ while ((pos = val.find('\0', pos)) != val.npos)
+ val[pos] = '\n';
+ LOG(INFO) << (*it).first << "==" << val;
+ ++it;
+ }
+}
+
+} // namespace
+
+namespace net {
+
+namespace {
+
+#ifdef WIN32
+// We use an artificially small buffer size on windows because the async IO
+// system will artifiially delay IO completions when we use large buffers.
+const int kReadBufferSize = 2 * 1024;
+#else
+const int kReadBufferSize = 8 * 1024;
+#endif
+
+// Convert a FlipHeaderBlock into an HttpResponseInfo.
+// |headers| input parameter with the FlipHeaderBlock.
+// |info| output parameter for the HttpResponseInfo.
+// Returns true if successfully converted. False if there was a failure
+// or if the FlipHeaderBlock was invalid.
+bool FlipHeadersToHttpResponse(const flip::FlipHeaderBlock& headers,
+ HttpResponseInfo* response) {
+ std::string version;
+ std::string status;
+
+ // The "status" and "version" headers are required.
+ flip::FlipHeaderBlock::const_iterator it;
+ it = headers.find("status");
+ if (it == headers.end()) {
+ LOG(ERROR) << "FlipHeaderBlock without status header.";
+ return false;
+ }
+ status = it->second;
+
+ // Grab the version. If not provided by the server,
+ it = headers.find("version");
+ if (it == headers.end()) {
+ LOG(ERROR) << "FlipHeaderBlock without version header.";
+ return false;
+ }
+ version = it->second;
+
+ std::string raw_headers(version);
+ raw_headers.push_back(' ');
+ raw_headers.append(status);
+ raw_headers.push_back('\0');
+ for (it = headers.begin(); it != headers.end(); ++it) {
+ // For each value, if the server sends a NUL-separated
+ // list of values, we separate that back out into
+ // individual headers for each value in the list.
+ // e.g.
+ // Set-Cookie "foo\0bar"
+ // becomes
+ // Set-Cookie: foo\0
+ // Set-Cookie: bar\0
+ std::string value = it->second;
+ size_t start = 0;
+ size_t end = 0;
+ do {
+ end = value.find('\0', start);
+ std::string tval;
+ if (end != value.npos)
+ tval = value.substr(start, (end - start));
+ else
+ tval = value.substr(start);
+ 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;
+}
+
+// Create a FlipHeaderBlock for a Flip SYN_STREAM Frame from
+// a HttpRequestInfo block.
+void CreateFlipHeadersFromHttpRequest(
+ const HttpRequestInfo& info, flip::FlipHeaderBlock* headers) {
+ static const char kHttpProtocolVersion[] = "HTTP/1.1";
+
+ HttpUtil::HeadersIterator it(info.extra_headers.begin(),
+ info.extra_headers.end(),
+ "\r\n");
+ while (it.GetNext()) {
+ std::string name = StringToLowerASCII(it.name());
+ if (headers->find(name) == headers->end()) {
+ (*headers)[name] = it.values();
+ } else {
+ std::string new_value = (*headers)[name];
+ new_value += "\0";
+ new_value += it.values();
+ (*headers)[name] = new_value;
+ }
+ }
+
+ // TODO(mbelshe): Add Proxy headers here. (See http_network_transaction.cc)
+ // TODO(mbelshe): Add authentication headers here.
+
+ (*headers)["method"] = info.method;
+ (*headers)["url"] = info.url.spec();
+ (*headers)["version"] = kHttpProtocolVersion;
+ if (info.user_agent.length())
+ (*headers)["user-agent"] = info.user_agent;
+ if (!info.referrer.is_empty())
+ (*headers)["referer"] = info.referrer.spec();
+
+ // Honor load flags that impact proxy caches.
+ if (info.load_flags & LOAD_BYPASS_CACHE) {
+ (*headers)["pragma"] = "no-cache";
+ (*headers)["cache-control"] = "no-cache";
+ } else if (info.load_flags & LOAD_VALIDATE_CACHE) {
+ (*headers)["cache-control"] = "max-age=0";
+ }
+}
+
+void AdjustSocketBufferSizes(ClientSocket* socket) {
+ // Adjust socket buffer sizes.
+ // FLIP uses one socket, and we want a really big buffer.
+ // This greatly helps on links with packet loss - we can even
+ // outperform Vista's dynamic window sizing algorithm.
+ // TODO(mbelshe): more study.
+ const int kSocketBufferSize = 512 * 1024;
+ socket->SetReceiveBufferSize(kSocketBufferSize);
+ socket->SetSendBufferSize(kSocketBufferSize);
+}
+
+} // namespace
+
+// static
+bool FlipSession::use_ssl_ = true;
+
+FlipSession::FlipSession(const std::string& host, HttpNetworkSession* session)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ connect_callback_(this, &FlipSession::OnTCPConnect)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ ssl_connect_callback_(this, &FlipSession::OnSSLConnect)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ read_callback_(this, &FlipSession::OnReadComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ write_callback_(this, &FlipSession::OnWriteComplete)),
+ domain_(host),
+ session_(session),
+ 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),
+ error_(OK),
+ state_(IDLE),
+ streams_initiated_count_(0),
+ streams_pushed_count_(0),
+ streams_pushed_and_claimed_count_(0),
+ streams_abandoned_count_(0) {
+ // TODO(mbelshe): consider randomization of the stream_hi_water_mark.
+
+ flip_framer_.set_visitor(this);
+
+ session_->ssl_config_service()->GetSSLConfig(&ssl_config_);
+
+ // TODO(agl): This is a temporary hack for testing reasons. In the medium
+ // term we'll want to use NPN for all HTTPS connections and use the protocol
+ // suggested.
+ //
+ // In the event that the server supports Next Protocol Negotiation, but
+ // doesn't support either of these protocols, we'll request the first
+ // protocol in the list. Because of that, HTTP is listed first because it's
+ // what we'll actually fallback to in the case that the server doesn't
+ // support SPDY.
+ ssl_config_.next_protos = "\007http1.1\004spdy";
+}
+
+FlipSession::~FlipSession() {
+ // Cleanup all the streams.
+ CloseAllStreams(net::ERR_ABORTED);
+
+ if (connection_->is_initialized()) {
+ // With Flip we can't recycle sockets.
+ connection_->socket()->Disconnect();
+ }
+
+ // TODO(willchan): Don't hardcode port 80 here.
+ DCHECK(!session_->flip_session_pool()->HasSession(
+ HostResolver::RequestInfo(domain_, 80)));
+
+ // Record per-session histograms here.
+ 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);
+}
+
+void FlipSession::InitializeWithSocket(ClientSocketHandle* connection) {
+ static StatsCounter flip_sessions("flip.sessions");
+ flip_sessions.Increment();
+
+ AdjustSocketBufferSizes(connection->socket());
+
+ state_ = CONNECTED;
+ connection_.reset(connection);
+
+ // This is a newly initialized session that no client should have a handle to
+ // yet, so there's no need to start writing data as in OnTCPConnect(), but we
+ // should start reading data.
+ ReadSocket();
+}
+
+net::Error FlipSession::Connect(const std::string& group_name,
+ const HostResolver::RequestInfo& host,
+ RequestPriority priority,
+ LoadLog* load_log) {
+ DCHECK(priority >= FLIP_PRIORITY_HIGHEST && priority <= FLIP_PRIORITY_LOWEST);
+
+ // If the connect process is started, let the caller continue.
+ if (state_ > IDLE)
+ return net::OK;
+
+ state_ = CONNECTING;
+
+ static StatsCounter flip_sessions("flip.sessions");
+ flip_sessions.Increment();
+
+ int rv = connection_->Init(group_name, host, priority, &connect_callback_,
+ session_->tcp_socket_pool(), load_log);
+ DCHECK(rv <= 0);
+
+ // If the connect is pending, we still return ok. The APIs enqueue
+ // work until after the connect completes asynchronously later.
+ if (rv == net::ERR_IO_PENDING)
+ return net::OK;
+ return static_cast<net::Error>(rv);
+}
+
+scoped_refptr<FlipStream> FlipSession::GetOrCreateStream(
+ const HttpRequestInfo& request,
+ const UploadDataStream* upload_data,
+ LoadLog* log) {
+ const GURL& url = request.url;
+ const std::string& path = url.PathForRequest();
+
+ scoped_refptr<FlipStream> stream;
+
+ // Check if we have a push stream for this path.
+ if (request.method == "GET") {
+ stream = GetPushStream(path);
+ if (stream) {
+ DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_);
+ streams_pushed_and_claimed_count_++;
+ return stream;
+ }
+ }
+
+ // Check if we have a pending push stream for this url.
+ PendingStreamMap::iterator it;
+ it = pending_streams_.find(path);
+ if (it != pending_streams_.end()) {
+ DCHECK(!it->second);
+ // Server will assign a stream id when the push stream arrives. Use 0 for
+ // now.
+ LoadLog::AddEvent(log, LoadLog::TYPE_FLIP_STREAM_ADOPTED_PUSH_STREAM);
+ FlipStream* stream = new FlipStream(this, 0, true, log);
+ stream->set_path(path);
+ it->second = stream;
+ return it->second;
+ }
+
+ const flip::FlipStreamId stream_id = GetNewStreamId();
+
+ // If we still don't have a stream, activate one now.
+ stream = new FlipStream(this, stream_id, false, log);
+ stream->set_priority(request.priority);
+ stream->set_path(path);
+ ActivateStream(stream);
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount",
+ static_cast<int>(request.priority), 0, 10, 11);
+
+ LOG(INFO) << "FlipStream: Creating stream " << stream_id << " for " << url;
+
+ // TODO(mbelshe): Optimize memory allocations
+ DCHECK(request.priority >= FLIP_PRIORITY_HIGHEST &&
+ request.priority <= FLIP_PRIORITY_LOWEST);
+
+ // Convert from HttpRequestHeaders to Flip Headers.
+ flip::FlipHeaderBlock headers;
+ CreateFlipHeadersFromHttpRequest(request, &headers);
+
+ flip::FlipControlFlags flags = flip::CONTROL_FLAG_NONE;
+ if (!request.upload_data || !upload_data->size())
+ flags = flip::CONTROL_FLAG_FIN;
+
+ // Create a SYN_STREAM packet and add to the output queue.
+ scoped_ptr<flip::FlipSynStreamControlFrame> syn_frame(
+ flip_framer_.CreateSynStream(stream_id, request.priority, flags, false,
+ &headers));
+ int length = flip::FlipFrame::size() + syn_frame->length();
+ IOBuffer* buffer = new IOBuffer(length);
+ memcpy(buffer->data(), syn_frame->data(), length);
+ queue_.push(FlipIOBuffer(buffer, length, request.priority, stream));
+
+ static StatsCounter flip_requests("flip.requests");
+ flip_requests.Increment();
+
+ LOG(INFO) << "FETCHING: " << request.url.spec();
+ streams_initiated_count_++;
+
+ LOG(INFO) << "FLIP SYN_STREAM HEADERS ----------------------------------";
+ DumpFlipHeaders(headers);
+
+ // Schedule to write to the socket after we've made it back
+ // to the message loop so that we can aggregate multiple
+ // requests.
+ // TODO(mbelshe): Should we do the "first" request immediately?
+ // maybe we should only 'do later' for subsequent
+ // requests.
+ WriteSocketLater();
+
+ return stream;
+}
+
+int FlipSession::WriteStreamData(flip::FlipStreamId stream_id,
+ net::IOBuffer* data, int len) {
+ LOG(INFO) << "Writing Stream Data for stream " << stream_id << " (" << len
+ << " bytes)";
+ const int kMss = 1430; // 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 kMaxFlipFrameChunkSize = (2 * kMss) - flip::FlipFrame::size();
+
+ // Find our stream
+ DCHECK(IsStreamActive(stream_id));
+ scoped_refptr<FlipStream> stream = active_streams_[stream_id];
+ CHECK(stream->stream_id() == stream_id);
+ if (!stream)
+ return ERR_INVALID_FLIP_STREAM;
+
+ // TODO(mbelshe): Setting of the FIN is assuming that the caller will pass
+ // all data to write in a single chunk. Is this always true?
+
+ // Set the flags on the upload.
+ flip::FlipDataFlags flags = flip::DATA_FLAG_FIN;
+ if (len > kMaxFlipFrameChunkSize) {
+ len = kMaxFlipFrameChunkSize;
+ flags = flip::DATA_FLAG_NONE;
+ }
+
+ // TODO(mbelshe): reduce memory copies here.
+ scoped_ptr<flip::FlipDataFrame> frame(
+ flip_framer_.CreateDataFrame(stream_id, data->data(), len, flags));
+ int length = flip::FlipFrame::size() + frame->length();
+ IOBufferWithSize* buffer = new IOBufferWithSize(length);
+ memcpy(buffer->data(), frame->data(), length);
+ queue_.push(FlipIOBuffer(buffer, length, stream->priority(), stream));
+
+ // Whenever we queue onto the socket we need to ensure that we will write to
+ // it later.
+ WriteSocketLater();
+
+ return ERR_IO_PENDING;
+}
+
+bool FlipSession::CancelStream(flip::FlipStreamId stream_id) {
+ LOG(INFO) << "Cancelling stream " << stream_id;
+ if (!IsStreamActive(stream_id))
+ return false;
+
+ // TODO(mbelshe): We should send a FIN_STREAM control frame here
+ // so that the server can cancel a large send.
+
+ // TODO(mbelshe): Write a method for tearing down a stream
+ // that cleans it out of the active list, the pending list,
+ // etc.
+ scoped_refptr<FlipStream> stream = active_streams_[stream_id];
+ DeactivateStream(stream_id);
+ return true;
+}
+
+bool FlipSession::IsStreamActive(flip::FlipStreamId stream_id) const {
+ return ContainsKey(active_streams_, stream_id);
+}
+
+LoadState FlipSession::GetLoadState() const {
+ // NOTE: The application only queries the LoadState via the
+ // FlipNetworkTransaction, 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 FlipSession::OnTCPConnect(int result) {
+ LOG(INFO) << "Flip socket connected (result=" << result << ")";
+
+ // We shouldn't be coming through this path if we didn't just open a fresh
+ // socket (or have an error trying to do so).
+ DCHECK(!connection_->socket() || !connection_->is_reused());
+
+ UpdateConnectionTypeHistograms(CONNECTION_SPDY, result >= 0);
+
+ if (result != net::OK) {
+ DCHECK_LT(result, 0);
+ CloseSessionOnError(static_cast<net::Error>(result));
+ return;
+ }
+
+ AdjustSocketBufferSizes(connection_->socket());
+
+ if (use_ssl_) {
+ // Add a SSL socket on top of our existing transport socket.
+ ClientSocket* socket = connection_->release_socket();
+ // TODO(mbelshe): Fix the hostname. This is BROKEN without having
+ // a real hostname.
+ socket = session_->socket_factory()->CreateSSLClientSocket(
+ socket, "" /* request_->url.HostNoBrackets() */ , ssl_config_);
+ connection_->set_socket(socket);
+ is_secure_ = true;
+ // TODO(willchan): Plumb LoadLog into FLIP code.
+ int status = connection_->socket()->Connect(&ssl_connect_callback_, NULL);
+ if (status != ERR_IO_PENDING)
+ OnSSLConnect(status);
+ } else {
+ DCHECK_EQ(state_, CONNECTING);
+ state_ = CONNECTED;
+
+ // Make sure we get any pending data sent.
+ WriteSocketLater();
+ // Start reading
+ ReadSocket();
+ }
+}
+
+void FlipSession::OnSSLConnect(int result) {
+ // TODO(mbelshe): We need to replicate the functionality of
+ // HttpNetworkTransaction::DoSSLConnectComplete here, where it calls
+ // HandleCertificateError() and such.
+ if (IsCertificateError(result))
+ result = OK; // TODO(mbelshe): pretend we're happy anyway.
+
+ if (result == OK) {
+ DCHECK_EQ(state_, CONNECTING);
+ state_ = CONNECTED;
+
+ // After we've connected, send any data to the server, and then issue
+ // our read.
+ WriteSocketLater();
+ ReadSocket();
+ } else {
+ DCHECK_LT(result, 0); // It should be an error, not a byte count.
+ CloseSessionOnError(static_cast<net::Error>(result));
+ }
+}
+
+void FlipSession::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!
+
+ LOG(INFO) << "Flip socket read: " << bytes_read << " bytes";
+
+ read_pending_ = false;
+
+ if (bytes_read <= 0) {
+ // Session is tearing down.
+ net::Error error = static_cast<net::Error>(bytes_read);
+ if (error == OK)
+ error = ERR_CONNECTION_CLOSED;
+ CloseSessionOnError(error);
+ return;
+ }
+
+ // The FlipFramer 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<FlipSession> self(this);
+
+ char *data = read_buffer_->data();
+ while (bytes_read &&
+ flip_framer_.error_code() == flip::FlipFramer::FLIP_NO_ERROR) {
+ uint32 bytes_processed = flip_framer_.ProcessInput(data, bytes_read);
+ bytes_read -= bytes_processed;
+ data += bytes_processed;
+ if (flip_framer_.state() == flip::FlipFramer::FLIP_DONE)
+ flip_framer_.Reset();
+ }
+
+ if (state_ != CLOSED)
+ ReadSocket();
+}
+
+void FlipSession::OnWriteComplete(int result) {
+ DCHECK(write_pending_);
+ DCHECK(in_flight_write_.size());
+ DCHECK(result != 0); // This shouldn't happen for write.
+
+ write_pending_ = false;
+
+ LOG(INFO) << "Flip write complete (result=" << result << ") for stream: "
+ << in_flight_write_.stream()->stream_id();
+
+ 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()) {
+ scoped_refptr<FlipStream> stream = in_flight_write_.stream();
+ DCHECK(stream.get());
+
+ // 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_GT(result, static_cast<int>(flip::FlipFrame::size()));
+ result -= static_cast<int>(flip::FlipFrame::size());
+ }
+
+ // 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));
+ }
+}
+
+void FlipSession::ReadSocket() {
+ if (read_pending_)
+ return;
+
+ if (state_ == CLOSED) {
+ NOTREACHED();
+ return;
+ }
+
+ CHECK(connection_.get());
+ CHECK(connection_->socket());
+ int bytes_read = connection_->socket()->Read(read_buffer_.get(),
+ kReadBufferSize,
+ &read_callback_);
+ switch (bytes_read) {
+ case 0:
+ // Socket is closed!
+ // TODO(mbelshe): Need to abort any active streams here.
+ DCHECK(!active_streams_.size());
+ return;
+ case net::ERR_IO_PENDING:
+ // Waiting for data. Nothing to do now.
+ read_pending_ = true;
+ return;
+ 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, NewRunnableMethod(
+ this, &FlipSession::OnReadComplete, bytes_read));
+ break;
+ }
+}
+
+void FlipSession::WriteSocketLater() {
+ if (delayed_write_pending_)
+ return;
+
+ delayed_write_pending_ = true;
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &FlipSession::WriteSocket));
+}
+
+void FlipSession::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).
+ while (in_flight_write_.buffer() || queue_.size()) {
+ if (!in_flight_write_.buffer()) {
+ // Grab the next FlipFrame to send.
+ FlipIOBuffer next_buffer = queue_.top();
+ queue_.pop();
+
+ // We've deferred compression until just before we write it to the socket,
+ // which is now. At this time, we don't compress our data frames.
+ flip::FlipFrame uncompressed_frame(next_buffer.buffer()->data(), false);
+ size_t size;
+ if (uncompressed_frame.is_control_frame()) {
+ scoped_ptr<flip::FlipFrame> compressed_frame(
+ flip_framer_.CompressFrame(&uncompressed_frame));
+ size = compressed_frame->length() + flip::FlipFrame::size();
+
+ DCHECK(size > 0);
+
+ // TODO(mbelshe): We have too much copying of data here.
+ IOBufferWithSize* buffer = new IOBufferWithSize(size);
+ memcpy(buffer->data(), compressed_frame->data(), size);
+
+ // Attempt to send the frame.
+ in_flight_write_ = FlipIOBuffer(buffer, size, 0, next_buffer.stream());
+ } else {
+ size = uncompressed_frame.length() + flip::FlipFrame::size();
+ in_flight_write_ = next_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(), &write_callback_);
+ 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 FlipSession::CloseAllStreams(net::Error code) {
+ LOG(INFO) << "Closing all FLIP Streams";
+
+ static StatsCounter abandoned_streams("flip.abandoned_streams");
+ static StatsCounter abandoned_push_streams("flip.abandoned_push_streams");
+
+ if (active_streams_.size()) {
+ abandoned_streams.Add(active_streams_.size());
+
+ // Create a copy of the list, since aborting streams can invalidate
+ // our list.
+ FlipStream** list = new FlipStream*[active_streams_.size()];
+ ActiveStreamMap::const_iterator it;
+ int index = 0;
+ for (it = active_streams_.begin(); it != active_streams_.end(); ++it)
+ list[index++] = it->second;
+
+ // Issue the aborts.
+ for (--index; index >= 0; index--) {
+ LOG(ERROR) << "ABANDONED (stream_id=" << list[index]->stream_id()
+ << "): " << list[index]->path();
+ list[index]->OnClose(code);
+ }
+
+ // Clear out anything pending.
+ active_streams_.clear();
+
+ delete[] list;
+ }
+
+ if (pushed_streams_.size()) {
+ streams_abandoned_count_ += pushed_streams_.size();
+ abandoned_push_streams.Add(pushed_streams_.size());
+ pushed_streams_.clear();
+ }
+}
+
+int FlipSession::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 FlipSession::CloseSessionOnError(net::Error err) {
+ DCHECK_LT(err, OK);
+ LOG(INFO) << "Flip::CloseSessionOnError(" << err << ")";
+
+ // 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;
+ CloseAllStreams(err);
+ session_->flip_session_pool()->Remove(this);
+ }
+}
+
+void FlipSession::ActivateStream(FlipStream* stream) {
+ const flip::FlipStreamId id = stream->stream_id();
+ DCHECK(!IsStreamActive(id));
+
+ active_streams_[id] = stream;
+}
+
+void FlipSession::DeactivateStream(flip::FlipStreamId id) {
+ DCHECK(IsStreamActive(id));
+
+ // Verify it is not on the pushed_streams_ list.
+ ActiveStreamList::iterator it;
+ for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) {
+ scoped_refptr<FlipStream> curr = *it;
+ if (id == curr->stream_id()) {
+ pushed_streams_.erase(it);
+ break;
+ }
+ }
+
+ active_streams_.erase(id);
+}
+
+scoped_refptr<FlipStream> FlipSession::GetPushStream(const std::string& path) {
+ static StatsCounter used_push_streams("flip.claimed_push_streams");
+
+ LOG(INFO) << "Looking for push stream: " << path;
+
+ scoped_refptr<FlipStream> stream;
+
+ // We just walk a linear list here.
+ ActiveStreamList::iterator it;
+ for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) {
+ stream = *it;
+ if (path == stream->path()) {
+ CHECK(stream->pushed());
+ pushed_streams_.erase(it);
+ used_push_streams.Increment();
+ LOG(INFO) << "Push Stream Claim for: " << path;
+ break;
+ }
+ }
+
+ return stream;
+}
+
+void FlipSession::GetSSLInfo(SSLInfo* ssl_info) {
+ if (is_secure_) {
+ SSLClientSocket* ssl_socket =
+ reinterpret_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLInfo(ssl_info);
+ }
+}
+
+void FlipSession::OnError(flip::FlipFramer* framer) {
+ LOG(ERROR) << "FlipSession error: " << framer->error_code();
+ CloseSessionOnError(net::ERR_FLIP_PROTOCOL_ERROR);
+}
+
+void FlipSession::OnStreamFrameData(flip::FlipStreamId stream_id,
+ const char* data,
+ size_t len) {
+ LOG(INFO) << "Flip data for stream " << stream_id << ", " << len << " bytes";
+ bool valid_stream = IsStreamActive(stream_id);
+ if (!valid_stream) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received data frame for invalid stream " << stream_id;
+ return;
+ }
+
+ scoped_refptr<FlipStream> stream = active_streams_[stream_id];
+ bool success = stream->OnDataReceived(data, len);
+ // |len| == 0 implies a closed stream.
+ if (!success || !len)
+ DeactivateStream(stream_id);
+}
+
+void FlipSession::OnSyn(const flip::FlipSynStreamControlFrame* frame,
+ const flip::FlipHeaderBlock* headers) {
+ flip::FlipStreamId stream_id = frame->stream_id();
+
+ // Server-initiated streams should have even sequence numbers.
+ if ((stream_id & 0x1) != 0) {
+ LOG(ERROR) << "Received invalid OnSyn stream id " << stream_id;
+ return;
+ }
+
+ if (IsStreamActive(stream_id)) {
+ LOG(ERROR) << "Received OnSyn for active stream " << stream_id;
+ return;
+ }
+
+ streams_pushed_count_++;
+
+ LOG(INFO) << "FlipSession: Syn received for stream: " << stream_id;
+
+ LOG(INFO) << "FLIP SYN RESPONSE HEADERS -----------------------";
+ DumpFlipHeaders(*headers);
+
+ // TODO(mbelshe): DCHECK that this is a GET method?
+
+ const std::string& path = ContainsKey(*headers, "path") ?
+ headers->find("path")->second : "";
+
+ // Verify that the response had a URL for us.
+ DCHECK(!path.empty());
+ if (path.empty()) {
+ LOG(WARNING) << "Pushed stream did not contain a path.";
+ return;
+ }
+
+ scoped_refptr<FlipStream> stream;
+
+ // Check if we already have a delegate awaiting this stream.
+ PendingStreamMap::iterator it;
+ it = pending_streams_.find(path);
+ if (it != pending_streams_.end()) {
+ stream = it->second;
+ pending_streams_.erase(it);
+ }
+
+ if (stream) {
+ CHECK(stream->pushed());
+ CHECK(stream->stream_id() == 0);
+ stream->set_stream_id(stream_id);
+ } else {
+ // TODO(mbelshe): can we figure out how to use a LoadLog here?
+ stream = new FlipStream(this, stream_id, true, NULL);
+
+ // A new HttpResponseInfo object needs to be generated so the call to
+ // OnResponseReceived below has something to fill in.
+ // When a FlipNetworkTransaction is created for this resource, the
+ // response_info is copied over and this version is destroyed.
+ //
+ // TODO(cbentzel): Minimize allocations and copies of HttpResponseInfo
+ // object. Should it just be part of FlipStream?
+ HttpResponseInfo* response_info = new HttpResponseInfo();
+ stream->set_response_info_pointer(response_info);
+ }
+
+ pushed_streams_.push_back(stream);
+
+ // Activate a stream and parse the headers.
+ ActivateStream(stream);
+
+ stream->set_path(path);
+
+ // TODO(mbelshe): For now we convert from our nice hash map back
+ // to a string of headers; this is because the HttpResponseInfo
+ // is a bit rigid for its http (non-flip) design.
+ HttpResponseInfo response;
+ if (FlipHeadersToHttpResponse(*headers, &response)) {
+ GetSSLInfo(&response.ssl_info);
+ stream->OnResponseReceived(response);
+ } else {
+ stream->OnClose(ERR_INVALID_RESPONSE);
+ DeactivateStream(stream_id);
+ return;
+ }
+
+ LOG(INFO) << "Got pushed stream for " << stream->path();
+
+ static StatsCounter push_requests("flip.pushed_streams");
+ push_requests.Increment();
+}
+
+void FlipSession::OnSynReply(const flip::FlipSynReplyControlFrame* frame,
+ const flip::FlipHeaderBlock* headers) {
+ DCHECK(headers);
+ flip::FlipStreamId stream_id = frame->stream_id();
+ bool valid_stream = IsStreamActive(stream_id);
+ if (!valid_stream) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received SYN_REPLY for invalid stream " << stream_id;
+ return;
+ }
+
+ LOG(INFO) << "FLIP SYN_REPLY RESPONSE HEADERS for stream: " << stream_id;
+ DumpFlipHeaders(*headers);
+
+ // We record content declared as being pushed so that we don't
+ // request a duplicate stream which is already scheduled to be
+ // sent to us.
+ flip::FlipHeaderBlock::const_iterator it;
+ it = headers->find("X-Associated-Content");
+ if (it != headers->end()) {
+ const std::string& content = it->second;
+ std::string::size_type start = 0;
+ std::string::size_type end = 0;
+ do {
+ end = content.find("||", start);
+ if (end == std::string::npos)
+ end = content.length();
+ std::string url = content.substr(start, end - start);
+ std::string::size_type pos = url.find("??");
+ if (pos == std::string::npos)
+ break;
+ url = url.substr(pos + 2);
+ GURL gurl(url);
+ std::string path = gurl.PathForRequest();
+ if (path.length())
+ pending_streams_[path] = NULL;
+ else
+ LOG(INFO) << "Invalid X-Associated-Content path: " << url;
+ start = end + 2;
+ } while (start < content.length());
+ }
+
+ scoped_refptr<FlipStream> stream = active_streams_[stream_id];
+ CHECK(stream->stream_id() == stream_id);
+ CHECK(!stream->cancelled());
+ HttpResponseInfo response;
+ if (FlipHeadersToHttpResponse(*headers, &response)) {
+ GetSSLInfo(&response.ssl_info);
+ stream->OnResponseReceived(response);
+ } else {
+ stream->OnClose(ERR_INVALID_RESPONSE);
+ DeactivateStream(stream_id);
+ }
+}
+
+void FlipSession::OnControl(const flip::FlipControlFrame* frame) {
+ flip::FlipHeaderBlock headers;
+ uint32 type = frame->type();
+ if (type == flip::SYN_STREAM || type == flip::SYN_REPLY) {
+ if (!flip_framer_.ParseHeaderBlock(frame, &headers)) {
+ LOG(WARNING) << "Could not parse Flip Control Frame Header";
+ // TODO(mbelshe): Error the session?
+ return;
+ }
+ }
+
+ switch (type) {
+ case flip::SYN_STREAM:
+ LOG(INFO) << "Flip SynStream for stream " << frame->stream_id();
+ OnSyn(reinterpret_cast<const flip::FlipSynStreamControlFrame*>(frame),
+ &headers);
+ break;
+ case flip::SYN_REPLY:
+ LOG(INFO) << "Flip SynReply for stream " << frame->stream_id();
+ OnSynReply(
+ reinterpret_cast<const flip::FlipSynReplyControlFrame*>(frame),
+ &headers);
+ break;
+ case flip::FIN_STREAM:
+ LOG(INFO) << "Flip Fin for stream " << frame->stream_id();
+ OnFin(reinterpret_cast<const flip::FlipFinStreamControlFrame*>(frame));
+ break;
+ default:
+ DCHECK(false); // Error!
+ }
+}
+
+void FlipSession::OnFin(const flip::FlipFinStreamControlFrame* frame) {
+ flip::FlipStreamId stream_id = frame->stream_id();
+ bool valid_stream = IsStreamActive(stream_id);
+ if (!valid_stream) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received FIN for invalid stream" << stream_id;
+ return;
+ }
+ scoped_refptr<FlipStream> stream = active_streams_[stream_id];
+ CHECK(stream->stream_id() == stream_id);
+ CHECK(!stream->cancelled());
+ if (frame->status() == 0) {
+ stream->OnDataReceived(NULL, 0);
+ } else {
+ LOG(ERROR) << "Flip stream closed: " << frame->status();
+ // TODO(mbelshe): Map from Flip-protocol errors to something sensical.
+ // For now, it doesn't matter much - it is a protocol error.
+ stream->OnClose(ERR_FAILED);
+ }
+
+ DeactivateStream(stream_id);
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
new file mode 100644
index 0000000..e91753d
--- /dev/null
+++ b/net/spdy/spdy_session.h
@@ -0,0 +1,237 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_SPDY_SPDY_SESSION_H_
+#define NET_SPDY_SPDY_SESSION_H_
+
+#include <deque>
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/ref_counted.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_config_service.h"
+#include "net/base/upload_data_stream.h"
+#include "net/socket/client_socket.h"
+#include "net/socket/client_socket_handle.h"
+#include "testing/platform_test.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+
+namespace net {
+
+class FlipStream;
+class HttpNetworkSession;
+class HttpRequestInfo;
+class HttpResponseInfo;
+class LoadLog;
+class SSLInfo;
+
+class FlipSession : public base::RefCounted<FlipSession>,
+ public flip::FlipFramerVisitorInterface {
+ public:
+ // Get the domain for this FlipSession.
+ const std::string& domain() const { return domain_; }
+
+ // Connect the FLIP Socket.
+ // Returns net::Error::OK on success.
+ // Note that this call does not wait for the connect to complete. Callers can
+ // immediately start using the FlipSession while it connects.
+ net::Error Connect(const std::string& group_name,
+ const HostResolver::RequestInfo& host,
+ RequestPriority priority,
+ LoadLog* load_log);
+
+ // Get a stream for a given |request|. In the typical case, this will involve
+ // the creation of a new stream (and will send the SYN frame). If the server
+ // initiates a stream, it might already exist for a given path. The server
+ // might also not have initiated the stream yet, but indicated it will via
+ // X-Associated-Content.
+ // Returns the new or existing stream. Never returns NULL.
+ scoped_refptr<FlipStream> GetOrCreateStream(const HttpRequestInfo& request,
+ const UploadDataStream* upload_data, LoadLog* log);
+
+ // Write a data frame to the stream.
+ // Used to create and queue a data frame for the given stream.
+ int WriteStreamData(flip::FlipStreamId stream_id, net::IOBuffer* data,
+ int len);
+
+ // Cancel a stream.
+ bool CancelStream(flip::FlipStreamId stream_id);
+
+ // Check if a stream is active.
+ bool IsStreamActive(flip::FlipStreamId 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;
+
+ // Enable or disable SSL.
+ static void SetSSLMode(bool enable) { use_ssl_ = enable; }
+ static bool SSLMode() { return use_ssl_; }
+
+ protected:
+ friend class FlipSessionPool;
+
+ enum State {
+ IDLE,
+ CONNECTING,
+ CONNECTED,
+ CLOSED
+ };
+
+ // Provide access to the framer for testing.
+ flip::FlipFramer* GetFramer() { return &flip_framer_; }
+
+ // Create a new FlipSession.
+ // |host| is the hostname that this session connects to.
+ FlipSession(const std::string& host, HttpNetworkSession* session);
+
+ // Closes all open streams. Used as part of shutdown.
+ void CloseAllStreams(net::Error code);
+
+ private:
+ friend class base::RefCounted<FlipSession>;
+
+ typedef std::map<int, scoped_refptr<FlipStream> > ActiveStreamMap;
+ typedef std::list<scoped_refptr<FlipStream> > ActiveStreamList;
+ typedef std::map<std::string, scoped_refptr<FlipStream> > PendingStreamMap;
+ typedef std::priority_queue<FlipIOBuffer> OutputQueue;
+
+ virtual ~FlipSession();
+
+ // Used by FlipSessionPool to initialize with a pre-existing socket.
+ void InitializeWithSocket(ClientSocketHandle* connection);
+
+ // FlipFramerVisitorInterface
+ virtual void OnError(flip::FlipFramer*);
+ virtual void OnStreamFrameData(flip::FlipStreamId stream_id,
+ const char* data,
+ size_t len);
+ virtual void OnControl(const flip::FlipControlFrame* frame);
+
+ // Control frame handlers.
+ void OnSyn(const flip::FlipSynStreamControlFrame* frame,
+ const flip::FlipHeaderBlock* headers);
+ void OnSynReply(const flip::FlipSynReplyControlFrame* frame,
+ const flip::FlipHeaderBlock* headers);
+ void OnFin(const flip::FlipFinStreamControlFrame* frame);
+
+ // IO Callbacks
+ void OnTCPConnect(int result);
+ void OnSSLConnect(int result);
+ void OnReadComplete(int result);
+ void OnWriteComplete(int result);
+
+ // Start reading from the socket.
+ void ReadSocket();
+
+ // Write current data to the socket.
+ void WriteSocketLater();
+ void WriteSocket();
+
+ // Get a new stream id.
+ int GetNewStreamId();
+
+ // 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.
+ void CloseSessionOnError(net::Error err);
+
+ // Track active streams in the active stream list.
+ void ActivateStream(FlipStream* stream);
+ void DeactivateStream(flip::FlipStreamId id);
+
+ // 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<FlipStream> GetPushStream(const std::string& url);
+
+ void GetSSLInfo(SSLInfo* ssl_info);
+
+ // Callbacks for the Flip session.
+ CompletionCallbackImpl<FlipSession> connect_callback_;
+ CompletionCallbackImpl<FlipSession> ssl_connect_callback_;
+ CompletionCallbackImpl<FlipSession> read_callback_;
+ CompletionCallbackImpl<FlipSession> write_callback_;
+
+ // The domain this session is connected to.
+ std::string domain_;
+
+ SSLConfig ssl_config_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ // 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.
+
+ // TODO(mbelshe): We need to track these stream lists better.
+ // I suspect it is possible to remove a stream from
+ // one list, but not the other.
+
+ // Map from stream id to all active streams. Streams are active in the sense
+ // that they have a consumer (typically FlipNetworkTransaction 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_;
+ // List of all the streams that have already started to be pushed by the
+ // server, but do not have consumers yet.
+ ActiveStreamList pushed_streams_;
+ // List of streams declared in X-Associated-Content headers, but do not have
+ // consumers yet.
+ // The key is a string representing the path of the URI being pushed.
+ PendingStreamMap pending_streams_;
+
+ // As we gather data to be sent, we put it into the output queue.
+ OutputQueue queue_;
+
+ // The packet we are currently sending.
+ bool write_pending_; // Will be true when a write is in progress.
+ FlipIOBuffer 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 FlipSession.
+ bool is_secure_;
+
+ // Flip Frame state.
+ flip::FlipFramer flip_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_;
+
+ // Some statistics counters for the session.
+ int streams_initiated_count_;
+ int streams_pushed_count_;
+ int streams_pushed_and_claimed_count_;
+ int streams_abandoned_count_;
+
+ static bool use_ssl_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_H_
diff --git a/net/spdy/spdy_session_pool.cc b/net/spdy/spdy_session_pool.cc
new file mode 100644
index 0000000..13d9942
--- /dev/null
+++ b/net/spdy/spdy_session_pool.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/spdy/spdy_session_pool.h"
+
+#include "base/logging.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+// The maximum number of sessions to open to a single domain.
+static const size_t kMaxSessionsPerDomain = 1;
+
+FlipSessionPool::FlipSessionPool() {}
+FlipSessionPool::~FlipSessionPool() {
+ CloseAllSessions();
+}
+
+scoped_refptr<FlipSession> FlipSessionPool::Get(
+ const HostResolver::RequestInfo& info, HttpNetworkSession* session) {
+ const std::string& domain = info.hostname();
+ scoped_refptr<FlipSession> flip_session;
+ FlipSessionList* list = GetSessionList(domain);
+ if (list) {
+ if (list->size() >= kMaxSessionsPerDomain) {
+ flip_session = list->front();
+ list->pop_front();
+ }
+ } else {
+ list = AddSessionList(domain);
+ }
+
+ DCHECK(list);
+ if (!flip_session)
+ flip_session = new FlipSession(domain, session);
+
+ DCHECK(flip_session);
+ list->push_back(flip_session);
+ DCHECK(list->size() <= kMaxSessionsPerDomain);
+ return flip_session;
+}
+
+scoped_refptr<FlipSession> FlipSessionPool::GetFlipSessionFromSocket(
+ const HostResolver::RequestInfo& info,
+ HttpNetworkSession* session,
+ ClientSocketHandle* connection) {
+ const std::string& domain = info.hostname();
+ FlipSessionList* list = GetSessionList(domain);
+ if (!list)
+ list = AddSessionList(domain);
+ DCHECK(list->empty());
+ scoped_refptr<FlipSession> flip_session(new FlipSession(domain, session));
+ flip_session->InitializeWithSocket(connection);
+ list->push_back(flip_session);
+ return flip_session;
+}
+
+bool FlipSessionPool::HasSession(const HostResolver::RequestInfo& info) const {
+ const std::string& domain = info.hostname();
+ if (GetSessionList(domain))
+ return true;
+ return false;
+}
+
+void FlipSessionPool::Remove(const scoped_refptr<FlipSession>& session) {
+ std::string domain = session->domain();
+ FlipSessionList* list = GetSessionList(domain);
+ CHECK(list);
+ list->remove(session);
+ if (list->empty())
+ RemoveSessionList(domain);
+}
+
+FlipSessionPool::FlipSessionList*
+ FlipSessionPool::AddSessionList(const std::string& domain) {
+ DCHECK(sessions_.find(domain) == sessions_.end());
+ return sessions_[domain] = new FlipSessionList();
+}
+
+FlipSessionPool::FlipSessionList*
+ FlipSessionPool::GetSessionList(const std::string& domain) {
+ FlipSessionsMap::iterator it = sessions_.find(domain);
+ if (it == sessions_.end())
+ return NULL;
+ return it->second;
+}
+
+const FlipSessionPool::FlipSessionList*
+ FlipSessionPool::GetSessionList(const std::string& domain) const {
+ FlipSessionsMap::const_iterator it = sessions_.find(domain);
+ if (it == sessions_.end())
+ return NULL;
+ return it->second;
+}
+
+void FlipSessionPool::RemoveSessionList(const std::string& domain) {
+ FlipSessionList* list = GetSessionList(domain);
+ if (list) {
+ delete list;
+ sessions_.erase(domain);
+ } else {
+ DCHECK(false) << "removing orphaned session list";
+ }
+}
+
+void FlipSessionPool::CloseAllSessions() {
+ while (sessions_.size()) {
+ FlipSessionList* list = sessions_.begin()->second;
+ DCHECK(list);
+ sessions_.erase(sessions_.begin()->first);
+ while (list->size()) {
+ scoped_refptr<FlipSession> session = list->front();
+ list->pop_front();
+ session->CloseAllStreams(net::ERR_ABORTED);
+ }
+ delete list;
+ }
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_session_pool.h b/net/spdy/spdy_session_pool.h
new file mode 100644
index 0000000..98d78dd
--- /dev/null
+++ b/net/spdy/spdy_session_pool.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2009 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/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "net/base/host_resolver.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class FlipSession;
+class HttpNetworkSession;
+
+// This is a very simple pool for open FlipSessions.
+// TODO(mbelshe): Make this production ready.
+class FlipSessionPool : public base::RefCounted<FlipSessionPool> {
+ public:
+ FlipSessionPool();
+
+ // Either returns an existing FlipSession or creates a new FlipSession for
+ // use.
+ scoped_refptr<FlipSession> Get(
+ const HostResolver::RequestInfo& info, HttpNetworkSession* session);
+
+ // Builds a FlipSession from an existing socket. Users should try calling
+ // Get() first to use an existing FlipSession so we don't get multiple
+ // FlipSessions per domain. Note that ownership of |connection| is
+ // transferred from the caller to the FlipSession.
+ scoped_refptr<FlipSession> GetFlipSessionFromSocket(
+ const HostResolver::RequestInfo& info,
+ HttpNetworkSession* session,
+ ClientSocketHandle* connection);
+
+ // TODO(willchan): Consider renaming to HasReusableSession, since perhaps we
+ // should be creating a new session.
+ bool HasSession(const HostResolver::RequestInfo& info) const;
+
+ // Close all Flip Sessions; used for debugging.
+ void CloseAllSessions();
+
+ private:
+ friend class base::RefCounted<FlipSessionPool>;
+ friend class FlipSession; // Needed for Remove().
+ friend class FlipSessionPoolPeer; // For testing.
+
+ typedef std::list<scoped_refptr<FlipSession> > FlipSessionList;
+ typedef std::map<std::string, FlipSessionList*> FlipSessionsMap;
+
+ virtual ~FlipSessionPool();
+
+ // Removes a FlipSession from the FlipSessionPool.
+ void Remove(const scoped_refptr<FlipSession>& session);
+
+ // Helper functions for manipulating the lists.
+ FlipSessionList* AddSessionList(const std::string& domain);
+ FlipSessionList* GetSessionList(const std::string& domain);
+ const FlipSessionList* GetSessionList(const std::string& domain) const;
+ void RemoveSessionList(const std::string& domain);
+
+ // This is our weak session pool - one session per domain.
+ FlipSessionsMap sessions_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlipSessionPool);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_POOL_H_
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
new file mode 100644
index 0000000..57d6c2d
--- /dev/null
+++ b/net/spdy/spdy_session_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2009 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/base/test_completion_callback.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class FlipSessionTest : public PlatformTest {
+ public:
+};
+
+// Test the FlipIOBuffer class.
+TEST_F(FlipSessionTest, FlipIOBuffer) {
+ std::priority_queue<FlipIOBuffer> queue_;
+ const size_t kQueueSize = 100;
+
+ // Insert 100 items; pri 100 to 1.
+ for (size_t index = 0; index < kQueueSize; ++index) {
+ FlipIOBuffer buffer(new IOBuffer(), 0, kQueueSize - index, NULL);
+ queue_.push(buffer);
+ }
+
+ // Insert several priority 0 items last.
+ const size_t kNumDuplicates = 12;
+ IOBufferWithSize* buffers[kNumDuplicates];
+ for (size_t index = 0; index < kNumDuplicates; ++index) {
+ buffers[index] = new IOBufferWithSize(index+1);
+ queue_.push(FlipIOBuffer(buffers[index], buffers[index]->size(), 0, NULL));
+ }
+
+ EXPECT_EQ(kQueueSize + kNumDuplicates, queue_.size());
+
+ // Verify the P0 items come out in FIFO order.
+ for (size_t index = 0; index < kNumDuplicates; ++index) {
+ FlipIOBuffer buffer = queue_.top();
+ EXPECT_EQ(0, buffer.priority());
+ EXPECT_EQ(index + 1, buffer.size());
+ queue_.pop();
+ }
+
+ int priority = 1;
+ while (queue_.size()) {
+ FlipIOBuffer buffer = queue_.top();
+ EXPECT_EQ(priority++, buffer.priority());
+ queue_.pop();
+ }
+}
+
+} // namespace net
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
new file mode 100644
index 0000000..78d263d
--- /dev/null
+++ b/net/spdy/spdy_stream.cc
@@ -0,0 +1,456 @@
+// Copyright (c) 2010 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/logging.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+FlipStream::FlipStream(FlipSession* session, flip::FlipStreamId stream_id,
+ bool pushed, LoadLog* log)
+ : stream_id_(stream_id),
+ priority_(0),
+ pushed_(pushed),
+ download_finished_(false),
+ metrics_(Singleton<BandwidthMetrics>::get()),
+ session_(session),
+ response_(NULL),
+ request_body_stream_(NULL),
+ response_complete_(false),
+ io_state_(STATE_NONE),
+ response_status_(OK),
+ user_callback_(NULL),
+ user_buffer_(NULL),
+ user_buffer_len_(0),
+ cancelled_(false),
+ load_log_(log),
+ send_bytes_(0),
+ recv_bytes_(0),
+ histograms_recorded_(false) {}
+
+FlipStream::~FlipStream() {
+ DLOG(INFO) << "Deleting FlipStream for stream " << stream_id_;
+
+ // TODO(willchan): We're still calling CancelStream() too many times, because
+ // inactive pending/pushed streams will still have stream_id_ set.
+ if (stream_id_) {
+ session_->CancelStream(stream_id_);
+ } else if (!response_complete_) {
+ NOTREACHED();
+ }
+}
+
+uint64 FlipStream::GetUploadProgress() const {
+ if (!request_body_stream_.get())
+ return 0;
+
+ return request_body_stream_->position();
+}
+
+const HttpResponseInfo* FlipStream::GetResponseInfo() const {
+ return response_;
+}
+
+int FlipStream::ReadResponseHeaders(CompletionCallback* callback) {
+ // Note: The FlipStream may have already received the response headers, so
+ // this call may complete synchronously.
+ CHECK(callback);
+ CHECK(io_state_ == STATE_NONE);
+ CHECK(!cancelled_);
+
+ // The SYN_REPLY has already been received.
+ if (response_->headers)
+ return OK;
+
+ io_state_ = STATE_READ_HEADERS;
+ CHECK(!user_callback_);
+ user_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int FlipStream::ReadResponseBody(
+ IOBuffer* buf, int buf_len, CompletionCallback* callback) {
+ DCHECK_EQ(io_state_, STATE_NONE);
+ CHECK(buf);
+ CHECK(buf_len);
+ CHECK(callback);
+ CHECK(!cancelled_);
+
+ // If we have data buffered, complete the IO immediately.
+ if (response_body_.size()) {
+ int bytes_read = 0;
+ while (response_body_.size() && 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(new_buffer);
+ }
+ bytes_read += bytes_to_copy;
+ }
+ if (bytes_read > 0)
+ recv_bytes_ += bytes_read;
+ return bytes_read;
+ } else if (response_complete_) {
+ return response_status_;
+ }
+
+ CHECK(!user_callback_);
+ CHECK(!user_buffer_);
+ CHECK(user_buffer_len_ == 0);
+
+ user_callback_ = callback;
+ user_buffer_ = buf;
+ user_buffer_len_ = buf_len;
+ return ERR_IO_PENDING;
+}
+
+int FlipStream::SendRequest(UploadDataStream* upload_data,
+ HttpResponseInfo* response,
+ CompletionCallback* callback) {
+ CHECK(callback);
+ CHECK(!cancelled_);
+ CHECK(response);
+
+ if (response_) {
+ *response = *response_;
+ delete response_;
+ }
+ response_ = response;
+
+ if (upload_data) {
+ if (upload_data->size())
+ request_body_stream_.reset(upload_data);
+ else
+ delete upload_data;
+ }
+
+ send_time_ = base::TimeTicks::Now();
+
+ DCHECK_EQ(io_state_, STATE_NONE);
+ if (!pushed_)
+ io_state_ = STATE_SEND_HEADERS;
+ else {
+ if (response_->headers) {
+ io_state_ = STATE_READ_BODY;
+ } else {
+ io_state_ = STATE_READ_HEADERS;
+ }
+ }
+ int result = DoLoop(OK);
+ if (result == ERR_IO_PENDING) {
+ CHECK(!user_callback_);
+ user_callback_ = callback;
+ }
+ return result;
+}
+
+void FlipStream::Cancel() {
+ cancelled_ = true;
+ user_callback_ = NULL;
+
+ session_->CancelStream(stream_id_);
+}
+
+void FlipStream::OnResponseReceived(const HttpResponseInfo& response) {
+ metrics_.StartStream();
+
+ CHECK(!response_->headers);
+
+ *response_ = response; // TODO(mbelshe): avoid copy.
+ DCHECK(response_->headers);
+
+ recv_first_byte_time_ = base::TimeTicks::Now();
+
+ if (io_state_ == STATE_NONE) {
+ CHECK(pushed_);
+ io_state_ = STATE_READ_HEADERS;
+ } else if (io_state_ == STATE_READ_HEADERS_COMPLETE) {
+ // This FlipStream could be in this state in both true and false pushed_
+ // conditions.
+ // The false pushed_ condition (client request) will always go through
+ // this state.
+ // The true pushed_condition (server push) can be in this state when the
+ // client requests an X-Associated-Content piece of content prior
+ // to when the server push happens.
+ } else {
+ NOTREACHED();
+ }
+
+ int rv = DoLoop(OK);
+
+ if (user_callback_)
+ DoCallback(rv);
+}
+
+bool FlipStream::OnDataReceived(const char* data, int length) {
+ DCHECK_GE(length, 0);
+ LOG(INFO) << "FlipStream: Data (" << length << " bytes) received for "
+ << stream_id_;
+
+ // 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_->headers) {
+ OnClose(ERR_SYN_REPLY_NOT_RECEIVED);
+ return false;
+ }
+
+ if (length > 0)
+ recv_bytes_ += length;
+ recv_last_byte_time_ = base::TimeTicks::Now();
+
+ // A zero-length read means that the stream is being closed.
+ if (!length) {
+ metrics_.StopStream();
+ download_finished_ = true;
+ OnClose(net::OK);
+ return true;
+ }
+
+ // Track our bandwidth.
+ metrics_.RecordBytes(length);
+
+ if (length > 0) {
+ // TODO(mbelshe): If read is pending, we should copy the data straight into
+ // the read buffer here. For now, we'll queue it always.
+ // TODO(mbelshe): We need to have some throttling on this. We shouldn't
+ // buffer an infinite amount of data.
+
+ IOBufferWithSize* io_buffer = new IOBufferWithSize(length);
+ memcpy(io_buffer->data(), data, length);
+
+ response_body_.push_back(io_buffer);
+ }
+
+ // Note that data may be received for a FlipStream prior to the user calling
+ // ReadResponseBody(), therefore user_callback_ may be NULL. This may often
+ // happen for server initiated streams.
+ if (user_callback_) {
+ int rv;
+ if (user_buffer_) {
+ rv = ReadResponseBody(user_buffer_, user_buffer_len_, user_callback_);
+ CHECK(rv != ERR_IO_PENDING);
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+ } else {
+ rv = OK;
+ }
+ DoCallback(rv);
+ }
+
+ return true;
+}
+
+void FlipStream::OnWriteComplete(int status) {
+ // TODO(mbelshe): Check for cancellation here. If we're cancelled, we
+ // should discontinue the DoLoop.
+
+ if (status > 0)
+ send_bytes_ += status;
+
+ DoLoop(status);
+}
+
+void FlipStream::OnClose(int status) {
+ response_complete_ = true;
+ response_status_ = status;
+ stream_id_ = 0;
+
+ if (user_callback_)
+ DoCallback(status);
+
+ UpdateHistograms();
+}
+
+int FlipStream::DoLoop(int result) {
+ do {
+ State state = io_state_;
+ io_state_ = STATE_NONE;
+ switch (state) {
+ // State machine 1: Send headers and wait for response headers.
+ case STATE_SEND_HEADERS:
+ CHECK(result == OK);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_SEND_HEADERS);
+ result = DoSendHeaders();
+ break;
+ case STATE_SEND_HEADERS_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_SEND_HEADERS);
+ result = DoSendHeadersComplete(result);
+ break;
+ case STATE_SEND_BODY:
+ CHECK(result == OK);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_SEND_BODY);
+ result = DoSendBody();
+ break;
+ case STATE_SEND_BODY_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_SEND_BODY);
+ result = DoSendBodyComplete(result);
+ break;
+ case STATE_READ_HEADERS:
+ CHECK(result == OK);
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_READ_HEADERS);
+ result = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_READ_HEADERS);
+ result = DoReadHeadersComplete(result);
+ break;
+
+ // State machine 2: Read body.
+ // NOTE(willchan): Currently unused. Currently we handle this stuff in
+ // the OnDataReceived()/OnClose()/ReadResponseHeaders()/etc. Only reason
+ // to do this is for consistency with the Http code.
+ case STATE_READ_BODY:
+ LoadLog::BeginEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_READ_BODY);
+ result = DoReadBody();
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ LoadLog::EndEvent(load_log_,
+ LoadLog::TYPE_FLIP_STREAM_READ_BODY);
+ result = DoReadBodyComplete(result);
+ break;
+ case STATE_DONE:
+ DCHECK(result != ERR_IO_PENDING);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (result != ERR_IO_PENDING && io_state_ != STATE_NONE);
+
+ return result;
+}
+
+void FlipStream::DoCallback(int rv) {
+ CHECK(rv != ERR_IO_PENDING);
+ CHECK(user_callback_);
+
+ // Since Run may result in being called back, clear user_callback_ in advance.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(rv);
+}
+
+int FlipStream::DoSendHeaders() {
+ // The FlipSession will always call us back when the send is complete.
+ // TODO(willchan): This code makes the assumption that for the non-push stream
+ // case, the client code calls SendRequest() after creating the stream and
+ // before yielding back to the MessageLoop. This is true in the current code,
+ // but is not obvious from the headers. We should make the code handle
+ // SendRequest() being called after the SYN_REPLY has been received.
+ io_state_ = STATE_SEND_HEADERS_COMPLETE;
+ return ERR_IO_PENDING;
+}
+
+int FlipStream::DoSendHeadersComplete(int result) {
+ if (result < 0)
+ return result;
+
+ CHECK(result > 0);
+
+ // There is no body, skip that state.
+ if (!request_body_stream_.get()) {
+ io_state_ = STATE_READ_HEADERS;
+ 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 FlipStream::DoSendBody() {
+ // If we're already in the STATE_SENDING_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;
+ int buf_len = static_cast<int>(request_body_stream_->buf_len());
+ return session_->WriteStreamData(stream_id_,
+ request_body_stream_->buf(),
+ buf_len);
+}
+
+int FlipStream::DoSendBodyComplete(int result) {
+ if (result < 0)
+ return result;
+
+ CHECK(result != 0);
+
+ request_body_stream_->DidConsume(result);
+
+ if (!request_body_stream_->eof())
+ io_state_ = STATE_SEND_BODY;
+ else
+ io_state_ = STATE_READ_HEADERS;
+
+ return OK;
+}
+
+int FlipStream::DoReadHeaders() {
+ io_state_ = STATE_READ_HEADERS_COMPLETE;
+ return response_->headers ? OK : ERR_IO_PENDING;
+}
+
+int FlipStream::DoReadHeadersComplete(int result) {
+ return result;
+}
+
+int FlipStream::DoReadBody() {
+ // TODO(mbelshe): merge FlipStreamParser with FlipStream and then this
+ // makes sense.
+ return ERR_IO_PENDING;
+}
+
+int FlipStream::DoReadBodyComplete(int result) {
+ // TODO(mbelshe): merge FlipStreamParser with FlipStream and then this
+ // makes sense.
+ return ERR_IO_PENDING;
+}
+
+void FlipStream::UpdateHistograms() {
+ if (histograms_recorded_)
+ return;
+
+ histograms_recorded_ = true;
+
+ // 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/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
new file mode 100644
index 0000000..ab2659e
--- /dev/null
+++ b/net/spdy/spdy_stream.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2010 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 <string>
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "net/base/bandwidth_metrics.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_log.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+class FlipSession;
+class HttpRequestInfo;
+class HttpResponseInfo;
+class UploadData;
+class UploadDataStream;
+
+// The FlipStream is used by the FlipSession to represent each stream known
+// on the FlipSession.
+// Streams can be created either by the client or by the server. When they
+// are initiated by the client, both the FlipSession and client object (such as
+// a FlipNetworkTransaction) will maintain a reference to the stream. When
+// initiated by the server, only the FlipSession will maintain any reference,
+// until such a time as a client object requests a stream for the path.
+class FlipStream : public base::RefCounted<FlipStream> {
+ public:
+ // FlipStream constructor
+ FlipStream(FlipSession* session, flip::FlipStreamId stream_id, bool pushed,
+ LoadLog* log);
+
+ // Ideally I'd use two abstract classes as interfaces for these two sections,
+ // but since we're ref counted, I can't make both abstract classes inherit
+ // from RefCounted or we'll have two separate ref counts for the same object.
+ // TODO(willchan): Consider using linked_ptr here orcreating proxy wrappers
+ // for FlipStream to provide the appropriate interface.
+
+ // ===================================================
+ // Interface for [Http|Flip]NetworkTransaction to use.
+
+ // Sends the request. If |upload_data| is non-NULL, sends that in the request
+ // body. |callback| is used when this completes asynchronously. Note that
+ // the actual SYN_STREAM packet will have already been sent by this point.
+ // Also note that FlipStream takes ownership of |upload_data|.
+ int SendRequest(UploadDataStream* upload_data,
+ HttpResponseInfo* response,
+ CompletionCallback* callback);
+
+ // Reads the response headers. Returns a net error code.
+ int ReadResponseHeaders(CompletionCallback* callback);
+
+ // Reads the response body. Returns a net error code or the number of bytes
+ // read.
+ int ReadResponseBody(
+ IOBuffer* buf, int buf_len, CompletionCallback* callback);
+
+ // Cancels the stream. Note that this does not immediately cause deletion of
+ // the stream. This function is used to cancel any callbacks from being
+ // invoked. TODO(willchan): It should also free up any memory associated with
+ // the stream, such as IOBuffers.
+ void Cancel();
+
+ // Returns the number of bytes uploaded.
+ uint64 GetUploadProgress() const;
+
+ const HttpResponseInfo* GetResponseInfo() const;
+
+ // Is this stream a pushed stream from the server.
+ bool pushed() const { return pushed_; }
+
+ // =================================
+ // Interface for FlipSession to use.
+
+ flip::FlipStreamId stream_id() const { return stream_id_; }
+ void set_stream_id(flip::FlipStreamId stream_id) { stream_id_ = stream_id; }
+
+ // 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; }
+
+ int priority() const { return priority_; }
+ void set_priority(int priority) { priority_ = priority; }
+
+ // Called by the FlipSession when a response (e.g. a SYN_REPLY) has been
+ // received for this stream. |path| is the path of the URL for a server
+ // initiated stream, otherwise is empty.
+ void OnResponseReceived(const HttpResponseInfo& response);
+
+ // Called by the FlipSession 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.
+ // Returns true on success and false on error.
+ bool OnDataReceived(const char* buffer, int bytes);
+
+ // Called by the FlipSession 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 status);
+
+ // Called by the FlipSession 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);
+
+ bool cancelled() const { return cancelled_; }
+
+ void set_response_info_pointer(HttpResponseInfo* response_info) {
+ response_ = response_info;
+ }
+
+ private:
+ friend class base::RefCounted<FlipStream>;
+
+ enum State {
+ STATE_NONE,
+ STATE_SEND_HEADERS,
+ STATE_SEND_HEADERS_COMPLETE,
+ STATE_SEND_BODY,
+ STATE_SEND_BODY_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_DONE
+ };
+
+ ~FlipStream();
+
+ // Try to make progress sending/receiving the request/response.
+ int DoLoop(int result);
+
+ // Call the user callback.
+ void DoCallback(int rv);
+
+ // The implementations of each state of the state machine.
+ int DoSendHeaders();
+ int DoSendHeadersComplete(int result);
+ int DoSendBody();
+ int DoSendBodyComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoReadBody();
+ int DoReadBodyComplete(int result);
+
+ // Update the histograms. Can safely be called repeatedly, but should only
+ // be called after the stream has completed.
+ void UpdateHistograms();
+
+ flip::FlipStreamId stream_id_;
+ std::string path_;
+ int priority_;
+ const bool pushed_;
+ // 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_;
+ bool download_finished_;
+ ScopedBandwidthMetrics metrics_;
+
+ scoped_refptr<FlipSession> session_;
+
+ HttpResponseInfo* response_;
+ scoped_ptr<UploadDataStream> request_body_stream_;
+
+ bool response_complete_; // TODO(mbelshe): fold this into the io_state.
+ State io_state_;
+
+ // Since we buffer the response, we also buffer the response status.
+ // Not valid until response_complete_ is true.
+ int response_status_;
+
+ CompletionCallback* user_callback_;
+
+ // User provided buffer for the ReadResponseBody() response.
+ scoped_refptr<IOBuffer> user_buffer_;
+ int user_buffer_len_;
+
+ bool cancelled_;
+
+ scoped_refptr<LoadLog> load_log_;
+
+ base::TimeTicks send_time_;
+ base::TimeTicks recv_first_byte_time_;
+ base::TimeTicks recv_last_byte_time_;
+ int send_bytes_;
+ int recv_bytes_;
+ bool histograms_recorded_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlipStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_H_
diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_unittest.cc
new file mode 100644
index 0000000..f019b5f
--- /dev/null
+++ b/net/spdy/spdy_stream_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 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/ref_counted.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/ssl_config_service.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class FlipSessionPoolPeer {
+ public:
+ explicit FlipSessionPoolPeer(const scoped_refptr<FlipSessionPool>& pool)
+ : pool_(pool) {}
+
+ void RemoveFlipSession(const scoped_refptr<FlipSession>& session) {
+ pool_->Remove(session);
+ }
+
+ private:
+ const scoped_refptr<FlipSessionPool> pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlipSessionPoolPeer);
+};
+
+namespace {
+
+// Create a proxy service which fails on all requests (falls back to direct).
+ProxyService* CreateNullProxyService() {
+ return ProxyService::CreateNull();
+}
+
+// Helper to manage the lifetimes of the dependencies for a
+// FlipNetworkTransaction.
+class SessionDependencies {
+ public:
+ // Default set of dependencies -- "null" proxy service.
+ SessionDependencies()
+ : host_resolver(new MockHostResolver),
+ proxy_service(CreateNullProxyService()),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ flip_session_pool(new FlipSessionPool) {}
+
+ // Custom proxy service dependency.
+ explicit SessionDependencies(ProxyService* proxy_service)
+ : host_resolver(new MockHostResolver),
+ proxy_service(proxy_service),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ flip_session_pool(new FlipSessionPool) {}
+
+ scoped_refptr<MockHostResolverBase> host_resolver;
+ scoped_refptr<ProxyService> proxy_service;
+ scoped_refptr<SSLConfigService> ssl_config_service;
+ MockClientSocketFactory socket_factory;
+ scoped_refptr<FlipSessionPool> flip_session_pool;
+};
+
+HttpNetworkSession* CreateSession(SessionDependencies* session_deps) {
+ return new HttpNetworkSession(NULL,
+ session_deps->host_resolver,
+ session_deps->proxy_service,
+ &session_deps->socket_factory,
+ session_deps->ssl_config_service,
+ session_deps->flip_session_pool);
+}
+
+class FlipStreamTest : public testing::Test {
+ protected:
+ FlipStreamTest()
+ : session_(CreateSession(&session_deps_)),
+ pool_peer_(session_->flip_session_pool()) {}
+
+ scoped_refptr<FlipSession> CreateFlipSession() {
+ HostResolver::RequestInfo resolve_info("www.google.com", 80);
+ scoped_refptr<FlipSession> session(
+ session_->flip_session_pool()->Get(resolve_info, session_));
+ return session;
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunAllPending();
+ }
+
+ SessionDependencies session_deps_;
+ scoped_refptr<HttpNetworkSession> session_;
+ FlipSessionPoolPeer pool_peer_;
+};
+
+// Needs fixing, see http://crbug.com/28622
+TEST_F(FlipStreamTest, SendRequest) {
+ scoped_refptr<FlipSession> session(CreateFlipSession());
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+
+ scoped_refptr<FlipStream> stream(new FlipStream(session, 1, false, NULL));
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(NULL, &response, &callback));
+
+ // Need to manually remove the flip session since normally it gets removed on
+ // socket close/error, but we aren't communicating over a socket here.
+ pool_peer_.RemoveFlipSession(session);
+}
+
+// TODO(willchan): Write a longer test for FlipStream that exercises all
+// methods.
+
+} // namespace
+
+} // namespace net
diff --git a/net/spdy/spdy_transaction_factory.h b/net/spdy/spdy_transaction_factory.h
new file mode 100644
index 0000000..46571bf
--- /dev/null
+++ b/net/spdy/spdy_transaction_factory.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2009 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_TRANSACTION_FACTORY_H__
+#define NET_SPDY_SPDY_TRANSACTION_FACTORY_H__
+
+#include "net/http/http_transaction_factory.h"
+#include "net/spdy/spdy_network_transaction.h"
+
+namespace net {
+
+class FlipTransactionFactory : public HttpTransactionFactory {
+ public:
+ explicit FlipTransactionFactory(HttpNetworkSession* session)
+ : session_(session) {
+ }
+ virtual ~FlipTransactionFactory() {}
+
+ // HttpTransactionFactory Interface.
+ virtual HttpTransaction* CreateTransaction() {
+ return new FlipNetworkTransaction(session_);
+ }
+ virtual HttpCache* GetCache() {
+ return NULL;
+ }
+ virtual void Suspend(bool suspend) {
+ }
+
+ private:
+ scoped_refptr<HttpNetworkSession> session_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_TRANSACTION_FACTORY_H__