WebSocketBasicStream is the basic implementation of the WebSocket protocol over
TCP/IP with no extensions in use.

This CL implements the logic to read and write frames from the stream that is used after connection.

BUG=257680
TEST=net_unittests

Review URL: https://chromiumcodereview.appspot.com/18792002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220573 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/net.gyp b/net/net.gyp
index a189432..d297e26 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -1089,6 +1089,8 @@
         'url_request/url_request_throttler_manager.h',
         'url_request/view_cache_helper.cc',
         'url_request/view_cache_helper.h',
+        'websockets/websocket_basic_stream.cc',
+        'websockets/websocket_basic_stream.h',
         'websockets/websocket_channel.cc',
         'websockets/websocket_channel.h',
         'websockets/websocket_errors.cc',
@@ -1857,6 +1859,7 @@
         'url_request/url_request_throttler_unittest.cc',
         'url_request/url_request_unittest.cc',
         'url_request/view_cache_helper_unittest.cc',
+        'websockets/websocket_basic_stream_test.cc',
         'websockets/websocket_channel_test.cc',
         'websockets/websocket_errors_unittest.cc',
         'websockets/websocket_frame_parser_unittest.cc',
diff --git a/net/websockets/README b/net/websockets/README
index 558a4511..1e01f61 100644
--- a/net/websockets/README
+++ b/net/websockets/README
@@ -30,6 +30,9 @@
 presents a high-level interface to the renderer process similar to a
 multiplexing proxy. This is not yet used in any stable Chromium version.
 
+websocket_basic_stream.cc
+websocket_basic_stream.h
+websocket_basic_stream_test.cc
 websocket_channel.cc
 websocket_channel.h
 websocket_channel_test.cc
