diff options
-rw-r--r-- | net/base/net_error_list.h | 3 | ||||
-rw-r--r-- | net/base/net_log_event_type_list.h | 7 | ||||
-rw-r--r-- | net/base/run_all_unittests.cc | 3 | ||||
-rw-r--r-- | net/http/http_network_layer.cc | 3 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 6 | ||||
-rw-r--r-- | net/spdy/spdy_framer.cc | 586 | ||||
-rw-r--r-- | net/spdy/spdy_framer.h | 140 | ||||
-rw-r--r-- | net/spdy/spdy_framer_test.cc | 11 | ||||
-rw-r--r-- | net/spdy/spdy_protocol.h | 68 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 185 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 131 | ||||
-rw-r--r-- | net/spdy/spdy_session_unittest.cc | 143 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 7 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 4 | ||||
-rw-r--r-- | net/tools/flip_server/spdy_interface.cc | 11 | ||||
-rw-r--r-- | net/tools/flip_server/spdy_interface.h | 6 |
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(¤t_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(¤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; + 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); |