Backport SPDY ping changes from chrome 16.

	Bug: 5524112

Backported Chrome 16 changes for SPDY Ping. The Chrome changes
are:
http://codereview.chromium.org/8036016
http://codereview.chromium.org/8230037
http://codereview.chromium.org/8319015
http://codereview.chromium.org/8330002

Also enable the SPDY protocol.

Change-Id: I2c25c19135ec1c75361d51fdbfbbbbb19f0a3098
Signed-off-by: Selim Gurun <sgurun@google.com>
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 @@
 // 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_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 @@
   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 @@
     } 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 @@
       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 kCompressorWindowSizeInBits = 11;
 const int kCompressorMemLevel = 1;
 
+// Adler ID for the SPDY header compressor dictionary.
 uLong dictionary_id = 0;
 
 }  // namespace
@@ -68,6 +69,9 @@
 // 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 @@
 #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 @@
   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 @@
       // 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 @@
 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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
 }
 
 /* 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 @@
 }
 
 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 @@
   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 @@
   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 @@
       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 @@
   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 @@
       }
       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 @@
           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 @@
           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 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 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 @@
     }
     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 @@
       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 @@
       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::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 @@
   // 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 @@
     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 @@
     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 @@
   // 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 @@
                                              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 @@
   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 @@
   // 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 @@
   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 @@
   // 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 @@
   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 @@
   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 @@
   // 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 @@
   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 @@
       ++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 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 @@
   // 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 @@
 
 // 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 @@
     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 @@
   }
 
   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 @@
   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 @@
   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 @@
 // 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 @@
       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 @@
   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 @@
             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 @@
   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 @@
     priority = stream->priority();
   }
   QueueFrame(rst_frame.get(), priority, NULL);
-
   DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
 }
 
@@ -604,6 +648,8 @@
 
   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 @@
     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 @@
   }
 }
 
+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 @@
   // 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::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 @@
   // 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 @@
       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 @@
 
  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 @@
                  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 @@
   // 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 @@
                                  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 @@
                             // 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 @@
   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 @@
   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);
+  }
 };
 
-namespace {
+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_;
+};
+
 
 // Test the SpdyIOBuffer class.
 TEST_F(SpdySessionTest, SpdyIOBuffer) {
@@ -121,6 +165,101 @@
   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 @@
   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 @@
   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 @@
 // 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 @@
   }
 }
 
+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 @@
 
   // 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);