diff --git a/net/websockets/websocket_basic_stream.cc b/net/websockets/websocket_basic_stream.cc
new file mode 100644
index 0000000..5b02b18
--- /dev/null
+++ b/net/websockets/websocket_basic_stream.cc
@@ -0,0 +1,259 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_basic_stream.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_frame_parser.h"
+
+namespace net {
+
+namespace {
+
+// The number of bytes to attempt to read at a time.
+// TODO(ricea): See if there is a better number or algorithm to fulfill our
+// requirements:
+//  1. We would like to use minimal memory on low-bandwidth or idle connections
+//  2. We would like to read as close to line speed as possible on
+//     high-bandwidth connections
+//  3. We can't afford to cause jank on the IO thread by copying large buffers
+//     around
+//  4. We would like to hit any sweet-spots that might exist in terms of network
+//     packet sizes / encryption block sizes / IPC alignment issues, etc.
+const int kReadBufferSize = 32 * 1024;
+
+}  // namespace
+
+WebSocketBasicStream::WebSocketBasicStream(
+    scoped_ptr<ClientSocketHandle> connection)
+    : read_buffer_(new IOBufferWithSize(kReadBufferSize)),
+      connection_(connection.Pass()),
+      generate_websocket_masking_key_(&GenerateWebSocketMaskingKey) {
+  DCHECK(connection_->is_initialized());
+}
+
+WebSocketBasicStream::~WebSocketBasicStream() { Close(); }
+
+int WebSocketBasicStream::ReadFrames(
+    ScopedVector<WebSocketFrameChunk>* frame_chunks,
+    const CompletionCallback& callback) {
+  DCHECK(frame_chunks->empty());
+  // If there is data left over after parsing the HTTP headers, attempt to parse
+  // it as WebSocket frames.
+  if (http_read_buffer_) {
+    DCHECK_GE(http_read_buffer_->offset(), 0);
+    // We cannot simply copy the data into read_buffer_, as it might be too
+    // large.
+    scoped_refptr<GrowableIOBuffer> buffered_data;
+    buffered_data.swap(http_read_buffer_);
+    DCHECK(http_read_buffer_.get() == NULL);
+    if (!parser_.Decode(buffered_data->StartOfBuffer(),
+                        buffered_data->offset(),
+                        frame_chunks))
+      return WebSocketErrorToNetError(parser_.websocket_error());
+    if (!frame_chunks->empty())
+      return OK;
+  }
+
+  // Run until socket stops giving us data or we get some chunks.
+  while (true) {
+    // base::Unretained(this) here is safe because net::Socket guarantees not to
+    // call any callbacks after Disconnect(), which we call from the
+    // destructor. The caller of ReadFrames() is required to keep |frame_chunks|
+    // valid.
+    int result = connection_->socket()
+                     ->Read(read_buffer_.get(),
+                            read_buffer_->size(),
+                            base::Bind(&WebSocketBasicStream::OnReadComplete,
+                                       base::Unretained(this),
+                                       base::Unretained(frame_chunks),
+                                       callback));
+    if (result == ERR_IO_PENDING)
+      return result;
+    result = HandleReadResult(result, frame_chunks);
+    if (result != ERR_IO_PENDING)
+      return result;
+  }
+}
+
+int WebSocketBasicStream::WriteFrames(
+    ScopedVector<WebSocketFrameChunk>* frame_chunks,
+    const CompletionCallback& callback) {
+  // This function always concatenates all frames into a single buffer.
+  // TODO(ricea): Investigate whether it would be better in some cases to
+  // perform multiple writes with smaller buffers.
+  //
+  // First calculate the size of the buffer we need to allocate.
+  typedef ScopedVector<WebSocketFrameChunk>::const_iterator Iterator;
+  const int kMaximumTotalSize = std::numeric_limits<int>::max();
+  int total_size = 0;
+  for (Iterator it = frame_chunks->begin(); it != frame_chunks->end(); ++it) {
+    WebSocketFrameChunk* chunk = *it;
+    DCHECK(chunk->header)
+        << "Only complete frames are supported by WebSocketBasicStream";
+    DCHECK(chunk->final_chunk)
+        << "Only complete frames are supported by WebSocketBasicStream";
+    // Force the masked bit on.
+    chunk->header->masked = true;
+    // We enforce flow control so the renderer should never be able to force us
+    // to cache anywhere near 2GB of frames.
+    int chunk_size =
+        chunk->data->size() + GetWebSocketFrameHeaderSize(*(chunk->header));
+    CHECK_GE(kMaximumTotalSize - total_size, chunk_size)
+        << "Aborting to prevent overflow";
+    total_size += chunk_size;
+  }
+  scoped_refptr<IOBufferWithSize> combined_buffer(
+      new IOBufferWithSize(total_size));
+  char* dest = combined_buffer->data();
+  int remaining_size = total_size;
+  for (Iterator it = frame_chunks->begin(); it != frame_chunks->end(); ++it) {
+    WebSocketFrameChunk* chunk = *it;
+    WebSocketMaskingKey mask = generate_websocket_masking_key_();
+    int result = WriteWebSocketFrameHeader(
+        *(chunk->header), &mask, dest, remaining_size);
+    DCHECK(result != ERR_INVALID_ARGUMENT)
+        << "WriteWebSocketFrameHeader() says that " << remaining_size
+        << " is not enough to write the header in. This should not happen.";
+    CHECK_GE(result, 0) << "Potentially security-critical check failed";
+    dest += result;
+    remaining_size -= result;
+
+    const char* const frame_data = chunk->data->data();
+    const int frame_size = chunk->data->size();
+    CHECK_GE(remaining_size, frame_size);
+    std::copy(frame_data, frame_data + frame_size, dest);
+    MaskWebSocketFramePayload(mask, 0, dest, frame_size);
+    dest += frame_size;
+    remaining_size -= frame_size;
+  }
+  DCHECK_EQ(0, remaining_size) << "Buffer size calculation was wrong; "
+                               << remaining_size << " bytes left over.";
+  scoped_refptr<DrainableIOBuffer> drainable_buffer(
+      new DrainableIOBuffer(combined_buffer, total_size));
+  return WriteEverything(drainable_buffer, callback);
+}
+
+void WebSocketBasicStream::Close() { connection_->socket()->Disconnect(); }
+
+std::string WebSocketBasicStream::GetSubProtocol() const {
+  return sub_protocol_;
+}
+
+std::string WebSocketBasicStream::GetExtensions() const { return extensions_; }
+
+int WebSocketBasicStream::SendHandshakeRequest(
+    const GURL& url,
+    const HttpRequestHeaders& headers,
+    HttpResponseInfo* response_info,
+    const CompletionCallback& callback) {
+  // TODO(ricea): Implement handshake-related functionality.
+  NOTREACHED();
+  return ERR_NOT_IMPLEMENTED;
+}
+
+int WebSocketBasicStream::ReadHandshakeResponse(
+    const CompletionCallback& callback) {
+  NOTREACHED();
+  return ERR_NOT_IMPLEMENTED;
+}
+
+/*static*/
+scoped_ptr<WebSocketBasicStream>
+WebSocketBasicStream::CreateWebSocketBasicStreamForTesting(
+    scoped_ptr<ClientSocketHandle> connection,
+    const scoped_refptr<GrowableIOBuffer>& http_read_buffer,
+    const std::string& sub_protocol,
+    const std::string& extensions,
+    WebSocketMaskingKeyGeneratorFunction key_generator_function) {
+  scoped_ptr<WebSocketBasicStream> stream(
+      new WebSocketBasicStream(connection.Pass()));
+  if (http_read_buffer) {
+    stream->http_read_buffer_ = http_read_buffer;
+  }
+  stream->sub_protocol_ = sub_protocol;
+  stream->extensions_ = extensions;
+  stream->generate_websocket_masking_key_ = key_generator_function;
+  return stream.Pass();
+}
+
+int WebSocketBasicStream::WriteEverything(
+    const scoped_refptr<DrainableIOBuffer>& buffer,
+    const CompletionCallback& callback) {
+  while (buffer->BytesRemaining() > 0) {
+    // The use of base::Unretained() here is safe because on destruction we
+    // disconnect the socket, preventing any further callbacks.
+    int result = connection_->socket()
+                     ->Write(buffer.get(),
+                             buffer->BytesRemaining(),
+                             base::Bind(&WebSocketBasicStream::OnWriteComplete,
+                                        base::Unretained(this),
+                                        buffer,
+                                        callback));
+    if (result > 0) {
+      buffer->DidConsume(result);
+    } else {
+      return result;
+    }
+  }
+  return OK;
+}
+
+void WebSocketBasicStream::OnWriteComplete(
+    const scoped_refptr<DrainableIOBuffer>& buffer,
+    const CompletionCallback& callback,
+    int result) {
+  if (result < 0) {
+    DCHECK(result != ERR_IO_PENDING);
+    callback.Run(result);
+    return;
+  }
+
+  DCHECK(result != 0);
+  buffer->DidConsume(result);
+  result = WriteEverything(buffer, callback);
+  if (result != ERR_IO_PENDING)
+    callback.Run(result);
+}
+
+int WebSocketBasicStream::HandleReadResult(
+    int result,
+    ScopedVector<WebSocketFrameChunk>* frame_chunks) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK(frame_chunks->empty());
+  if (result < 0)
+    return result;
+  if (result == 0)
+    return ERR_CONNECTION_CLOSED;
+  if (!parser_.Decode(read_buffer_->data(), result, frame_chunks))
+    return WebSocketErrorToNetError(parser_.websocket_error());
+  if (!frame_chunks->empty())
+    return OK;
+  return ERR_IO_PENDING;
+}
+
+void WebSocketBasicStream::OnReadComplete(
+    ScopedVector<WebSocketFrameChunk>* frame_chunks,
+    const CompletionCallback& callback,
+    int result) {
+  result = HandleReadResult(result, frame_chunks);
+  if (result == ERR_IO_PENDING)
+    result = ReadFrames(frame_chunks, callback);
+  if (result != ERR_IO_PENDING)
+    callback.Run(result);
+}
+
+}  // namespace net
diff --git a/net/websockets/websocket_basic_stream.h b/net/websockets/websocket_basic_stream.h
new file mode 100644
index 0000000..1231da8
--- /dev/null
+++ b/net/websockets/websocket_basic_stream.h
@@ -0,0 +1,129 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_BASIC_STREAM_H_
+#define NET_WEBSOCKETS_WEBSOCKET_BASIC_STREAM_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/websockets/websocket_frame_parser.h"
+#include "net/websockets/websocket_stream.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class DrainableIOBuffer;
+class GrowableIOBuffer;
+class HttpRequestHeaders;
+class HttpResponseInfo;
+class IOBufferWithSize;
+struct WebSocketFrameChunk;
+
+// Implementation of WebSocketStream for non-multiplexed ws:// connections (or
+// the physical side of a multiplexed ws:// connection).
+class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream {
+ public:
+  typedef WebSocketMaskingKey (*WebSocketMaskingKeyGeneratorFunction)();
+
+  // This class should not normally be constructed directly; see
+  // WebSocketStream::CreateAndConnectStream.
+  explicit WebSocketBasicStream(scoped_ptr<ClientSocketHandle> connection);
+
+  // The destructor has to make sure the connection is closed when we finish so
+  // that it does not get returned to the pool.
+  virtual ~WebSocketBasicStream();
+
+  // WebSocketStream implementation.
+  virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+                         const CompletionCallback& callback) OVERRIDE;
+
+  virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+                          const CompletionCallback& callback) OVERRIDE;
+
+  virtual void Close() OVERRIDE;
+
+  virtual std::string GetSubProtocol() const OVERRIDE;
+
+  virtual std::string GetExtensions() const OVERRIDE;
+
+  // Writes WebSocket handshake request HTTP-style to the connection. Adds
+  // "Sec-WebSocket-Key" header; this should not be included in |headers|.
+  virtual int SendHandshakeRequest(const GURL& url,
+                                   const HttpRequestHeaders& headers,
+                                   HttpResponseInfo* response_info,
+                                   const CompletionCallback& callback) OVERRIDE;
+
+  virtual int ReadHandshakeResponse(
+      const CompletionCallback& callback) OVERRIDE;
+
+  ////////////////////////////////////////////////////////////////////////////
+  // Methods for testing only.
+
+  static scoped_ptr<WebSocketBasicStream> CreateWebSocketBasicStreamForTesting(
+      scoped_ptr<ClientSocketHandle> connection,
+      const scoped_refptr<GrowableIOBuffer>& http_read_buffer,
+      const std::string& sub_protocol,
+      const std::string& extensions,
+      WebSocketMaskingKeyGeneratorFunction key_generator_function);
+
+ private:
+  // Returns OK or calls |callback| when the |buffer| is fully drained or
+  // something has failed.
+  int WriteEverything(const scoped_refptr<DrainableIOBuffer>& buffer,
+                      const CompletionCallback& callback);
+
+  // Wraps the |callback| to continue writing until everything has been written.
+  void OnWriteComplete(const scoped_refptr<DrainableIOBuffer>& buffer,
+                       const CompletionCallback& callback,
+                       int result);
+
+  // Attempts to parse the output of a read as WebSocket frames. On success,
+  // returns OK and places the frame(s) in frame_chunks.
+  int HandleReadResult(int result,
+                       ScopedVector<WebSocketFrameChunk>* frame_chunks);
+
+  // Called when a read completes. Parses the result and (unless no complete
+  // header has been received) calls |callback|.
+  void OnReadComplete(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+                      const CompletionCallback& callback,
+                      int result);
+
+  // Storage for pending reads. All active WebSockets spend all the time with a
+  // call to ReadFrames() pending, so there is no benefit in trying to share
+  // this between sockets.
+  scoped_refptr<IOBufferWithSize> read_buffer_;
+
+  // The connection, wrapped in a ClientSocketHandle so that we can prevent it
+  // from being returned to the pool.
+  scoped_ptr<ClientSocketHandle> connection_;
+
+  // Only used during handshake. Some data may be left in this buffer after the
+  // handshake, in which case it will be picked up during the first call to
+  // ReadFrames(). The type is GrowableIOBuffer for compatibility with
+  // net::HttpStreamParser, which is used to parse the handshake.
+  scoped_refptr<GrowableIOBuffer> http_read_buffer_;
+
+  // This keeps the current parse state (including any incomplete headers) and
+  // parses frames.
+  WebSocketFrameParser parser_;
+
+  // The negotated sub-protocol, or empty for none.
+  std::string sub_protocol_;
+
+  // The extensions negotiated with the remote server.
+  std::string extensions_;
+
+  // This can be overridden in tests to make the output deterministic. We don't
+  // use a Callback here because a function pointer is faster and good enough
+  // for our purposes.
+  WebSocketMaskingKeyGeneratorFunction generate_websocket_masking_key_;
+};
+
+}  // namespace net
+
+#endif  // NET_WEBSOCKETS_WEBSOCKET_BASIC_STREAM_H_
diff --git a/net/websockets/websocket_basic_stream_test.cc b/net/websockets/websocket_basic_stream_test.cc
new file mode 100644
index 0000000..ec2e51a
--- /dev/null
+++ b/net/websockets/websocket_basic_stream_test.cc
@@ -0,0 +1,513 @@
+// Copyright 2013 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.
+//
+// Tests for WebSocketBasicStream. Note that we do not attempt to verify that
+// frame parsing itself functions correctly, as that is covered by the
+// WebSocketFrameParser tests.
+
+#include "net/websockets/websocket_basic_stream.h"
+
+#include "base/basictypes.h"
+#include "base/port.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+// TODO(ricea): Add tests for
+// - Empty frames (data & control)
+// - Non-NULL masking key
+// - A frame larger than kReadBufferSize;
+
+const char kSampleFrame[] = "\x81\x06Sample";
+const size_t kSampleFrameSize = arraysize(kSampleFrame) - 1;
+const char kPartialLargeFrame[] =
+    "\x81\x7F\x00\x00\x00\x00\x7F\xFF\xFF\xFF"
+    "chromiunum ad pasco per loca insanis pullum manducat frumenti";
+const size_t kPartialLargeFrameSize = arraysize(kPartialLargeFrame) - 1;
+const size_t kLargeFrameHeaderSize = 10;
+const size_t kLargeFrameDeclaredPayloadSize = 0x7FFFFFFF;
+const char kMultipleFrames[] = "\x81\x01X\x81\x01Y\x81\x01Z";
+const size_t kMultipleFramesSize = arraysize(kMultipleFrames) - 1;
+// This frame encodes a payload length of 7 in two bytes, which is always
+// invalid.
+const char kInvalidFrame[] = "\x81\x7E\x00\x07Invalid";
+const size_t kInvalidFrameSize = arraysize(kInvalidFrame) - 1;
+const char kWriteFrame[] = "\x81\x85\x00\x00\x00\x00Write";
+const size_t kWriteFrameSize = arraysize(kWriteFrame) - 1;
+const WebSocketMaskingKey kNulMaskingKey = {{'\0', '\0', '\0', '\0'}};
+
+// Generates a ScopedVector<WebSocketFrameChunk> which will have a wire format
+// matching kWriteFrame.
+ScopedVector<WebSocketFrameChunk> GenerateWriteFrame() {
+  scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk);
+  const size_t payload_size =
+      kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize +
+                         WebSocketFrameHeader::kMaskingKeyLength);
+  chunk->data = new IOBufferWithSize(payload_size);
+  memcpy(chunk->data->data(),
+         kWriteFrame + kWriteFrameSize - payload_size,
+         payload_size);
+  chunk->final_chunk = true;
+  scoped_ptr<WebSocketFrameHeader> header(
+      new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeText));
+  header->final = true;
+  header->masked = true;
+  header->payload_length = payload_size;
+  chunk->header = header.Pass();
+  ScopedVector<WebSocketFrameChunk> chunks;
+  chunks.push_back(chunk.release());
+  return chunks.Pass();
+}
+
+// A masking key generator function which generates the identity mask,
+// ie. "\0\0\0\0".
+WebSocketMaskingKey GenerateNulMaskingKey() { return kNulMaskingKey; }
+
+// Base class for WebSocketBasicStream test fixtures.
+class WebSocketBasicStreamTest : public ::testing::Test {
+ protected:
+  scoped_ptr<WebSocketBasicStream> stream_;
+  CapturingNetLog net_log_;
+};
+
+// A fixture for tests which only perform normal socket operations.
+class WebSocketBasicStreamSocketTest : public WebSocketBasicStreamTest {
+ protected:
+  WebSocketBasicStreamSocketTest()
+      : histograms_("a"), pool_(1, 1, &histograms_, &factory_) {}
+
+  virtual ~WebSocketBasicStreamSocketTest() {
+    // stream_ has a reference to socket_data_ (via MockTCPClientSocket) and so
+    // should be destroyed first.
+    stream_.reset();
+  }
+
+  scoped_ptr<ClientSocketHandle> MakeTransportSocket(MockRead reads[],
+                                                     size_t reads_count,
+                                                     MockWrite writes[],
+                                                     size_t writes_count) {
+    socket_data_.reset(
+        new StaticSocketDataProvider(reads, reads_count, writes, writes_count));
+    socket_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+    factory_.AddSocketDataProvider(socket_data_.get());
+
+    scoped_ptr<ClientSocketHandle> transport_socket(new ClientSocketHandle);
+    scoped_refptr<MockTransportSocketParams> params;
+    transport_socket->Init("a",
+                           params,
+                           MEDIUM,
+                           CompletionCallback(),
+                           &pool_,
+                           bound_net_log_.bound());
+    return transport_socket.Pass();
+  }
+
+  void SetHttpReadBuffer(const char* data, size_t size) {
+    http_read_buffer_ = new GrowableIOBuffer;
+    http_read_buffer_->SetCapacity(size);
+    memcpy(http_read_buffer_->data(), data, size);
+    http_read_buffer_->set_offset(size);
+  }
+
+  void CreateStream(MockRead reads[],
+                    size_t reads_count,
+                    MockWrite writes[],
+                    size_t writes_count) {
+    stream_ = WebSocketBasicStream::CreateWebSocketBasicStreamForTesting(
+        MakeTransportSocket(reads, reads_count, writes, writes_count),
+        http_read_buffer_,
+        sub_protocol_,
+        extensions_,
+        &GenerateNulMaskingKey);
+  }
+
+  template <size_t N>
+  void CreateReadOnly(MockRead (&reads)[N]) {
+    CreateStream(reads, N, NULL, 0);
+  }
+
+  template <size_t N>
+  void CreateWriteOnly(MockWrite (&writes)[N]) {
+    CreateStream(NULL, 0, writes, N);
+  }
+
+  void CreateNullStream() { CreateStream(NULL, 0, NULL, 0); }
+
+  scoped_ptr<SocketDataProvider> socket_data_;
+  MockClientSocketFactory factory_;
+  ClientSocketPoolHistograms histograms_;
+  MockTransportClientSocketPool pool_;
+  CapturingBoundNetLog(bound_net_log_);
+  ScopedVector<WebSocketFrameChunk> frame_chunks_;
+  TestCompletionCallback cb_;
+  scoped_refptr<GrowableIOBuffer> http_read_buffer_;
+  std::string sub_protocol_;
+  std::string extensions_;
+};
+
+TEST_F(WebSocketBasicStreamSocketTest, ConstructionWorks) {
+  CreateNullStream();
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, SyncReadWorks) {
+  MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize)};
+  CreateReadOnly(reads);
+  int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+  EXPECT_EQ(OK, result);
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+  EXPECT_TRUE(frame_chunks_[0]->header->final);
+  EXPECT_TRUE(frame_chunks_[0]->final_chunk);
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, AsyncReadWorks) {
+  MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kSampleFrameSize)};
+  CreateReadOnly(reads);
+  int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+  ASSERT_EQ(ERR_IO_PENDING, result);
+  EXPECT_EQ(OK, cb_.WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+  // Don't repeat all the tests from SyncReadWorks; just enough to be sure the
+  // frame was really read.
+}
+
+// ReadFrames will not return a frame whose header has not been wholly received.
+TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSync) {
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, kSampleFrame, 1),
+      MockRead(SYNCHRONOUS, kSampleFrame + 1, kSampleFrameSize - 1)};
+  CreateReadOnly(reads);
+  int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+  ASSERT_EQ(OK, result);
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+}
+
+// The same behaviour applies to asynchronous reads.
+TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedAsync) {
+  MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1),
+                      MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)};
+  CreateReadOnly(reads);
+  int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+  ASSERT_EQ(ERR_IO_PENDING, result);
+  EXPECT_EQ(OK, cb_.WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+}
+
+// If it receives an incomplete header in a synchronous call, then has to wait
+// for the rest of the frame, ReadFrames will return ERR_IO_PENDING.
+TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSyncAsync) {
+  MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, 1),
+                      MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)};
+  CreateReadOnly(reads);
+  int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+  ASSERT_EQ(ERR_IO_PENDING, result);
+  EXPECT_EQ(OK, cb_.WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+}
+
+// An extended header should also return ERR_IO_PENDING if it is not completely
+// received.
+TEST_F(WebSocketBasicStreamSocketTest, FragmentedLargeHeader) {
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize - 1),
+      MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
+  CreateReadOnly(reads);
+  EXPECT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+}
+
+// A frame that does not arrive in a single read should arrive in chunks.
+TEST_F(WebSocketBasicStreamSocketTest, LargeFrameFirstChunk) {
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize)};
+  CreateReadOnly(reads);
+  EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(kLargeFrameDeclaredPayloadSize,
+            frame_chunks_[0]->header->payload_length);
+  EXPECT_TRUE(frame_chunks_[0]->header->final);
+  EXPECT_FALSE(frame_chunks_[0]->final_chunk);
+  EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize,
+            static_cast<size_t>(frame_chunks_[0]->data->size()));
+}
+
+// If only the header arrives, we should get a zero-byte chunk.
+TEST_F(WebSocketBasicStreamSocketTest, HeaderOnlyChunk) {
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize)};
+  CreateReadOnly(reads);
+  EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  ASSERT_EQ(1U, frame_chunks_.size());
+  EXPECT_FALSE(frame_chunks_[0]->final_chunk);
+  EXPECT_TRUE(frame_chunks_[0]->data.get() == NULL);
+}
+
+// The second and subsequent chunks of a frame have no header.
+TEST_F(WebSocketBasicStreamSocketTest, LargeFrameTwoChunks) {
+  static const size_t kChunkSize = 16;
+  MockRead reads[] = {
+      MockRead(ASYNC, kPartialLargeFrame, kChunkSize),
+      MockRead(ASYNC, kPartialLargeFrame + kChunkSize, kChunkSize)};
+  CreateReadOnly(reads);
+  TestCompletionCallback cb[2];
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb[0].callback()));
+  EXPECT_EQ(OK, cb[0].WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+
+  frame_chunks_.clear();
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb[1].callback()));
+  EXPECT_EQ(OK, cb[1].WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_FALSE(frame_chunks_[0]->header);
+}
+
+// Only the final chunk of a frame has final_chunk set.
+TEST_F(WebSocketBasicStreamSocketTest, OnlyFinalChunkIsFinal) {
+  static const size_t kFirstChunkSize = 4;
+  MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kFirstChunkSize),
+                      MockRead(ASYNC,
+                               kSampleFrame + kFirstChunkSize,
+                               kSampleFrameSize - kFirstChunkSize)};
+  CreateReadOnly(reads);
+  TestCompletionCallback cb[2];
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb[0].callback()));
+  EXPECT_EQ(OK, cb[0].WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_FALSE(frame_chunks_[0]->final_chunk);
+
+  frame_chunks_.clear();
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb[1].callback()));
+  EXPECT_EQ(OK, cb[1].WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->final_chunk);
+}
+
+// Multiple frames that arrive together should be parsed correctly.
+TEST_F(WebSocketBasicStreamSocketTest, ThreeFramesTogether) {
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  ASSERT_EQ(3U, frame_chunks_.size());
+  EXPECT_TRUE(frame_chunks_[0]->final_chunk);
+  EXPECT_TRUE(frame_chunks_[1]->final_chunk);
+  EXPECT_TRUE(frame_chunks_[2]->final_chunk);
+}
+
+// ERR_CONNECTION_CLOSED must be returned on close.
+TEST_F(WebSocketBasicStreamSocketTest, SyncClose) {
+  MockRead reads[] = {MockRead(SYNCHRONOUS, "", 0)};
+  CreateReadOnly(reads);
+
+  EXPECT_EQ(ERR_CONNECTION_CLOSED,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, AsyncClose) {
+  MockRead reads[] = {MockRead(ASYNC, "", 0)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
+}
+
+// The result should be the same if the socket returns
+// ERR_CONNECTION_CLOSED. This is not expected to happen on an established
+// connection; a Read of size 0 is the expected behaviour. The key point of this
+// test is to confirm that ReadFrames() behaviour is identical in both cases.
+TEST_F(WebSocketBasicStreamSocketTest, SyncCloseWithErr) {
+  MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)};
+  CreateReadOnly(reads);
+
+  EXPECT_EQ(ERR_CONNECTION_CLOSED,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseWithErr) {
+  MockRead reads[] = {MockRead(ASYNC, ERR_CONNECTION_CLOSED)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, SyncErrorsPassedThrough) {
+  // ERR_INSUFFICIENT_RESOURCES here represents an arbitrary error that
+  // WebSocketBasicStream gives no special handling to.
+  MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES)};
+  CreateReadOnly(reads);
+
+  EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, AsyncErrorsPassedThrough) {
+  MockRead reads[] = {MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES, cb_.WaitForResult());
+}
+
+// If we get a frame followed by a close, we should receive them separately.
+TEST_F(WebSocketBasicStreamSocketTest, CloseAfterFrame) {
+  MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize),
+                      MockRead(SYNCHRONOUS, "", 0)};
+  CreateReadOnly(reads);
+
+  EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(1U, frame_chunks_.size());
+  frame_chunks_.clear();
+  EXPECT_EQ(ERR_CONNECTION_CLOSED,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+}
+
+// Synchronous close after an async frame header is handled by a different code
+// path.
+TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseAfterIncompleteHeader) {
+  MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1U),
+                      MockRead(SYNCHRONOUS, "", 0)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
+}
+
+// When Stream::Read returns ERR_CONNECTION_CLOSED we get the same result via a
+// slightly different code path.
+TEST_F(WebSocketBasicStreamSocketTest, AsyncErrCloseAfterIncompleteHeader) {
+  MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1U),
+                      MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
+}
+
+// If there was a frame read at the same time as the response headers (and the
+// handshake succeeded), then we should parse it.
+TEST_F(WebSocketBasicStreamSocketTest, HttpReadBufferIsUsed) {
+  SetHttpReadBuffer(kSampleFrame, kSampleFrameSize);
+  CreateNullStream();
+
+  EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->data);
+  EXPECT_EQ(6, frame_chunks_[0]->data->size());
+}
+
+// Check that a frame whose header partially arrived at the end of the response
+// headers works correctly.
+TEST_F(WebSocketBasicStreamSocketTest, PartialFrameHeaderInHttpResponse) {
+  SetHttpReadBuffer(kSampleFrame, 1);
+  MockRead reads[] = {MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(OK, cb_.WaitForResult());
+  ASSERT_EQ(1U, frame_chunks_.size());
+  ASSERT_TRUE(frame_chunks_[0]->data);
+  EXPECT_EQ(6, frame_chunks_[0]->data->size());
+  ASSERT_TRUE(frame_chunks_[0]->header);
+  EXPECT_EQ(WebSocketFrameHeader::kOpCodeText,
+            frame_chunks_[0]->header->opcode);
+}
+
+// Check that an invalid frame results in an error.
+TEST_F(WebSocketBasicStreamSocketTest, SyncInvalidFrame) {
+  MockRead reads[] = {MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize)};
+  CreateReadOnly(reads);
+
+  EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, AsyncInvalidFrame) {
+  MockRead reads[] = {MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize)};
+  CreateReadOnly(reads);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, cb_.WaitForResult());
+}
+
+// Check that writing a frame all at once works.
+TEST_F(WebSocketBasicStreamSocketTest, WriteAtOnce) {
+  MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, kWriteFrameSize)};
+  CreateWriteOnly(writes);
+  frame_chunks_ = GenerateWriteFrame();
+
+  EXPECT_EQ(OK, stream_->WriteFrames(&frame_chunks_, cb_.callback()));
+}
+
+// Check that completely async writing works.
+TEST_F(WebSocketBasicStreamSocketTest, AsyncWriteAtOnce) {
+  MockWrite writes[] = {MockWrite(ASYNC, kWriteFrame, kWriteFrameSize)};
+  CreateWriteOnly(writes);
+  frame_chunks_ = GenerateWriteFrame();
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->WriteFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(OK, cb_.WaitForResult());
+}
+
+// Check that writing a frame to an extremely full kernel buffer (so that it
+// ends up being sent in bits) works. The WriteFrames() callback should not be
+// called until all parts have been written.
+TEST_F(WebSocketBasicStreamSocketTest, WriteInBits) {
+  MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, 4),
+                        MockWrite(ASYNC, kWriteFrame + 4, 4),
+                        MockWrite(ASYNC, kWriteFrame + 8, kWriteFrameSize - 8)};
+  CreateWriteOnly(writes);
+  frame_chunks_ = GenerateWriteFrame();
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_->WriteFrames(&frame_chunks_, cb_.callback()));
+  EXPECT_EQ(OK, cb_.WaitForResult());
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, GetExtensionsWorks) {
+  extensions_ = "inflate-uuencode";
+  CreateNullStream();
+
+  EXPECT_EQ("inflate-uuencode", stream_->GetExtensions());
+}
+
+TEST_F(WebSocketBasicStreamSocketTest, GetSubProtocolWorks) {
+  sub_protocol_ = "cyberchat";
+  CreateNullStream();
+
+  EXPECT_EQ("cyberchat", stream_->GetSubProtocol());
+}
+
+}  // namespace
+}  // namespace net