summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/base/net_error_list.h3
-rw-r--r--net/base/net_log_event_type_list.h7
-rw-r--r--net/base/run_all_unittests.cc3
-rw-r--r--net/http/http_network_layer.cc3
-rw-r--r--net/http/http_network_transaction.cc6
-rw-r--r--net/spdy/spdy_framer.cc586
-rw-r--r--net/spdy/spdy_framer.h140
-rw-r--r--net/spdy/spdy_framer_test.cc11
-rw-r--r--net/spdy/spdy_protocol.h68
-rw-r--r--net/spdy/spdy_session.cc185
-rw-r--r--net/spdy/spdy_session.h131
-rw-r--r--net/spdy/spdy_session_unittest.cc143
-rw-r--r--net/spdy/spdy_test_util.cc7
-rw-r--r--net/spdy/spdy_test_util.h4
-rw-r--r--net/tools/flip_server/spdy_interface.cc11
-rw-r--r--net/tools/flip_server/spdy_interface.h6
16 files changed, 1252 insertions, 62 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 1147267..6cdfa06 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -444,6 +444,9 @@ NET_ERROR(RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, -346)
// headers are missing, so we're expecting additional frames to complete them.
NET_ERROR(INCOMPLETE_SPDY_HEADERS, -347)
+// SPDY server didn't respond to the PING message.
+NET_ERROR(SPDY_PING_FAILED, -352)
+
// The cache does not have the requested entry.
NET_ERROR(CACHE_MISS, -400)
diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h
index f9ab1b3..34c4a26 100644
--- a/net/base/net_log_event_type_list.h
+++ b/net/base/net_log_event_type_list.h
@@ -778,6 +778,13 @@ EVENT_TYPE(SPDY_SESSION_RST_STREAM)
// }
EVENT_TYPE(SPDY_SESSION_SEND_RST_STREAM)
+// Sending of a SPDY PING frame.
+// The following parameters are attached:
+// {
+// "unique_id": <The unique id of the PING message>,
+// }
+EVENT_TYPE(SPDY_SESSION_PING)
+
// Receipt of a SPDY GOAWAY frame.
// The following parameters are attached:
// {
diff --git a/net/base/run_all_unittests.cc b/net/base/run_all_unittests.cc
index a844354..bcadda0 100644
--- a/net/base/run_all_unittests.cc
+++ b/net/base/run_all_unittests.cc
@@ -7,14 +7,17 @@
#include "crypto/nss_util.h"
#include "net/base/net_test_suite.h"
#include "net/socket/client_socket_pool_base.h"
+#include "net/spdy/spdy_session.h"
using net::internal::ClientSocketPoolBaseHelper;
+using net::SpdySession;
int main(int argc, char** argv) {
// Record histograms, so we can get histograms data in tests.
base::StatisticsRecorder recorder;
NetTestSuite test_suite(argc, argv);
ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ SpdySession::set_enable_ping_based_connection_checking(false);
#if defined(OS_WIN)
// We want to be sure to init NSPR on the main thread.
diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc
index ce3fab8..8253eac 100644
--- a/net/http/http_network_layer.cc
+++ b/net/http/http_network_layer.cc
@@ -41,6 +41,7 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) {
static const char kOff[] = "off";
static const char kSSL[] = "ssl";
static const char kDisableSSL[] = "no-ssl";
+ static const char kDisablePing[] = "no-ping";
static const char kExclude[] = "exclude"; // Hosts to exclude
static const char kDisableCompression[] = "no-compress";
static const char kDisableAltProtocols[] = "no-alt-protocols";
@@ -98,6 +99,8 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) {
} else if (option == kSSL) {
HttpStreamFactory::set_force_spdy_over_ssl(true);
HttpStreamFactory::set_force_spdy_always(true);
+ } else if (option == kDisablePing) {
+ SpdySession::set_enable_ping_based_connection_checking(false);
} else if (option == kExclude) {
HttpStreamFactory::add_forced_spdy_exclusion(value);
} else if (option == kDisableCompression) {
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 55330e2..7f0ac4f 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -1203,7 +1203,11 @@ int HttpNetworkTransaction::HandleIOError(int error) {
if (ShouldResendRequest(error)) {
ResetConnectionAndRequestForResend();
error = OK;
- }
+ }
+ break;
+ case ERR_SPDY_PING_FAILED:
+ ResetConnectionAndRequestForResend();
+ error = OK;
break;
}
return error;
diff --git a/net/spdy/spdy_framer.cc b/net/spdy/spdy_framer.cc
index e178165..878b199 100644
--- a/net/spdy/spdy_framer.cc
+++ b/net/spdy/spdy_framer.cc
@@ -31,6 +31,7 @@ const int kCompressorLevel = 9;
const int kCompressorWindowSizeInBits = 11;
const int kCompressorMemLevel = 1;
+// Adler ID for the SPDY header compressor dictionary.
uLong dictionary_id = 0;
} // namespace
@@ -68,6 +69,9 @@ size_t SpdyFramer::kControlFrameBufferInitialSize = 8 * 1024;
// TODO(mbelshe): We should make this stream-based so there are no limits.
size_t SpdyFramer::kControlFrameBufferMaxSize = 16 * 1024;
+const SpdyStreamId SpdyFramer::kInvalidStream = -1;
+const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024;
+
#ifdef DEBUG_SPDY_STATE_CHANGES
#define CHANGE_STATE(newstate) \
{ \
@@ -82,14 +86,68 @@ size_t SpdyFramer::kControlFrameBufferMaxSize = 16 * 1024;
#define CHANGE_STATE(newstate) (state_ = newstate)
#endif
+int DecompressHeaderBlockInZStream(z_stream* decompressor) {
+ int rv = inflate(decompressor, Z_SYNC_FLUSH);
+ if (rv == Z_NEED_DICT) {
+ // Need to try again with the right dictionary.
+ if (decompressor->adler == dictionary_id) {
+ rv = inflateSetDictionary(decompressor,
+ (const Bytef*)SpdyFramer::kDictionary,
+ SpdyFramer::kDictionarySize);
+ if (rv == Z_OK)
+ rv = inflate(decompressor, Z_SYNC_FLUSH);
+ }
+ }
+ return rv;
+}
+
+// Retrieve serialized length of SpdyHeaderBlock.
+size_t GetSerializedLength(const SpdyHeaderBlock* headers) {
+ size_t total_length = SpdyControlFrame::kNumNameValuePairsSize;
+ SpdyHeaderBlock::const_iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ // We add space for the length of the name and the length of the value as
+ // well as the length of the name and the length of the value.
+ total_length += SpdyControlFrame::kLengthOfNameSize +
+ it->first.size() +
+ SpdyControlFrame::kLengthOfValueSize +
+ it->second.size();
+ }
+ return total_length;
+}
+
+// Serializes a SpdyHeaderBlock.
+void WriteHeaderBlock(SpdyFrameBuilder* frame, const SpdyHeaderBlock* headers) {
+ frame->WriteUInt16(headers->size()); // Number of headers.
+ SpdyHeaderBlock::const_iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ bool wrote_header;
+ wrote_header = frame->WriteString(it->first);
+ wrote_header &= frame->WriteString(it->second);
+ DCHECK(wrote_header);
+ }
+}
+
+// Creates a FlagsAndLength.
+FlagsAndLength CreateFlagsAndLength(SpdyControlFlags flags, size_t length) {
+ DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(static_cast<uint32>(length));
+ DCHECK_EQ(0, flags & ~kControlFlagsMask);
+ flags_length.flags_[0] = flags;
+ return flags_length;
+}
+
SpdyFramer::SpdyFramer()
: state_(SPDY_RESET),
error_code_(SPDY_NO_ERROR),
- remaining_payload_(0),
+ remaining_data_(0),
remaining_control_payload_(0),
+ remaining_control_header_(0),
current_frame_buffer_(NULL),
current_frame_len_(0),
current_frame_capacity_(0),
+ validate_control_frame_sizes_(true),
enable_compression_(compression_default_),
visitor_(NULL) {
}
@@ -105,6 +163,54 @@ SpdyFramer::~SpdyFramer() {
delete [] current_frame_buffer_;
}
+const char* SpdyFramer::StatusCodeToString(int status_code) {
+ switch (status_code) {
+ case INVALID:
+ return "INVALID";
+ case PROTOCOL_ERROR:
+ return "PROTOCOL_ERROR";
+ case INVALID_STREAM:
+ return "INVALID_STREAM";
+ case REFUSED_STREAM:
+ return "REFUSED_STREAM";
+ case UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case CANCEL:
+ return "CANCEL";
+ case INTERNAL_ERROR:
+ return "INTERNAL_ERROR";
+ case FLOW_CONTROL_ERROR:
+ return "FLOW_CONTROL_ERROR";
+ }
+ return "UNKNOWN_STATUS";
+}
+
+const char* SpdyFramer::ControlTypeToString(SpdyControlType type) {
+ switch (type) {
+ case SYN_STREAM:
+ return "SYN_STREAM";
+ case SYN_REPLY:
+ return "SYN_REPLY";
+ case RST_STREAM:
+ return "RST_STREAM";
+ case SETTINGS:
+ return "SETTINGS";
+ case NOOP:
+ return "NOOP";
+ case PING:
+ return "PING";
+ case GOAWAY:
+ return "GOAWAY";
+ case HEADERS:
+ return "HEADERS";
+ case WINDOW_UPDATE:
+ return "WINDOW_UPDATE";
+ case NUM_CONTROL_FRAME_TYPES:
+ break;
+ }
+ return "UNKNOWN_CONTROL_TYPE";
+}
+
size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
DCHECK(visitor_);
DCHECK(data);
@@ -132,10 +238,36 @@ size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
// 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!)
+ // TODO(hkhalil): Remove -- while loop above prevents proper handling of
+ // zero-length control frames.
case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
ProcessControlFrameHeader();
continue;
+ case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: {
+ // Control frames that contain header blocks (SYN_STREAM, SYN_REPLY,
+ // HEADERS) take a different path through the state machine - they
+ // will go:
+ // 1. SPDY_INTERPRET_CONTROL_FRAME_COMMON HEADER
+ // 2. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
+ // 3. SPDY_CONTROL_FRAME_HEADER_BLOCK
+ //
+ // All other control frames will use the alternate route:
+ // 1. SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER
+ // 2. SPDY_CONTROL_FRAME_PAYLOAD
+ int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ continue;
+ }
+
+ case SPDY_CONTROL_FRAME_HEADER_BLOCK: {
+ int bytes_read = ProcessControlFrameHeaderBlock(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ continue;
+ }
+
case SPDY_CONTROL_FRAME_PAYLOAD: {
size_t bytes_read = ProcessControlFramePayload(data, len);
len -= bytes_read;
@@ -162,8 +294,9 @@ size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
void SpdyFramer::Reset() {
state_ = SPDY_RESET;
error_code_ = SPDY_NO_ERROR;
- remaining_payload_ = 0;
+ remaining_data_ = 0;
remaining_control_payload_ = 0;
+ remaining_control_header_ = 0;
current_frame_len_ = 0;
if (current_frame_capacity_ != kControlFrameBufferInitialSize) {
delete [] current_frame_buffer_;
@@ -238,9 +371,169 @@ bool SpdyFramer::ParseHeaderBlock(const SpdyFrame* frame,
return false;
}
+size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len,
+ size_t max_bytes) {
+ size_t bytes_to_read = std::min(*len, max_bytes);
+ DCHECK_GE(current_frame_capacity_, current_frame_len_ + bytes_to_read);
+ memcpy(&current_frame_buffer_[current_frame_len_], *data, bytes_to_read);
+ current_frame_len_ += bytes_to_read;
+ *data += bytes_to_read;
+ *len -= bytes_to_read;
+ return bytes_to_read;
+}
+
+size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data,
+ size_t len) {
+ DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_);
+ DCHECK_GT(remaining_control_header_, 0u);
+ size_t original_len = len;
+
+ if (remaining_control_header_) {
+ size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
+ remaining_control_header_);
+ remaining_control_header_ -= bytes_read;
+ if (remaining_control_header_ == 0) {
+ SpdyControlFrame control_frame(current_frame_buffer_, false);
+ DCHECK(control_frame.type() == SYN_STREAM ||
+ control_frame.type() == SYN_REPLY ||
+ control_frame.type() == HEADERS);
+ visitor_->OnControl(&control_frame);
+
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ }
+ }
+ return original_len - len;
+}
+
+// Does not buffer the control payload. Instead, either passes directly to the
+// visitor or decompresses and then passes directly to the visitor, via
+// IncrementallyDeliverControlFrameHeaderData() or
+// IncrementallyDecompressControlFrameHeaderData() respectively.
+size_t SpdyFramer::NewProcessControlFrameHeaderBlock(const char* data,
+ size_t data_len) {
+ DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_);
+ SpdyControlFrame control_frame(current_frame_buffer_, false);
+ bool processed_successfully = true;
+ DCHECK(control_frame.type() == SYN_STREAM ||
+ control_frame.type() == SYN_REPLY ||
+ control_frame.type() == HEADERS);
+ size_t process_bytes = std::min(data_len, remaining_control_payload_);
+ DCHECK_GT(process_bytes, 0u);
+
+ if (enable_compression_) {
+ processed_successfully = IncrementallyDecompressControlFrameHeaderData(
+ &control_frame, data, process_bytes);
+ } else {
+ processed_successfully = IncrementallyDeliverControlFrameHeaderData(
+ &control_frame, data, process_bytes);
+ }
+ remaining_control_payload_ -= process_bytes;
+
+ // Handle the case that there is no futher data in this frame.
+ if (remaining_control_payload_ == 0 && processed_successfully) {
+ // The complete header block has been delivered. We send a zero-length
+ // OnControlFrameHeaderData() to indicate this.
+ visitor_->OnControlFrameHeaderData(
+ GetControlFrameStreamId(&control_frame), NULL, 0);
+
+ // If this is a FIN, tell the caller.
+ if (control_frame.flags() & CONTROL_FLAG_FIN) {
+ visitor_->OnStreamFrameData(GetControlFrameStreamId(&control_frame),
+ NULL, 0);
+ }
+
+ CHANGE_STATE(SPDY_RESET);
+ }
+
+ // Handle error.
+ if (!processed_successfully) {
+ return data_len;
+ }
+
+ // Return amount processed.
+ return process_bytes;
+}
+
+size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data,
+ size_t data_len) {
+ DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_);
+ size_t original_data_len = data_len;
+ SpdyControlFrame control_frame(current_frame_buffer_, false);
+ bool read_successfully = true;
+ DCHECK(control_frame.type() == SYN_STREAM ||
+ control_frame.type() == SYN_REPLY ||
+ control_frame.type() == HEADERS);
+
+ if (enable_compression_) {
+ // Note that the header block is held in the frame's payload, and is not
+ // part of the frame's headers.
+ if (remaining_control_payload_ > 0) {
+ size_t bytes_read = UpdateCurrentFrameBuffer(
+ &data,
+ &data_len,
+ remaining_control_payload_);
+ remaining_control_payload_ -= bytes_read;
+ if (remaining_control_payload_ == 0) {
+ read_successfully = IncrementallyDecompressControlFrameHeaderData(
+ &control_frame);
+ }
+ }
+ } else {
+ size_t bytes_to_send = std::min(data_len, remaining_control_payload_);
+ DCHECK_GT(bytes_to_send, 0u);
+ read_successfully = IncrementallyDeliverControlFrameHeaderData(
+ &control_frame, data, bytes_to_send);
+ data_len -= bytes_to_send;
+ remaining_control_payload_ -= bytes_to_send;
+ }
+ if (remaining_control_payload_ == 0 && read_successfully) {
+ // The complete header block has been delivered.
+ visitor_->OnControlFrameHeaderData(GetControlFrameStreamId(&control_frame),
+ NULL, 0);
+
+ // If this is a FIN, tell the caller.
+ if (control_frame.flags() & CONTROL_FLAG_FIN) {
+ visitor_->OnStreamFrameData(GetControlFrameStreamId(&control_frame),
+ NULL, 0);
+ }
+
+ CHANGE_STATE(SPDY_RESET);
+ }
+ if (!read_successfully) {
+ return original_data_len;
+ }
+ return original_data_len - data_len;
+}
+
+/* static */
+bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data,
+ size_t header_length,
+ SpdyHeaderBlock* block) {
+ SpdyFrameBuilder 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))
+ return false;
+ if (!builder.ReadString(&iter, &value))
+ return false;
+ if (block->find(name) == block->end()) {
+ (*block)[name] = value;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
SpdyStreamId stream_id, SpdyStreamId associated_stream_id, int priority,
- SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) {
+ SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) {
SpdyFrameBuilder frame;
DCHECK_GT(stream_id, static_cast<SpdyStreamId>(0));
@@ -255,7 +548,7 @@ SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
frame.WriteUInt16(ntohs(priority) << 6); // Priority.
frame.WriteUInt16(headers->size()); // Number of headers.
- SpdyHeaderBlock::iterator it;
+ SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame.WriteString(it->first);
@@ -272,16 +565,17 @@ SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
flags_length.flags_[0] = flags;
frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
- scoped_ptr<SpdyFrame> syn_frame(frame.take());
+ scoped_ptr<SpdySynStreamControlFrame> syn_frame(
+ reinterpret_cast<SpdySynStreamControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdySynStreamControlFrame*>(
- CompressFrame(*syn_frame.get()));
+ CompressControlFrame(*syn_frame.get()));
}
- return reinterpret_cast<SpdySynStreamControlFrame*>(syn_frame.release());
+ return syn_frame.release();
}
SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id,
- SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) {
+ SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) {
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
@@ -294,7 +588,7 @@ SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id,
frame.WriteUInt16(0); // Unused
frame.WriteUInt16(headers->size()); // Number of headers.
- SpdyHeaderBlock::iterator it;
+ SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame.WriteString(it->first);
@@ -311,12 +605,13 @@ SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id,
flags_length.flags_[0] = flags;
frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
- scoped_ptr<SpdyFrame> reply_frame(frame.take());
+ scoped_ptr<SpdySynReplyControlFrame> reply_frame(
+ reinterpret_cast<SpdySynReplyControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdySynReplyControlFrame*>(
- CompressFrame(*reply_frame.get()));
+ CompressControlFrame(*reply_frame.get()));
}
- return reinterpret_cast<SpdySynReplyControlFrame*>(reply_frame.release());
+ return reply_frame.release();
}
/* static */
@@ -356,12 +651,23 @@ SpdySettingsControlFrame* SpdyFramer::CreateSettings(
}
/* static */
-SpdyControlFrame* SpdyFramer::CreateNopFrame() {
+SpdyNoOpControlFrame* SpdyFramer::CreateNopFrame() {
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(NOOP);
frame.WriteUInt32(0);
- return reinterpret_cast<SpdyControlFrame*>(frame.take());
+ return reinterpret_cast<SpdyNoOpControlFrame*>(frame.take());
+}
+
+/* static */
+SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) {
+ SpdyFrameBuilder frame;
+ frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
+ frame.WriteUInt16(PING);
+ size_t ping_size = SpdyPingControlFrame::size() - SpdyFrame::size();
+ frame.WriteUInt32(ping_size);
+ frame.WriteUInt32(unique_id);
+ return reinterpret_cast<SpdyPingControlFrame*>(frame.take());
}
/* static */
@@ -379,7 +685,7 @@ SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway(
}
SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id,
- SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) {
+ SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) {
// Basically the same as CreateSynReply().
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
@@ -392,7 +698,7 @@ SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id,
frame.WriteUInt16(0); // Unused
frame.WriteUInt16(headers->size()); // Number of headers.
- SpdyHeaderBlock::iterator it;
+ SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame.WriteString(it->first);
@@ -409,12 +715,13 @@ SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id,
flags_length.flags_[0] = flags;
frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
- scoped_ptr<SpdyFrame> headers_frame(frame.take());
+ scoped_ptr<SpdyHeadersControlFrame> headers_frame(
+ reinterpret_cast<SpdyHeadersControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdyHeadersControlFrame*>(
- CompressFrame(*headers_frame.get()));
+ CompressControlFrame(*headers_frame.get()));
}
- return reinterpret_cast<SpdyHeadersControlFrame*>(headers_frame.release());
+ return headers_frame.release();
}
/* static */
@@ -550,6 +857,10 @@ const char* SpdyFramer::StateToString(int state) {
return "IGNORE_REMAINING_PAYLOAD";
case SPDY_FORWARD_STREAM_FRAME:
return "FORWARD_STREAM_FRAME";
+ case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
+ return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK";
+ case SPDY_CONTROL_FRAME_HEADER_BLOCK:
+ return "SPDY_CONTROL_FRAME_HEADER_BLOCK";
}
return "UNKNOWN_STATE";
}
@@ -593,12 +904,7 @@ size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) {
do {
if (current_frame_len_ < SpdyFrame::size()) {
size_t bytes_desired = SpdyFrame::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;
+ UpdateCurrentFrameBuffer(&data, &len, bytes_desired);
// Check for an empty data frame.
if (current_frame_len_ == SpdyFrame::size() &&
!current_frame.is_control_frame() &&
@@ -611,10 +917,10 @@ size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) {
}
break;
}
- remaining_payload_ = current_frame.length();
+ remaining_data_ = current_frame.length();
// This is just a sanity check for help debugging early frame errors.
- if (remaining_payload_ > 1000000u) {
+ if (remaining_data_ > 1000000u) {
LOG(WARNING) <<
"Unexpectedly large frame. Spdy session is likely corrupt.";
}
@@ -672,8 +978,10 @@ void SpdyFramer::ProcessControlFrameHeader() {
SpdySettingsControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
+ // TODO(hkhalil): Remove NOOP.
case NOOP:
// NOOP. Swallow it.
+ DLOG(INFO) << "Attempted frame size validation for NOOP. Resetting.";
CHANGE_STATE(SPDY_AUTO_RESET);
return;
case GOAWAY:
@@ -691,8 +999,13 @@ void SpdyFramer::ProcessControlFrameHeader() {
SpdyWindowUpdateControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
+ case PING:
+ if (current_control_frame.length() !=
+ SpdyPingControlFrame::size() - SpdyControlFrame::size())
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
default:
- LOG(WARNING) << "Valid spdy control frame with unknown type: "
+ LOG(WARNING) << "Valid spdy control frame with unhandled type: "
<< current_control_frame.type();
DCHECK(false);
set_error(SPDY_INVALID_CONTROL_FRAME);
@@ -713,14 +1026,10 @@ size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) {
size_t original_len = len;
do {
if (remaining_control_payload_) {
- size_t amount_to_consume = std::min(remaining_control_payload_, len);
- memcpy(&current_frame_buffer_[current_frame_len_], data,
- amount_to_consume);
- current_frame_len_ += amount_to_consume;
- data += amount_to_consume;
- len -= amount_to_consume;
- remaining_control_payload_ -= amount_to_consume;
- remaining_payload_ -= amount_to_consume;
+ size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
+ remaining_control_payload_);
+ remaining_control_payload_ -= bytes_read;
+ remaining_data_ -= bytes_read;
if (remaining_control_payload_)
break;
}
@@ -744,8 +1053,8 @@ size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) {
size_t original_len = len;
SpdyDataFrame current_data_frame(current_frame_buffer_, false);
- if (remaining_payload_) {
- size_t amount_to_forward = std::min(remaining_payload_, len);
+ if (remaining_data_) {
+ size_t amount_to_forward = std::min(remaining_data_, len);
if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) {
if (current_data_frame.flags() & DATA_FLAG_COMPRESSED) {
z_stream* decompressor =
@@ -787,11 +1096,11 @@ size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) {
}
data += amount_to_forward;
len -= amount_to_forward;
- remaining_payload_ -= amount_to_forward;
+ remaining_data_ -= amount_to_forward;
// If the FIN flag is set, and there is no more data in this data
// frame, inform the visitor of EOF via a 0-length data frame.
- if (!remaining_payload_ &&
+ if (!remaining_data_ &&
current_data_frame.flags() & DATA_FLAG_FIN) {
visitor_->OnStreamFrameData(current_data_frame.stream_id(), NULL, 0);
CleanupDecompressorForStream(current_data_frame.stream_id());
@@ -915,6 +1224,194 @@ SpdyControlFrame* SpdyFramer::DecompressControlFrame(
DecompressFrameWithZStream(frame, decompressor));
}
+// Incrementally decompress the control frame's header block, feeding the
+// result to the visitor in chunks. Continue this until the visitor
+// indicates that it cannot process any more data, or (more commonly) we
+// run out of data to deliver.
+bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData(
+ const SpdyControlFrame* control_frame) {
+ z_stream* decomp = GetHeaderDecompressor();
+ int payload_length;
+ int header_length;
+ const char* payload;
+ bool read_successfully = true;
+ bool more = true;
+ char buffer[kHeaderDataChunkMaxSize];
+
+ if (!GetFrameBoundaries(
+ *control_frame, &payload_length, &header_length, &payload)) {
+ DLOG(ERROR) << "Control frame of type "
+ << SpdyFramer::ControlTypeToString(control_frame->type())
+ <<" doesn't have headers";
+ return false;
+ }
+ decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
+ decomp->avail_in = payload_length;
+ const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+ DCHECK_LT(0u, stream_id);
+ while (more && read_successfully) {
+ decomp->next_out = reinterpret_cast<Bytef*>(buffer);
+ decomp->avail_out = arraysize(buffer);
+ int rv = DecompressHeaderBlockInZStream(decomp);
+ if (rv != Z_OK) {
+ set_error(SPDY_DECOMPRESS_FAILURE);
+ DLOG(WARNING) << "inflate failure: " << rv;
+ more = read_successfully = false;
+ } else {
+ DCHECK_GT(arraysize(buffer), decomp->avail_out);
+ size_t len = arraysize(buffer) - decomp->avail_out;
+ read_successfully = visitor_->OnControlFrameHeaderData(stream_id, buffer,
+ len);
+ if (!read_successfully) {
+ // Assume that the problem was the header block was too large for the
+ // visitor.
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ }
+ more = decomp->avail_in > 0;
+ }
+ }
+ return read_successfully;
+}
+
+// Incrementally decompress the control frame's header block, feeding the
+// result to the visitor in chunks. Continue this until the visitor
+// indicates that it cannot process any more data, or (more commonly) we
+// run out of data to deliver.
+bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData(
+ const SpdyControlFrame* control_frame,
+ const char* data,
+ size_t len) {
+ // Get a decompressor or set error.
+ z_stream* decomp = GetHeaderDecompressor();
+ if (decomp == NULL) {
+ LOG(DFATAL) << "Couldn't get decompressor for handling compressed headers.";
+ set_error(SPDY_DECOMPRESS_FAILURE);
+ return false;
+ }
+
+ bool processed_successfully = true;
+ char buffer[kHeaderDataChunkMaxSize];
+
+ decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data));
+ decomp->avail_in = len;
+ const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+ DCHECK_LT(0u, stream_id);
+ while (decomp->avail_in > 0 && processed_successfully) {
+ decomp->next_out = reinterpret_cast<Bytef*>(buffer);
+ decomp->avail_out = arraysize(buffer);
+ int rv = DecompressHeaderBlockInZStream(decomp);
+ if (rv != Z_OK) {
+ set_error(SPDY_DECOMPRESS_FAILURE);
+ DLOG(WARNING) << "inflate failure: " << rv;
+ processed_successfully = false;
+ } else {
+ size_t decompressed_len = arraysize(buffer) - decomp->avail_out;
+ if (decompressed_len > 0) {
+ processed_successfully = visitor_->OnControlFrameHeaderData(
+ stream_id, buffer, decompressed_len);
+ }
+ if (!processed_successfully) {
+ // Assume that the problem was the header block was too large for the
+ // visitor.
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ }
+ }
+ }
+ return processed_successfully;
+}
+
+bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData(
+ const SpdyControlFrame* control_frame, const char* data, size_t len) {
+ bool read_successfully = true;
+ const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+ DCHECK_LT(0u, stream_id);
+ while (read_successfully && len > 0) {
+ size_t bytes_to_deliver = std::min(len, kHeaderDataChunkMaxSize);
+ read_successfully = visitor_->OnControlFrameHeaderData(stream_id, data,
+ bytes_to_deliver);
+ data += bytes_to_deliver;
+ len -= bytes_to_deliver;
+ if (!read_successfully) {
+ // Assume that the problem was the header block was too large for the
+ // visitor.
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ }
+ }
+ return read_successfully;
+}
+
+size_t SpdyFramer::GetMinimumControlFrameSize(SpdyControlType type) {
+ switch (type) {
+ case SYN_STREAM:
+ return SpdySynStreamControlFrame::size();
+ case SYN_REPLY:
+ return SpdySynReplyControlFrame::size();
+ case RST_STREAM:
+ return SpdyRstStreamControlFrame::size();
+ case SETTINGS:
+ return SpdySettingsControlFrame::size();
+ case NOOP:
+ return SpdyNoOpControlFrame::size();
+ case PING:
+ return SpdyPingControlFrame::size();
+ case GOAWAY:
+ return SpdyGoAwayControlFrame::size();
+ case HEADERS:
+ return SpdyHeadersControlFrame::size();
+ case WINDOW_UPDATE:
+ return SpdyWindowUpdateControlFrame::size();
+ case NUM_CONTROL_FRAME_TYPES:
+ break;
+ }
+ LOG(ERROR) << "Unknown SPDY control frame type " << type;
+ return 0x7FFFFFFF; // Max signed 32bit int
+}
+
+/* static */
+SpdyStreamId SpdyFramer::GetControlFrameStreamId(
+ const SpdyControlFrame* control_frame) {
+ SpdyStreamId stream_id = kInvalidStream;
+ if (control_frame != NULL) {
+ switch (control_frame->type()) {
+ case SYN_STREAM:
+ stream_id = reinterpret_cast<const SpdySynStreamControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case SYN_REPLY:
+ stream_id = reinterpret_cast<const SpdySynReplyControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case HEADERS:
+ stream_id = reinterpret_cast<const SpdyHeadersControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case RST_STREAM:
+ stream_id = reinterpret_cast<const SpdyRstStreamControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ case WINDOW_UPDATE:
+ stream_id = reinterpret_cast<const SpdyWindowUpdateControlFrame*>(
+ control_frame)->stream_id();
+ break;
+ // All of the following types are not part of a particular stream.
+ // They all fall through to the invalid control frame type case.
+ // (The default case isn't used so that the compile will break if a new
+ // control frame type is added but not included here.)
+ case SETTINGS:
+ case NOOP:
+ case PING:
+ case GOAWAY:
+ case NUM_CONTROL_FRAME_TYPES: // makes compiler happy
+ break;
+ }
+ }
+ return stream_id;
+}
+
+void SpdyFramer::set_validate_control_frame_sizes(bool value) {
+ validate_control_frame_sizes_ = value;
+}
+
SpdyDataFrame* SpdyFramer::DecompressDataFrame(const SpdyDataFrame& frame) {
z_stream* decompressor = GetStreamDecompressor(frame.stream_id());
if (!decompressor)
@@ -1121,10 +1618,15 @@ size_t SpdyFramer::BytesSafeToRead() const {
return SpdyFrame::size() - current_frame_len_;
case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
return 0;
+ // TODO(rtenneti): Add support for SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
+ // and SPDY_CONTROL_FRAME_HEADER_BLOCK.
+ case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
+ case SPDY_CONTROL_FRAME_HEADER_BLOCK:
+ return 0;
case SPDY_CONTROL_FRAME_PAYLOAD:
case SPDY_IGNORE_REMAINING_PAYLOAD:
case SPDY_FORWARD_STREAM_FRAME:
- return remaining_payload_;
+ return remaining_data_;
}
// We should never get to here.
return 0;
@@ -1139,7 +1641,7 @@ void SpdyFramer::set_error(SpdyError error) {
void SpdyFramer::ExpandControlFrameBuffer(size_t size) {
size_t alloc_size = size + SpdyFrame::size();
- DCHECK_LT(alloc_size, kControlFrameBufferMaxSize);
+ DCHECK_LE(alloc_size, kControlFrameBufferMaxSize);
if (alloc_size <= current_frame_capacity_)
return;
char* new_buffer = new char[alloc_size];
diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h
index 332a47f..b861a3f 100644
--- a/net/spdy/spdy_framer.h
+++ b/net/spdy/spdy_framer.h
@@ -58,9 +58,28 @@ class SpdyFramerVisitorInterface {
// Called if an error is detected in the SpdyFrame protocol.
virtual void OnError(SpdyFramer* framer) = 0;
- // Called when a Control Frame is received.
+ // Called when a control frame is received.
virtual void OnControl(const SpdyControlFrame* frame) = 0;
+ // Called when a chunk of header data is available. This is called
+ // after OnControl() is called with the control frame associated with the
+ // header data being delivered here.
+ // |stream_id| The stream receiving the header data.
+ // |header_data| A buffer containing the header data chunk received.
+ // |len| The length of the header data buffer. A length of zero indicates
+ // that the header data block has been completely sent.
+ // When this function returns true the visitor indicates that it accepted
+ // all of the data. Returning false indicates that that an unrecoverable
+ // error has occurred, such as bad header data or resource exhaustion.
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) = 0;
+
+ // Called when a data frame header is received. The frame's data
+ // payload will be provided via subsequent calls to
+ // OnStreamFrameData().
+ virtual void OnDataFrameHeader(const SpdyDataFrame* frame) = 0;
+
// Called when data is received.
// |stream_id| The stream receiving data.
// |data| A buffer containing the data received.
@@ -86,7 +105,9 @@ class SpdyFramer {
SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER,
SPDY_CONTROL_FRAME_PAYLOAD,
SPDY_IGNORE_REMAINING_PAYLOAD,
- SPDY_FORWARD_STREAM_FRAME
+ SPDY_FORWARD_STREAM_FRAME,
+ SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK,
+ SPDY_CONTROL_FRAME_HEADER_BLOCK,
};
// SPDY error codes.
@@ -102,6 +123,14 @@ class SpdyFramer {
LAST_ERROR, // Must be the last entry in the enum.
};
+ // Constant for invalid (or unknown) stream IDs.
+ static const SpdyStreamId kInvalidStream;
+
+ // The maximum size of header data chunks delivered to the framer visitor
+ // through OnControlFrameHeaderData. (It is exposed here for unit test
+ // purposes.)
+ static const size_t kHeaderDataChunkMaxSize;
+
// Create a new Framer.
SpdyFramer();
virtual ~SpdyFramer();
@@ -138,6 +167,14 @@ class SpdyFramer {
// Returns true if successfully parsed, false otherwise.
bool ParseHeaderBlock(const SpdyFrame* frame, SpdyHeaderBlock* block);
+ // Given a buffer containing a decompressed header block in SPDY
+ // serialized format, parse out a SpdyHeaderBlock, putting the results
+ // in the given header block.
+ // Returns true if successfully parsed, false otherwise.
+ static bool ParseHeaderBlockInBuffer(const char* header_data,
+ size_t header_length,
+ SpdyHeaderBlock* block);
+
// Create a SpdySynStreamControlFrame.
// |stream_id| is the id for this stream.
// |associated_stream_id| is the associated stream id for this stream.
@@ -151,7 +188,7 @@ class SpdyFramer {
int priority,
SpdyControlFlags flags,
bool compressed,
- SpdyHeaderBlock* headers);
+ const SpdyHeaderBlock* headers);
// Create a SpdySynReplyControlFrame.
// |stream_id| is the stream for this frame.
@@ -162,7 +199,7 @@ class SpdyFramer {
SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id,
SpdyControlFlags flags,
bool compressed,
- SpdyHeaderBlock* headers);
+ const SpdyHeaderBlock* headers);
static SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id,
SpdyStatusCodes status);
@@ -172,7 +209,11 @@ class SpdyFramer {
// TODO(mbelshe): add the name/value pairs!!
static SpdySettingsControlFrame* CreateSettings(const SpdySettings& values);
- static SpdyControlFrame* CreateNopFrame();
+ static SpdyNoOpControlFrame* CreateNopFrame();
+
+ // Creates an instance of SpdyPingControlFrame. The unique_id is used to
+ // identify the ping request/response.
+ static SpdyPingControlFrame* CreatePingFrame(uint32 unique_id);
// Creates an instance of SpdyGoAwayControlFrame. The GOAWAY frame is used
// prior to the shutting down of the TCP connection, and includes the
@@ -187,7 +228,7 @@ class SpdyFramer {
SpdyHeadersControlFrame* CreateHeaders(SpdyStreamId stream_id,
SpdyControlFlags flags,
bool compressed,
- SpdyHeaderBlock* headers);
+ const SpdyHeaderBlock* headers);
// Creates an instance of SpdyWindowUpdateControlFrame. The WINDOW_UPDATE
// frame is used to implement per stream flow control in SPDY.
@@ -242,9 +283,29 @@ class SpdyFramer {
// Returns true if a frame could be compressed.
bool IsCompressible(const SpdyFrame& frame) const;
+ // Get the minimum size of the control frame for the given control frame
+ // type. This is useful for validating frame blocks.
+ static size_t GetMinimumControlFrameSize(SpdyControlType type);
+
+ // Get the stream ID for the given control frame (SYN_STREAM, SYN_REPLY, and
+ // HEADERS). If the control frame is NULL or of another type, this
+ // function returns kInvalidStream.
+ static SpdyStreamId GetControlFrameStreamId(
+ const SpdyControlFrame* control_frame);
+
+ // For ease of testing and experimentation we can tweak compression on/off.
+ void set_enable_compression(bool value);
+
+ // SPDY will by default validate the length of incoming control
+ // frames. Set validation to false if you do not want this behavior.
+ void set_validate_control_frame_sizes(bool value);
+ static void set_enable_compression_default(bool value);
+
// For debugging.
static const char* StateToString(int state);
static const char* ErrorCodeToString(int error_code);
+ static const char* StatusCodeToString(int status_code);
+ static const char* ControlTypeToString(SpdyControlType type);
static void set_protocol_version(int version) { spdy_version_= version; }
static int protocol_version() { return spdy_version_; }
@@ -271,14 +332,11 @@ class SpdyFramer {
friend void test::FramerSetEnableCompressionHelper(SpdyFramer* 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);
-
-
// The initial size of the control frame buffer; this is used internally
// as we parse through control frames. (It is exposed here for unit test
// purposes.)
+ // This is only used when compression is enabled; otherwise,
+ // kUncompressedControlFrameBufferInitialSize is used.
static size_t kControlFrameBufferInitialSize;
// The maximum size of the control frame buffer that we support.
@@ -293,6 +351,11 @@ class SpdyFramer {
size_t ProcessCommonHeader(const char* data, size_t len);
void ProcessControlFrameHeader();
size_t ProcessControlFramePayload(const char* data, size_t len);
+ size_t ProcessControlFrameBeforeHeaderBlock(const char* data, size_t len);
+ // TODO(hkhalil): Rename to ProcessControlFrameHeaderBlock once said flag is
+ // removed, replacing the old method.
+ size_t NewProcessControlFrameHeaderBlock(const char* data, size_t len);
+ size_t ProcessControlFrameHeaderBlock(const char* data, size_t len);
size_t ProcessDataFramePayload(const char* data, size_t len);
// Get (and lazily initialize) the ZLib state.
@@ -317,6 +380,36 @@ class SpdyFramer {
// Not used (yet)
size_t BytesSafeToRead() const;
+ // Deliver the given control frame's compressed headers block to the visitor
+ // in decompressed form, in chunks. Returns true if the visitor has
+ // accepted all of the chunks.
+ bool IncrementallyDecompressControlFrameHeaderData(
+ const SpdyControlFrame* frame);
+
+ // Deliver the given control frame's compressed headers block to the visitor
+ // in decompressed form, in chunks. Returns true if the visitor has
+ // accepted all of the chunks.
+ bool IncrementallyDecompressControlFrameHeaderData(
+ const SpdyControlFrame* frame,
+ const char* data,
+ size_t len);
+
+ // Deliver the given control frame's uncompressed headers block to the
+ // visitor in chunks. Returns true if the visitor has accepted all of the
+ // chunks.
+ bool IncrementallyDeliverControlFrameHeaderData(const SpdyControlFrame* frame,
+ const char* data,
+ size_t len);
+
+ // Utility to copy the given data block to the current frame buffer, up
+ // to the given maximum number of bytes, and update the buffer
+ // data (pointer and length). Returns the number of bytes
+ // read, and:
+ // *data is advanced the number of bytes read.
+ // *len is reduced by the number of bytes read.
+ size_t UpdateCurrentFrameBuffer(const char** data, size_t* len,
+ size_t max_bytes);
+
// Set the error code and moves the framer into the error state.
void set_error(SpdyError error);
@@ -331,15 +424,38 @@ class SpdyFramer {
int num_stream_compressors() const { return stream_compressors_.size(); }
int num_stream_decompressors() const { return stream_decompressors_.size(); }
+ // The initial size of the control frame buffer when compression is disabled.
+ // This exists because we don't do stream (de)compressed control frame data to
+ // our visitor; we instead buffer the entirety of the control frame and then
+ // decompress in one fell swoop.
+ // Since this is only used for control frame headers, the maximum control
+ // frame header size (18B) is sufficient; all remaining control frame data is
+ // streamed to the visitor.
+ // In addition to the maximum control frame header size, we account for any
+ // LOAS checksumming (16B) that may occur in the VTL case.
+ static const size_t kUncompressedControlFrameBufferInitialSize = 18 + 16;
+
+ // The size of the buffer into which compressed frames are inflated.
+ static const size_t kDecompressionBufferSize = 8 * 1024;
+
SpdyState state_;
SpdyError error_code_;
- size_t remaining_payload_;
+ size_t remaining_data_;
+
+ // The number of bytes remaining to read from the current control frame's
+ // payload.
size_t remaining_control_payload_;
+ // The number of bytes remaining to read from the current control frame's
+ // headers. Note that header data blocks (for control types that have them)
+ // are part of the frame's payload, and not the frame's headers.
+ size_t remaining_control_header_;
+
char* current_frame_buffer_;
size_t current_frame_len_; // Number of bytes read into the current_frame_.
size_t current_frame_capacity_;
+ bool validate_control_frame_sizes_;
bool enable_compression_; // Controls all compression
// SPDY header compressors.
scoped_ptr<z_stream> header_compressor_;
diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_test.cc
index a4f7b81..157bf74 100644
--- a/net/spdy/spdy_framer_test.cc
+++ b/net/spdy/spdy_framer_test.cc
@@ -142,6 +142,17 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface {
++fin_flag_count_;
}
+ bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ DCHECK(false);
+ return false;
+ }
+
+ void OnDataFrameHeader(const SpdyDataFrame* frame) {
+ DCHECK(false);
+ }
+
// Convenience function which runs a framer simulation with particular input.
void SimulateInFramer(const unsigned char* input, size_t size) {
framer_.set_enable_compression(false);
diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h
index aa28b3c..48fde83 100644
--- a/net/spdy/spdy_protocol.h
+++ b/net/spdy/spdy_protocol.h
@@ -191,8 +191,10 @@ enum SpdySettingsFlags {
enum SpdySettingsIds {
SETTINGS_UPLOAD_BANDWIDTH = 0x1,
SETTINGS_DOWNLOAD_BANDWIDTH = 0x2,
+ // Network round trip time in milliseconds.
SETTINGS_ROUND_TRIP_TIME = 0x3,
SETTINGS_MAX_CONCURRENT_STREAMS = 0x4,
+ // TCP congestion window in packets.
SETTINGS_CURRENT_CWND = 0x5,
// Downstream byte retransmission rate in percentage.
SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6,
@@ -278,6 +280,15 @@ struct SpdySettingsControlFrameBlock : SpdyFrameBlock {
// Variable data here.
};
+// A NOOP Control Frame structure.
+struct SpdyNoopControlFrameBlock : SpdyFrameBlock {
+};
+
+// A PING Control Frame structure.
+struct SpdyPingControlFrameBlock : SpdyFrameBlock {
+ uint32 unique_id_;
+};
+
// A GOAWAY Control Frame structure.
struct SpdyGoAwayControlFrameBlock : SpdyFrameBlock {
SpdyStreamId last_accepted_stream_id_;
@@ -297,6 +308,7 @@ struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock {
// A structure for the 8 bit flags and 24 bit ID fields.
union SettingsFlagsAndId {
+ // Sets both flags and id to the value for flags-and-id as sent over the wire
SettingsFlagsAndId(uint32 val) : id_(val) {}
uint8 flags() const { return flags_[0]; }
void set_flags(uint8 flags) { flags_[0] = flags; }
@@ -451,10 +463,25 @@ class SpdyControlFrame : public SpdyFrame {
mutable_block()->control_.type_ = htons(type);
}
+ // Returns true if this control frame is of a type that has a header block,
+ // otherwise it returns false.
+ bool has_header_block() const {
+ return type() == SYN_STREAM || type() == SYN_REPLY || type() == HEADERS;
+ }
+
// Returns the size of the SpdyFrameBlock structure.
// Note: this is not the size of the SpdyControlFrame class.
static size_t size() { return sizeof(SpdyFrameBlock); }
+ // The size of the 'Number of Name/Value pairs' field in a Name/Value block.
+ static const size_t kNumNameValuePairsSize = 2;
+
+ // The size of the 'Length of a name' field in a Name/Value block.
+ static const size_t kLengthOfNameSize = 2;
+
+ // The size of the 'Length of a value' field in a Name/Value block.
+ static const size_t kLengthOfValueSize = 2;
+
private:
const struct SpdyFrameBlock* block() const {
return frame_;
@@ -568,7 +595,12 @@ class SpdyRstStreamControlFrame : public SpdyControlFrame {
}
SpdyStatusCodes status() const {
- return static_cast<SpdyStatusCodes>(ntohl(block()->status_));
+ SpdyStatusCodes status =
+ static_cast<SpdyStatusCodes>(ntohl(block()->status_));
+ if (status < INVALID || status >= NUM_STATUS_CODES) {
+ status = INVALID;
+ }
+ return status;
}
void set_status(SpdyStatusCodes status) {
mutable_block()->status_ = htonl(static_cast<uint32>(status));
@@ -624,6 +656,40 @@ class SpdySettingsControlFrame : public SpdyControlFrame {
DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame);
};
+class SpdyNoOpControlFrame : public SpdyControlFrame {
+ public:
+ SpdyNoOpControlFrame() : SpdyControlFrame(size()) {}
+ SpdyNoOpControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ static size_t size() { return sizeof(SpdyNoopControlFrameBlock); }
+};
+
+class SpdyPingControlFrame : public SpdyControlFrame {
+ public:
+ SpdyPingControlFrame() : SpdyControlFrame(size()) {}
+ SpdyPingControlFrame(char* data, bool owns_buffer)
+ : SpdyControlFrame(data, owns_buffer) {}
+
+ uint32 unique_id() const {
+ return ntohl(block()->unique_id_);
+ }
+
+ void set_unique_id(uint32 unique_id) {
+ mutable_block()->unique_id_ = htonl(unique_id);
+ }
+
+ static size_t size() { return sizeof(SpdyPingControlFrameBlock); }
+
+ private:
+ const struct SpdyPingControlFrameBlock* block() const {
+ return static_cast<SpdyPingControlFrameBlock*>(frame_);
+ }
+ struct SpdyPingControlFrameBlock* mutable_block() {
+ return static_cast<SpdyPingControlFrameBlock*>(frame_);
+ }
+};
+
class SpdyGoAwayControlFrame : public SpdyControlFrame {
public:
SpdyGoAwayControlFrame() : SpdyControlFrame(size()) {}
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index e88d587..08b9d92 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -172,6 +172,23 @@ class NetLogSpdyRstParameter : public NetLog::EventParameters {
DISALLOW_COPY_AND_ASSIGN(NetLogSpdyRstParameter);
};
+class NetLogSpdyPingParameter : public NetLog::EventParameters {
+ public:
+ explicit NetLogSpdyPingParameter(uint32 unique_id) : unique_id_(unique_id) {}
+
+ virtual Value* ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("unique_id", unique_id_);
+ return dict;
+ }
+
+ private:
+ ~NetLogSpdyPingParameter() {}
+ const uint32 unique_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogSpdyPingParameter);
+};
+
class NetLogSpdyGoAwayParameter : public NetLog::EventParameters {
public:
NetLogSpdyGoAwayParameter(spdy::SpdyStreamId last_stream_id,
@@ -210,6 +227,18 @@ bool SpdySession::use_flow_control_ = false;
// static
size_t SpdySession::max_concurrent_stream_limit_ = 256;
+// static
+bool SpdySession::enable_ping_based_connection_checking_ = true;
+
+// static
+int SpdySession::connection_at_risk_of_loss_ms_ = 0;
+
+// static
+int SpdySession::trailing_ping_delay_time_ms_ = 1000;
+
+// static
+int SpdySession::hung_interval_ms_ = 10000;
+
SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
SpdySessionPool* spdy_session_pool,
SpdySettingsStorage* spdy_settings,
@@ -242,6 +271,12 @@ SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
sent_settings_(false),
received_settings_(false),
stalled_streams_(0),
+ pings_in_flight_(0),
+ next_ping_id_(1),
+ received_data_time_(base::TimeTicks::Now()),
+ trailing_ping_pending_(false),
+ check_ping_status_pending_(false),
+ need_to_send_ping_(false),
initial_send_window_size_(spdy::kSpdyStreamInitialWindowSize),
initial_recv_window_size_(spdy::kSpdyStreamInitialWindowSize),
net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)) {
@@ -466,6 +501,8 @@ int SpdySession::WriteSynStream(
const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id];
CHECK_EQ(stream->stream_id(), stream_id);
+ SendPrefacePingIfNoneInFlight();
+
scoped_ptr<spdy::SpdySynStreamControlFrame> syn_frame(
spdy_framer_.CreateSynStream(
stream_id, 0,
@@ -484,6 +521,12 @@ int SpdySession::WriteSynStream(
new NetLogSpdySynParameter(headers, flags, stream_id, 0)));
}
+ // Some servers don't like too many pings, so we limit our current sending to
+ // no more than one ping for any syn sent. To do this, we avoid ever setting
+ // this to true unless we send a syn (which we have just done). This approach
+ // may change over time as servers change their responses to pings.
+ need_to_send_ping_ = true;
+
return ERR_IO_PENDING;
}
@@ -497,6 +540,8 @@ int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id,
if (!stream)
return ERR_INVALID_SPDY_STREAM;
+ SendPrefacePingIfNoneInFlight();
+
if (len > kMaxSpdyFrameChunkSize) {
len = kMaxSpdyFrameChunkSize;
flags = static_cast<spdy::SpdyDataFlags>(flags & ~spdy::DATA_FLAG_FIN);
@@ -563,7 +608,6 @@ void SpdySession::ResetStream(
priority = stream->priority();
}
QueueFrame(rst_frame.get(), priority, NULL);
-
DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
}
@@ -604,6 +648,8 @@ void SpdySession::OnReadComplete(int bytes_read) {
bytes_received_ += bytes_read;
+ received_data_time_ = base::TimeTicks::Now();
+
// The SpdyFramer will use callbacks onto |this| as it parses frames.
// When errors occur, those callbacks can lead to teardown of all references
// to |this|, so maintain a reference to self during this call for safe
@@ -1221,6 +1267,9 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) {
case spdy::GOAWAY:
OnGoAway(*reinterpret_cast<const spdy::SpdyGoAwayControlFrame*>(frame));
break;
+ case spdy::PING:
+ OnPing(*reinterpret_cast<const spdy::SpdyPingControlFrame*>(frame));
+ break;
case spdy::SETTINGS:
OnSettings(
*reinterpret_cast<const spdy::SpdySettingsControlFrame*>(frame));
@@ -1250,6 +1299,17 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) {
}
}
+bool SpdySession::OnControlFrameHeaderData(spdy::SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ DCHECK(false);
+ return false;
+}
+
+void SpdySession::OnDataFrameHeader(const spdy::SpdyDataFrame* frame) {
+ DCHECK(false);
+}
+
void SpdySession::OnRst(const spdy::SpdyRstStreamControlFrame& frame) {
spdy::SpdyStreamId stream_id = frame.stream_id();
@@ -1296,6 +1356,32 @@ void SpdySession::OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) {
// closed.
}
+void SpdySession::OnPing(const spdy::SpdyPingControlFrame& frame) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_PING,
+ make_scoped_refptr(new NetLogSpdyPingParameter(frame.unique_id())));
+
+ // Send response to a PING from server.
+ if (frame.unique_id() % 2 == 0) {
+ WritePingFrame(frame.unique_id());
+ return;
+ }
+
+ --pings_in_flight_;
+ if (pings_in_flight_ < 0) {
+ CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR, true);
+ return;
+ }
+
+ if (pings_in_flight_ > 0)
+ return;
+
+ if (!need_to_send_ping_)
+ return;
+
+ PlanToSendTrailingPing();
+}
+
void SpdySession::OnSettings(const spdy::SpdySettingsControlFrame& frame) {
spdy::SpdySettings settings;
if (spdy_framer_.ParseSettings(&frame, &settings)) {
@@ -1434,6 +1520,103 @@ void SpdySession::HandleSettings(const spdy::SpdySettings& settings) {
}
}
+void SpdySession::SendPrefacePingIfNoneInFlight() {
+ if (pings_in_flight_ || trailing_ping_pending_ ||
+ !enable_ping_based_connection_checking_)
+ return;
+
+ const base::TimeDelta kConnectionAtRiskOfLoss =
+ base::TimeDelta::FromMilliseconds(connection_at_risk_of_loss_ms_);
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ // If we haven't heard from server, then send a preface-PING.
+ if ((now - received_data_time_) > kConnectionAtRiskOfLoss)
+ SendPrefacePing();
+
+ PlanToSendTrailingPing();
+}
+
+void SpdySession::SendPrefacePing() {
+ // TODO(rtenneti): Send preface pings when more servers support additional
+ // pings.
+ // WritePingFrame(next_ping_id_);
+}
+
+void SpdySession::PlanToSendTrailingPing() {
+ if (trailing_ping_pending_)
+ return;
+
+ trailing_ping_pending_ = true;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(&SpdySession::SendTrailingPing),
+ trailing_ping_delay_time_ms_);
+}
+
+void SpdySession::SendTrailingPing() {
+ DCHECK(trailing_ping_pending_);
+ trailing_ping_pending_ = false;
+ WritePingFrame(next_ping_id_);
+}
+
+void SpdySession::WritePingFrame(uint32 unique_id) {
+ scoped_ptr<spdy::SpdyPingControlFrame> ping_frame(
+ spdy_framer_.CreatePingFrame(next_ping_id_));
+ QueueFrame(ping_frame.get(), SPDY_PRIORITY_HIGHEST, NULL);
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_PING,
+ make_scoped_refptr(new NetLogSpdyPingParameter(next_ping_id_)));
+ }
+ if (unique_id % 2 != 0) {
+ next_ping_id_ += 2;
+ ++pings_in_flight_;
+ need_to_send_ping_ = false;
+ PlanToCheckPingStatus();
+ }
+}
+
+void SpdySession::PlanToCheckPingStatus() {
+ if (check_ping_status_pending_)
+ return;
+
+ check_ping_status_pending_ = true;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &SpdySession::CheckPingStatus, base::TimeTicks::Now()),
+ hung_interval_ms_);
+}
+
+void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) {
+ // Check if we got a response back for all PINGs we had sent.
+ if (pings_in_flight_ == 0) {
+ check_ping_status_pending_ = false;
+ return;
+ }
+
+ DCHECK(check_ping_status_pending_);
+
+ const base::TimeDelta kHungInterval =
+ base::TimeDelta::FromMilliseconds(hung_interval_ms_);
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta delay = kHungInterval - (now - received_data_time_);
+
+ if (delay.InMilliseconds() < 0 || received_data_time_ < last_check_time) {
+ DCHECK(now - received_data_time_ > kHungInterval);
+ CloseSessionOnError(net::ERR_SPDY_PING_FAILED, true);
+ return;
+ }
+
+ // Check the status of connection after a delay.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(&SpdySession::CheckPingStatus, now),
+ delay.InMilliseconds());
+}
+
void SpdySession::RecordHistograms() {
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession",
streams_initiated_count_,
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
index ed00e4f..39480b9 100644
--- a/net/spdy/spdy_session.h
+++ b/net/spdy/spdy_session.h
@@ -103,7 +103,8 @@ class SpdySession : public base::RefCounted<SpdySession>,
// NOTE: This function can have false negatives on some platforms.
bool VerifyDomainAuthentication(const std::string& domain);
- // Send the SYN frame for |stream_id|.
+ // Send the SYN frame for |stream_id|. This also sends PING message to check
+ // the status of the connection.
int WriteSynStream(
spdy::SpdyStreamId stream_id,
RequestPriority priority,
@@ -154,6 +155,14 @@ class SpdySession : public base::RefCounted<SpdySession>,
return max_concurrent_stream_limit_;
}
+ // Enable sending of PING frame with each request.
+ static void set_enable_ping_based_connection_checking(bool enable) {
+ enable_ping_based_connection_checking_ = enable;
+ }
+ static bool enable_ping_based_connection_checking() {
+ return enable_ping_based_connection_checking_;
+ }
+
// Send WINDOW_UPDATE frame, called by a stream whenever receive window
// size is increased.
void SendWindowUpdate(spdy::SpdyStreamId stream_id, int delta_window_size);
@@ -208,6 +217,8 @@ class SpdySession : public base::RefCounted<SpdySession>,
private:
friend class base::RefCounted<SpdySession>;
+ // Allow tests to access our innards for testing purposes.
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, Ping);
FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GetActivePushStream);
struct PendingCreateStream {
@@ -270,6 +281,7 @@ class SpdySession : public base::RefCounted<SpdySession>,
const linked_ptr<spdy::SpdyHeaderBlock>& headers);
void OnRst(const spdy::SpdyRstStreamControlFrame& frame);
void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame);
+ void OnPing(const spdy::SpdyPingControlFrame& frame);
void OnSettings(const spdy::SpdySettingsControlFrame& frame);
void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame);
@@ -284,6 +296,31 @@ class SpdySession : public base::RefCounted<SpdySession>,
// SETTINGS ontrol frame, update our SpdySession accordingly.
void HandleSettings(const spdy::SpdySettings& settings);
+ // Send the PING (preface-PING and trailing-PING) frames.
+ void SendPrefacePingIfNoneInFlight();
+
+ // Send PING if there are no PINGs in flight and we haven't heard from server.
+ void SendPrefacePing();
+
+ // Send a PING after delay. Don't post a PING if there is already
+ // a trailing PING pending.
+ void PlanToSendTrailingPing();
+
+ // Send a PING if there is no |trailing_ping_pending_|. This PING verifies
+ // that the requests are being received by the server.
+ void SendTrailingPing();
+
+ // Send the PING frame.
+ void WritePingFrame(uint32 unique_id);
+
+ // Post a CheckPingStatus call after delay. Don't post if there is already
+ // CheckPingStatus running.
+ void PlanToCheckPingStatus();
+
+ // Check the status of the connection. It calls |CloseSessionOnError| if we
+ // haven't received any data in |kHungInterval| time period.
+ void CheckPingStatus(base::TimeTicks last_check_time);
+
// Start reading from the socket.
// Returns OK on success, or an error on failure.
net::Error ReadSocket();
@@ -335,6 +372,46 @@ class SpdySession : public base::RefCounted<SpdySession>,
size_t len);
virtual void OnControl(const spdy::SpdyControlFrame* frame);
+ virtual bool OnControlFrameHeaderData(spdy::SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len);
+
+ virtual void OnDataFrameHeader(const spdy::SpdyDataFrame* frame);
+
+ // --------------------------
+ // Helper methods for testing
+ // --------------------------
+ static void set_connection_at_risk_of_loss_ms(int duration) {
+ connection_at_risk_of_loss_ms_ = duration;
+ }
+ static int connection_at_risk_of_loss_ms() {
+ return connection_at_risk_of_loss_ms_;
+ }
+
+ static void set_trailing_ping_delay_time_ms(int duration) {
+ trailing_ping_delay_time_ms_ = duration;
+ }
+ static int trailing_ping_delay_time_ms() {
+ return trailing_ping_delay_time_ms_;
+ }
+
+ static void set_hung_interval_ms(int duration) {
+ hung_interval_ms_ = duration;
+ }
+ static int hung_interval_ms() {
+ return hung_interval_ms_;
+ }
+
+ int64 pings_in_flight() const { return pings_in_flight_; }
+
+ uint32 next_ping_id() const { return next_ping_id_; }
+
+ base::TimeTicks received_data_time() const { return received_data_time_; }
+
+ bool trailing_ping_pending() const { return trailing_ping_pending_; }
+
+ bool check_ping_status_pending() const { return check_ping_status_pending_; }
+
// Callbacks for the Spdy session.
CompletionCallbackImpl<SpdySession> read_callback_;
CompletionCallbackImpl<SpdySession> write_callback_;
@@ -424,6 +501,28 @@ class SpdySession : public base::RefCounted<SpdySession>,
// frame.
int stalled_streams_; // Count of streams that were ever stalled.
+ // Count of all pings on the wire, for which we have not gotten a response.
+ int64 pings_in_flight_;
+
+ // This is the next ping_id (unique_id) to be sent in PING frame.
+ uint32 next_ping_id_;
+
+ // This is the last time we have received data.
+ base::TimeTicks received_data_time_;
+
+ // Indicate if we have already scheduled a delayed task to send a trailing
+ // ping (and we never have more than one scheduled at a time).
+ bool trailing_ping_pending_;
+
+ // Indicate if we have already scheduled a delayed task to check the ping
+ // status.
+ bool check_ping_status_pending_;
+
+ // Indicate if we need to send a ping (generally, a trailing ping). This helps
+ // us to decide if we need yet another trailing ping, or if it would be a
+ // waste of effort (and MUST not be done).
+ bool need_to_send_ping_;
+
// Initial send window size for the session; can be changed by an
// arriving SETTINGS frame; newly created streams use this value for the
// initial send window size.
@@ -440,6 +539,36 @@ class SpdySession : public base::RefCounted<SpdySession>,
static bool use_ssl_;
static bool use_flow_control_;
static size_t max_concurrent_stream_limit_;
+
+ // This enables or disables connection health checking system.
+ static bool enable_ping_based_connection_checking_;
+
+ // |connection_at_risk_of_loss_ms_| is an optimization to avoid sending
+ // wasteful preface pings (when we just got some data).
+ //
+ // If it is zero (the most conservative figure), then we always send the
+ // preface ping (when none are in flight).
+ //
+ // It is common for TCP/IP sessions to time out in about 3-5 minutes.
+ // Certainly if it has been more than 3 minutes, we do want to send a preface
+ // ping.
+ //
+ // We don't think any connection will time out in under about 10 seconds. So
+ // this might as well be set to something conservative like 10 seconds. Later,
+ // we could adjust it to send fewer pings perhaps.
+ static int connection_at_risk_of_loss_ms_;
+
+ // This is the amount of time (in milliseconds) we wait before sending a
+ // trailing ping. We use a trailing ping (sent after all data) to get an
+ // effective acknowlegement from the server that it has indeed received all
+ // (prior) data frames. With that assurance, we are willing to enter into a
+ // wait state for responses to our last data frame(s) without further pings.
+ static int trailing_ping_delay_time_ms_;
+
+ // The amount of time (in milliseconds) that we are willing to tolerate with
+ // no data received (of any form), while there is a ping in flight, before we
+ // declare the connection to be hung.
+ static int hung_interval_ms_;
};
class NetLogSpdySynParameter : public NetLog::EventParameters {
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
index cdcfb35..7956a83 100644
--- a/net/spdy/spdy_session_unittest.cc
+++ b/net/spdy/spdy_session_unittest.cc
@@ -19,9 +19,53 @@ class SpdySessionTest : public PlatformTest {
static void TurnOffCompression() {
spdy::SpdyFramer::set_enable_compression_default(false);
}
+ protected:
+ virtual void TearDown() {
+ // Wanted to be 100% sure PING is disabled.
+ SpdySession::set_enable_ping_based_connection_checking(false);
+ }
+};
+
+class TestSpdyStreamDelegate : public net::SpdyStream::Delegate {
+ public:
+ explicit TestSpdyStreamDelegate(OldCompletionCallback* callback)
+ : callback_(callback) {}
+ virtual ~TestSpdyStreamDelegate() {}
+
+ virtual bool OnSendHeadersComplete(int status) { return true; }
+
+ virtual int OnSendBody() {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
+ base::Time response_time,
+ int status) {
+ return status;
+ }
+
+ virtual void OnDataReceived(const char* buffer, int bytes) {
+ }
+
+ virtual void OnDataSent(int length) {
+ }
+
+ virtual void OnClose(int status) {
+ OldCompletionCallback* callback = callback_;
+ callback_ = NULL;
+ callback->Run(OK);
+ }
+
+ virtual void set_chunk_callback(net::ChunkCallback *) {}
+
+ private:
+ OldCompletionCallback* callback_;
};
-namespace {
// Test the SpdyIOBuffer class.
TEST_F(SpdySessionTest, SpdyIOBuffer) {
@@ -121,6 +165,101 @@ TEST_F(SpdySessionTest, GoAway) {
session2 = NULL;
}
+TEST_F(SpdySessionTest, Ping) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(false, OK);
+ scoped_ptr<spdy::SpdyFrame> read_ping(ConstructSpdyPing());
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ CreateMockRead(*read_ping),
+ MockRead(false, 0, 0) // EOF
+ };
+ scoped_ptr<spdy::SpdyFrame> write_ping(ConstructSpdyPing());
+ MockRead writes[] = {
+ CreateMockRead(*write_ping),
+ CreateMockRead(*write_ping),
+ };
+ StaticSocketDataProvider data(
+ reads, arraysize(reads), writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(false, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ static const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ const std::string kTestHost("www.google.com");
+ const int kTestPort = 80;
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+ SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(test_host_port_pair,
+ MEDIUM,
+ GURL(),
+ false,
+ false));
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ EXPECT_EQ(OK,
+ connection->Init(test_host_port_pair.ToString(),
+ transport_params,
+ MEDIUM,
+ NULL,
+ http_session->transport_socket_pool(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+ scoped_refptr<SpdyStream> spdy_stream1;
+ TestOldCompletionCallback callback1;
+ EXPECT_EQ(OK, session->CreateStream(url,
+ MEDIUM,
+ &spdy_stream1,
+ BoundNetLog(),
+ &callback1));
+ scoped_ptr<TestSpdyStreamDelegate> delegate(
+ new TestSpdyStreamDelegate(&callback1));
+ spdy_stream1->SetDelegate(delegate.get());
+
+ base::TimeTicks before_ping_time = base::TimeTicks::Now();
+
+ // Enable sending of PING.
+ SpdySession::set_enable_ping_based_connection_checking(true);
+ SpdySession::set_connection_at_risk_of_loss_ms(0);
+ SpdySession::set_trailing_ping_delay_time_ms(0);
+ SpdySession::set_hung_interval_ms(50);
+
+ session->SendPrefacePingIfNoneInFlight();
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+
+ EXPECT_EQ(0, session->pings_in_flight());
+ EXPECT_GT(session->next_ping_id(), static_cast<uint32>(1));
+ EXPECT_FALSE(session->trailing_ping_pending());
+ // TODO(rtenneti): check_ping_status_pending works in debug mode with
+ // breakpoints, but fails if run in stand alone mode.
+ // EXPECT_FALSE(session->check_ping_status_pending());
+ EXPECT_GE(session->received_data_time(), before_ping_time);
+
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+ // Delete the first session.
+ session = NULL;
+}
+
class StreamReleaserCallback : public CallbackRunner<Tuple1<int> > {
public:
StreamReleaserCallback(SpdySession* session,
@@ -527,6 +666,4 @@ TEST_F(SpdySessionTest, IPPoolingCloseCurrentSessions) {
IPPoolingTest(true);
}
-} // namespace
-
} // namespace net
diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc
index 1adcb24..b19702a 100644
--- a/net/spdy/spdy_test_util.cc
+++ b/net/spdy/spdy_test_util.cc
@@ -194,6 +194,13 @@ spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings) {
return framer.CreateSettings(settings);
}
+// Construct a SPDY PING frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyPing() {
+ spdy::SpdyFramer framer;
+ return framer.CreatePingFrame(1);
+}
+
// Construct a SPDY GOAWAY frame.
// Returns the constructed frame. The caller takes ownership of the frame.
spdy::SpdyFrame* ConstructSpdyGoAway() {
diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h
index 4cfed13..1e8606d 100644
--- a/net/spdy/spdy_test_util.h
+++ b/net/spdy/spdy_test_util.h
@@ -155,6 +155,10 @@ int ConstructSpdyReplyString(const char* const extra_headers[],
// Returns the constructed frame. The caller takes ownership of the frame.
spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings);
+// Construct a SPDY PING frame.
+// Returns the constructed frame. The caller takes ownership of the frame.
+spdy::SpdyFrame* ConstructSpdyPing();
+
// Construct a SPDY GOAWAY frame.
// Returns the constructed frame. The caller takes ownership of the frame.
spdy::SpdyFrame* ConstructSpdyGoAway();
diff --git a/net/tools/flip_server/spdy_interface.cc b/net/tools/flip_server/spdy_interface.cc
index 278f0dd..9ce8033 100644
--- a/net/tools/flip_server/spdy_interface.cc
+++ b/net/tools/flip_server/spdy_interface.cc
@@ -278,6 +278,17 @@ void SpdySM::OnControl(const SpdyControlFrame* frame) {
}
}
+bool SpdySM::OnControlFrameHeaderData(spdy::SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ DCHECK(false);
+ return false;
+}
+
+void SpdySM::OnDataFrameHeader(const spdy::SpdyDataFrame* frame) {
+ DCHECK(false);
+}
+
void SpdySM::OnStreamFrameData(SpdyStreamId stream_id,
const char* data, size_t len) {
VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id
diff --git a/net/tools/flip_server/spdy_interface.h b/net/tools/flip_server/spdy_interface.h
index f407d2e..179b10c 100644
--- a/net/tools/flip_server/spdy_interface.h
+++ b/net/tools/flip_server/spdy_interface.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -62,6 +62,10 @@ class SpdySM : public spdy::SpdyFramerVisitorInterface,
// SpdyFramerVisitor interface.
virtual void OnControl(const spdy::SpdyControlFrame* frame);
+ virtual bool OnControlFrameHeaderData(spdy::SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len);
+ virtual void OnDataFrameHeader(const spdy::SpdyDataFrame* frame);
virtual void OnStreamFrameData(spdy::SpdyStreamId stream_id,
const char* data, size_t len);