From dab9c7d4a7c5d4c5157043ee4db2d792b8a2f3b6 Mon Sep 17 00:00:00 2001 From: "mbelshe@chromium.org" Date: Sat, 6 Feb 2010 21:44:32 +0000 Subject: 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 --- net/flip/flip_bitmasks.h | 24 - net/flip/flip_frame_builder.cc | 181 ----- net/flip/flip_frame_builder.h | 163 ---- net/flip/flip_framer.cc | 772 ------------------ net/flip/flip_framer.h | 260 ------ net/flip/flip_framer_test.cc | 407 ---------- net/flip/flip_io_buffer.cc | 29 - net/flip/flip_io_buffer.h | 55 -- net/flip/flip_network_transaction.cc | 297 ------- net/flip/flip_network_transaction.h | 120 --- net/flip/flip_network_transaction_unittest.cc | 1084 ------------------------- net/flip/flip_protocol.h | 387 --------- net/flip/flip_protocol_test.cc | 178 ---- net/flip/flip_session.cc | 1056 ------------------------ net/flip/flip_session.h | 237 ------ net/flip/flip_session_pool.cc | 121 --- net/flip/flip_session_pool.h | 76 -- net/flip/flip_session_unittest.cc | 56 -- net/flip/flip_stream.cc | 456 ----------- net/flip/flip_stream.h | 211 ----- net/flip/flip_stream_unittest.cc | 123 --- net/flip/flip_transaction_factory.h | 36 - net/http/http_cache.cc | 2 +- net/http/http_network_layer.cc | 8 +- net/http/http_network_session.cc | 2 +- net/http/http_network_transaction.cc | 6 +- net/http/http_network_transaction_unittest.cc | 2 +- net/net.gyp | 44 +- net/spdy/spdy_bitmasks.h | 24 + net/spdy/spdy_frame_builder.cc | 181 +++++ net/spdy/spdy_frame_builder.h | 163 ++++ net/spdy/spdy_framer.cc | 772 ++++++++++++++++++ net/spdy/spdy_framer.h | 260 ++++++ net/spdy/spdy_framer_test.cc | 407 ++++++++++ net/spdy/spdy_io_buffer.cc | 29 + net/spdy/spdy_io_buffer.h | 55 ++ net/spdy/spdy_network_transaction.cc | 297 +++++++ net/spdy/spdy_network_transaction.h | 120 +++ net/spdy/spdy_network_transaction_unittest.cc | 1084 +++++++++++++++++++++++++ net/spdy/spdy_protocol.h | 387 +++++++++ net/spdy/spdy_protocol_test.cc | 178 ++++ net/spdy/spdy_session.cc | 1056 ++++++++++++++++++++++++ net/spdy/spdy_session.h | 237 ++++++ net/spdy/spdy_session_pool.cc | 121 +++ net/spdy/spdy_session_pool.h | 76 ++ net/spdy/spdy_session_unittest.cc | 56 ++ net/spdy/spdy_stream.cc | 456 +++++++++++ net/spdy/spdy_stream.h | 211 +++++ net/spdy/spdy_stream_unittest.cc | 123 +++ net/spdy/spdy_transaction_factory.h | 36 + 50 files changed, 6361 insertions(+), 6361 deletions(-) delete mode 100644 net/flip/flip_bitmasks.h delete mode 100644 net/flip/flip_frame_builder.cc delete mode 100644 net/flip/flip_frame_builder.h delete mode 100644 net/flip/flip_framer.cc delete mode 100644 net/flip/flip_framer.h delete mode 100644 net/flip/flip_framer_test.cc delete mode 100644 net/flip/flip_io_buffer.cc delete mode 100644 net/flip/flip_io_buffer.h delete mode 100644 net/flip/flip_network_transaction.cc delete mode 100644 net/flip/flip_network_transaction.h delete mode 100644 net/flip/flip_network_transaction_unittest.cc delete mode 100644 net/flip/flip_protocol.h delete mode 100644 net/flip/flip_protocol_test.cc delete mode 100644 net/flip/flip_session.cc delete mode 100644 net/flip/flip_session.h delete mode 100644 net/flip/flip_session_pool.cc delete mode 100644 net/flip/flip_session_pool.h delete mode 100644 net/flip/flip_session_unittest.cc delete mode 100644 net/flip/flip_stream.cc delete mode 100644 net/flip/flip_stream.h delete mode 100644 net/flip/flip_stream_unittest.cc delete mode 100644 net/flip/flip_transaction_factory.h create mode 100644 net/spdy/spdy_bitmasks.h create mode 100644 net/spdy/spdy_frame_builder.cc create mode 100644 net/spdy/spdy_frame_builder.h create mode 100644 net/spdy/spdy_framer.cc create mode 100644 net/spdy/spdy_framer.h create mode 100644 net/spdy/spdy_framer_test.cc create mode 100644 net/spdy/spdy_io_buffer.cc create mode 100644 net/spdy/spdy_io_buffer.h create mode 100644 net/spdy/spdy_network_transaction.cc create mode 100644 net/spdy/spdy_network_transaction.h create mode 100644 net/spdy/spdy_network_transaction_unittest.cc create mode 100644 net/spdy/spdy_protocol.h create mode 100644 net/spdy/spdy_protocol_test.cc create mode 100644 net/spdy/spdy_session.cc create mode 100644 net/spdy/spdy_session.h create mode 100644 net/spdy/spdy_session_pool.cc create mode 100644 net/spdy/spdy_session_pool.h create mode 100644 net/spdy/spdy_session_unittest.cc create mode 100644 net/spdy/spdy_stream.cc create mode 100644 net/spdy/spdy_stream.h create mode 100644 net/spdy/spdy_stream_unittest.cc create mode 100644 net/spdy/spdy_transaction_factory.h diff --git a/net/flip/flip_bitmasks.h b/net/flip/flip_bitmasks.h deleted file mode 100644 index f5ed3c2..0000000 --- a/net/flip/flip_bitmasks.h +++ /dev/null @@ -1,24 +0,0 @@ -// 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_FLIP_BITMASKS_H_ -#define NET_FLIP_FLIP_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_FLIP_FLIP_BITMASKS_H_ diff --git a/net/flip/flip_frame_builder.cc b/net/flip/flip_frame_builder.cc deleted file mode 100644 index 61851b5..0000000 --- a/net/flip/flip_frame_builder.cc +++ /dev/null @@ -1,181 +0,0 @@ -// 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 - -#include "flip_frame_builder.h" // cross-google3 directory naming. -#include "flip_protocol.h" - -namespace flip { - -// We mark a read only FlipFrameBuilder with a special capacity_. -static const size_t kCapacityReadOnly = std::numeric_limits::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(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(buffer_); - - if (!IteratorHasRoomFor(*iter, sizeof(*result))) - return false; - - *result = ntohs(*(reinterpret_cast(*iter))); - - UpdateIter(iter, sizeof(*result)); - return true; -} - -bool FlipFrameBuilder::ReadUInt32(void** iter, uint32* result) const { - DCHECK(iter); - if (!*iter) - *iter = const_cast(buffer_); - - if (!IteratorHasRoomFor(*iter, sizeof(*result))) - return false; - - *result = ntohl(*(reinterpret_cast(*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(*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(*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::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(value.size()))) - return false; - - return WriteBytes(value.data(), static_cast(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/flip/flip_frame_builder.h b/net/flip/flip_frame_builder.h deleted file mode 100644 index 32b6035..0000000 --- a/net/flip/flip_frame_builder.h +++ /dev/null @@ -1,163 +0,0 @@ -// 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 // for htonl() functions -#else -#include -#endif - -#include - -#include "base/logging.h" -#include "flip_protocol.h" // cross-google3 directory naming. - -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(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(*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/flip/flip_framer.cc b/net/flip/flip_framer.cc deleted file mode 100644 index f200ac6..0000000 --- a/net/flip/flip_framer.cc +++ /dev/null @@ -1,772 +0,0 @@ -// 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 "flip_framer.h" // cross-google3 directory naming. -#include "flip_frame_builder.h" -#include "flip_bitmasks.h" - -#if defined(USE_SYSTEM_ZLIB) -#include -#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(¤t_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 decompressed(new char[decompressed_max_size]); - decompressor_->next_in = reinterpret_cast( - const_cast(data)); - decompressor_->avail_in = amount_to_forward; - decompressor_->next_out = - reinterpret_cast(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 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(kLengthMask)); - FlagsAndLength flags_length; - flags_length.length_ = htonl(static_cast(length)); - flags_length.flags_[0] = flags; - frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length)); - - scoped_ptr syn_frame(frame.take()); - if (compressed) { - return reinterpret_cast( - CompressFrame(syn_frame.get())); - } - return reinterpret_cast(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(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(kLengthMask)); - FlagsAndLength flags_length; - flags_length.length_ = htonl(static_cast(length)); - flags_length.flags_[0] = flags; - frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length)); - - scoped_ptr reply_frame(frame.take()); - if (compressed) { - return reinterpret_cast( - CompressFrame(reply_frame.get())); - } - return reinterpret_cast(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(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 data_frame(frame.take()); - if (flags & DATA_FLAG_COMPRESSED) - return reinterpret_cast(CompressFrame(data_frame.get())); - return reinterpret_cast(data_frame.release()); -} - -/* static */ -FlipControlFrame* FlipFramer::CreateNopFrame() { - FlipFrameBuilder frame; - frame.WriteUInt16(kControlFlagMask | kFlipProtocolVersion); - frame.WriteUInt16(NOOP); - frame.WriteUInt32(0); - return reinterpret_cast(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(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(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(frame); - switch (control_frame->type()) { - case SYN_STREAM: - case SYN_REPLY: - { - const FlipSynStreamControlFrame *syn_frame = - reinterpret_cast(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(*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(const_cast(payload)); - compressor_->avail_in = payload_length; - compressor_->next_out = reinterpret_cast(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(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(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(const_cast(payload)); - decompressor_->avail_in = payload_length; - decompressor_->next_out = reinterpret_cast(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(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/flip/flip_framer.h b/net/flip/flip_framer.h deleted file mode 100644 index b333176..0000000 --- a/net/flip/flip_framer.h +++ /dev/null @@ -1,260 +0,0 @@ -// 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_FLIP_FRAMER_H_ -#define NET_FLIP_FLIP_FRAMER_H_ - -#ifdef _WIN32 -#include -#else -#include -#endif -#include -#include - -#include "base/basictypes.h" -#include "base/logging.h" -#include "base/scoped_ptr.h" -#include "testing/gtest/include/gtest/gtest_prod.h" -#include "flip_protocol.h" // cross-google3 directory naming. - -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 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. - 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 compressor_; - scoped_ptr decompressor_; - FlipFramerVisitorInterface* visitor_; - - static bool compression_default_; -}; - -} // namespace flip - -#endif // NET_FLIP_FLIP_FRAMER_H_ - diff --git a/net/flip/flip_framer_test.cc b/net/flip/flip_framer_test.cc deleted file mode 100644 index c78854d..0000000 --- a/net/flip/flip_framer_test.cc +++ /dev/null @@ -1,407 +0,0 @@ -// 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 -#include - -#include "base/scoped_ptr.h" -#include "flip_framer.h" // cross-google3 directory naming. -#include "flip_protocol.h" -#include "flip_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(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 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 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 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 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 - frame1(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, true, &headers)); - scoped_ptr - 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 frame3(framer.DecompressFrame(frame1.get())); - - // Decompress the second frame - scoped_ptr 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 - frame1(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, false, &headers)); - - // Decompress the frame - scoped_ptr 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/flip/flip_io_buffer.cc b/net/flip/flip_io_buffer.cc deleted file mode 100644 index 87d8d21..0000000 --- a/net/flip/flip_io_buffer.cc +++ /dev/null @@ -1,29 +0,0 @@ -// 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/flip/flip_io_buffer.h" -#include "net/flip/flip_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/flip/flip_io_buffer.h b/net/flip/flip_io_buffer.h deleted file mode 100644 index c16c39c..0000000 --- a/net/flip/flip_io_buffer.h +++ /dev/null @@ -1,55 +0,0 @@ -// 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_FLIP_IO_BUFFER_H_ -#define NET_FLIP_FLIP_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& 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 buffer_; - int priority_; - uint64 position_; - scoped_refptr stream_; - static uint64 order_; // Maintains a FIFO order for equal priorities. -}; - -} // namespace net - -#endif // NET_FLIP_FLIP_IO_BUFFER_H_ diff --git a/net/flip/flip_network_transaction.cc b/net/flip/flip_network_transaction.cc deleted file mode 100644 index 440e666..0000000 --- a/net/flip/flip_network_transaction.cc +++ /dev/null @@ -1,297 +0,0 @@ -// 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/flip/flip_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/flip/flip_stream.h" -#include "net/http/http_network_session.h" -#include "net/http/http_request_info.h" -#include "net/http/http_response_info.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/flip/flip_network_transaction.h b/net/flip/flip_network_transaction.h deleted file mode 100644 index 26084c3..0000000 --- a/net/flip/flip_network_transaction.h +++ /dev/null @@ -1,120 +0,0 @@ -// 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 -#include - -#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/flip/flip_session.h" -#include "net/http/http_response_info.h" -#include "net/http/http_transaction.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 load_log_; - - scoped_refptr flip_; - - CompletionCallbackImpl io_callback_; - CompletionCallback* user_callback_; - - // Used to pass onto the FlipStream - scoped_refptr user_buffer_; - int user_buffer_len_; - - scoped_refptr 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 stream_; - - DISALLOW_COPY_AND_ASSIGN(FlipNetworkTransaction); -}; - -} // namespace net - -#endif // NET_HTTP_NETWORK_TRANSACTION_H_ diff --git a/net/flip/flip_network_transaction_unittest.cc b/net/flip/flip_network_transaction_unittest.cc deleted file mode 100644 index 3dc1b90..0000000 --- a/net/flip/flip_network_transaction_unittest.cc +++ /dev/null @@ -1,1084 +0,0 @@ -// 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/flip/flip_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/flip/flip_protocol.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 "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 host_resolver; - scoped_refptr proxy_service; - scoped_refptr ssl_config_service; - MockClientSocketFactory socket_factory; - scoped_refptr 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 { - 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 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 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 session = - CreateSession(&session_deps); - scoped_ptr trans(new FlipNetworkTransaction(session)); -} - -TEST_F(FlipNetworkTransactionTest, Get) { - MockWrite writes[] = { - MockWrite(true, reinterpret_cast(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kGetSynReply), - arraysize(kGetSynReply)), - MockRead(true, reinterpret_cast(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 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(kPostSyn), - arraysize(kPostSyn)), - MockWrite(true, reinterpret_cast(kPostUploadFrame), - arraysize(kPostUploadFrame)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kPostSynReply), - arraysize(kPostSynReply)), - MockRead(true, reinterpret_cast(kPostBodyFrame), - arraysize(kPostBodyFrame)), - MockRead(true, 0, 0) // EOF - }; - - scoped_refptr 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(kEmptyPostSyn), - arraysize(kEmptyPostSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kPostSynReply), - arraysize(kPostSynReply)), - MockRead(true, reinterpret_cast(kPostBodyFrame), - arraysize(kGetBodyFrame)), - MockRead(true, 0, 0) // EOF - }; - - scoped_refptr 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(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 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(kGetSyn), - arraysize(kGetSyn)), - MockRead(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(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 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(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(test_cases[i].syn_reply), - test_cases[i].syn_reply_length), - MockRead(true, reinterpret_cast(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 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 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(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(test_cases[i].syn_reply), - test_cases[i].syn_reply_length), - MockRead(true, reinterpret_cast(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 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(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(test_cases[i].syn_reply), - test_cases[i].syn_reply_length), - MockRead(true, reinterpret_cast(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 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(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(syn_reply), - arraysize(syn_reply)), - MockRead(true, reinterpret_cast(kGetBodyFrame), - arraysize(kGetBodyFrame)), - MockRead(true, ERR_IO_PENDING), // Force a pause - MockRead(true, reinterpret_cast(syn_push), - arraysize(syn_push)), - MockRead(true, reinterpret_cast(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 session(CreateSession(&session_deps)); - scoped_refptr 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(kGetSyn), 10), - // Followed by ERROR! - MockWrite(true, ERR_FAILED), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kGetSynReply), - arraysize(kGetSynReply)), - MockRead(true, reinterpret_cast(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 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 writes(ChopFrame( - reinterpret_cast(kGetSyn), arraysize(kGetSyn), kChunks)); - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kGetSynReply), - arraysize(kGetSynReply)), - MockRead(true, reinterpret_cast(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 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(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kGetSynReply), - arraysize(kGetSynReply)), - MockRead(true, reinterpret_cast(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 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(kGetSynCompressed), - arraysize(kGetSynCompressed)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kGetSynReply), - arraysize(kGetSynReply)), - MockRead(true, reinterpret_cast(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 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(kGetSyn), - arraysize(kGetSyn)), - MockWrite(true, 0, 0) // EOF - }; - - MockRead reads[] = { - MockRead(true, reinterpret_cast(kGetSynReply), - arraysize(kGetSynReply)), - MockRead(true, reinterpret_cast(kGetBodyFrame), - arraysize(kGetBodyFrame)), - MockRead(true, 0, 0) // EOF - }; - - scoped_refptr 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 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/flip/flip_protocol.h b/net/flip/flip_protocol.h deleted file mode 100644 index fb82ba6..0000000 --- a/net/flip/flip_protocol.h +++ /dev/null @@ -1,387 +0,0 @@ -// 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_FLIP_FLIP_PROTOCOL_H_ -#define NET_FLIP_FLIP_PROTOCOL_H_ - -#ifdef WIN32 -#include -#else -#include -#endif - -#include "base/basictypes.h" -#include "base/logging.h" -#include "flip_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(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(data)), - owns_buffer_(owns_buffer) { - DCHECK(frame_); - } - - virtual ~FlipFrame() { - if (owns_buffer_) { - char* buffer = reinterpret_cast(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(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(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(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(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(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(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(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(frame_); - } - DISALLOW_COPY_AND_ASSIGN(FlipFinStreamControlFrame); -}; - -} // namespace flip - -#endif // NET_FLIP_FLIP_PROTOCOL_H_ diff --git a/net/flip/flip_protocol_test.cc b/net/flip/flip_protocol_test.cc deleted file mode 100644 index 9a29149..0000000 --- a/net/flip/flip_protocol_test.cc +++ /dev/null @@ -1,178 +0,0 @@ -// 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 "flip_protocol.h" - -#include "base/scoped_ptr.h" -#include "net/flip/flip_bitmasks.h" -#include "net/flip/flip_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 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 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 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/flip/flip_session.cc b/net/flip/flip_session.cc deleted file mode 100644 index 47528fa..0000000 --- a/net/flip/flip_session.cc +++ /dev/null @@ -1,1056 +0,0 @@ -// 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/flip/flip_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/flip/flip_frame_builder.h" -#include "net/flip/flip_protocol.h" -#include "net/flip/flip_stream.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/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(rv); -} - -scoped_refptr FlipSession::GetOrCreateStream( - const HttpRequestInfo& request, - const UploadDataStream* upload_data, - LoadLog* log) { - const GURL& url = request.url; - const std::string& path = url.PathForRequest(); - - scoped_refptr 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(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 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 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 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 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(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(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(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 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 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(flip::FlipFrame::size())); - result -= static_cast(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(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 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 curr = *it; - if (id == curr->stream_id()) { - pushed_streams_.erase(it); - break; - } - } - - active_streams_.erase(id); -} - -scoped_refptr FlipSession::GetPushStream(const std::string& path) { - static StatsCounter used_push_streams("flip.claimed_push_streams"); - - LOG(INFO) << "Looking for push stream: " << path; - - scoped_refptr 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(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 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 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 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(frame), - &headers); - break; - case flip::SYN_REPLY: - LOG(INFO) << "Flip SynReply for stream " << frame->stream_id(); - OnSynReply( - reinterpret_cast(frame), - &headers); - break; - case flip::FIN_STREAM: - LOG(INFO) << "Flip Fin for stream " << frame->stream_id(); - OnFin(reinterpret_cast(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 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/flip/flip_session.h b/net/flip/flip_session.h deleted file mode 100644 index f0af900..0000000 --- a/net/flip/flip_session.h +++ /dev/null @@ -1,237 +0,0 @@ -// 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_FLIP_SESSION_H_ -#define NET_FLIP_FLIP_SESSION_H_ - -#include -#include -#include -#include -#include - -#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/flip/flip_framer.h" -#include "net/flip/flip_io_buffer.h" -#include "net/flip/flip_protocol.h" -#include "net/flip/flip_session_pool.h" -#include "net/socket/client_socket.h" -#include "net/socket/client_socket_handle.h" -#include "testing/platform_test.h" - -namespace net { - -class FlipStream; -class HttpNetworkSession; -class HttpRequestInfo; -class HttpResponseInfo; -class LoadLog; -class SSLInfo; - -class FlipSession : public base::RefCounted, - 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 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; - - typedef std::map > ActiveStreamMap; - typedef std::list > ActiveStreamList; - typedef std::map > PendingStreamMap; - typedef std::priority_queue 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 GetPushStream(const std::string& url); - - void GetSSLInfo(SSLInfo* ssl_info); - - // Callbacks for the Flip session. - CompletionCallbackImpl connect_callback_; - CompletionCallbackImpl ssl_connect_callback_; - CompletionCallbackImpl read_callback_; - CompletionCallbackImpl write_callback_; - - // The domain this session is connected to. - std::string domain_; - - SSLConfig ssl_config_; - - scoped_refptr session_; - - // The socket handle for this session. - scoped_ptr connection_; - - // The read buffer used to read data from the socket. - scoped_refptr 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_FLIP_FLIP_SESSION_H_ diff --git a/net/flip/flip_session_pool.cc b/net/flip/flip_session_pool.cc deleted file mode 100644 index c45788b..0000000 --- a/net/flip/flip_session_pool.cc +++ /dev/null @@ -1,121 +0,0 @@ -// 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/flip/flip_session_pool.h" - -#include "base/logging.h" -#include "net/flip/flip_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 FlipSessionPool::Get( - const HostResolver::RequestInfo& info, HttpNetworkSession* session) { - const std::string& domain = info.hostname(); - scoped_refptr 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 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 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& 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 session = list->front(); - list->pop_front(); - session->CloseAllStreams(net::ERR_ABORTED); - } - delete list; - } -} - -} // namespace net diff --git a/net/flip/flip_session_pool.h b/net/flip/flip_session_pool.h deleted file mode 100644 index ebfe251..0000000 --- a/net/flip/flip_session_pool.h +++ /dev/null @@ -1,76 +0,0 @@ -// 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_FLIP_SESSION_POOL_H_ -#define NET_FLIP_FLIP_SESSION_POOL_H_ - -#include -#include -#include - -#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 { - public: - FlipSessionPool(); - - // Either returns an existing FlipSession or creates a new FlipSession for - // use. - scoped_refptr 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 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; - friend class FlipSession; // Needed for Remove(). - friend class FlipSessionPoolPeer; // For testing. - - typedef std::list > FlipSessionList; - typedef std::map FlipSessionsMap; - - virtual ~FlipSessionPool(); - - // Removes a FlipSession from the FlipSessionPool. - void Remove(const scoped_refptr& 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_FLIP_FLIP_SESSION_POOL_H_ diff --git a/net/flip/flip_session_unittest.cc b/net/flip/flip_session_unittest.cc deleted file mode 100644 index ca048ea..0000000 --- a/net/flip/flip_session_unittest.cc +++ /dev/null @@ -1,56 +0,0 @@ -// 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/flip/flip_io_buffer.h" - -#include "net/base/test_completion_callback.h" -#include "net/flip/flip_session.h" -#include "net/flip/flip_stream.h" -#include "net/socket/socket_test_util.h" -#include "testing/platform_test.h" - -namespace net { - -class FlipSessionTest : public PlatformTest { - public: -}; - -// Test the FlipIOBuffer class. -TEST_F(FlipSessionTest, FlipIOBuffer) { - std::priority_queue 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/flip/flip_stream.cc b/net/flip/flip_stream.cc deleted file mode 100644 index 08106f0..0000000 --- a/net/flip/flip_stream.cc +++ /dev/null @@ -1,456 +0,0 @@ -// 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/flip/flip_stream.h" - -#include "base/logging.h" -#include "net/flip/flip_session.h" -#include "net/http/http_request_info.h" -#include "net/http/http_response_info.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::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 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(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/flip/flip_stream.h b/net/flip/flip_stream.h deleted file mode 100644 index 083235b..0000000 --- a/net/flip/flip_stream.h +++ /dev/null @@ -1,211 +0,0 @@ -// 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_FLIP_FLIP_STREAM_H_ -#define NET_FLIP_FLIP_STREAM_H_ - -#include -#include - -#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/flip/flip_framer.h" -#include "net/flip/flip_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 { - 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; - - 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 > response_body_; - bool download_finished_; - ScopedBandwidthMetrics metrics_; - - scoped_refptr session_; - - HttpResponseInfo* response_; - scoped_ptr 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 user_buffer_; - int user_buffer_len_; - - bool cancelled_; - - scoped_refptr 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_FLIP_FLIP_STREAM_H_ diff --git a/net/flip/flip_stream_unittest.cc b/net/flip/flip_stream_unittest.cc deleted file mode 100644 index 5b3064a..0000000 --- a/net/flip/flip_stream_unittest.cc +++ /dev/null @@ -1,123 +0,0 @@ -// 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/flip/flip_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/flip/flip_session.h" -#include "net/flip/flip_session_pool.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 "testing/gtest/include/gtest/gtest.h" - -namespace net { - -class FlipSessionPoolPeer { - public: - explicit FlipSessionPoolPeer(const scoped_refptr& pool) - : pool_(pool) {} - - void RemoveFlipSession(const scoped_refptr& session) { - pool_->Remove(session); - } - - private: - const scoped_refptr 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 host_resolver; - scoped_refptr proxy_service; - scoped_refptr ssl_config_service; - MockClientSocketFactory socket_factory; - scoped_refptr 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 CreateFlipSession() { - HostResolver::RequestInfo resolve_info("www.google.com", 80); - scoped_refptr session( - session_->flip_session_pool()->Get(resolve_info, session_)); - return session; - } - - virtual void TearDown() { - MessageLoop::current()->RunAllPending(); - } - - SessionDependencies session_deps_; - scoped_refptr session_; - FlipSessionPoolPeer pool_peer_; -}; - -// Needs fixing, see http://crbug.com/28622 -TEST_F(FlipStreamTest, SendRequest) { - scoped_refptr session(CreateFlipSession()); - HttpRequestInfo request; - request.method = "GET"; - request.url = GURL("http://www.google.com/"); - TestCompletionCallback callback; - HttpResponseInfo response; - - scoped_refptr 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/flip/flip_transaction_factory.h b/net/flip/flip_transaction_factory.h deleted file mode 100644 index ce50686..0000000 --- a/net/flip/flip_transaction_factory.h +++ /dev/null @@ -1,36 +0,0 @@ -// 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_FLIP_TRANSACTION_FACTORY_H__ -#define NET_FLIP_FLIP_TRANSACTION_FACTORY_H__ - -#include "net/flip/flip_network_transaction.h" -#include "net/http/http_transaction_factory.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 session_; -}; - -} // namespace net - -#endif // NET_FLIP_FLIP_TRANSACTION_FACTORY_H__ diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc index b7989d1..b6cff8e 100644 --- a/net/http/http_cache.cc +++ b/net/http/http_cache.cc @@ -20,13 +20,13 @@ #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/disk_cache/disk_cache.h" -#include "net/flip/flip_session_pool.h" #include "net/http/http_cache_transaction.h" #include "net/http/http_network_layer.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/spdy/spdy_session_pool.h" namespace net { diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc index c8022b7..6e5df6a 100644 --- a/net/http/http_network_layer.cc +++ b/net/http/http_network_layer.cc @@ -6,13 +6,13 @@ #include "base/logging.h" #include "base/string_util.h" -#include "net/flip/flip_framer.h" -#include "net/flip/flip_network_transaction.h" -#include "net/flip/flip_session.h" -#include "net/flip/flip_session_pool.h" #include "net/http/http_network_session.h" #include "net/http/http_network_transaction.h" #include "net/socket/client_socket_factory.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_network_transaction.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" namespace net { diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc index 9f4cba7..40f8416 100644 --- a/net/http/http_network_session.cc +++ b/net/http/http_network_session.cc @@ -5,7 +5,7 @@ #include "net/http/http_network_session.h" #include "base/logging.h" -#include "net/flip/flip_session_pool.h" +#include "net/spdy/spdy_session_pool.h" namespace net { diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index d89a84a..1ad8b6a 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -20,9 +20,6 @@ #include "net/base/net_util.h" #include "net/base/ssl_cert_request_info.h" #include "net/base/upload_data_stream.h" -#include "net/flip/flip_session.h" -#include "net/flip/flip_session_pool.h" -#include "net/flip/flip_stream.h" #include "net/http/http_auth.h" #include "net/http/http_auth_handler.h" #include "net/http/http_basic_stream.h" @@ -36,6 +33,9 @@ #include "net/socket/socks5_client_socket.h" #include "net/socket/socks_client_socket.h" #include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_stream.h" using base::Time; diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 3b977bd..f3991ce 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -15,7 +15,7 @@ #include "net/base/ssl_info.h" #include "net/base/test_completion_callback.h" #include "net/base/upload_data.h" -#include "net/flip/flip_session_pool.h" +#include "net/spdy/spdy_session_pool.h" #include "net/http/http_auth_handler_ntlm.h" #include "net/http/http_basic_stream.h" #include "net/http/http_network_session.h" diff --git a/net/net.gyp b/net/net.gyp index 3f63d82..31e3dc4 100755 --- a/net/net.gyp +++ b/net/net.gyp @@ -274,23 +274,6 @@ 'disk_cache/storage_block.h', 'disk_cache/trace.cc', 'disk_cache/trace.h', - 'flip/flip_bitmasks.h', - 'flip/flip_frame_builder.cc', - 'flip/flip_frame_builder.h', - 'flip/flip_framer.cc', - 'flip/flip_framer.h', - 'flip/flip_io_buffer.cc', - 'flip/flip_io_buffer.h', - 'flip/flip_network_transaction.cc', - 'flip/flip_network_transaction.h', - 'flip/flip_protocol.h', - 'flip/flip_session.cc', - 'flip/flip_session.h', - 'flip/flip_session_pool.cc', - 'flip/flip_session_pool.h', - 'flip/flip_stream.cc', - 'flip/flip_stream.h', - 'flip/flip_transaction_factory.h', 'ftp/ftp_auth_cache.cc', 'ftp/ftp_auth_cache.h', 'ftp/ftp_ctrl_response_buffer.cc', @@ -455,6 +438,23 @@ 'socket_stream/socket_stream_metrics.h', 'socket_stream/socket_stream_throttle.cc', 'socket_stream/socket_stream_throttle.h', + 'spdy/spdy_bitmasks.h', + 'spdy/spdy_frame_builder.cc', + 'spdy/spdy_frame_builder.h', + 'spdy/spdy_framer.cc', + 'spdy/spdy_framer.h', + 'spdy/spdy_io_buffer.cc', + 'spdy/spdy_io_buffer.h', + 'spdy/spdy_network_transaction.cc', + 'spdy/spdy_network_transaction.h', + 'spdy/spdy_protocol.h', + 'spdy/spdy_session.cc', + 'spdy/spdy_session.h', + 'spdy/spdy_session_pool.cc', + 'spdy/spdy_session_pool.h', + 'spdy/spdy_stream.cc', + 'spdy/spdy_stream.h', + 'spdy/spdy_transaction_factory.h', 'url_request/request_tracker.h', 'url_request/url_request.cc', 'url_request/url_request.h', @@ -633,11 +633,6 @@ 'ftp/ftp_network_transaction_unittest.cc', 'ftp/ftp_util_unittest.cc', 'http/des_unittest.cc', - 'flip/flip_framer_test.cc', - 'flip/flip_network_transaction_unittest.cc', - 'flip/flip_protocol_test.cc', - 'flip/flip_session_unittest.cc', - 'flip/flip_stream_unittest.cc', 'http/http_auth_cache_unittest.cc', 'http/http_auth_handler_basic_unittest.cc', 'http/http_auth_handler_digest_unittest.cc', @@ -674,6 +669,11 @@ 'socket/tcp_pinger_unittest.cc', 'socket_stream/socket_stream_metrics_unittest.cc', 'socket_stream/socket_stream_unittest.cc', + 'spdy/spdy_framer_test.cc', + 'spdy/spdy_network_transaction_unittest.cc', + 'spdy/spdy_protocol_test.cc', + 'spdy/spdy_session_unittest.cc', + 'spdy/spdy_stream_unittest.cc', 'url_request/request_tracker_unittest.cc', 'url_request/url_request_unittest.cc', 'url_request/url_request_unittest.h', 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 + +#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::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(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(buffer_); + + if (!IteratorHasRoomFor(*iter, sizeof(*result))) + return false; + + *result = ntohs(*(reinterpret_cast(*iter))); + + UpdateIter(iter, sizeof(*result)); + return true; +} + +bool FlipFrameBuilder::ReadUInt32(void** iter, uint32* result) const { + DCHECK(iter); + if (!*iter) + *iter = const_cast(buffer_); + + if (!IteratorHasRoomFor(*iter, sizeof(*result))) + return false; + + *result = ntohl(*(reinterpret_cast(*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(*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(*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::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(value.size()))) + return false; + + return WriteBytes(value.data(), static_cast(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 // for htonl() functions +#else +#include +#endif + +#include + +#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(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(*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 +#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(¤t_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 decompressed(new char[decompressed_max_size]); + decompressor_->next_in = reinterpret_cast( + const_cast(data)); + decompressor_->avail_in = amount_to_forward; + decompressor_->next_out = + reinterpret_cast(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 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(kLengthMask)); + FlagsAndLength flags_length; + flags_length.length_ = htonl(static_cast(length)); + flags_length.flags_[0] = flags; + frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length)); + + scoped_ptr syn_frame(frame.take()); + if (compressed) { + return reinterpret_cast( + CompressFrame(syn_frame.get())); + } + return reinterpret_cast(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(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(kLengthMask)); + FlagsAndLength flags_length; + flags_length.length_ = htonl(static_cast(length)); + flags_length.flags_[0] = flags; + frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length)); + + scoped_ptr reply_frame(frame.take()); + if (compressed) { + return reinterpret_cast( + CompressFrame(reply_frame.get())); + } + return reinterpret_cast(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(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 data_frame(frame.take()); + if (flags & DATA_FLAG_COMPRESSED) + return reinterpret_cast(CompressFrame(data_frame.get())); + return reinterpret_cast(data_frame.release()); +} + +/* static */ +FlipControlFrame* FlipFramer::CreateNopFrame() { + FlipFrameBuilder frame; + frame.WriteUInt16(kControlFlagMask | kFlipProtocolVersion); + frame.WriteUInt16(NOOP); + frame.WriteUInt32(0); + return reinterpret_cast(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(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(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(frame); + switch (control_frame->type()) { + case SYN_STREAM: + case SYN_REPLY: + { + const FlipSynStreamControlFrame *syn_frame = + reinterpret_cast(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(*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(const_cast(payload)); + compressor_->avail_in = payload_length; + compressor_->next_out = reinterpret_cast(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(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(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(const_cast(payload)); + decompressor_->avail_in = payload_length; + decompressor_->next_out = reinterpret_cast(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(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 +#else +#include +#endif +#include +#include + +#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 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 compressor_; + scoped_ptr 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 +#include + +#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(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 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 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 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 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 + frame1(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, true, &headers)); + scoped_ptr + 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 frame3(framer.DecompressFrame(frame1.get())); + + // Decompress the second frame + scoped_ptr 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 + frame1(framer.CreateSynStream(1, 1, CONTROL_FLAG_NONE, false, &headers)); + + // Decompress the frame + scoped_ptr 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& 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 buffer_; + int priority_; + uint64 position_; + scoped_refptr 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 +#include + +#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 load_log_; + + scoped_refptr flip_; + + CompletionCallbackImpl io_callback_; + CompletionCallback* user_callback_; + + // Used to pass onto the FlipStream + scoped_refptr user_buffer_; + int user_buffer_len_; + + scoped_refptr 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 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 host_resolver; + scoped_refptr proxy_service; + scoped_refptr ssl_config_service; + MockClientSocketFactory socket_factory; + scoped_refptr 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 { + 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 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 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 session = + CreateSession(&session_deps); + scoped_ptr trans(new FlipNetworkTransaction(session)); +} + +TEST_F(FlipNetworkTransactionTest, Get) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast(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 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(kPostSyn), + arraysize(kPostSyn)), + MockWrite(true, reinterpret_cast(kPostUploadFrame), + arraysize(kPostUploadFrame)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kPostSynReply), + arraysize(kPostSynReply)), + MockRead(true, reinterpret_cast(kPostBodyFrame), + arraysize(kPostBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr 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(kEmptyPostSyn), + arraysize(kEmptyPostSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kPostSynReply), + arraysize(kPostSynReply)), + MockRead(true, reinterpret_cast(kPostBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr 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(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 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(kGetSyn), + arraysize(kGetSyn)), + MockRead(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(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 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(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(test_cases[i].syn_reply), + test_cases[i].syn_reply_length), + MockRead(true, reinterpret_cast(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 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 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(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(test_cases[i].syn_reply), + test_cases[i].syn_reply_length), + MockRead(true, reinterpret_cast(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 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(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(test_cases[i].syn_reply), + test_cases[i].syn_reply_length), + MockRead(true, reinterpret_cast(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 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(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(syn_reply), + arraysize(syn_reply)), + MockRead(true, reinterpret_cast(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, ERR_IO_PENDING), // Force a pause + MockRead(true, reinterpret_cast(syn_push), + arraysize(syn_push)), + MockRead(true, reinterpret_cast(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 session(CreateSession(&session_deps)); + scoped_refptr 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(kGetSyn), 10), + // Followed by ERROR! + MockWrite(true, ERR_FAILED), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast(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 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 writes(ChopFrame( + reinterpret_cast(kGetSyn), arraysize(kGetSyn), kChunks)); + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast(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 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(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast(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 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(kGetSynCompressed), + arraysize(kGetSynCompressed)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast(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 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(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr 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 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 +#else +#include +#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(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(data)), + owns_buffer_(owns_buffer) { + DCHECK(frame_); + } + + virtual ~FlipFrame() { + if (owns_buffer_) { + char* buffer = reinterpret_cast(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(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(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(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(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(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(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(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(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 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 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 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(rv); +} + +scoped_refptr FlipSession::GetOrCreateStream( + const HttpRequestInfo& request, + const UploadDataStream* upload_data, + LoadLog* log) { + const GURL& url = request.url; + const std::string& path = url.PathForRequest(); + + scoped_refptr 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(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 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 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 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 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(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(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(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 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 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(flip::FlipFrame::size())); + result -= static_cast(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(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 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 curr = *it; + if (id == curr->stream_id()) { + pushed_streams_.erase(it); + break; + } + } + + active_streams_.erase(id); +} + +scoped_refptr FlipSession::GetPushStream(const std::string& path) { + static StatsCounter used_push_streams("flip.claimed_push_streams"); + + LOG(INFO) << "Looking for push stream: " << path; + + scoped_refptr 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(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 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 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 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(frame), + &headers); + break; + case flip::SYN_REPLY: + LOG(INFO) << "Flip SynReply for stream " << frame->stream_id(); + OnSynReply( + reinterpret_cast(frame), + &headers); + break; + case flip::FIN_STREAM: + LOG(INFO) << "Flip Fin for stream " << frame->stream_id(); + OnFin(reinterpret_cast(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 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 +#include +#include +#include +#include + +#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, + 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 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; + + typedef std::map > ActiveStreamMap; + typedef std::list > ActiveStreamList; + typedef std::map > PendingStreamMap; + typedef std::priority_queue 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 GetPushStream(const std::string& url); + + void GetSSLInfo(SSLInfo* ssl_info); + + // Callbacks for the Flip session. + CompletionCallbackImpl connect_callback_; + CompletionCallbackImpl ssl_connect_callback_; + CompletionCallbackImpl read_callback_; + CompletionCallbackImpl write_callback_; + + // The domain this session is connected to. + std::string domain_; + + SSLConfig ssl_config_; + + scoped_refptr session_; + + // The socket handle for this session. + scoped_ptr connection_; + + // The read buffer used to read data from the socket. + scoped_refptr 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 FlipSessionPool::Get( + const HostResolver::RequestInfo& info, HttpNetworkSession* session) { + const std::string& domain = info.hostname(); + scoped_refptr 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 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 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& 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 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 +#include +#include + +#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 { + public: + FlipSessionPool(); + + // Either returns an existing FlipSession or creates a new FlipSession for + // use. + scoped_refptr 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 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; + friend class FlipSession; // Needed for Remove(). + friend class FlipSessionPoolPeer; // For testing. + + typedef std::list > FlipSessionList; + typedef std::map FlipSessionsMap; + + virtual ~FlipSessionPool(); + + // Removes a FlipSession from the FlipSessionPool. + void Remove(const scoped_refptr& 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 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::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 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(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 +#include + +#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 { + 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; + + 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 > response_body_; + bool download_finished_; + ScopedBandwidthMetrics metrics_; + + scoped_refptr session_; + + HttpResponseInfo* response_; + scoped_ptr 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 user_buffer_; + int user_buffer_len_; + + bool cancelled_; + + scoped_refptr 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& pool) + : pool_(pool) {} + + void RemoveFlipSession(const scoped_refptr& session) { + pool_->Remove(session); + } + + private: + const scoped_refptr 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 host_resolver; + scoped_refptr proxy_service; + scoped_refptr ssl_config_service; + MockClientSocketFactory socket_factory; + scoped_refptr 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 CreateFlipSession() { + HostResolver::RequestInfo resolve_info("www.google.com", 80); + scoped_refptr session( + session_->flip_session_pool()->Get(resolve_info, session_)); + return session; + } + + virtual void TearDown() { + MessageLoop::current()->RunAllPending(); + } + + SessionDependencies session_deps_; + scoped_refptr session_; + FlipSessionPoolPeer pool_peer_; +}; + +// Needs fixing, see http://crbug.com/28622 +TEST_F(FlipStreamTest, SendRequest) { + scoped_refptr session(CreateFlipSession()); + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + TestCompletionCallback callback; + HttpResponseInfo response; + + scoped_refptr 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 session_; +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_TRANSACTION_FACTORY_H__ -- cgit v1.1