summaryrefslogtreecommitdiffstats
path: root/net/websockets/websocket_channel.cc
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/websockets/websocket_channel.cc
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/websockets/websocket_channel.cc')
-rw-r--r--net/websockets/websocket_channel.cc667
1 files changed, 667 insertions, 0 deletions
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