summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-17 13:42:54 +0000
committerricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-17 13:42:54 +0000
commit999bcaadd3735a0003f2637ae15b763d44f3d867 (patch)
tree992111ca9fbd3ee2f004298e7da997859f68b2ba /net
parentf6eed389769b4d0eb3ab9385e7b8500fbac5634a (diff)
downloadchromium_src-999bcaadd3735a0003f2637ae15b763d44f3d867.zip
chromium_src-999bcaadd3735a0003f2637ae15b763d44f3d867.tar.gz
chromium_src-999bcaadd3735a0003f2637ae15b763d44f3d867.tar.bz2
WebSocketChannel implements the transport-independent parts of the WebSocket protocol. It also provides the interface to the content library.
The unit tests are still incomplete at the moment. BUG=230756 TEST=net_unittests --gtest_filter='WebSocketChannel*' Review URL: https://chromiumcodereview.appspot.com/12764006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@212019 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/net.gyp4
-rw-r--r--net/websockets/README10
-rw-r--r--net/websockets/websocket_channel.cc667
-rw-r--r--net/websockets/websocket_channel.h259
-rw-r--r--net/websockets/websocket_channel_test.cc1177
-rw-r--r--net/websockets/websocket_event_interface.h76
-rw-r--r--net/websockets/websocket_mux.h39
7 files changed, 2230 insertions, 2 deletions
diff --git a/net/net.gyp b/net/net.gyp
index b97c69f..615e5be 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -1075,6 +1075,8 @@
'url_request/url_request_throttler_manager.h',
'url_request/view_cache_helper.cc',
'url_request/view_cache_helper.h',
+ 'websockets/websocket_channel.cc',
+ 'websockets/websocket_channel.h',
'websockets/websocket_errors.cc',
'websockets/websocket_errors.h',
'websockets/websocket_frame.cc',
@@ -1085,6 +1087,7 @@
'websockets/websocket_handshake_handler.h',
'websockets/websocket_job.cc',
'websockets/websocket_job.h',
+ 'websockets/websocket_mux.h',
'websockets/websocket_net_log_params.cc',
'websockets/websocket_net_log_params.h',
'websockets/websocket_stream.cc',
@@ -1829,6 +1832,7 @@
'url_request/url_request_throttler_unittest.cc',
'url_request/url_request_unittest.cc',
'url_request/view_cache_helper_unittest.cc',
+ 'websockets/websocket_channel_test.cc',
'websockets/websocket_errors_unittest.cc',
'websockets/websocket_frame_parser_unittest.cc',
'websockets/websocket_frame_unittest.cc',
diff --git a/net/websockets/README b/net/websockets/README
index 5575201..558a451 100644
--- a/net/websockets/README
+++ b/net/websockets/README
@@ -17,8 +17,7 @@ websocket_handshake_handler_spdy2_unittest.cc
websocket_handshake_handler_spdy3_unittest.cc
websocket_job.cc
websocket_job.h
-websocket_job_spdy2_unittest.cc
-websocket_job_spdy3_unittest.cc
+websocket_job_unittest.cc
websocket_net_log_params.cc
websocket_net_log_params.h
websocket_net_log_params_unittest.cc
@@ -31,15 +30,22 @@ performs framing and implements protocol semantics in the browser process, and
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_channel.cc
+websocket_channel.h
+websocket_channel_test.cc
websocket_errors.cc
websocket_errors.h
websocket_errors_unittest.cc
+websocket_event_interface.h
websocket_frame.cc
websocket_frame.h
websocket_frame_parser.cc
websocket_frame_parser.h
websocket_frame_parser_unittest.cc
websocket_frame_unittest.cc
+websocket_mux.h
+websocket_stream_base.h
+websocket_stream.cc
websocket_stream.h
A pre-submit check helps us keep this README file up-to-date:
diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc
new file mode 100644
index 0000000..e27337c
--- /dev/null
+++ b/net/websockets/websocket_channel.cc
@@ -0,0 +1,667 @@
+// 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_channel.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h" // for size_t
+#include "base/bind.h"
+#include "base/safe_numerics.h"
+#include "base/strings/string_util.h"
+#include "net/base/big_endian.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_mux.h"
+#include "net/websockets/websocket_stream.h"
+
+namespace net {
+
+namespace {
+
+const int kDefaultSendQuotaLowWaterMark = 1 << 16;
+const int kDefaultSendQuotaHighWaterMark = 1 << 17;
+const size_t kWebSocketCloseCodeLength = 2;
+
+// Concatenate the data from two IOBufferWithSize objects into a single one.
+IOBufferWithSize* ConcatenateIOBuffers(
+ const scoped_refptr<IOBufferWithSize>& part1,
+ const scoped_refptr<IOBufferWithSize>& part2) {
+ int newsize = part1->size() + part2->size();
+ IOBufferWithSize* newbuffer = new IOBufferWithSize(newsize);
+ std::copy(part1->data(), part1->data() + part1->size(), newbuffer->data());
+ std::copy(part2->data(),
+ part2->data() + part2->size(),
+ newbuffer->data() + part1->size());
+ return newbuffer;
+}
+
+} // namespace
+
+// A class to encapsulate a set of frames and information about the size of
+// those frames.
+class WebSocketChannel::SendBuffer {
+ public:
+ SendBuffer() : total_bytes_(0) {}
+
+ // Add a WebSocketFrameChunk to the buffer and increase total_bytes_.
+ void AddFrame(scoped_ptr<WebSocketFrameChunk> chunk);
+
+ // Return a pointer to the frames_ for write purposes.
+ ScopedVector<WebSocketFrameChunk>* GetFrames() { return &frames_; }
+
+ private:
+ // The frames_ that will be sent in the next call to WriteFrames().
+ ScopedVector<WebSocketFrameChunk> frames_;
+
+ // The total size of the buffers in |frames_|. This will be used to measure
+ // the throughput of the link.
+ // TODO(ricea): Measure the throughput of the link.
+ size_t total_bytes_;
+};
+
+void WebSocketChannel::SendBuffer::AddFrame(
+ scoped_ptr<WebSocketFrameChunk> chunk) {
+ total_bytes_ += chunk->data->size();
+ frames_.push_back(chunk.release());
+}
+
+// Implementation of WebSocketStream::ConnectDelegate that simply forwards the
+// calls on to the WebSocketChannel that created it.
+class WebSocketChannel::ConnectDelegate
+ : public WebSocketStream::ConnectDelegate {
+ public:
+ explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
+
+ virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
+ creator_->OnConnectSuccess(stream.Pass());
+ }
+
+ virtual void OnFailure(uint16 websocket_error) OVERRIDE {
+ creator_->OnConnectFailure(websocket_error);
+ }
+
+ private:
+ // A pointer to the WebSocketChannel that created us. We do not need to worry
+ // about this pointer being stale, because deleting WebSocketChannel cancels
+ // the connect process, deleting this object and preventing its callbacks from
+ // being called.
+ WebSocketChannel* const creator_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectDelegate);
+};
+
+WebSocketChannel::WebSocketChannel(
+ const GURL& socket_url,
+ scoped_ptr<WebSocketEventInterface> event_interface)
+ : socket_url_(socket_url),
+ event_interface_(event_interface.Pass()),
+ send_quota_low_water_mark_(kDefaultSendQuotaLowWaterMark),
+ send_quota_high_water_mark_(kDefaultSendQuotaHighWaterMark),
+ current_send_quota_(0),
+ closing_code_(0),
+ state_(FRESHLY_CONSTRUCTED) {}
+
+WebSocketChannel::~WebSocketChannel() {
+ // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
+ // destroyed first.
+ stream_.reset();
+}
+
+void WebSocketChannel::SendAddChannelRequest(
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context) {
+ // Delegate to the tested version.
+ SendAddChannelRequestWithFactory(
+ requested_subprotocols,
+ origin,
+ url_request_context,
+ base::Bind(&WebSocketStream::CreateAndConnectStream));
+}
+
+void WebSocketChannel::SendFrame(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const std::vector<char>& data) {
+ if (data.size() > INT_MAX) {
+ NOTREACHED() << "Frame size sanity check failed";
+ return;
+ }
+ if (stream_ == NULL) {
+ LOG(DFATAL) << "Got SendFrame without a connection established; "
+ << "misbehaving renderer? fin=" << fin << " op_code=" << op_code
+ << " data.size()=" << data.size();
+ return;
+ }
+ if (state_ == SEND_CLOSED || state_ == CLOSED) {
+ VLOG(1) << "SendFrame called in state " << state_
+ << ". This may be a bug, or a harmless race.";
+ return;
+ }
+ if (state_ != CONNECTED) {
+ NOTREACHED() << "SendFrame() called in state " << state_;
+ return;
+ }
+ if (data.size() > base::checked_numeric_cast<size_t>(current_send_quota_)) {
+ FailChannel(SEND_GOING_AWAY,
+ kWebSocketMuxErrorSendQuotaViolation,
+ "Send quota exceeded");
+ return;
+ }
+ if (!WebSocketFrameHeader::IsKnownDataOpCode(op_code)) {
+ LOG(DFATAL) << "Got SendFrame with bogus op_code " << op_code
+ << "; misbehaving renderer? fin=" << fin
+ << " data.size()=" << data.size();
+ return;
+ }
+ current_send_quota_ -= data.size();
+ // TODO(ricea): If current_send_quota_ has dropped below
+ // send_quota_low_water_mark_, we may want to consider increasing the "low
+ // water mark" and "high water mark", but only if we think we are not
+ // saturating the link to the WebSocket server.
+ // TODO(ricea): For kOpCodeText, do UTF-8 validation?
+ scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
+ std::copy(data.begin(), data.end(), buffer->data());
+ SendIOBufferWithSize(fin, op_code, buffer);
+}
+
+void WebSocketChannel::SendFlowControl(int64 quota) {
+ DCHECK_EQ(CONNECTED, state_);
+ // TODO(ricea): Add interface to WebSocketStream and implement.
+ // stream_->SendFlowControl(quota);
+}
+
+void WebSocketChannel::StartClosingHandshake(uint16 code,
+ const std::string& reason) {
+ if (state_ == SEND_CLOSED || state_ == CLOSED) {
+ VLOG(1) << "StartClosingHandshake called in state " << state_
+ << ". This may be a bug, or a harmless race.";
+ return;
+ }
+ if (state_ != CONNECTED) {
+ NOTREACHED() << "StartClosingHandshake() called in state " << state_;
+ return;
+ }
+ // TODO(ricea): Validate |code|? Check that |reason| is valid UTF-8?
+ // TODO(ricea): There should be a timeout for the closing handshake.
+ SendClose(code, reason); // Sets state_ to SEND_CLOSED
+}
+
+void WebSocketChannel::SendAddChannelRequestForTesting(
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory) {
+ SendAddChannelRequestWithFactory(
+ requested_subprotocols, origin, url_request_context, factory);
+}
+
+void WebSocketChannel::SendAddChannelRequestWithFactory(
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory) {
+ DCHECK_EQ(FRESHLY_CONSTRUCTED, state_);
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
+ new ConnectDelegate(this));
+ stream_request_ = factory.Run(socket_url_,
+ requested_subprotocols,
+ origin,
+ url_request_context,
+ BoundNetLog(),
+ connect_delegate.Pass());
+ state_ = CONNECTING;
+}
+
+void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) {
+ DCHECK(stream);
+ DCHECK_EQ(CONNECTING, state_);
+ stream_ = stream.Pass();
+ state_ = CONNECTED;
+ event_interface_->OnAddChannelResponse(false, stream_->GetSubProtocol());
+
+ // TODO(ricea): Get flow control information from the WebSocketStream once we
+ // have a multiplexing WebSocketStream.
+ current_send_quota_ = send_quota_high_water_mark_;
+ event_interface_->OnFlowControl(send_quota_high_water_mark_);
+
+ // We don't need this any more.
+ stream_request_.reset();
+ ReadFrames();
+}
+
+void WebSocketChannel::OnConnectFailure(uint16 websocket_error) {
+ DCHECK_EQ(CONNECTING, state_);
+ state_ = CLOSED;
+ stream_request_.reset();
+ event_interface_->OnAddChannelResponse(true, "");
+}
+
+void WebSocketChannel::WriteFrames() {
+ int result = OK;
+ do {
+ // This use of base::Unretained is safe because we own the WebSocketStream
+ // and destroying it cancels all callbacks.
+ result = stream_->WriteFrames(
+ data_being_sent_->GetFrames(),
+ base::Bind(
+ &WebSocketChannel::OnWriteDone, base::Unretained(this), false));
+ if (result != ERR_IO_PENDING) {
+ OnWriteDone(true, result);
+ }
+ } while (result == OK && data_being_sent_);
+}
+
+void WebSocketChannel::OnWriteDone(bool synchronous, int result) {
+ DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(data_being_sent_);
+ switch (result) {
+ case OK:
+ if (data_to_send_next_) {
+ data_being_sent_ = data_to_send_next_.Pass();
+ if (!synchronous) {
+ WriteFrames();
+ }
+ } else {
+ data_being_sent_.reset();
+ if (current_send_quota_ < send_quota_low_water_mark_) {
+ // TODO(ricea): Increase low_water_mark and high_water_mark if
+ // throughput is high, reduce them if throughput is low. Low water
+ // mark needs to be >= the bandwidth delay product *of the IPC
+ // channel*. Because factors like context-switch time, thread wake-up
+ // time, and bus speed come into play it is complex and probably needs
+ // to be determined empirically.
+ DCHECK_LE(send_quota_low_water_mark_, send_quota_high_water_mark_);
+ // TODO(ricea): Truncate quota by the quota specified by the remote
+ // server, if the protocol in use supports quota.
+ int fresh_quota = send_quota_high_water_mark_ - current_send_quota_;
+ current_send_quota_ += fresh_quota;
+ event_interface_->OnFlowControl(fresh_quota);
+ }
+ }
+ return;
+
+ // If a recoverable error condition existed, it would go here.
+
+ default:
+ DCHECK_LT(result, 0)
+ << "WriteFrames() should only return OK or ERR_ codes";
+ stream_->Close();
+ state_ = CLOSED;
+ event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
+ "Abnormal Closure");
+ return;
+ }
+}
+
+void WebSocketChannel::ReadFrames() {
+ int result = OK;
+ do {
+ // This use of base::Unretained is safe because we own the WebSocketStream,
+ // and any pending reads will be cancelled when it is destroyed.
+ result = stream_->ReadFrames(
+ &read_frame_chunks_,
+ base::Bind(
+ &WebSocketChannel::OnReadDone, base::Unretained(this), false));
+ if (result != ERR_IO_PENDING) {
+ OnReadDone(true, result);
+ }
+ } while (result == OK);
+}
+
+void WebSocketChannel::OnReadDone(bool synchronous, int result) {
+ DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ DCHECK_NE(ERR_IO_PENDING, result);
+ switch (result) {
+ case OK:
+ // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection
+ // with no data read, not an empty response.
+ DCHECK(!read_frame_chunks_.empty())
+ << "ReadFrames() returned OK, but nothing was read.";
+ for (size_t i = 0; i < read_frame_chunks_.size(); ++i) {
+ scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]);
+ read_frame_chunks_[i] = NULL;
+ ProcessFrameChunk(chunk.Pass());
+ }
+ read_frame_chunks_.clear();
+ // We need to always keep a call to ReadFrames pending.
+ if (!synchronous) {
+ ReadFrames();
+ }
+ return;
+
+ default: {
+ DCHECK_LT(result, 0)
+ << "ReadFrames() should only return OK or ERR_ codes";
+ stream_->Close();
+ state_ = CLOSED;
+ uint16 code = kWebSocketErrorAbnormalClosure;
+ std::string reason = "Abnormal Closure";
+ if (closing_code_ != 0) {
+ code = closing_code_;
+ reason = closing_reason_;
+ }
+ event_interface_->OnDropChannel(code, reason);
+ return;
+ }
+ }
+}
+
+void WebSocketChannel::ProcessFrameChunk(
+ scoped_ptr<WebSocketFrameChunk> chunk) {
+ bool is_first_chunk = false;
+ if (chunk->header) {
+ DCHECK(current_frame_header_ == NULL)
+ << "Received the header for a new frame without notification that "
+ << "the previous frame was complete.";
+ is_first_chunk = true;
+ current_frame_header_.swap(chunk->header);
+ if (current_frame_header_->masked) {
+ // RFC6455 Section 5.1 "A client MUST close a connection if it detects a
+ // masked frame."
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ "Masked frame from server");
+ return;
+ }
+ }
+ if (!current_frame_header_) {
+ // If we rejected the previous chunk as invalid, then we will have reset
+ // current_frame_header_ to avoid using it. More chunks of the invalid frame
+ // may still arrive, so this is not necessarily a bug on our side. However,
+ // if this happens when state_ is CONNECTED, it is definitely a bug.
+ DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received "
+ << "(final_chunk = " << chunk->final_chunk
+ << ", data size = " << chunk->data->size()
+ << ")";
+ return;
+ }
+ scoped_refptr<IOBufferWithSize> data_buffer;
+ data_buffer.swap(chunk->data);
+ const bool is_final_chunk = chunk->final_chunk;
+ chunk.reset();
+ WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
+ if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
+ if (!is_final_chunk) {
+ // TODO(ricea): Enforce a maximum size of 125 bytes on the control frames
+ // we accept.
+ VLOG(2) << "Encountered a split control frame, opcode " << opcode;
+ if (incomplete_control_frame_body_) {
+ // The really horrid case. We need to create a new IOBufferWithSize
+ // combining the new one and the old one. This should virtually never
+ // happen.
+ // TODO(ricea): This algorithm is O(N^2). Use a fixed 127-byte buffer
+ // instead.
+ VLOG(3) << "Hit the really horrid case";
+ incomplete_control_frame_body_ =
+ ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
+ } else {
+ // The merely horrid case. Store the IOBufferWithSize to use when the
+ // rest of the control frame arrives.
+ incomplete_control_frame_body_.swap(data_buffer);
+ }
+ return; // Handle when complete.
+ }
+ if (incomplete_control_frame_body_) {
+ VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
+ data_buffer =
+ ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
+ incomplete_control_frame_body_ = NULL; // Frame now complete.
+ }
+ }
+
+ // Apply basic sanity checks to the |payload_length| field from the frame
+ // header. We can only apply a strict check when we know we have the whole
+ // frame in one chunk.
+ DCHECK_GE(current_frame_header_->payload_length,
+ base::checked_numeric_cast<uint64>(data_buffer->size()));
+ DCHECK(!is_first_chunk || !is_final_chunk ||
+ current_frame_header_->payload_length ==
+ base::checked_numeric_cast<uint64>(data_buffer->size()));
+
+ // Respond to the frame appropriately to its type.
+ HandleFrame(opcode, is_first_chunk, is_final_chunk, data_buffer);
+
+ if (is_final_chunk) {
+ // Make sure we do not apply this frame header to any future chunks.
+ current_frame_header_.reset();
+ }
+}
+
+void WebSocketChannel::HandleFrame(
+ const WebSocketFrameHeader::OpCode opcode,
+ bool is_first_chunk,
+ bool is_final_chunk,
+ const scoped_refptr<IOBufferWithSize>& data_buffer) {
+ DCHECK_NE(RECV_CLOSED, state_)
+ << "HandleFrame() does not support being called re-entrantly from within "
+ "SendClose()";
+ if (state_ == CLOSED) {
+ std::string frame_name;
+ switch (opcode) {
+ case WebSocketFrameHeader::kOpCodeText: // fall-thru
+ case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
+ case WebSocketFrameHeader::kOpCodeContinuation:
+ frame_name = "Data frame";
+ break;
+
+ case WebSocketFrameHeader::kOpCodePing:
+ frame_name = "Ping";
+ break;
+
+ case WebSocketFrameHeader::kOpCodePong:
+ frame_name = "Pong";
+ break;
+
+ case WebSocketFrameHeader::kOpCodeClose:
+ frame_name = "Close";
+ break;
+
+ default:
+ frame_name = "Unknown frame type";
+ break;
+ }
+ // SEND_REAL_ERROR makes no difference here, as we won't send another Close
+ // frame.
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ frame_name + " received after close");
+ return;
+ }
+ switch (opcode) {
+ case WebSocketFrameHeader::kOpCodeText: // fall-thru
+ case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
+ case WebSocketFrameHeader::kOpCodeContinuation:
+ if (state_ == CONNECTED) {
+ const bool final = is_final_chunk && current_frame_header_->final;
+ // TODO(ricea): Can this copy be eliminated?
+ const char* const data_begin = data_buffer->data();
+ const char* const data_end = data_begin + data_buffer->size();
+ const std::vector<char> data(data_begin, data_end);
+ // TODO(ricea): Handle the (improbable) case when ReadFrames returns far
+ // more data at once than we want to send in a single IPC (in which case
+ // we need to buffer the data and return to the event loop with a
+ // callback to send the rest in 32K chunks).
+
+ // Send the received frame to the renderer process.
+ event_interface_->OnDataFrame(
+ final,
+ is_first_chunk ? opcode : WebSocketFrameHeader::kOpCodeContinuation,
+ data);
+ } else {
+ VLOG(3) << "Ignored data packet received in state " << state_;
+ }
+ return;
+
+ case WebSocketFrameHeader::kOpCodePing:
+ VLOG(1) << "Got Ping of size " << data_buffer->size();
+ if (state_ == CONNECTED) {
+ SendIOBufferWithSize(
+ true, WebSocketFrameHeader::kOpCodePong, data_buffer);
+ } else {
+ VLOG(3) << "Ignored ping in state " << state_;
+ }
+ return;
+
+ case WebSocketFrameHeader::kOpCodePong:
+ VLOG(1) << "Got Pong of size " << data_buffer->size();
+ // We do not need to do anything with pong messages.
+ return;
+
+ case WebSocketFrameHeader::kOpCodeClose: {
+ uint16 code = kWebSocketNormalClosure;
+ std::string reason;
+ ParseClose(data_buffer, &code, &reason);
+ // TODO(ricea): Find a way to safely log the message from the close
+ // message (escape control codes and so on).
+ VLOG(1) << "Got Close with code " << code;
+ switch (state_) {
+ case CONNECTED:
+ state_ = RECV_CLOSED;
+ SendClose(code, reason); // Sets state_ to CLOSED
+ event_interface_->OnClosingHandshake();
+ closing_code_ = code;
+ closing_reason_ = reason;
+ break;
+
+ case SEND_CLOSED:
+ state_ = CLOSED;
+ // From RFC6455 section 7.1.5: "Each endpoint
+ // will see the status code sent by the other end as _The WebSocket
+ // Connection Close Code_."
+ closing_code_ = code;
+ closing_reason_ = reason;
+ break;
+
+ default:
+ LOG(DFATAL) << "Got Close in unexpected state " << state_;
+ break;
+ }
+ return;
+ }
+
+ default:
+ FailChannel(
+ SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
+ return;
+ }
+}
+
+void WebSocketChannel::SendIOBufferWithSize(
+ bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const scoped_refptr<IOBufferWithSize>& buffer) {
+ DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
+ DCHECK(stream_);
+ scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code));
+ header->final = fin;
+ header->masked = true;
+ header->payload_length = buffer->size();
+ scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk());
+ chunk->header = header.Pass();
+ chunk->final_chunk = true;
+ chunk->data = buffer;
+ if (data_being_sent_) {
+ // Either the link to the WebSocket server is saturated, or we are simply
+ // processing a batch of messages.
+ // TODO(ricea): We need to keep some statistics to work out which situation
+ // we are in and adjust quota appropriately.
+ if (!data_to_send_next_)
+ data_to_send_next_.reset(new SendBuffer);
+ data_to_send_next_->AddFrame(chunk.Pass());
+ } else {
+ data_being_sent_.reset(new SendBuffer);
+ data_being_sent_->AddFrame(chunk.Pass());
+ WriteFrames();
+ }
+}
+
+void WebSocketChannel::FailChannel(ExposeError expose,
+ uint16 code,
+ const std::string& reason) {
+ DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ // TODO(ricea): Logging.
+ State old_state = state_;
+ if (state_ == CONNECTED) {
+ uint16 send_code = kWebSocketErrorGoingAway;
+ std::string send_reason = "Internal Error";
+ if (expose == SEND_REAL_ERROR) {
+ send_code = code;
+ send_reason = reason;
+ }
+ SendClose(send_code, send_reason); // Sets state_ to SEND_CLOSED
+ }
+ // Careful study of RFC6455 section 7.1.7 and 7.1.1 indicates we should close
+ // the connection ourselves without waiting for the closing handshake.
+ stream_->Close();
+ state_ = CLOSED;
+
+ // We may be in the middle of processing several chunks. We should not re-use
+ // the frame header.
+ current_frame_header_.reset();
+ if (old_state != CLOSED) {
+ event_interface_->OnDropChannel(code, reason);
+ }
+}
+
+void WebSocketChannel::SendClose(uint16 code, const std::string& reason) {
+ DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
+ // TODO(ricea): Ensure reason.length() <= 123
+ size_t payload_length = kWebSocketCloseCodeLength + reason.length();
+ scoped_refptr<IOBufferWithSize> body =
+ new IOBufferWithSize(base::checked_numeric_cast<int>(payload_length));
+ WriteBigEndian(body->data(), code);
+ COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength,
+ they_should_both_be_two);
+ std::copy(
+ reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength);
+ SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body);
+ state_ = (state_ == CONNECTED) ? SEND_CLOSED : CLOSED;
+}
+
+void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
+ uint16* code,
+ std::string* reason) {
+ const char* data = buffer->data();
+ size_t size = base::checked_numeric_cast<size_t>(buffer->size());
+ reason->clear();
+ if (size < kWebSocketCloseCodeLength) {
+ *code = kWebSocketErrorNoStatusReceived;
+ if (size != 0) {
+ VLOG(1) << "Close frame with payload size " << size << " received "
+ << "(the first byte is " << std::hex << static_cast<int>(data[0])
+ << ")";
+ return;
+ }
+ return;
+ }
+ uint16 unchecked_code = 0;
+ ReadBigEndian(data, &unchecked_code);
+ COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
+ they_should_both_be_two_bytes);
+ if (unchecked_code >= static_cast<uint16>(kWebSocketNormalClosure) &&
+ unchecked_code <=
+ static_cast<uint16>(kWebSocketErrorPrivateReservedMax)) {
+ *code = unchecked_code;
+ } else {
+ VLOG(1) << "Close frame contained code outside of the valid range: "
+ << unchecked_code;
+ *code = kWebSocketErrorAbnormalClosure;
+ }
+ std::string text(data + kWebSocketCloseCodeLength, data + size);
+ // TODO(ricea): Is this check strict enough? In particular, check the
+ // "Security Considerations" from RFC3629.
+ if (IsStringUTF8(text)) {
+ reason->swap(text);
+ }
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h
new file mode 100644
index 0000000..7d34dae
--- /dev/null
+++ b/net/websockets/websocket_channel.h
@@ -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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_CHANNEL_H_
+#define NET_WEBSOCKETS_WEBSOCKET_CHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_stream.h"
+
+namespace net {
+
+class URLRequestContext;
+class WebSocketEventInterface;
+
+// Transport-independent implementation of WebSockets. Implements protocol
+// semantics that do not depend on the underlying transport. Provides the
+// interface to the content layer. Some WebSocket concepts are used here without
+// definition; please see the RFC at http://tools.ietf.org/html/rfc6455 for
+// clarification.
+class NET_EXPORT WebSocketChannel {
+ public:
+ // The type of a WebSocketStream factory callback. Must match the signature of
+ // WebSocketStream::CreateAndConnectStream().
+ typedef base::Callback<scoped_ptr<WebSocketStreamRequest>(
+ const GURL&,
+ const std::vector<std::string>&,
+ const GURL&,
+ URLRequestContext*,
+ const BoundNetLog&,
+ scoped_ptr<WebSocketStream::ConnectDelegate>)> WebSocketStreamFactory;
+
+ // Creates a new WebSocketChannel with the specified parameters.
+ // SendAddChannelRequest() must be called immediately afterwards to start the
+ // connection process.
+ WebSocketChannel(const GURL& socket_url,
+ scoped_ptr<WebSocketEventInterface> event_interface);
+ virtual ~WebSocketChannel();
+
+ // Starts the connection process.
+ void SendAddChannelRequest(
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context);
+
+ // Sends a data frame to the remote side. The frame should usually be no
+ // larger than 32KB to prevent the time required to copy the buffers from from
+ // unduly delaying other tasks that need to run on the IO thread. This method
+ // has a hard limit of 2GB. It is the responsibility of the caller to ensure
+ // that they have sufficient send quota to send this data, otherwise the
+ // connection will be closed without sending. |fin| indicates the last frame
+ // in a message, equivalent to "FIN" as specified in section 5.2 of
+ // RFC6455. |data| is the "Payload Data". If |op_code| is kOpCodeText, or it
+ // is kOpCodeContinuation and the type the message is Text, then |data| must
+ // be a chunk of a valid UTF-8 message, however there is no requirement for
+ // |data| to be split on character boundaries.
+ void SendFrame(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const std::vector<char>& data);
+
+ // Sends |quota| units of flow control to the remote side. If the underlying
+ // transport has a concept of |quota|, then it permits the remote server to
+ // send up to |quota| units of data.
+ void SendFlowControl(int64 quota);
+
+ // Start the closing handshake for a client-initiated shutdown of the
+ // connection. There is no API to close the connection without a closing
+ // handshake, but destroying the WebSocketChannel object while connected will
+ // effectively do that. |code| must be in the range 1000-4999. |reason| should
+ // be a valid UTF-8 string or empty.
+ //
+ // This does *not* trigger the event OnClosingHandshake(). The caller should
+ // assume that the closing handshake has started and perform the equivalent
+ // processing to OnClosingHandshake() if necessary.
+ void StartClosingHandshake(uint16 code, const std::string& reason);
+
+ // Starts the connection process, using a specified factory function rather
+ // than the default. This is exposed for testing.
+ void SendAddChannelRequestForTesting(
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory);
+
+ private:
+ // We have a simple linear progression of states from FRESHLY_CONSTRUCTED to
+ // CLOSED, except that the SEND_CLOSED and RECV_CLOSED states may be skipped
+ // in case of error.
+ enum State {
+ FRESHLY_CONSTRUCTED,
+ CONNECTING,
+ CONNECTED,
+ SEND_CLOSED, // We have sent a Close frame but not received a Close frame.
+ RECV_CLOSED, // Used briefly between receiving a Close frame and sending
+ // the response. Once we have responded, the state changes
+ // to CLOSED.
+ CLOSED, // The Closing Handshake has completed or the connection is failed.
+ };
+
+ // When failing a channel, we may or may not want to send the real reason for
+ // failing to the remote server. This enum is used by FailChannel() to
+ // choose.
+ enum ExposeError {
+ SEND_REAL_ERROR,
+ SEND_GOING_AWAY,
+ };
+
+ // Our implementation of WebSocketStream::ConnectDelegate. We do not inherit
+ // from WebSocketStream::ConnectDelegate directly to avoid cluttering our
+ // public interface with the implementation of those methods, and because the
+ // lifetime of a WebSocketChannel is longer than the lifetime of the
+ // connection process.
+ class ConnectDelegate;
+
+ // Starts the connection progress, using a specified factory function.
+ void SendAddChannelRequestWithFactory(
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory);
+
+ // Success callback from WebSocketStream::CreateAndConnectStream(). Reports
+ // success to the event interface.
+ void OnConnectSuccess(scoped_ptr<WebSocketStream> stream);
+
+ // Failure callback from WebSocketStream::CreateAndConnectStream(). Reports
+ // failure to the event interface.
+ void OnConnectFailure(uint16 websocket_error);
+
+ // Calls WebSocketStream::WriteFrames() with the appropriate arguments
+ void WriteFrames();
+
+ // Callback from WebSocketStream::WriteFrames. Sends pending data or adjusts
+ // the send quota of the renderer channel as appropriate. |result| is a net
+ // error code, usually OK. If |synchronous| is true, then OnWriteDone() is
+ // being called from within the WriteFrames() loop and does not need to call
+ // WriteFrames() itself.
+ void OnWriteDone(bool synchronous, int result);
+
+ // Calls WebSocketStream::ReadFrames() with the appropriate arguments.
+ void ReadFrames();
+
+ // Callback from WebSocketStream::ReadFrames. Handles any errors and processes
+ // the returned chunks appropriately to their type. |result| is a net error
+ // code. If |synchronous| is true, then OnReadDone() is being called from
+ // within the ReadFrames() loop and does not need to call ReadFrames() itself.
+ void OnReadDone(bool synchronous, int result);
+
+ // Processes a single chunk that has been read from the stream.
+ void ProcessFrameChunk(scoped_ptr<WebSocketFrameChunk> chunk);
+
+ // Handle a frame that we have received enough of to process. May call
+ // event_interface_ methods, send responses to the server, and change the
+ // value of state_.
+ void HandleFrame(const WebSocketFrameHeader::OpCode opcode,
+ bool is_first_chunk,
+ bool is_final_chunk,
+ const scoped_refptr<IOBufferWithSize>& data_buffer);
+
+ // Low-level method to send a single frame. Used for both data and control
+ // frames. Either sends the frame immediately or buffers it to be scheduled
+ // when the current write finishes. |fin| and |op_code| are defined as for
+ // SendFrame() above, except that |op_code| may also be a control frame
+ // opcode.
+ void SendIOBufferWithSize(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const scoped_refptr<IOBufferWithSize>& buffer);
+
+ // Perform the "Fail the WebSocket Connection" operation as defined in
+ // RFC6455. The supplied code and reason are sent back to the renderer in an
+ // OnDropChannel message. If state_ is CONNECTED then a Close message is sent
+ // to the remote host. If |expose| is SEND_REAL_ERROR then the remote host is
+ // given the same status code we gave the renderer; otherwise it is sent a
+ // fixed "Going Away" code. Resets current_frame_header_, closes the
+ // stream_, and sets state_ to CLOSED.
+ void FailChannel(ExposeError expose, uint16 code, const std::string& reason);
+
+ // Sends a Close frame to Start the WebSocket Closing Handshake, or to respond
+ // to a Close frame from the server.
+ void SendClose(uint16 code, const std::string& reason);
+
+ // Parses a Close frame. If no status code is supplied, then |code| is set to
+ // 1005 (No status code) with empty |reason|. If the supplied code is
+ // outside the valid range, then 1002 (Protocol error) is set instead. If the
+ // reason text is not valid UTF-8, then |reason| is set to an empty string
+ // instead.
+ void ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
+ uint16* code,
+ std::string* reason);
+
+ // The URL to which we connect.
+ const GURL socket_url_;
+
+ // The object receiving events.
+ const scoped_ptr<WebSocketEventInterface> event_interface_;
+
+ // The WebSocketStream to which we are sending/receiving data.
+ scoped_ptr<WebSocketStream> stream_;
+
+ // A data structure containing a vector of frames to be sent and the total
+ // number of bytes contained in the vector.
+ class SendBuffer;
+ // Data that is currently pending write, or NULL if no write is pending.
+ scoped_ptr<SendBuffer> data_being_sent_;
+ // Data that is queued up to write after the current write completes.
+ // Only non-NULL when such data actually exists.
+ scoped_ptr<SendBuffer> data_to_send_next_;
+
+ // Destination for the current call to WebSocketStream::ReadFrames
+ ScopedVector<WebSocketFrameChunk> read_frame_chunks_;
+ // Frame header for the frame currently being received. Only non-NULL while we
+ // are processing the frame. If the frame arrives in multiple chunks, can
+ // remain non-NULL while we wait for additional chunks to arrive. If the
+ // header of the frame was invalid, this is set to NULL, the channel is
+ // failed, and subsequent chunks of the same frame will be ignored.
+ scoped_ptr<WebSocketFrameHeader> current_frame_header_;
+ // Handle to an in-progress WebSocketStream creation request. Only non-NULL
+ // during the connection process.
+ scoped_ptr<WebSocketStreamRequest> stream_request_;
+ // Although it will almost never happen in practice, we can be passed an
+ // incomplete control frame, in which case we need to keep the data around
+ // long enough to reassemble it. This variable will be NULL the rest of the
+ // time.
+ scoped_refptr<IOBufferWithSize> incomplete_control_frame_body_;
+ // The point at which we give the renderer a quota refresh (quota units).
+ // "quota units" are currently bytes. TODO(ricea): Update the definition of
+ // quota units when necessary.
+ int send_quota_low_water_mark_;
+ // The amount which we refresh the quota to when it reaches the
+ // low_water_mark (quota units).
+ int send_quota_high_water_mark_;
+ // The current amount of quota that the renderer has available for sending
+ // on this logical channel (quota units).
+ int current_send_quota_;
+
+ // Storage for the status code and reason from the time we receive the Close
+ // frame until the connection is closed and we can call OnDropChannel().
+ uint16 closing_code_;
+ std::string closing_reason_;
+
+ // The current state of the channel. Mainly used for sanity checking, but also
+ // used to track the close state.
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketChannel);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_CHANNEL_H_
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
new file mode 100644
index 0000000..25e20ef
--- /dev/null
+++ b/net/websockets/websocket_channel_test.cc
@@ -0,0 +1,1177 @@
+// 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_channel.h"
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_piece.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_mux.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// Printing helpers to allow GoogleMock to print frame chunks. These are
+// explicitly designed to look like the static initialisation format we use in
+// these tests. They have to live in the net namespace in order to be found by
+// GoogleMock; a nested anonymous namespace will not work.
+
+std::ostream& operator<<(std::ostream& os, const WebSocketFrameHeader& header) {
+ return os << "{" << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", "
+ << header.opcode << ", "
+ << (header.masked ? "MASKED" : "NOT_MASKED") << ", "
+ << header.payload_length << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const WebSocketFrameChunk& chunk) {
+ os << "{";
+ if (chunk.header) {
+ os << *chunk.header;
+ } else {
+ os << "{NO_HEADER}";
+ }
+ return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK")
+ << ", \"" << base::StringPiece(chunk.data->data(),
+ chunk.data->size()) << "\"}";
+}
+
+namespace {
+
+using ::testing::AnyNumber;
+using ::testing::Field;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Pointee;
+using ::testing::Return;
+using ::testing::StrictMock;
+using ::testing::_;
+
+// This mock is for testing expectations about how the EventInterface is used.
+class MockWebSocketEventInterface : public WebSocketEventInterface {
+ public:
+ MOCK_METHOD2(OnAddChannelResponse, void(bool, const std::string&));
+ MOCK_METHOD3(OnDataFrame,
+ void(bool, WebSocketMessageType, const std::vector<char>&));
+ MOCK_METHOD1(OnFlowControl, void(int64));
+ MOCK_METHOD0(OnClosingHandshake, void(void));
+ MOCK_METHOD2(OnDropChannel, void(uint16, const std::string&));
+};
+
+// This fake EventInterface is for tests which need a WebSocketEventInterface
+// implementation but are not verifying how it is used.
+class FakeWebSocketEventInterface : public WebSocketEventInterface {
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_protocol) OVERRIDE {}
+ virtual void OnDataFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) OVERRIDE {}
+ virtual void OnFlowControl(int64 quota) OVERRIDE {}
+ virtual void OnClosingHandshake() OVERRIDE {}
+ virtual void OnDropChannel(uint16 code, const std::string& reason) OVERRIDE {}
+};
+
+// This fake WebSocketStream is for tests that require a WebSocketStream but are
+// not testing the way it is used. It has minimal functionality to return
+// the |protocol| and |extensions| that it was constructed with.
+class FakeWebSocketStream : public WebSocketStream {
+ public:
+ // Constructs with empty protocol and extensions.
+ FakeWebSocketStream() {}
+
+ // Constructs with specified protocol and extensions.
+ FakeWebSocketStream(const std::string& protocol,
+ const std::string& extensions)
+ : protocol_(protocol), extensions_(extensions) {}
+
+ virtual int SendHandshakeRequest(
+ const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadHandshakeResponse(
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual void Close() OVERRIDE {}
+
+ // Returns the string passed to the constructor.
+ virtual std::string GetSubProtocol() const OVERRIDE { return protocol_; }
+
+ // Returns the string passed to the constructor.
+ virtual std::string GetExtensions() const OVERRIDE { return extensions_; }
+
+ private:
+ // The string to return from GetSubProtocol().
+ std::string protocol_;
+
+ // The string to return from GetExtensions().
+ std::string extensions_;
+};
+
+// To make the static initialisers easier to read, we use enums rather than
+// bools.
+
+// NO_HEADER means there shouldn't be a header included in the generated
+// WebSocketFrameChunk. The static initialiser always has a header, but we can
+// avoid specifying the rest of the fields.
+enum IsFinal {
+ NO_HEADER,
+ NOT_FINAL_FRAME,
+ FINAL_FRAME
+};
+
+enum IsMasked {
+ NOT_MASKED,
+ MASKED
+};
+
+enum IsFinalChunk {
+ NOT_FINAL_CHUNK,
+ FINAL_CHUNK
+};
+
+// This is used to initialise a WebSocketFrameChunk but is statically
+// initialisable.
+struct InitFrameChunk {
+ struct FrameHeader {
+ IsFinal final;
+ // Reserved fields omitted for now. Add them if you need them.
+ WebSocketFrameHeader::OpCode opcode;
+ IsMasked masked;
+ // payload_length is the length of the whole frame. The length of the data
+ // members from every chunk in the frame must add up to the payload_length.
+ uint64 payload_length;
+ };
+ FrameHeader header;
+
+ // Directly equivalent to WebSocketFrameChunk::final_chunk
+ IsFinalChunk final_chunk;
+
+ // Will be used to create the IOBuffer member. Can be NULL for NULL data. Is a
+ // nul-terminated string for ease-of-use. This means it is not 8-bit clean,
+ // but this is not an issue for test data.
+ const char* const data;
+};
+
+// Convert a const array of InitFrameChunks to the format used at
+// runtime. Templated on the size of the array to save typing.
+template <size_t N>
+ScopedVector<WebSocketFrameChunk> CreateFrameChunkVector(
+ const InitFrameChunk (&source_chunks)[N]) {
+ ScopedVector<WebSocketFrameChunk> result_chunks;
+ result_chunks.reserve(N);
+ for (size_t i = 0; i < N; ++i) {
+ scoped_ptr<WebSocketFrameChunk> result_chunk(new WebSocketFrameChunk);
+ size_t chunk_length =
+ source_chunks[i].data ? strlen(source_chunks[i].data) : 0;
+ if (source_chunks[i].header.final != NO_HEADER) {
+ const InitFrameChunk::FrameHeader& source_header =
+ source_chunks[i].header;
+ scoped_ptr<WebSocketFrameHeader> result_header(
+ new WebSocketFrameHeader(source_header.opcode));
+ result_header->final = (source_header.final == FINAL_FRAME);
+ result_header->opcode = source_header.opcode;
+ result_header->masked = (source_header.masked == MASKED);
+ result_header->payload_length = source_header.payload_length;
+ DCHECK(chunk_length <= source_header.payload_length);
+ result_chunk->header.swap(result_header);
+ }
+ result_chunk->final_chunk = (source_chunks[i].final_chunk == FINAL_CHUNK);
+ if (source_chunks[i].data) {
+ result_chunk->data = new IOBufferWithSize(chunk_length);
+ memcpy(result_chunk->data->data(), source_chunks[i].data, chunk_length);
+ }
+ result_chunks.push_back(result_chunk.release());
+ }
+ return result_chunks.Pass();
+}
+
+// A GoogleMock action which can be used to respond to call to ReadFrames with
+// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(chunks));
+ACTION_P(ReturnChunks, source_chunks) {
+ *arg0 = CreateFrameChunkVector(source_chunks);
+ return OK;
+}
+
+// A FakeWebSocketStream whose ReadFrames() function returns data.
+class ReadableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ enum IsSync {
+ SYNC,
+ ASYNC
+ };
+
+ // After constructing the object, call PrepareReadFrames() once for each
+ // time you wish it to return from the test.
+ ReadableFakeWebSocketStream() : index_(0), read_frames_pending_(false) {}
+
+ // Check that all the prepared responses have been consumed.
+ virtual ~ReadableFakeWebSocketStream() {
+ CHECK(index_ >= responses_.size());
+ CHECK(!read_frames_pending_);
+ }
+
+ // Prepares a fake responses. Fake responses will be returned from
+ // ReadFrames() in the same order they were prepared with PrepareReadFrames()
+ // and PrepareReadFramesError(). If |async| is ASYNC, then ReadFrames() will
+ // return ERR_IO_PENDING and the callback will be scheduled to run on the
+ // message loop. This requires the test case to run the message loop. If
+ // |async| is SYNC, the response will be returned synchronously. |error| is
+ // returned directly from ReadFrames() in the synchronous case, or passed to
+ // the callback in the asynchronous case. |chunks| will be converted to a
+ // ScopedVector<WebSocketFrameChunks> and copied to the pointer that was
+ // passed to ReadFrames().
+ template <size_t N>
+ void PrepareReadFrames(IsSync async,
+ int error,
+ const InitFrameChunk (&chunks)[N]) {
+ responses_.push_back(
+ new Response(async, error, CreateFrameChunkVector(chunks)));
+ }
+
+ // Prepares a fake error response (ie. there is no data).
+ void PrepareReadFramesError(IsSync async, int error) {
+ responses_.push_back(
+ new Response(async, error, ScopedVector<WebSocketFrameChunk>()));
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ CHECK(!read_frames_pending_);
+ if (index_ >= responses_.size())
+ return ERR_IO_PENDING;
+ if (responses_[index_]->async == ASYNC) {
+ read_frames_pending_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ReadableFakeWebSocketStream::DoCallback,
+ base::Unretained(this),
+ frame_chunks,
+ callback));
+ return ERR_IO_PENDING;
+ } else {
+ frame_chunks->swap(responses_[index_]->chunks);
+ return responses_[index_++]->error;
+ }
+ }
+
+ private:
+ void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ read_frames_pending_ = false;
+ frame_chunks->swap(responses_[index_]->chunks);
+ callback.Run(responses_[index_++]->error);
+ return;
+ }
+
+ struct Response {
+ Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks)
+ : async(async), error(error), chunks(chunks.Pass()) {}
+
+ IsSync async;
+ int error;
+ ScopedVector<WebSocketFrameChunk> chunks;
+
+ private:
+ // Bad things will happen if we attempt to copy or assign "chunks".
+ DISALLOW_COPY_AND_ASSIGN(Response);
+ };
+ ScopedVector<Response> responses_;
+
+ // The index into the responses_ array of the next response to be returned.
+ size_t index_;
+
+ // True when an async response from ReadFrames() is pending. This only applies
+ // to "real" async responses. Once all the prepared responses have been
+ // returned, ReadFrames() returns ERR_IO_PENDING but read_frames_pending_ is
+ // not set to true.
+ bool read_frames_pending_;
+};
+
+// A FakeWebSocketStream where writes always complete successfully and
+// synchronously.
+class WriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+};
+
+// A FakeWebSocketStream where writes always fail.
+class UnWriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_CONNECTION_RESET;
+ }
+};
+
+// A FakeWebSocketStream which echoes any frames written back. Clears the
+// "masked" header bit, but makes no other checks for validity. Tests using this
+// must run the MessageLoop to receive the callback(s). If a message with opcode
+// Close is echoed, then an ERR_CONNECTION_CLOSED is returned in the next
+// callback. The test must do something to cause WriteFrames() to be called,
+// otherwise the ReadFrames() callback will never be called.
+class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {}
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ // Users of WebSocketStream will not expect the ReadFrames() callback to be
+ // called from within WriteFrames(), so post it to the message loop instead.
+ stored_frame_chunks_.insert(
+ stored_frame_chunks_.end(), frame_chunks->begin(), frame_chunks->end());
+ frame_chunks->weak_clear();
+ PostCallback();
+ return OK;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ read_callback_ = callback;
+ read_frame_chunks_ = frame_chunks;
+ if (done_)
+ PostCallback();
+ return ERR_IO_PENDING;
+ }
+
+ private:
+ void PostCallback() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&EchoeyFakeWebSocketStream::DoCallback,
+ base::Unretained(this)));
+ }
+
+ void DoCallback() {
+ if (done_) {
+ read_callback_.Run(ERR_CONNECTION_CLOSED);
+ } else if (!stored_frame_chunks_.empty()) {
+ done_ = MoveFrameChunks(read_frame_chunks_);
+ read_frame_chunks_ = NULL;
+ read_callback_.Run(OK);
+ }
+ }
+
+ // Copy the chunks stored in stored_frame_chunks_ to |out|, while clearing the
+ // "masked" header bit. Returns true if a Close Frame was seen, false
+ // otherwise.
+ bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* out) {
+ bool seen_close = false;
+ *out = stored_frame_chunks_.Pass();
+ for (ScopedVector<WebSocketFrameChunk>::iterator it = out->begin();
+ it != out->end();
+ ++it) {
+ WebSocketFrameHeader* header = (*it)->header.get();
+ if (header) {
+ header->masked = false;
+ if (header->opcode == WebSocketFrameHeader::kOpCodeClose)
+ seen_close = true;
+ }
+ }
+ return seen_close;
+ }
+
+ ScopedVector<WebSocketFrameChunk> stored_frame_chunks_;
+ CompletionCallback read_callback_;
+ // Owned by the caller of ReadFrames().
+ ScopedVector<WebSocketFrameChunk>* read_frame_chunks_;
+ // True if we should close the connection.
+ bool done_;
+};
+
+// This mock is for verifying that WebSocket protocol semantics are obeyed (to
+// the extent that they are implemented in WebSocketCommon).
+class MockWebSocketStream : public WebSocketStream {
+ public:
+ MOCK_METHOD2(ReadFrames,
+ int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback));
+ MOCK_METHOD2(WriteFrames,
+ int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback));
+ MOCK_METHOD0(Close, void());
+ MOCK_CONST_METHOD0(GetSubProtocol, std::string());
+ MOCK_CONST_METHOD0(GetExtensions, std::string());
+ MOCK_METHOD0(AsWebSocketStream, WebSocketStream*());
+ MOCK_METHOD4(SendHandshakeRequest,
+ int(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback));
+ MOCK_METHOD1(ReadHandshakeResponse, int(const CompletionCallback& callback));
+};
+
+struct ArgumentCopyingWebSocketFactory {
+ scoped_ptr<WebSocketStreamRequest> Factory(
+ const GURL& socket_url,
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const BoundNetLog& net_log,
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate) {
+ this->socket_url = socket_url;
+ this->requested_subprotocols = requested_subprotocols;
+ this->origin = origin;
+ this->url_request_context = url_request_context;
+ this->net_log = net_log;
+ this->connect_delegate = connect_delegate.Pass();
+ return make_scoped_ptr(new WebSocketStreamRequest);
+ }
+
+ GURL socket_url;
+ GURL origin;
+ std::vector<std::string> requested_subprotocols;
+ URLRequestContext* url_request_context;
+ BoundNetLog net_log;
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate;
+};
+
+// Converts a std::string to a std::vector<char>. For test purposes, it is
+// convenient to be able to specify data as a string, but the
+// WebSocketEventInterface requires the vector<char> type.
+std::vector<char> AsVector(const std::string& s) {
+ return std::vector<char>(s.begin(), s.end());
+}
+
+// Base class for all test fixtures.
+class WebSocketChannelTest : public ::testing::Test {
+ protected:
+ WebSocketChannelTest() : stream_(new FakeWebSocketStream) {}
+
+ // Creates a new WebSocketChannel and connects it, using the settings stored
+ // in |connect_data_|.
+ void CreateChannelAndConnect() {
+ channel_.reset(
+ new WebSocketChannel(connect_data_.url, CreateEventInterface()));
+ channel_->SendAddChannelRequestForTesting(
+ connect_data_.requested_subprotocols,
+ connect_data_.origin,
+ &connect_data_.url_request_context,
+ base::Bind(&ArgumentCopyingWebSocketFactory::Factory,
+ base::Unretained(&connect_data_.factory)));
+ }
+
+ // Same as CreateChannelAndConnect(), but calls the on_success callback as
+ // well. This method is virtual so that subclasses can also set the stream.
+ virtual void CreateChannelAndConnectSuccessfully() {
+ CreateChannelAndConnect();
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+ }
+
+ // Returns a WebSocketEventInterface to be passed to the WebSocketChannel.
+ // This implementation returns a newly-created fake. Subclasses may return a
+ // mock instead.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() {
+ return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface);
+ }
+
+ // This method serves no other purpose than to provide a nice syntax for
+ // assigning to stream_. class T must be a subclass of WebSocketStream or you
+ // will have unpleasant compile errors.
+ template <class T>
+ void set_stream(scoped_ptr<T> stream) {
+ // Since the definition of "PassAs" depends on the type T, the C++ standard
+ // requires the "template" keyword to indicate that "PassAs" should be
+ // parsed as a template method.
+ stream_ = stream.template PassAs<WebSocketStream>();
+ }
+
+ // A struct containing the data that will be used to connect the channel.
+ struct ConnectData {
+ // URL to (pretend to) connect to.
+ GURL url;
+ // Origin of the request
+ GURL origin;
+ // Requested protocols for the request.
+ std::vector<std::string> requested_subprotocols;
+ // URLRequestContext object.
+ URLRequestContext url_request_context;
+ // A fake WebSocketFactory that just records its arguments.
+ ArgumentCopyingWebSocketFactory factory;
+ };
+ ConnectData connect_data_;
+
+ // The channel we are testing. Not initialised until SetChannel() is called.
+ scoped_ptr<WebSocketChannel> channel_;
+
+ // A mock or fake stream for tests that need one.
+ scoped_ptr<WebSocketStream> stream_;
+};
+
+// Base class for tests which verify that EventInterface methods are called
+// appropriately.
+class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelEventInterfaceTest()
+ : event_interface_(new StrictMock<MockWebSocketEventInterface>) {}
+
+ // Tests using this fixture must set expectations on the event_interface_ mock
+ // object before calling CreateChannelAndConnect() or
+ // CreateChannelAndConnectSuccessfully(). This will only work once per test
+ // case, but once should be enough.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() OVERRIDE {
+ return scoped_ptr<WebSocketEventInterface>(event_interface_.release());
+ }
+
+ scoped_ptr<MockWebSocketEventInterface> event_interface_;
+};
+
+// Base class for tests which verify that WebSocketStream methods are called
+// appropriately by using a MockWebSocketStream.
+class WebSocketChannelStreamTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelStreamTest()
+ : mock_stream_(new StrictMock<MockWebSocketStream>) {}
+
+ virtual void CreateChannelAndConnectSuccessfully() OVERRIDE {
+ set_stream(mock_stream_.Pass());
+ WebSocketChannelTest::CreateChannelAndConnectSuccessfully();
+ }
+
+ scoped_ptr<MockWebSocketStream> mock_stream_;
+};
+
+// Simple test that everything that should be passed to the factory function is
+// passed to the factory function.
+TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) {
+ connect_data_.url = GURL("ws://example.com/test");
+ connect_data_.origin = GURL("http://example.com/test");
+ connect_data_.requested_subprotocols.push_back("Sinbad");
+
+ CreateChannelAndConnect();
+
+ EXPECT_EQ(connect_data_.url, connect_data_.factory.socket_url);
+ EXPECT_EQ(connect_data_.origin, connect_data_.factory.origin);
+ EXPECT_EQ(connect_data_.requested_subprotocols,
+ connect_data_.factory.requested_subprotocols);
+ EXPECT_EQ(&connect_data_.url_request_context,
+ connect_data_.factory.url_request_context);
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) {
+ // false means success.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, ""));
+ // OnFlowControl is always called immediately after connect to provide initial
+ // quota to the renderer.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) {
+ // true means failure.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, ""));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate
+ ->OnFailure(kWebSocketErrorNoStatusReceived);
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "Bob"));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate->OnSuccess(
+ scoped_ptr<WebSocketStream>(new FakeWebSocketStream("Bob", "")));
+}
+
+// The first frames from the server can arrive together with the handshake, in
+// which case they will be available as soon as ReadFrames() is called the first
+// time.
+TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could accept the handshake, but then immediately send a
+// Close frame.
+TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23},
+ FINAL_CHUNK, "\x03\xf3Internal Server Error"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorInternalServerError,
+ "Internal Server Error"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could close the connection immediately after sending the
+// handshake response (most likely a bug in the server).
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"},
+ };
+ // We use this checkpoint object to verify that the callback isn't called
+ // until we expect it to be.
+ MockFunction<void(int)> checkpoint;
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ base::MessageLoop::current()->RunUntilIdle();
+ checkpoint.Call(2);
+}
+
+// Extra data can arrive while a read is being processed, resulting in the next
+// read completing synchronously.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"},
+ };
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "WORLD"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks2);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("WORLD")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Data frames that arrive in fragments are turned into individual frames
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // Here we have one message split into 3 frames which arrive in 3 chunks. The
+ // first frame is entirely in the first chunk, the second frame is split
+ // across all the chunks, and the final frame is entirely in the final
+ // chunk. The frame fragments are converted to separate frames so that they
+ // can be delivered immediatedly. So the EventInterface should see a Text
+ // message with 5 frames.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "THREE"},
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED,
+ 7},
+ NOT_FINAL_CHUNK, " "},
+ };
+ static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK,
+ "SMALL"}};
+ static const InitFrameChunk chunks3[] = {
+ {{NO_HEADER}, FINAL_CHUNK, " "},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 6},
+ FINAL_CHUNK, "FRAMES"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("THREE")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("SMALL")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("FRAMES")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// In the case when a single-frame message because fragmented, it must be
+// correctly transformed to multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // A single-frame Text message arrives in three chunks. This should be
+ // delivered as three frames.
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12},
+ NOT_FINAL_CHUNK, "TIME"},
+ };
+ static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK,
+ " FOR "}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector(" FOR ")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If a control message is fragmented, it must be re-assembled before being
+// delivered. A control message can only be fragmented at the network level; it
+// is not permitted to be split into multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, "\x03\xe8"},
+ };
+ static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK,
+ "Clo"}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "se"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketNormalClosure, "Close"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed by the remote host without a closing handshake.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncAbnormalClosure) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// A connection reset should produce the same event as an unexpected closure.
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_RESET);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed in the middle of a Close message (server bug, etc.)
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, "\x03\xe8"},
+ };
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.1 "A client MUST close a connection if it detects a masked frame."
+TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK,
+ "HELLO"}
+ };
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.2 "If an unknown opcode is received, the receiving endpoint MUST
+// _Fail the WebSocket Connection_."
+TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {{{FINAL_FRAME, 4, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.4 "Control frames ... MAY be injected in the middle of a
+// fragmented message."
+TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // We have one message of type Text split into two frames. In the middle is a
+ // control message of type Pong.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 6},
+ FINAL_CHUNK, "SPLIT "},
+ };
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, 0},
+ FINAL_CHUNK, ""}
+ };
+ static const InitFrameChunk chunks3[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 7},
+ FINAL_CHUNK, "MESSAGE"}
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("SPLIT ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("MESSAGE")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If the renderer sends lots of small writes, we don't want to update the quota
+// for each one.
+TEST_F(WebSocketChannelEventInterfaceTest, SmallWriteDoesntUpdateQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("B"));
+}
+
+// If we send enough to go below send_quota_low_water_mask_ we should get our
+// quota refreshed.
+TEST_F(WebSocketChannelEventInterfaceTest, LargeWriteUpdatesQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ // We use this checkpoint object to verify that the quota update comes after
+ // the write.
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ // TODO(ricea): If kDefaultSendQuotaHighWaterMark changes, then this value
+ // will need to be updated.
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, std::vector<char>(1 << 17, 'B'));
+ checkpoint.Call(2);
+}
+
+// Verify that our quota actually is refreshed when we are told it is.
+TEST_F(WebSocketChannelEventInterfaceTest, QuotaReallyIsRefreshed) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ // If quota was not really refreshed, we would get an OnDropChannel()
+ // message.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(3));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ // TODO(ricea): If kDefaultSendQuotaLowWaterMark and/or
+ // kDefaultSendQuotaHighWaterMark change, then this value will need to be
+ // updated.
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>((1 << 16) + 1, 'D'));
+ checkpoint.Call(2);
+ // We should have received more quota at this point.
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>((1 << 16) + 1, 'E'));
+ checkpoint.Call(3);
+}
+
+// If we send more than the available quota then the connection will be closed
+// with an error.
+TEST_F(WebSocketChannelEventInterfaceTest, WriteOverQuotaIsRejected) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ // TODO(ricea): Change this if kDefaultSendQuotaHighWaterMark changes.
+ EXPECT_CALL(*event_interface_, OnFlowControl(1 << 17));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>((1 << 17) + 1, 'C'));
+}
+
+// If a write fails, the channel is dropped.
+TEST_F(WebSocketChannelEventInterfaceTest, FailedWrite) {
+ set_stream(make_scoped_ptr(new UnWriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("H"));
+ checkpoint.Call(2);
+}
+
+// OnDropChannel() is called exactly once when StartClosingHandshake() is used.
+TEST_F(WebSocketChannelEventInterfaceTest, SendCloseDropsChannel) {
+ set_stream(make_scoped_ptr(new EchoeyFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketNormalClosure, "Fred"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "Fred");
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.1 "a client MUST mask all frames that it sends to the server".
+// WebSocketChannel actually only sets the mask bit in the header, it doesn't
+// perform masking itself (not all transports actually use masking).
+TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) {
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(
+ *mock_stream_,
+ WriteFrames(Pointee(ElementsAre(Pointee(Field(
+ &WebSocketFrameChunk::header,
+ Pointee(Field(&WebSocketFrameHeader::masked, true)))))),
+ _)).WillOnce(Return(ERR_IO_PENDING));
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("NEEDS MASKING"));
+}
+
+} // namespace
+} // namespace net
diff --git a/net/websockets/websocket_event_interface.h b/net/websockets/websocket_event_interface.h
new file mode 100644
index 0000000..7eb54bf
--- /dev/null
+++ b/net/websockets/websocket_event_interface.h
@@ -0,0 +1,76 @@
+// 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_EVENT_INTERFACE_H_
+#define NET_WEBSOCKETS_WEBSOCKET_EVENT_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Interface for events sent from the network layer to the content layer. These
+// events will generally be sent as-is to the renderer process.
+class NET_EXPORT WebSocketEventInterface {
+ public:
+ typedef int WebSocketMessageType;
+ virtual ~WebSocketEventInterface() {}
+ // Called in response to an AddChannelRequest. This generally means that a
+ // response has been received from the remote server, but the response might
+ // have been generated internally. If |fail| is true, the channel cannot be
+ // used and it is valid to delete the WebSocketChannel from within this
+ // callback.
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_subprotocol) = 0;
+
+ // Called when a data frame has been received from the remote host and needs
+ // to be forwarded to the renderer process. It is not safe to delete the
+ // WebSocketChannel object from within this callback.
+ virtual void OnDataFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) = 0;
+
+ // Called to provide more send quota for this channel to the renderer
+ // process. Currently the quota units are always bytes of message body
+ // data. In future it might depend on the type of multiplexing in use. It is
+ // not safe to delete the WebSocketChannel from within this callback.
+ virtual void OnFlowControl(int64 quota) = 0;
+
+ // Called when the remote server has Started the WebSocket Closing
+ // Handshake. The client should not attempt to send any more messages after
+ // receiving this message. It will be followed by OnDropChannel() when the
+ // closing handshake is complete. It is not safe to delete the
+ // WebSocketChannel from within this callback.
+ virtual void OnClosingHandshake() = 0;
+
+ // Called when the channel has been dropped, either due to a network close, a
+ // network error, or a protocol error. This may or may not be preceeded by a
+ // call to OnClosingHandshake().
+ //
+ // Warning: Both the |code| and |reason| are passed through to Javascript, so
+ // callers must take care not to provide details that could be useful to
+ // attackers attempting to use WebSockets to probe networks.
+ //
+ // The channel should not be used again after OnDropChannel() has been
+ // called.
+ //
+ // It is not safe to delete the WebSocketChannel from within this
+ // callback. It is recommended to delete the channel after returning to the
+ // event loop.
+ virtual void OnDropChannel(uint16 code, const std::string& reason) = 0;
+
+ protected:
+ WebSocketEventInterface() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebSocketEventInterface);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_EVENT_INTERFACE_H_
diff --git a/net/websockets/websocket_mux.h b/net/websockets/websocket_mux.h
new file mode 100644
index 0000000..9fc1f67
--- /dev/null
+++ b/net/websockets/websocket_mux.h
@@ -0,0 +1,39 @@
+// 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_MUX_H_
+#define NET_WEBSOCKETS_WEBSOCKET_MUX_H_
+
+namespace net {
+
+// Reason codes used by the mux extension.
+enum WebSocketMuxError {
+ // Codes starting with 2000 apply to the physical connection. They are used
+ // for dropping the control channel.
+ kWebSocketMuxErrorPhysicalConnectionFailed = 2000,
+ kWebSocketMuxErrorInvalidEncapsulatingMessage = 2001,
+ kWebSocketMuxErrorChannelIdTruncated = 2002,
+ kWebSocketMuxErrorEncapsulatedFrameIsTruncated = 2003,
+ kWebSocketMuxErrorUnknownMuxOpcode = 2004,
+ kWebSocketMuxErrorInvalidMuxControlBlock = 2005,
+ kWebSocketMuxErrorChannelAlreadyExists = 2006,
+ kWebSocketMuxErrorNewChannelSlotViolation = 2007,
+ kWebSocketMuxErrorNewChannelSlotOverflow = 2008,
+ kWebSocketMuxErrorBadRequest = 2009,
+ kWebSocketMuxErrorUnknownRequestEncoding = 2010,
+ kWebSocketMuxErrorBadResponse = 2011,
+ kWebSocketMuxErrorUnknownResponseEncoding = 2012,
+
+ // Codes starting with 3000 apply to the logical connection.
+ kWebSocketMuxErrorLogicalChannelFailed = 3000,
+ kWebSocketMuxErrorSendQuotaViolation = 3005,
+ kWebSocketMuxErrorSendQuotaOverflow = 3006,
+ kWebSocketMuxErrorIdleTimeout = 3007,
+ kWebSocketMuxErrorDropChannelAck = 3008,
+ kWebSocketMuxErrorBadFragmentation = 3009,
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_MUX_H_