summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-26 12:14:28 +0000
committerricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-26 12:14:28 +0000
commit2f5d9f69b5dc78dfcbd0be796c18db72bdf7c3d4 (patch)
treee0f2db0e8bd745ac079c5526d6d1f09b1520e822
parente78cd5309b94bb1fd941aed879b361fc7164b780 (diff)
downloadchromium_src-2f5d9f69b5dc78dfcbd0be796c18db72bdf7c3d4.zip
chromium_src-2f5d9f69b5dc78dfcbd0be796c18db72bdf7c3d4.tar.gz
chromium_src-2f5d9f69b5dc78dfcbd0be796c18db72bdf7c3d4.tar.bz2
Most of the WebSocket code does not need to care about the original
frame boundaries. Move the chunk to frame conversion logic to WebSocketBasicStream from WebSocketChannel and change all interfaces except for WebSocketFrameParser to use WebSocketFrame instead of WebSocketFrameChunk. BUG=288603 TEST=net_unittests --gtest_filter=WebSocket* Review URL: https://codereview.chromium.org/23604044 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@225452 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/websockets/websocket_basic_stream.cc272
-rw-r--r--net/websockets/websocket_basic_stream.h55
-rw-r--r--net/websockets/websocket_basic_stream_test.cc620
-rw-r--r--net/websockets/websocket_channel.cc237
-rw-r--r--net/websockets/websocket_channel.h49
-rw-r--r--net/websockets/websocket_channel_test.cc893
-rw-r--r--net/websockets/websocket_frame.cc27
-rw-r--r--net/websockets/websocket_frame.h34
-rw-r--r--net/websockets/websocket_stream.h56
9 files changed, 1208 insertions, 1035 deletions
diff --git a/net/websockets/websocket_basic_stream.cc b/net/websockets/websocket_basic_stream.cc
index 5b02b18..fb8fb0a 100644
--- a/net/websockets/websocket_basic_stream.cc
+++ b/net/websockets/websocket_basic_stream.cc
@@ -12,6 +12,7 @@
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/logging.h"
+#include "base/safe_numerics.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/socket/client_socket_handle.h"
@@ -23,6 +24,10 @@ namespace net {
namespace {
+// This uses type uint64 to match the definition of
+// WebSocketFrameHeader::payload_length in websocket_frame.h.
+const uint64 kMaxControlFramePayload = 125;
+
// The number of bytes to attempt to read at a time.
// TODO(ricea): See if there is a better number or algorithm to fulfill our
// requirements:
@@ -47,10 +52,9 @@ WebSocketBasicStream::WebSocketBasicStream(
WebSocketBasicStream::~WebSocketBasicStream() { Close(); }
-int WebSocketBasicStream::ReadFrames(
- ScopedVector<WebSocketFrameChunk>* frame_chunks,
- const CompletionCallback& callback) {
- DCHECK(frame_chunks->empty());
+int WebSocketBasicStream::ReadFrames(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
+ DCHECK(frames->empty());
// If there is data left over after parsing the HTTP headers, attempt to parse
// it as WebSocket frames.
if (http_read_buffer_) {
@@ -60,80 +64,80 @@ int WebSocketBasicStream::ReadFrames(
scoped_refptr<GrowableIOBuffer> buffered_data;
buffered_data.swap(http_read_buffer_);
DCHECK(http_read_buffer_.get() == NULL);
+ ScopedVector<WebSocketFrameChunk> frame_chunks;
if (!parser_.Decode(buffered_data->StartOfBuffer(),
buffered_data->offset(),
- frame_chunks))
+ &frame_chunks))
return WebSocketErrorToNetError(parser_.websocket_error());
- if (!frame_chunks->empty())
- return OK;
+ if (!frame_chunks.empty()) {
+ int result = ConvertChunksToFrames(&frame_chunks, frames);
+ if (result != ERR_IO_PENDING)
+ return result;
+ }
}
- // Run until socket stops giving us data or we get some chunks.
+ // Run until socket stops giving us data or we get some frames.
while (true) {
// base::Unretained(this) here is safe because net::Socket guarantees not to
// call any callbacks after Disconnect(), which we call from the
- // destructor. The caller of ReadFrames() is required to keep |frame_chunks|
+ // destructor. The caller of ReadFrames() is required to keep |frames|
// valid.
- int result = connection_->socket()
- ->Read(read_buffer_.get(),
- read_buffer_->size(),
- base::Bind(&WebSocketBasicStream::OnReadComplete,
- base::Unretained(this),
- base::Unretained(frame_chunks),
- callback));
+ int result = connection_->socket()->Read(
+ read_buffer_.get(),
+ read_buffer_->size(),
+ base::Bind(&WebSocketBasicStream::OnReadComplete,
+ base::Unretained(this),
+ base::Unretained(frames),
+ callback));
if (result == ERR_IO_PENDING)
return result;
- result = HandleReadResult(result, frame_chunks);
+ result = HandleReadResult(result, frames);
if (result != ERR_IO_PENDING)
return result;
+ DCHECK(frames->empty());
}
}
-int WebSocketBasicStream::WriteFrames(
- ScopedVector<WebSocketFrameChunk>* frame_chunks,
- const CompletionCallback& callback) {
+int WebSocketBasicStream::WriteFrames(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback) {
// This function always concatenates all frames into a single buffer.
// TODO(ricea): Investigate whether it would be better in some cases to
// perform multiple writes with smaller buffers.
//
// First calculate the size of the buffer we need to allocate.
- typedef ScopedVector<WebSocketFrameChunk>::const_iterator Iterator;
+ typedef ScopedVector<WebSocketFrame>::const_iterator Iterator;
const int kMaximumTotalSize = std::numeric_limits<int>::max();
int total_size = 0;
- for (Iterator it = frame_chunks->begin(); it != frame_chunks->end(); ++it) {
- WebSocketFrameChunk* chunk = *it;
- DCHECK(chunk->header)
- << "Only complete frames are supported by WebSocketBasicStream";
- DCHECK(chunk->final_chunk)
- << "Only complete frames are supported by WebSocketBasicStream";
+ for (Iterator it = frames->begin(); it != frames->end(); ++it) {
+ WebSocketFrame* frame = *it;
// Force the masked bit on.
- chunk->header->masked = true;
+ frame->header.masked = true;
// We enforce flow control so the renderer should never be able to force us
// to cache anywhere near 2GB of frames.
- int chunk_size =
- chunk->data->size() + GetWebSocketFrameHeaderSize(*(chunk->header));
- CHECK_GE(kMaximumTotalSize - total_size, chunk_size)
+ int frame_size = frame->header.payload_length +
+ GetWebSocketFrameHeaderSize(frame->header);
+ CHECK_GE(kMaximumTotalSize - total_size, frame_size)
<< "Aborting to prevent overflow";
- total_size += chunk_size;
+ total_size += frame_size;
}
scoped_refptr<IOBufferWithSize> combined_buffer(
new IOBufferWithSize(total_size));
char* dest = combined_buffer->data();
int remaining_size = total_size;
- for (Iterator it = frame_chunks->begin(); it != frame_chunks->end(); ++it) {
- WebSocketFrameChunk* chunk = *it;
+ for (Iterator it = frames->begin(); it != frames->end(); ++it) {
+ WebSocketFrame* frame = *it;
WebSocketMaskingKey mask = generate_websocket_masking_key_();
- int result = WriteWebSocketFrameHeader(
- *(chunk->header), &mask, dest, remaining_size);
- DCHECK(result != ERR_INVALID_ARGUMENT)
+ int result =
+ WriteWebSocketFrameHeader(frame->header, &mask, dest, remaining_size);
+ DCHECK_NE(ERR_INVALID_ARGUMENT, result)
<< "WriteWebSocketFrameHeader() says that " << remaining_size
<< " is not enough to write the header in. This should not happen.";
CHECK_GE(result, 0) << "Potentially security-critical check failed";
dest += result;
remaining_size -= result;
- const char* const frame_data = chunk->data->data();
- const int frame_size = chunk->data->size();
+ const char* const frame_data = frame->data->data();
+ const int frame_size = frame->header.payload_length;
CHECK_GE(remaining_size, frame_size);
std::copy(frame_data, frame_data + frame_size, dest);
MaskWebSocketFramePayload(mask, 0, dest, frame_size);
@@ -196,13 +200,13 @@ int WebSocketBasicStream::WriteEverything(
while (buffer->BytesRemaining() > 0) {
// The use of base::Unretained() here is safe because on destruction we
// disconnect the socket, preventing any further callbacks.
- int result = connection_->socket()
- ->Write(buffer.get(),
- buffer->BytesRemaining(),
- base::Bind(&WebSocketBasicStream::OnWriteComplete,
- base::Unretained(this),
- buffer,
- callback));
+ int result = connection_->socket()->Write(
+ buffer.get(),
+ buffer->BytesRemaining(),
+ base::Bind(&WebSocketBasicStream::OnWriteComplete,
+ base::Unretained(this),
+ buffer,
+ callback));
if (result > 0) {
buffer->DidConsume(result);
} else {
@@ -217,12 +221,12 @@ void WebSocketBasicStream::OnWriteComplete(
const CompletionCallback& callback,
int result) {
if (result < 0) {
- DCHECK(result != ERR_IO_PENDING);
+ DCHECK_NE(ERR_IO_PENDING, result);
callback.Run(result);
return;
}
- DCHECK(result != 0);
+ DCHECK_NE(0, result);
buffer->DidConsume(result);
result = WriteEverything(buffer, callback);
if (result != ERR_IO_PENDING)
@@ -231,27 +235,177 @@ void WebSocketBasicStream::OnWriteComplete(
int WebSocketBasicStream::HandleReadResult(
int result,
- ScopedVector<WebSocketFrameChunk>* frame_chunks) {
+ ScopedVector<WebSocketFrame>* frames) {
DCHECK_NE(ERR_IO_PENDING, result);
- DCHECK(frame_chunks->empty());
+ DCHECK(frames->empty());
if (result < 0)
return result;
if (result == 0)
return ERR_CONNECTION_CLOSED;
- if (!parser_.Decode(read_buffer_->data(), result, frame_chunks))
+ ScopedVector<WebSocketFrameChunk> frame_chunks;
+ if (!parser_.Decode(read_buffer_->data(), result, &frame_chunks))
return WebSocketErrorToNetError(parser_.websocket_error());
- if (!frame_chunks->empty())
- return OK;
- return ERR_IO_PENDING;
+ if (frame_chunks.empty())
+ return ERR_IO_PENDING;
+ return ConvertChunksToFrames(&frame_chunks, frames);
}
-void WebSocketBasicStream::OnReadComplete(
+int WebSocketBasicStream::ConvertChunksToFrames(
ScopedVector<WebSocketFrameChunk>* frame_chunks,
- const CompletionCallback& callback,
- int result) {
- result = HandleReadResult(result, frame_chunks);
+ ScopedVector<WebSocketFrame>* frames) {
+ for (size_t i = 0; i < frame_chunks->size(); ++i) {
+ scoped_ptr<WebSocketFrame> frame;
+ int result = ConvertChunkToFrame(
+ scoped_ptr<WebSocketFrameChunk>((*frame_chunks)[i]), &frame);
+ (*frame_chunks)[i] = NULL;
+ if (result != OK)
+ return result;
+ if (frame)
+ frames->push_back(frame.release());
+ }
+ // All the elements of |frame_chunks| are now NULL, so there is no point in
+ // calling delete on them all.
+ frame_chunks->weak_clear();
+ if (frames->empty())
+ return ERR_IO_PENDING;
+ return OK;
+}
+
+int WebSocketBasicStream::ConvertChunkToFrame(
+ scoped_ptr<WebSocketFrameChunk> chunk,
+ scoped_ptr<WebSocketFrame>* frame) {
+ DCHECK(frame->get() == NULL);
+ 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 (bug in WebSocketFrameParser?)";
+ is_first_chunk = true;
+ current_frame_header_.swap(chunk->header);
+ }
+ const int chunk_size = chunk->data ? chunk->data->size() : 0;
+ DCHECK(current_frame_header_) << "Unexpected header-less chunk received "
+ << "(final_chunk = " << chunk->final_chunk
+ << ", data size = " << chunk_size
+ << ") (bug in WebSocketFrameParser?)";
+ scoped_refptr<IOBufferWithSize> data_buffer;
+ data_buffer.swap(chunk->data);
+ const bool is_final_chunk = chunk->final_chunk;
+ const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
+ if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
+ bool protocol_error = false;
+ if (!current_frame_header_->final) {
+ DVLOG(1) << "WebSocket protocol error. Control frame, opcode=" << opcode
+ << " received with FIN bit unset.";
+ protocol_error = true;
+ }
+ if (current_frame_header_->payload_length > kMaxControlFramePayload) {
+ DVLOG(1) << "WebSocket protocol error. Control frame, opcode=" << opcode
+ << ", payload_length=" << current_frame_header_->payload_length
+ << " exceeds maximum payload length for a control message.";
+ protocol_error = true;
+ }
+ if (protocol_error) {
+ current_frame_header_.reset();
+ return ERR_WS_PROTOCOL_ERROR;
+ }
+ if (!is_final_chunk) {
+ DVLOG(2) << "Encountered a split control frame, opcode " << opcode;
+ if (incomplete_control_frame_body_) {
+ DVLOG(3) << "Appending to an existing split control frame.";
+ AddToIncompleteControlFrameBody(data_buffer);
+ } else {
+ DVLOG(3) << "Creating new storage for an incomplete control frame.";
+ incomplete_control_frame_body_ = new GrowableIOBuffer();
+ // This method checks for oversize control frames above, so as long as
+ // the frame parser is working correctly, this won't overflow. If a bug
+ // does cause it to overflow, it will CHECK() in
+ // AddToIncompleteControlFrameBody() without writing outside the buffer.
+ incomplete_control_frame_body_->SetCapacity(kMaxControlFramePayload);
+ AddToIncompleteControlFrameBody(data_buffer);
+ }
+ return OK;
+ }
+ if (incomplete_control_frame_body_) {
+ DVLOG(2) << "Rejoining a split control frame, opcode " << opcode;
+ AddToIncompleteControlFrameBody(data_buffer);
+ const int body_size = incomplete_control_frame_body_->offset();
+ DCHECK_EQ(body_size,
+ static_cast<int>(current_frame_header_->payload_length));
+ scoped_refptr<IOBufferWithSize> body = new IOBufferWithSize(body_size);
+ memcpy(body->data(),
+ incomplete_control_frame_body_->StartOfBuffer(),
+ body_size);
+ incomplete_control_frame_body_ = NULL; // Frame now complete.
+ DCHECK(is_final_chunk);
+ *frame = CreateFrame(is_final_chunk, body);
+ return OK;
+ }
+ }
+
+ // Apply basic sanity checks to the |payload_length| field from the frame
+ // header. A check for exact equality can only be used when the whole frame
+ // arrives in one chunk.
+ DCHECK_GE(current_frame_header_->payload_length,
+ base::checked_numeric_cast<uint64>(chunk_size));
+ DCHECK(!is_first_chunk || !is_final_chunk ||
+ current_frame_header_->payload_length ==
+ base::checked_numeric_cast<uint64>(chunk_size));
+
+ // Convert the chunk to a complete frame.
+ *frame = CreateFrame(is_final_chunk, data_buffer);
+ return OK;
+}
+
+scoped_ptr<WebSocketFrame> WebSocketBasicStream::CreateFrame(
+ bool is_final_chunk,
+ const scoped_refptr<IOBufferWithSize>& data) {
+ scoped_ptr<WebSocketFrame> result_frame;
+ const bool is_final_chunk_in_message =
+ is_final_chunk && current_frame_header_->final;
+ const int data_size = data ? data->size() : 0;
+ const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
+ // Empty frames convey no useful information unless they have the "final" bit
+ // set.
+ if (is_final_chunk_in_message || data_size > 0) {
+ result_frame.reset(new WebSocketFrame(opcode));
+ result_frame->header.CopyFrom(*current_frame_header_);
+ result_frame->header.final = is_final_chunk_in_message;
+ result_frame->header.payload_length = data_size;
+ result_frame->data = data;
+ // Ensure that opcodes Text and Binary are only used for the first frame in
+ // the message.
+ if (WebSocketFrameHeader::IsKnownDataOpCode(opcode))
+ current_frame_header_->opcode = WebSocketFrameHeader::kOpCodeContinuation;
+ }
+ // Make sure that a frame header is not applied to any chunks that do not
+ // belong to it.
+ if (is_final_chunk)
+ current_frame_header_.reset();
+ return result_frame.Pass();
+}
+
+void WebSocketBasicStream::AddToIncompleteControlFrameBody(
+ const scoped_refptr<IOBufferWithSize>& data_buffer) {
+ if (!data_buffer)
+ return;
+ const int new_offset =
+ incomplete_control_frame_body_->offset() + data_buffer->size();
+ CHECK_GE(incomplete_control_frame_body_->capacity(), new_offset)
+ << "Control frame body larger than frame header indicates; frame parser "
+ "bug?";
+ memcpy(incomplete_control_frame_body_->data(),
+ data_buffer->data(),
+ data_buffer->size());
+ incomplete_control_frame_body_->set_offset(new_offset);
+}
+
+void WebSocketBasicStream::OnReadComplete(ScopedVector<WebSocketFrame>* frames,
+ const CompletionCallback& callback,
+ int result) {
+ result = HandleReadResult(result, frames);
if (result == ERR_IO_PENDING)
- result = ReadFrames(frame_chunks, callback);
+ result = ReadFrames(frames, callback);
if (result != ERR_IO_PENDING)
callback.Run(result);
}
diff --git a/net/websockets/websocket_basic_stream.h b/net/websockets/websocket_basic_stream.h
index 1231da8..45c183c 100644
--- a/net/websockets/websocket_basic_stream.h
+++ b/net/websockets/websocket_basic_stream.h
@@ -22,6 +22,7 @@ class GrowableIOBuffer;
class HttpRequestHeaders;
class HttpResponseInfo;
class IOBufferWithSize;
+struct WebSocketFrame;
struct WebSocketFrameChunk;
// Implementation of WebSocketStream for non-multiplexed ws:// connections (or
@@ -39,10 +40,10 @@ class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream {
virtual ~WebSocketBasicStream();
// WebSocketStream implementation.
- virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE;
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE;
virtual void Close() OVERRIDE;
@@ -83,13 +84,43 @@ class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream {
int result);
// Attempts to parse the output of a read as WebSocket frames. On success,
- // returns OK and places the frame(s) in frame_chunks.
- int HandleReadResult(int result,
- ScopedVector<WebSocketFrameChunk>* frame_chunks);
+ // returns OK and places the frame(s) in |frames|.
+ int HandleReadResult(int result, ScopedVector<WebSocketFrame>* frames);
+
+ // Converts the chunks in |frame_chunks| into frames and writes them to
+ // |frames|. |frame_chunks| is destroyed in the process. Returns
+ // ERR_WS_PROTOCOL_ERROR if an invalid chunk was found. If one or more frames
+ // was added to |frames|, then returns OK, otherwise returns ERR_IO_PENDING.
+ int ConvertChunksToFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ ScopedVector<WebSocketFrame>* frames);
+
+ // Converts a |chunk| to a |frame|. |*frame| should be NULL on entry to this
+ // method. If |chunk| is an incomplete control frame, or an empty non-final
+ // frame, then |*frame| may still be NULL on exit. If an invalid control frame
+ // is found, returns ERR_WS_PROTOCOL_ERROR and the stream is no longer
+ // usable. Otherwise returns OK (even if frame is still NULL).
+ int ConvertChunkToFrame(scoped_ptr<WebSocketFrameChunk> chunk,
+ scoped_ptr<WebSocketFrame>* frame);
+
+ // Creates a frame based on the value of |is_final_chunk|, |data| and
+ // |current_frame_header_|. Clears |current_frame_header_| if |is_final_chunk|
+ // is true. |data| may be NULL if the frame has an empty payload. A frame with
+ // no data and the "final" flag not set is not useful; in this case the
+ // returned frame will be NULL. Otherwise, |current_frame_header_->opcode| is
+ // set to Continuation after use if it was Text or Binary, in accordance with
+ // WebSocket RFC6455 section 5.4.
+ scoped_ptr<WebSocketFrame> CreateFrame(
+ bool is_final_chunk,
+ const scoped_refptr<IOBufferWithSize>& data);
+
+ // Adds |data_buffer| to the end of |incomplete_control_frame_body_|, applying
+ // bounds checks.
+ void AddToIncompleteControlFrameBody(
+ const scoped_refptr<IOBufferWithSize>& data_buffer);
// Called when a read completes. Parses the result and (unless no complete
// header has been received) calls |callback|.
- void OnReadComplete(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ void OnReadComplete(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback,
int result);
@@ -102,6 +133,18 @@ class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream {
// from being returned to the pool.
scoped_ptr<ClientSocketHandle> connection_;
+ // Frame header for the frame currently being received. Only non-NULL while we
+ // are processing the frame. If the frame arrives in multiple chunks, it can
+ // remain non-NULL until additional chunks 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_;
+
+ // Although it should rarely happen in practice, a control frame can arrive
+ // broken into chunks. This variable provides storage for a partial control
+ // frame until the rest arrives. It will be NULL the rest of the time.
+ scoped_refptr<GrowableIOBuffer> incomplete_control_frame_body_;
+
// Only used during handshake. Some data may be left in this buffer after the
// handshake, in which case it will be picked up during the first call to
// ReadFrames(). The type is GrowableIOBuffer for compatibility with
diff --git a/net/websockets/websocket_basic_stream_test.cc b/net/websockets/websocket_basic_stream_test.cc
index ec2e51a..37a49f6 100644
--- a/net/websockets/websocket_basic_stream_test.cc
+++ b/net/websockets/websocket_basic_stream_test.cc
@@ -37,33 +37,21 @@ const size_t kMultipleFramesSize = arraysize(kMultipleFrames) - 1;
// invalid.
const char kInvalidFrame[] = "\x81\x7E\x00\x07Invalid";
const size_t kInvalidFrameSize = arraysize(kInvalidFrame) - 1;
+// Control frames must have the FIN bit set. This one does not.
+const char kPingFrameWithoutFin[] = "\x09\x00";
+const size_t kPingFrameWithoutFinSize = arraysize(kPingFrameWithoutFin) - 1;
+// Control frames must have a payload of 125 bytes or less. This one has
+// a payload of 126 bytes.
+const char k126BytePong[] =
+ "\x8a\x7e\x00\x7eZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
+const size_t k126BytePongSize = arraysize(k126BytePong) - 1;
+const char kCloseFrame[] = "\x88\x09\x03\xe8occludo";
+const size_t kCloseFrameSize = arraysize(kCloseFrame) - 1;
const char kWriteFrame[] = "\x81\x85\x00\x00\x00\x00Write";
const size_t kWriteFrameSize = arraysize(kWriteFrame) - 1;
const WebSocketMaskingKey kNulMaskingKey = {{'\0', '\0', '\0', '\0'}};
-// Generates a ScopedVector<WebSocketFrameChunk> which will have a wire format
-// matching kWriteFrame.
-ScopedVector<WebSocketFrameChunk> GenerateWriteFrame() {
- scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk);
- const size_t payload_size =
- kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize +
- WebSocketFrameHeader::kMaskingKeyLength);
- chunk->data = new IOBufferWithSize(payload_size);
- memcpy(chunk->data->data(),
- kWriteFrame + kWriteFrameSize - payload_size,
- payload_size);
- chunk->final_chunk = true;
- scoped_ptr<WebSocketFrameHeader> header(
- new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeText));
- header->final = true;
- header->masked = true;
- header->payload_length = payload_size;
- chunk->header = header.Pass();
- ScopedVector<WebSocketFrameChunk> chunks;
- chunks.push_back(chunk.release());
- return chunks.Pass();
-}
-
// A masking key generator function which generates the identity mask,
// ie. "\0\0\0\0".
WebSocketMaskingKey GenerateNulMaskingKey() { return kNulMaskingKey; }
@@ -131,11 +119,6 @@ class WebSocketBasicStreamSocketTest : public WebSocketBasicStreamTest {
CreateStream(reads, N, NULL, 0);
}
- template <size_t N>
- void CreateWriteOnly(MockWrite (&writes)[N]) {
- CreateStream(NULL, 0, writes, N);
- }
-
void CreateNullStream() { CreateStream(NULL, 0, NULL, 0); }
scoped_ptr<SocketDataProvider> socket_data_;
@@ -143,66 +126,145 @@ class WebSocketBasicStreamSocketTest : public WebSocketBasicStreamTest {
ClientSocketPoolHistograms histograms_;
MockTransportClientSocketPool pool_;
CapturingBoundNetLog(bound_net_log_);
- ScopedVector<WebSocketFrameChunk> frame_chunks_;
+ ScopedVector<WebSocketFrame> frames_;
TestCompletionCallback cb_;
scoped_refptr<GrowableIOBuffer> http_read_buffer_;
std::string sub_protocol_;
std::string extensions_;
};
+// A test fixture for the common case of tests that only perform a single read.
+class WebSocketBasicStreamSocketSingleReadTest
+ : public WebSocketBasicStreamSocketTest {
+ protected:
+ void CreateRead(const MockRead& read) {
+ reads_[0] = read;
+ CreateStream(reads_, 1U, NULL, 0);
+ }
+
+ MockRead reads_[1];
+};
+
+// A test fixture for tests that perform chunked reads.
+class WebSocketBasicStreamSocketChunkedReadTest
+ : public WebSocketBasicStreamSocketTest {
+ protected:
+ // Specify the behaviour if there aren't enough chunks to use all the data. If
+ // LAST_FRAME_BIG is specified, then the rest of the data will be
+ // put in the last chunk. If LAST_FRAME_NOT_BIG is specified, then the last
+ // frame will be no bigger than the rest of the frames (but it can be smaller,
+ // if not enough data remains).
+ enum LastFrameBehaviour {
+ LAST_FRAME_BIG,
+ LAST_FRAME_NOT_BIG
+ };
+
+ // Prepares a read from |data| of |data_size|, split into |number_of_chunks|,
+ // each of |chunk_size| (except that the last chunk may be larger or
+ // smaller). All reads must be either SYNCHRONOUS or ASYNC (not a mixture),
+ // and errors cannot be simulated. Once data is exhausted, further reads will
+ // return 0 (ie. connection closed).
+ void CreateChunkedRead(IoMode mode,
+ const char data[],
+ size_t data_size,
+ int chunk_size,
+ int number_of_chunks,
+ LastFrameBehaviour last_frame_behaviour) {
+ reads_.reset(new MockRead[number_of_chunks]);
+ const char* start = data;
+ for (int i = 0; i < number_of_chunks; ++i) {
+ int len = chunk_size;
+ const bool is_last_chunk = (i == number_of_chunks - 1);
+ if ((last_frame_behaviour == LAST_FRAME_BIG && is_last_chunk) ||
+ static_cast<int>(data + data_size - start) < len) {
+ len = static_cast<int>(data + data_size - start);
+ }
+ reads_[i] = MockRead(mode, start, len);
+ start += len;
+ }
+ CreateStream(reads_.get(), number_of_chunks, NULL, 0);
+ }
+
+ scoped_ptr<MockRead[]> reads_;
+};
+
+// Test fixture for write tests.
+class WebSocketBasicStreamSocketWriteTest
+ : public WebSocketBasicStreamSocketTest {
+ protected:
+ // All write tests use the same frame, so it is easiest to create it during
+ // test creation.
+ virtual void SetUp() OVERRIDE { PrepareWriteFrame(); }
+
+ // Creates a WebSocketFrame with a wire format matching kWriteFrame and adds
+ // it to |frames_|.
+ void PrepareWriteFrame() {
+ scoped_ptr<WebSocketFrame> frame(
+ new WebSocketFrame(WebSocketFrameHeader::kOpCodeText));
+ const size_t payload_size =
+ kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize +
+ WebSocketFrameHeader::kMaskingKeyLength);
+ frame->data = new IOBuffer(payload_size);
+ memcpy(frame->data->data(),
+ kWriteFrame + kWriteFrameSize - payload_size,
+ payload_size);
+ WebSocketFrameHeader& header = frame->header;
+ header.final = true;
+ header.masked = true;
+ header.payload_length = payload_size;
+ frames_.push_back(frame.release());
+ }
+
+ // Creates a stream that expects the listed writes.
+ template <size_t N>
+ void CreateWriteOnly(MockWrite (&writes)[N]) {
+ CreateStream(NULL, 0, writes, N);
+ }
+};
+
TEST_F(WebSocketBasicStreamSocketTest, ConstructionWorks) {
CreateNullStream();
}
-TEST_F(WebSocketBasicStreamSocketTest, SyncReadWorks) {
- MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize)};
- CreateReadOnly(reads);
- int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncReadWorks) {
+ CreateRead(MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize));
+ int result = stream_->ReadFrames(&frames_, cb_.callback());
EXPECT_EQ(OK, result);
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
- EXPECT_TRUE(frame_chunks_[0]->header->final);
- EXPECT_TRUE(frame_chunks_[0]->final_chunk);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
+ EXPECT_TRUE(frames_[0]->header.final);
}
-TEST_F(WebSocketBasicStreamSocketTest, AsyncReadWorks) {
- MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kSampleFrameSize)};
- CreateReadOnly(reads);
- int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncReadWorks) {
+ CreateRead(MockRead(ASYNC, kSampleFrame, kSampleFrameSize));
+ int result = stream_->ReadFrames(&frames_, cb_.callback());
ASSERT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, cb_.WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
// Don't repeat all the tests from SyncReadWorks; just enough to be sure the
// frame was really read.
}
// ReadFrames will not return a frame whose header has not been wholly received.
-TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSync) {
- MockRead reads[] = {
- MockRead(SYNCHRONOUS, kSampleFrame, 1),
- MockRead(SYNCHRONOUS, kSampleFrame + 1, kSampleFrameSize - 1)};
- CreateReadOnly(reads);
- int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, HeaderFragmentedSync) {
+ CreateChunkedRead(
+ SYNCHRONOUS, kSampleFrame, kSampleFrameSize, 1, 2, LAST_FRAME_BIG);
+ int result = stream_->ReadFrames(&frames_, cb_.callback());
ASSERT_EQ(OK, result);
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
}
// The same behaviour applies to asynchronous reads.
-TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedAsync) {
- MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1),
- MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)};
- CreateReadOnly(reads);
- int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, HeaderFragmentedAsync) {
+ CreateChunkedRead(
+ ASYNC, kSampleFrame, kSampleFrameSize, 1, 2, LAST_FRAME_BIG);
+ int result = stream_->ReadFrames(&frames_, cb_.callback());
ASSERT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, cb_.WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
}
// If it receives an incomplete header in a synchronous call, then has to wait
@@ -211,12 +273,11 @@ TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSyncAsync) {
MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, 1),
MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)};
CreateReadOnly(reads);
- int result = stream_->ReadFrames(&frame_chunks_, cb_.callback());
+ int result = stream_->ReadFrames(&frames_, cb_.callback());
ASSERT_EQ(ERR_IO_PENDING, result);
EXPECT_EQ(OK, cb_.WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
}
// An extended header should also return ERR_IO_PENDING if it is not completely
@@ -226,112 +287,164 @@ TEST_F(WebSocketBasicStreamSocketTest, FragmentedLargeHeader) {
MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize - 1),
MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
CreateReadOnly(reads);
- EXPECT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
}
-// A frame that does not arrive in a single read should arrive in chunks.
-TEST_F(WebSocketBasicStreamSocketTest, LargeFrameFirstChunk) {
- MockRead reads[] = {
- MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize)};
- CreateReadOnly(reads);
- EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(kLargeFrameDeclaredPayloadSize,
- frame_chunks_[0]->header->payload_length);
- EXPECT_TRUE(frame_chunks_[0]->header->final);
- EXPECT_FALSE(frame_chunks_[0]->final_chunk);
+// A frame that does not arrive in a single read should be broken into separate
+// frames.
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, LargeFrameFirstChunk) {
+ CreateRead(MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize));
+ EXPECT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_FALSE(frames_[0]->header.final);
EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize,
- static_cast<size_t>(frame_chunks_[0]->data->size()));
+ static_cast<size_t>(frames_[0]->header.payload_length));
}
-// If only the header arrives, we should get a zero-byte chunk.
+// If only the header of a data frame arrives, we should not receive a frame and
+// be told to wait. WebSocketBasicStream does two reads in this case, as after
+// the first read it has no frames to return.
TEST_F(WebSocketBasicStreamSocketTest, HeaderOnlyChunk) {
MockRead reads[] = {
- MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize)};
+ MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize),
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
CreateReadOnly(reads);
- EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
- ASSERT_EQ(1U, frame_chunks_.size());
- EXPECT_FALSE(frame_chunks_[0]->final_chunk);
- EXPECT_TRUE(frame_chunks_[0]->data.get() == NULL);
+ EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(0U, frames_.size());
}
-// The second and subsequent chunks of a frame have no header.
-TEST_F(WebSocketBasicStreamSocketTest, LargeFrameTwoChunks) {
- static const size_t kChunkSize = 16;
+// If the header and the body of a data frame arrive seperately, we should only
+// see one frame.
+TEST_F(WebSocketBasicStreamSocketTest, HeaderBodySeparated) {
MockRead reads[] = {
- MockRead(ASYNC, kPartialLargeFrame, kChunkSize),
- MockRead(ASYNC, kPartialLargeFrame + kChunkSize, kChunkSize)};
+ MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize),
+ MockRead(ASYNC,
+ kPartialLargeFrame + kLargeFrameHeaderSize,
+ kPartialLargeFrameSize - kLargeFrameHeaderSize)};
CreateReadOnly(reads);
+ EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_EQ(OK, cb_.WaitForResult());
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize,
+ frames_[0]->header.payload_length);
+}
+
+// If the header and body of a data frame arrive separately, the frame we see
+// should have the opcode from the header (not Continuation).
+TEST_F(WebSocketBasicStreamSocketTest, HeaderBodySeparatedOpCodeNotLost) {
+ MockRead reads[] = {
+ MockRead(ASYNC, kPartialLargeFrame, kLargeFrameHeaderSize),
+ MockRead(ASYNC,
+ kPartialLargeFrame + kLargeFrameHeaderSize,
+ kPartialLargeFrameSize - kLargeFrameHeaderSize)};
+ CreateReadOnly(reads);
+ EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_EQ(OK, cb_.WaitForResult());
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
+}
+
+// Every frame has a header with a correct payload_length field.
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, LargeFrameTwoChunks) {
+ const size_t kChunkSize = 16;
+ CreateChunkedRead(ASYNC,
+ kPartialLargeFrame,
+ kPartialLargeFrameSize,
+ kChunkSize,
+ 2,
+ LAST_FRAME_NOT_BIG);
TestCompletionCallback cb[2];
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb[0].callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[0].callback()));
EXPECT_EQ(OK, cb[0].WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->header);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(kChunkSize - kLargeFrameHeaderSize,
+ frames_[0]->header.payload_length);
- frame_chunks_.clear();
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb[1].callback()));
+ frames_.clear();
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[1].callback()));
EXPECT_EQ(OK, cb[1].WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_FALSE(frame_chunks_[0]->header);
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(kChunkSize, frames_[0]->header.payload_length);
}
-// Only the final chunk of a frame has final_chunk set.
-TEST_F(WebSocketBasicStreamSocketTest, OnlyFinalChunkIsFinal) {
+// Only the final frame of a fragmented message has |final| bit set.
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, OnlyFinalChunkIsFinal) {
static const size_t kFirstChunkSize = 4;
- MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kFirstChunkSize),
- MockRead(ASYNC,
- kSampleFrame + kFirstChunkSize,
- kSampleFrameSize - kFirstChunkSize)};
- CreateReadOnly(reads);
+ CreateChunkedRead(ASYNC,
+ kSampleFrame,
+ kSampleFrameSize,
+ kFirstChunkSize,
+ 2,
+ LAST_FRAME_BIG);
TestCompletionCallback cb[2];
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb[0].callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[0].callback()));
EXPECT_EQ(OK, cb[0].WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_FALSE(frame_chunks_[0]->final_chunk);
+ ASSERT_EQ(1U, frames_.size());
+ ASSERT_FALSE(frames_[0]->header.final);
- frame_chunks_.clear();
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb[1].callback()));
+ frames_.clear();
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[1].callback()));
EXPECT_EQ(OK, cb[1].WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->final_chunk);
+ ASSERT_EQ(1U, frames_.size());
+ ASSERT_TRUE(frames_[0]->header.final);
}
-// Multiple frames that arrive together should be parsed correctly.
-TEST_F(WebSocketBasicStreamSocketTest, ThreeFramesTogether) {
- MockRead reads[] = {
- MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize)};
- CreateReadOnly(reads);
+// All frames after the first have their opcode changed to Continuation.
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, ContinuationOpCodeUsed) {
+ const size_t kFirstChunkSize = 3;
+ const int kChunkCount = 3;
+ // The input data is one frame with opcode Text, which arrives in three
+ // separate chunks.
+ CreateChunkedRead(ASYNC,
+ kSampleFrame,
+ kSampleFrameSize,
+ kFirstChunkSize,
+ kChunkCount,
+ LAST_FRAME_BIG);
+ TestCompletionCallback cb[kChunkCount];
+
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[0].callback()));
+ EXPECT_EQ(OK, cb[0].WaitForResult());
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
+
+ // This test uses a loop to verify that the opcode for every frames generated
+ // after the first is converted to Continuation.
+ for (int i = 1; i < kChunkCount; ++i) {
+ frames_.clear();
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[i].callback()));
+ EXPECT_EQ(OK, cb[i].WaitForResult());
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation,
+ frames_[0]->header.opcode);
+ }
+}
- ASSERT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
- ASSERT_EQ(3U, frame_chunks_.size());
- EXPECT_TRUE(frame_chunks_[0]->final_chunk);
- EXPECT_TRUE(frame_chunks_[1]->final_chunk);
- EXPECT_TRUE(frame_chunks_[2]->final_chunk);
+// Multiple frames that arrive together should be parsed correctly.
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, ThreeFramesTogether) {
+ CreateRead(MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize));
+
+ ASSERT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(3U, frames_.size());
+ EXPECT_TRUE(frames_[0]->header.final);
+ EXPECT_TRUE(frames_[1]->header.final);
+ EXPECT_TRUE(frames_[2]->header.final);
}
// ERR_CONNECTION_CLOSED must be returned on close.
-TEST_F(WebSocketBasicStreamSocketTest, SyncClose) {
- MockRead reads[] = {MockRead(SYNCHRONOUS, "", 0)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncClose) {
+ CreateRead(MockRead(SYNCHRONOUS, "", 0));
EXPECT_EQ(ERR_CONNECTION_CLOSED,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ stream_->ReadFrames(&frames_, cb_.callback()));
}
-TEST_F(WebSocketBasicStreamSocketTest, AsyncClose) {
- MockRead reads[] = {MockRead(ASYNC, "", 0)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncClose) {
+ CreateRead(MockRead(ASYNC, "", 0));
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
}
@@ -339,53 +452,52 @@ TEST_F(WebSocketBasicStreamSocketTest, AsyncClose) {
// ERR_CONNECTION_CLOSED. This is not expected to happen on an established
// connection; a Read of size 0 is the expected behaviour. The key point of this
// test is to confirm that ReadFrames() behaviour is identical in both cases.
-TEST_F(WebSocketBasicStreamSocketTest, SyncCloseWithErr) {
- MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncCloseWithErr) {
+ CreateRead(MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED));
EXPECT_EQ(ERR_CONNECTION_CLOSED,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ stream_->ReadFrames(&frames_, cb_.callback()));
}
-TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseWithErr) {
- MockRead reads[] = {MockRead(ASYNC, ERR_CONNECTION_CLOSED)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncCloseWithErr) {
+ CreateRead(MockRead(ASYNC, ERR_CONNECTION_CLOSED));
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
}
-TEST_F(WebSocketBasicStreamSocketTest, SyncErrorsPassedThrough) {
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncErrorsPassedThrough) {
// ERR_INSUFFICIENT_RESOURCES here represents an arbitrary error that
// WebSocketBasicStream gives no special handling to.
- MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES)};
- CreateReadOnly(reads);
+ CreateRead(MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES));
EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ stream_->ReadFrames(&frames_, cb_.callback()));
}
-TEST_F(WebSocketBasicStreamSocketTest, AsyncErrorsPassedThrough) {
- MockRead reads[] = {MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncErrorsPassedThrough) {
+ CreateRead(MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES));
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES, cb_.WaitForResult());
}
// If we get a frame followed by a close, we should receive them separately.
-TEST_F(WebSocketBasicStreamSocketTest, CloseAfterFrame) {
- MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize),
- MockRead(SYNCHRONOUS, "", 0)};
- CreateReadOnly(reads);
-
- EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
- EXPECT_EQ(1U, frame_chunks_.size());
- frame_chunks_.clear();
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, CloseAfterFrame) {
+ // The chunk size equals the data size, so the second chunk is 0 size, closing
+ // the connection.
+ CreateChunkedRead(SYNCHRONOUS,
+ kSampleFrame,
+ kSampleFrameSize,
+ kSampleFrameSize,
+ 2,
+ LAST_FRAME_NOT_BIG);
+
+ EXPECT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_EQ(1U, frames_.size());
+ frames_.clear();
EXPECT_EQ(ERR_CONNECTION_CLOSED,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ stream_->ReadFrames(&frames_, cb_.callback()));
}
// Synchronous close after an async frame header is handled by a different code
@@ -395,8 +507,7 @@ TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseAfterIncompleteHeader) {
MockRead(SYNCHRONOUS, "", 0)};
CreateReadOnly(reads);
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
}
@@ -407,8 +518,7 @@ TEST_F(WebSocketBasicStreamSocketTest, AsyncErrCloseAfterIncompleteHeader) {
MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)};
CreateReadOnly(reads);
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult());
}
@@ -418,80 +528,186 @@ TEST_F(WebSocketBasicStreamSocketTest, HttpReadBufferIsUsed) {
SetHttpReadBuffer(kSampleFrame, kSampleFrameSize);
CreateNullStream();
- EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback()));
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->data);
- EXPECT_EQ(6, frame_chunks_[0]->data->size());
+ EXPECT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(1U, frames_.size());
+ ASSERT_TRUE(frames_[0]->data);
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
}
// Check that a frame whose header partially arrived at the end of the response
// headers works correctly.
-TEST_F(WebSocketBasicStreamSocketTest, PartialFrameHeaderInHttpResponse) {
+TEST_F(WebSocketBasicStreamSocketSingleReadTest,
+ PartialFrameHeaderInHttpResponse) {
SetHttpReadBuffer(kSampleFrame, 1);
- MockRead reads[] = {MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)};
- CreateReadOnly(reads);
+ CreateRead(MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1));
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
EXPECT_EQ(OK, cb_.WaitForResult());
- ASSERT_EQ(1U, frame_chunks_.size());
- ASSERT_TRUE(frame_chunks_[0]->data);
- EXPECT_EQ(6, frame_chunks_[0]->data->size());
- ASSERT_TRUE(frame_chunks_[0]->header);
- EXPECT_EQ(WebSocketFrameHeader::kOpCodeText,
- frame_chunks_[0]->header->opcode);
+ ASSERT_EQ(1U, frames_.size());
+ ASSERT_TRUE(frames_[0]->data);
+ EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
+}
+
+// Check that a control frame which partially arrives at the end of the response
+// headers works correctly.
+TEST_F(WebSocketBasicStreamSocketSingleReadTest,
+ PartialControlFrameInHttpResponse) {
+ const size_t kPartialFrameBytes = 3;
+ SetHttpReadBuffer(kCloseFrame, kPartialFrameBytes);
+ CreateRead(MockRead(ASYNC,
+ kCloseFrame + kPartialFrameBytes,
+ kCloseFrameSize - kPartialFrameBytes));
+
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_EQ(OK, cb_.WaitForResult());
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
+ EXPECT_EQ(kCloseFrameSize - 2, frames_[0]->header.payload_length);
+ EXPECT_EQ(
+ 0,
+ memcmp(frames_[0]->data->data(), kCloseFrame + 2, kCloseFrameSize - 2));
+}
+
+// Check that a control frame which partially arrives at the end of the response
+// headers works correctly. Synchronous version (unlikely in practice).
+TEST_F(WebSocketBasicStreamSocketSingleReadTest,
+ PartialControlFrameInHttpResponseSync) {
+ const size_t kPartialFrameBytes = 3;
+ SetHttpReadBuffer(kCloseFrame, kPartialFrameBytes);
+ CreateRead(MockRead(SYNCHRONOUS,
+ kCloseFrame + kPartialFrameBytes,
+ kCloseFrameSize - kPartialFrameBytes));
+
+ ASSERT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
}
// Check that an invalid frame results in an error.
-TEST_F(WebSocketBasicStreamSocketTest, SyncInvalidFrame) {
- MockRead reads[] = {MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncInvalidFrame) {
+ CreateRead(MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize));
EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+ stream_->ReadFrames(&frames_, cb_.callback()));
}
-TEST_F(WebSocketBasicStreamSocketTest, AsyncInvalidFrame) {
- MockRead reads[] = {MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize)};
- CreateReadOnly(reads);
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncInvalidFrame) {
+ CreateRead(MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize));
+
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, cb_.WaitForResult());
+}
+
+// A control frame without a FIN flag is invalid and should not be passed
+// through to higher layers. RFC6455 5.5 "All control frames ... MUST NOT be
+// fragmented."
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, ControlFrameWithoutFin) {
+ CreateRead(
+ MockRead(SYNCHRONOUS, kPingFrameWithoutFin, kPingFrameWithoutFinSize));
+
+ ASSERT_EQ(ERR_WS_PROTOCOL_ERROR,
+ stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_TRUE(frames_.empty());
+}
+
+// A control frame over 125 characters is invalid. RFC6455 5.5 "All control
+// frames MUST have a payload length of 125 bytes or less". Since we use a
+// 125-byte buffer to assemble fragmented control frames, we need to detect this
+// error before attempting to assemble the fragments.
+TEST_F(WebSocketBasicStreamSocketSingleReadTest, OverlongControlFrame) {
+ CreateRead(MockRead(SYNCHRONOUS, k126BytePong, k126BytePongSize));
+
+ EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
+ stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_TRUE(frames_.empty());
+}
+
+// A control frame over 125 characters should still be rejected if it is split
+// into multiple chunks.
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, SplitOverlongControlFrame) {
+ const size_t kFirstChunkSize = 16;
+ CreateChunkedRead(SYNCHRONOUS,
+ k126BytePong,
+ k126BytePongSize,
+ kFirstChunkSize,
+ 2,
+ LAST_FRAME_BIG);
+
+ EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
+ stream_->ReadFrames(&frames_, cb_.callback()));
+ EXPECT_TRUE(frames_.empty());
+}
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->ReadFrames(&frame_chunks_, cb_.callback()));
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest,
+ AsyncSplitOverlongControlFrame) {
+ const size_t kFirstChunkSize = 16;
+ CreateChunkedRead(ASYNC,
+ k126BytePong,
+ k126BytePongSize,
+ kFirstChunkSize,
+ 2,
+ LAST_FRAME_BIG);
+
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, cb_.WaitForResult());
+ // The caller should not call ReadFrames() again after receiving an error
+ // other than ERR_IO_PENDING.
+ EXPECT_TRUE(frames_.empty());
+}
+
+// In the synchronous case, ReadFrames assembles the whole control frame before
+// returning.
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, SyncControlFrameAssembly) {
+ const size_t kChunkSize = 3;
+ CreateChunkedRead(
+ SYNCHRONOUS, kCloseFrame, kCloseFrameSize, kChunkSize, 3, LAST_FRAME_BIG);
+
+ ASSERT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
+}
+
+// In the asynchronous case, the callback is not called until the control frame
+// has been completely assembled.
+TEST_F(WebSocketBasicStreamSocketChunkedReadTest, AsyncControlFrameAssembly) {
+ const size_t kChunkSize = 3;
+ CreateChunkedRead(
+ ASYNC, kCloseFrame, kCloseFrameSize, kChunkSize, 3, LAST_FRAME_BIG);
+
+ ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback()));
+ ASSERT_EQ(OK, cb_.WaitForResult());
+ ASSERT_EQ(1U, frames_.size());
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
}
// Check that writing a frame all at once works.
-TEST_F(WebSocketBasicStreamSocketTest, WriteAtOnce) {
+TEST_F(WebSocketBasicStreamSocketWriteTest, WriteAtOnce) {
MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, kWriteFrameSize)};
CreateWriteOnly(writes);
- frame_chunks_ = GenerateWriteFrame();
- EXPECT_EQ(OK, stream_->WriteFrames(&frame_chunks_, cb_.callback()));
+ EXPECT_EQ(OK, stream_->WriteFrames(&frames_, cb_.callback()));
}
// Check that completely async writing works.
-TEST_F(WebSocketBasicStreamSocketTest, AsyncWriteAtOnce) {
+TEST_F(WebSocketBasicStreamSocketWriteTest, AsyncWriteAtOnce) {
MockWrite writes[] = {MockWrite(ASYNC, kWriteFrame, kWriteFrameSize)};
CreateWriteOnly(writes);
- frame_chunks_ = GenerateWriteFrame();
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->WriteFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->WriteFrames(&frames_, cb_.callback()));
EXPECT_EQ(OK, cb_.WaitForResult());
}
// Check that writing a frame to an extremely full kernel buffer (so that it
// ends up being sent in bits) works. The WriteFrames() callback should not be
// called until all parts have been written.
-TEST_F(WebSocketBasicStreamSocketTest, WriteInBits) {
+TEST_F(WebSocketBasicStreamSocketWriteTest, WriteInBits) {
MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, 4),
MockWrite(ASYNC, kWriteFrame + 4, 4),
MockWrite(ASYNC, kWriteFrame + 8, kWriteFrameSize - 8)};
CreateWriteOnly(writes);
- frame_chunks_ = GenerateWriteFrame();
- ASSERT_EQ(ERR_IO_PENDING,
- stream_->WriteFrames(&frame_chunks_, cb_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->WriteFrames(&frames_, cb_.callback()));
EXPECT_EQ(OK, cb_.WaitForResult());
}
diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc
index 3db457c..d2150d4 100644
--- a/net/websockets/websocket_channel.cc
+++ b/net/websockets/websocket_channel.cc
@@ -27,10 +27,6 @@ const int kDefaultSendQuotaLowWaterMark = 1 << 16;
const int kDefaultSendQuotaHighWaterMark = 1 << 17;
const size_t kWebSocketCloseCodeLength = 2;
-// This uses type uint64 to match the definition of
-// WebSocketFrameHeader::payload_length in websocket_frame.h.
-const uint64 kMaxControlFramePayload = 125;
-
} // namespace
// A class to encapsulate a set of frames and information about the size of
@@ -39,26 +35,25 @@ class WebSocketChannel::SendBuffer {
public:
SendBuffer() : total_bytes_(0) {}
- // Add a WebSocketFrameChunk to the buffer and increase total_bytes_.
- void AddFrame(scoped_ptr<WebSocketFrameChunk> chunk);
+ // Add a WebSocketFrame to the buffer and increase total_bytes_.
+ void AddFrame(scoped_ptr<WebSocketFrame> chunk);
// Return a pointer to the frames_ for write purposes.
- ScopedVector<WebSocketFrameChunk>* frames() { return &frames_; }
+ ScopedVector<WebSocketFrame>* frames() { return &frames_; }
private:
// The frames_ that will be sent in the next call to WriteFrames().
- ScopedVector<WebSocketFrameChunk> frames_;
+ ScopedVector<WebSocketFrame> frames_;
- // The total size of the buffers in |frames_|. This will be used to measure
- // the throughput of the link.
+ // The total size of the payload data 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());
+void WebSocketChannel::SendBuffer::AddFrame(scoped_ptr<WebSocketFrame> frame) {
+ total_bytes_ += frame->header.payload_length;
+ frames_.push_back(frame.release());
}
// Implementation of WebSocketStream::ConnectDelegate that simply forwards the
@@ -98,7 +93,7 @@ WebSocketChannel::WebSocketChannel(
state_(FRESHLY_CONSTRUCTED) {}
WebSocketChannel::~WebSocketChannel() {
- // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
+ // The stream may hold a pointer to read_frames_, and so it needs to be
// destroyed first.
stream_.reset();
}
@@ -163,9 +158,9 @@ void WebSocketChannel::SendFrame(bool fin,
// water mark" and "high water mark", but only if the link to the WebSocket
// server is not saturated.
// TODO(ricea): For kOpCodeText, do UTF-8 validation?
- scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(data.size()));
std::copy(data.begin(), data.end(), buffer->data());
- SendIOBufferWithSize(fin, op_code, buffer);
+ SendIOBuffer(fin, op_code, buffer, data.size());
}
void WebSocketChannel::SendFlowControl(int64 quota) {
@@ -308,7 +303,7 @@ void WebSocketChannel::ReadFrames() {
// WebSocketStream, and any pending reads will be cancelled when it is
// destroyed.
result = stream_->ReadFrames(
- &read_frame_chunks_,
+ &read_frames_,
base::Bind(
&WebSocketChannel::OnReadDone, base::Unretained(this), false));
if (result != ERR_IO_PENDING) {
@@ -325,20 +320,27 @@ void WebSocketChannel::OnReadDone(bool synchronous, int 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())
+ DCHECK(!read_frames_.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());
+ for (size_t i = 0; i < read_frames_.size(); ++i) {
+ scoped_ptr<WebSocketFrame> frame(read_frames_[i]);
+ read_frames_[i] = NULL;
+ ProcessFrame(frame.Pass());
}
- read_frame_chunks_.clear();
+ read_frames_.clear();
// There should always be a call to ReadFrames pending.
+ // TODO(ricea): Unless we are out of quota.
if (!synchronous && state_ != CLOSED) {
ReadFrames();
}
return;
+ case ERR_WS_PROTOCOL_ERROR:
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ "WebSocket Protocol Error");
+ return;
+
default:
DCHECK_LT(result, 0)
<< "ReadFrames() should only return OK or ERR_ codes";
@@ -357,119 +359,33 @@ void WebSocketChannel::OnReadDone(bool synchronous, int result) {
}
}
-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 this channel rejected the previous chunk as invalid, then it will have
- // reset |current_frame_header_| and closed the channel. More chunks of the
- // invalid frame may still arrive, and it is not necessarily a bug for that
- // to happen. 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()
- << ")";
+void WebSocketChannel::ProcessFrame(scoped_ptr<WebSocketFrame> frame) {
+ if (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;
}
- scoped_refptr<IOBufferWithSize> data_buffer;
- data_buffer.swap(chunk->data);
- const bool is_final_chunk = chunk->final_chunk;
- chunk.reset();
- const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
- if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
- if (!current_frame_header_->final) {
- FailChannel(SEND_REAL_ERROR,
- kWebSocketErrorProtocolError,
- "Control message with FIN bit unset received");
- return;
- }
- if (current_frame_header_->payload_length > kMaxControlFramePayload) {
- FailChannel(SEND_REAL_ERROR,
- kWebSocketErrorProtocolError,
- "Control message has payload over 125 bytes");
- return;
- }
- if (!is_final_chunk) {
- VLOG(2) << "Encountered a split control frame, opcode " << opcode;
- if (incomplete_control_frame_body_) {
- VLOG(3) << "Appending to an existing split control frame.";
- AddToIncompleteControlFrameBody(data_buffer);
- } else {
- VLOG(3) << "Creating new storage for an incomplete control frame.";
- incomplete_control_frame_body_ = new GrowableIOBuffer();
- // This method checks for oversize control frames above, so as long as
- // the frame parser is working correctly, this won't overflow. If a bug
- // does cause it to overflow, it will CHECK() in
- // AddToIncompleteControlFrameBody() without writing outside the buffer.
- incomplete_control_frame_body_->SetCapacity(kMaxControlFramePayload);
- AddToIncompleteControlFrameBody(data_buffer);
- }
- return; // Handle when complete.
- }
- if (incomplete_control_frame_body_) {
- VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
- AddToIncompleteControlFrameBody(data_buffer);
- const int body_size = incomplete_control_frame_body_->offset();
- data_buffer = new IOBufferWithSize(body_size);
- memcpy(data_buffer->data(),
- incomplete_control_frame_body_->StartOfBuffer(),
- body_size);
- incomplete_control_frame_body_ = NULL; // Frame now complete.
- }
+ const WebSocketFrameHeader::OpCode opcode = frame->header.opcode;
+ if (WebSocketFrameHeader::IsKnownControlOpCode(opcode) &&
+ !frame->header.final) {
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ "Control message with FIN bit unset received");
+ return;
}
- // Apply basic sanity checks to the |payload_length| field from the frame
- // header. A check for exact equality can only be used when the whole frame
- // arrives 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 that this frame header is not applied to any future chunks.
- current_frame_header_.reset();
- }
-}
-
-void WebSocketChannel::AddToIncompleteControlFrameBody(
- const scoped_refptr<IOBufferWithSize>& data_buffer) {
- const int new_offset =
- incomplete_control_frame_body_->offset() + data_buffer->size();
- CHECK_GE(incomplete_control_frame_body_->capacity(), new_offset)
- << "Control frame body larger than frame header indicates; frame parser "
- "bug?";
- memcpy(incomplete_control_frame_body_->data(),
- data_buffer->data(),
- data_buffer->size());
- incomplete_control_frame_body_->set_offset(new_offset);
+ HandleFrame(
+ opcode, frame->header.final, frame->data, frame->header.payload_length);
}
-void WebSocketChannel::HandleFrame(
- const WebSocketFrameHeader::OpCode opcode,
- bool is_first_chunk,
- bool is_final_chunk,
- const scoped_refptr<IOBufferWithSize>& data_buffer) {
+void WebSocketChannel::HandleFrame(const WebSocketFrameHeader::OpCode opcode,
+ bool final,
+ const scoped_refptr<IOBuffer>& data_buffer,
+ size_t size) {
DCHECK_NE(RECV_CLOSED, state_)
<< "HandleFrame() does not support being called re-entrantly from within "
"SendClose()";
@@ -513,12 +429,11 @@ void WebSocketChannel::HandleFrame(
case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
case WebSocketFrameHeader::kOpCodeContinuation:
if (state_ == CONNECTED) {
- const bool final = is_final_chunk && current_frame_header_->final;
// TODO(ricea): Need to fail the connection if UTF-8 is invalid
// post-reassembly. Requires a streaming UTF-8 validator.
// 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 char* const data_end = data_begin + size;
const std::vector<char> data(data_begin, data_end);
// TODO(ricea): Handle the case when ReadFrames returns far
// more data at once than should be sent in a single IPC. This needs to
@@ -526,34 +441,31 @@ void WebSocketChannel::HandleFrame(
// cause of receiving very large chunks.
// Sends the received frame to the renderer process.
- event_interface_->OnDataFrame(
- final,
- is_first_chunk ? opcode : WebSocketFrameHeader::kOpCodeContinuation,
- data);
+ event_interface_->OnDataFrame(final, opcode, data);
} else {
VLOG(3) << "Ignored data packet received in state " << state_;
}
return;
case WebSocketFrameHeader::kOpCodePing:
- VLOG(1) << "Got Ping of size " << data_buffer->size();
+ VLOG(1) << "Got Ping of size " << size;
if (state_ == CONNECTED) {
- SendIOBufferWithSize(
- true, WebSocketFrameHeader::kOpCodePong, data_buffer);
+ SendIOBuffer(
+ true, WebSocketFrameHeader::kOpCodePong, data_buffer, size);
} else {
VLOG(3) << "Ignored ping in state " << state_;
}
return;
case WebSocketFrameHeader::kOpCodePong:
- VLOG(1) << "Got Pong of size " << data_buffer->size();
+ VLOG(1) << "Got Pong of size " << size;
// There is no need to do anything with pong messages.
return;
case WebSocketFrameHeader::kOpCodeClose: {
uint16 code = kWebSocketNormalClosure;
std::string reason;
- ParseClose(data_buffer, &code, &reason);
+ ParseClose(data_buffer, size, &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;
@@ -589,20 +501,18 @@ void WebSocketChannel::HandleFrame(
}
}
-void WebSocketChannel::SendIOBufferWithSize(
- bool fin,
- WebSocketFrameHeader::OpCode op_code,
- const scoped_refptr<IOBufferWithSize>& buffer) {
+void WebSocketChannel::SendIOBuffer(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const scoped_refptr<IOBuffer>& buffer,
+ size_t size) {
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;
+ scoped_ptr<WebSocketFrame> frame(new WebSocketFrame(op_code));
+ WebSocketFrameHeader& header = frame->header;
+ header.final = fin;
+ header.masked = true;
+ header.payload_length = size;
+ frame->data = buffer;
if (data_being_sent_) {
// Either the link to the WebSocket server is saturated, or several messages
// are being sent in a batch.
@@ -610,10 +520,10 @@ void WebSocketChannel::SendIOBufferWithSize(
// quota appropriately.
if (!data_to_send_next_)
data_to_send_next_.reset(new SendBuffer);
- data_to_send_next_->AddFrame(chunk.Pass());
+ data_to_send_next_->AddFrame(frame.Pass());
} else {
data_being_sent_.reset(new SendBuffer);
- data_being_sent_->AddFrame(chunk.Pass());
+ data_being_sent_->AddFrame(frame.Pass());
WriteFrames();
}
}
@@ -640,9 +550,6 @@ void WebSocketChannel::FailChannel(ExposeError expose,
stream_->Close();
state_ = CLOSED;
- // The channel may be in the middle of processing several chunks. It should
- // not use this frame header for subsequent chunks.
- current_frame_header_.reset();
if (old_state != CLOSED) {
event_interface_->OnDropChannel(code, reason);
}
@@ -651,29 +558,31 @@ void WebSocketChannel::FailChannel(ExposeError expose,
void WebSocketChannel::SendClose(uint16 code, const std::string& reason) {
DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
// TODO(ricea): Ensure reason.length() <= 123
- scoped_refptr<IOBufferWithSize> body;
+ scoped_refptr<IOBuffer> body;
+ size_t size = 0;
if (code == kWebSocketErrorNoStatusReceived) {
// Special case: translate kWebSocketErrorNoStatusReceived into a Close
// frame with no payload.
- body = new IOBufferWithSize(0);
+ body = new IOBuffer(0);
} else {
const size_t payload_length = kWebSocketCloseCodeLength + reason.length();
- body = new IOBufferWithSize(payload_length);
+ body = new IOBuffer(payload_length);
+ size = 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);
+ SendIOBuffer(true, WebSocketFrameHeader::kOpCodeClose, body, size);
state_ = (state_ == CONNECTED) ? SEND_CLOSED : CLOSE_WAIT;
}
-void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
+void WebSocketChannel::ParseClose(const scoped_refptr<IOBuffer>& buffer,
+ size_t size,
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;
diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h
index c997d6a..dda30cd 100644
--- a/net/websockets/websocket_channel.h
+++ b/net/websockets/websocket_channel.h
@@ -19,10 +19,10 @@
namespace net {
-class GrowableIOBuffer;
+class BoundNetLog;
+class IOBuffer;
class URLRequestContext;
class WebSocketEventInterface;
-class BoundNetLog;
// Transport-independent implementation of WebSockets. Implements protocol
// semantics that do not depend on the underlying transport. Provides the
@@ -164,37 +164,33 @@ class NET_EXPORT WebSocketChannel {
// 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);
-
- // Appends |data_buffer| to |incomplete_control_frame_body_|.
- void AddToIncompleteControlFrameBody(
- const scoped_refptr<IOBufferWithSize>& data_buffer);
+ // Processes a single frame that has been read from the stream.
+ void ProcessFrame(scoped_ptr<WebSocketFrame> frame);
// Handles a frame that the object has received enough of to process. May call
- // event_interface_ methods, send responses to the server, and change the
- // value of state_.
+ // |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);
+ bool final,
+ const scoped_refptr<IOBuffer>& data_buffer,
+ size_t size);
// 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);
+ void SendIOBuffer(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const scoped_refptr<IOBuffer>& buffer,
+ size_t size);
// Performs 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 passed to the renderer; otherwise it is sent a
- // fixed "Going Away" code. Resets current_frame_header_, closes the stream_,
- // and sets state_ to CLOSED.
+ // fixed "Going Away" code. 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
@@ -208,7 +204,8 @@ class NET_EXPORT WebSocketChannel {
// 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,
+ void ParseClose(const scoped_refptr<IOBuffer>& buffer,
+ size_t size,
uint16* code,
std::string* reason);
@@ -231,20 +228,12 @@ class NET_EXPORT WebSocketChannel {
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, it can
- // remain non-NULL until additional chunks 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_;
+ ScopedVector<WebSocketFrame> read_frames_;
+
// Handle to an in-progress WebSocketStream creation request. Only non-NULL
// during the connection process.
scoped_ptr<WebSocketStreamRequest> stream_request_;
- // Although it should rarely happen in practice, a control frame can arrive
- // broken into chunks. This variable provides storage for a partial control
- // frame until the rest arrives. It will be NULL the rest of the time.
- scoped_refptr<GrowableIOBuffer> incomplete_control_frame_body_;
+
// If the renderer's send quota reaches this level, it is sent a quota
// refresh. "quota units" are currently bytes. TODO(ricea): Update the
// definition of quota units when necessary.
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
index 25e9cdc..361f69f 100644
--- a/net/websockets/websocket_channel_test.cc
+++ b/net/websockets/websocket_channel_test.cc
@@ -34,40 +34,37 @@
#define CLOSE_DATA(code, string) WEBSOCKET_CLOSE_CODE_AS_STRING_##code string
#define WEBSOCKET_CLOSE_CODE_AS_STRING_NORMAL_CLOSURE "\x03\xe8"
#define WEBSOCKET_CLOSE_CODE_AS_STRING_GOING_AWAY "\x03\xe9"
+#define WEBSOCKET_CLOSE_CODE_AS_STRING_PROTOCOL_ERROR "\x03\xea"
#define WEBSOCKET_CLOSE_CODE_AS_STRING_SERVER_ERROR "\x03\xf3"
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
+// Printing helpers to allow GoogleMock to print frames. 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") << ", "
+ return os << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", "
<< header.opcode << ", "
- << (header.masked ? "MASKED" : "NOT_MASKED") << ", "
- << header.payload_length << "}";
+ << (header.masked ? "MASKED" : "NOT_MASKED");
}
-std::ostream& operator<<(std::ostream& os, const WebSocketFrameChunk& chunk) {
- os << "{";
- if (chunk.header) {
- os << *chunk.header;
- } else {
- os << "{NO_HEADER}";
+std::ostream& operator<<(std::ostream& os, const WebSocketFrame& frame) {
+ os << "{" << frame.header << ", ";
+ if (frame.data) {
+ return os << "\"" << base::StringPiece(frame.data->data(),
+ frame.header.payload_length)
+ << "\"}";
}
- return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK")
- << ", \""
- << base::StringPiece(chunk.data->data(), chunk.data->size())
- << "\"}";
+ return os << "NULL}";
}
std::ostream& operator<<(std::ostream& os,
- const ScopedVector<WebSocketFrameChunk>& vector) {
+ const ScopedVector<WebSocketFrame>& vector) {
os << "{";
bool first = true;
- for (ScopedVector<WebSocketFrameChunk>::const_iterator it = vector.begin();
+ for (ScopedVector<WebSocketFrame>::const_iterator it = vector.begin();
it != vector.end();
++it) {
if (!first) {
@@ -81,7 +78,7 @@ std::ostream& operator<<(std::ostream& os,
}
std::ostream& operator<<(std::ostream& os,
- const ScopedVector<WebSocketFrameChunk>* vector) {
+ const ScopedVector<WebSocketFrame>* vector) {
return os << '&' << *vector;
}
@@ -97,7 +94,7 @@ using ::testing::_;
// A selection of characters that have traditionally been mangled in some
// environment or other, for testing 8-bit cleanliness.
-const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL
+const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL
'\0', // nul
'\x7F', // DEL
'\x80', '\xFF', // NOT VALID UTF-8
@@ -170,12 +167,12 @@ class FakeWebSocketStream : public WebSocketStream {
return ERR_IO_PENDING;
}
- virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
return ERR_IO_PENDING;
}
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
return ERR_IO_PENDING;
}
@@ -198,12 +195,7 @@ class FakeWebSocketStream : public WebSocketStream {
// 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
};
@@ -213,54 +205,33 @@ enum IsMasked {
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;
+// This is used to initialise a WebSocketFrame but is statically initialisable.
+struct InitFrame {
+ IsFinal final;
+ // Reserved fields omitted for now. Add them if you need them.
+ WebSocketFrameHeader::OpCode opcode;
+ IsMasked masked;
// 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.
+ // nul-terminated string for ease-of-use. |header.payload_length| is
+ // initialised from |strlen(data)|. This means it is not 8-bit clean, but this
+ // is not an issue for test data.
const char* const data;
};
// For GoogleMock
-std::ostream& operator<<(std::ostream& os, const InitFrameChunk& chunk) {
- os << "{";
- if (chunk.header.final != NO_HEADER) {
- os << "{" << (chunk.header.final == FINAL_FRAME ? "FINAL_FRAME"
- : "NOT_FINAL_FRAME") << ", "
- << chunk.header.opcode << ", "
- << (chunk.header.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", "
- << chunk.header.payload_length << "}";
-
- } else {
- os << "{NO_HEADER}";
+std::ostream& operator<<(std::ostream& os, const InitFrame& frame) {
+ os << "{" << (frame.final == FINAL_FRAME ? "FINAL_FRAME" : "NOT_FINAL_FRAME")
+ << ", " << frame.opcode << ", "
+ << (frame.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", ";
+ if (frame.data) {
+ return os << "\"" << frame.data << "\"}";
}
- return os << ", " << (chunk.final_chunk == FINAL_CHUNK ? "FINAL_CHUNK"
- : "NOT_FINAL_CHUNK")
- << ", \"" << chunk.data << "\"}";
+ return os << "NULL}";
}
template <size_t N>
-std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) {
+std::ostream& operator<<(std::ostream& os, const InitFrame (&frames)[N]) {
os << "{";
bool first = true;
for (size_t i = 0; i < N; ++i) {
@@ -269,119 +240,92 @@ std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) {
} else {
first = false;
}
- os << chunks[i];
+ os << frames[i];
}
return os << "}";
}
-// Convert a const array of InitFrameChunks to the format used at
+// Convert a const array of InitFrame structs 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);
+ScopedVector<WebSocketFrame> CreateFrameVector(
+ const InitFrame (&source_frames)[N]) {
+ ScopedVector<WebSocketFrame> result_frames;
+ result_frames.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->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);
+ const InitFrame& source_frame = source_frames[i];
+ scoped_ptr<WebSocketFrame> result_frame(
+ new WebSocketFrame(source_frame.opcode));
+ size_t frame_length = source_frame.data ? strlen(source_frame.data) : 0;
+ WebSocketFrameHeader& result_header = result_frame->header;
+ result_header.final = (source_frame.final == FINAL_FRAME);
+ result_header.masked = (source_frame.masked == MASKED);
+ result_header.payload_length = frame_length;
+ if (source_frame.data) {
+ result_frame->data = new IOBuffer(frame_length);
+ memcpy(result_frame->data->data(), source_frame.data, frame_length);
}
- result_chunks.push_back(result_chunk.release());
+ result_frames.push_back(result_frame.release());
}
- return result_chunks.Pass();
+ return result_frames.Pass();
}
// A GoogleMock action which can be used to respond to call to ReadFrames with
-// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(&chunks));
-// |chunks| is an array of InitFrameChunks needs to be passed by pointer because
-// otherwise it will be reduced to a pointer and lose the array size
-// information.
-ACTION_P(ReturnChunks, source_chunks) {
- *arg0 = CreateFrameChunkVector(*source_chunks);
+// some frames. Use like ReadFrames(_, _).WillOnce(ReturnFrames(&frames));
+// |frames| is an array of InitFrame. |frames| needs to be passed by pointer
+// because otherwise it will be treated as a pointer and the array size
+// information will be lost.
+ACTION_P(ReturnFrames, source_frames) {
+ *arg0 = CreateFrameVector(*source_frames);
return OK;
}
// The implementation of a GoogleMock matcher which can be used to compare a
-// ScopedVector<WebSocketFrameChunk>* against an expectation defined as an array
-// of InitFrameChunks. Although it is possible to compose built-in GoogleMock
-// matchers to check the contents of a WebSocketFrameChunk, the results are so
+// ScopedVector<WebSocketFrame>* against an expectation defined as an array of
+// InitFrame objects. Although it is possible to compose built-in GoogleMock
+// matchers to check the contents of a WebSocketFrame, the results are so
// unreadable that it is better to use this matcher.
template <size_t N>
-class EqualsChunksMatcher
- : public ::testing::MatcherInterface<ScopedVector<WebSocketFrameChunk>*> {
+class EqualsFramesMatcher
+ : public ::testing::MatcherInterface<ScopedVector<WebSocketFrame>*> {
public:
- EqualsChunksMatcher(const InitFrameChunk (*expect_chunks)[N])
- : expect_chunks_(expect_chunks) {}
+ EqualsFramesMatcher(const InitFrame (*expect_frames)[N])
+ : expect_frames_(expect_frames) {}
- virtual bool MatchAndExplain(ScopedVector<WebSocketFrameChunk>* actual_chunks,
+ virtual bool MatchAndExplain(ScopedVector<WebSocketFrame>* actual_frames,
::testing::MatchResultListener* listener) const {
- if (actual_chunks->size() != N) {
- *listener << "the vector size is " << actual_chunks->size();
+ if (actual_frames->size() != N) {
+ *listener << "the vector size is " << actual_frames->size();
return false;
}
for (size_t i = 0; i < N; ++i) {
- const WebSocketFrameChunk& actual_chunk = *(*actual_chunks)[i];
- const InitFrameChunk& expected_chunk = (*expect_chunks_)[i];
- // Testing that the absence or presence of a header is the same for both.
- if ((!actual_chunk.header) !=
- (expected_chunk.header.final == NO_HEADER)) {
- *listener << "the header is "
- << (actual_chunk.header ? "present" : "absent");
+ const WebSocketFrame& actual_frame = *(*actual_frames)[i];
+ const InitFrame& expected_frame = (*expect_frames_)[i];
+ if (actual_frame.header.final != (expected_frame.final == FINAL_FRAME)) {
+ *listener << "the frame is marked as "
+ << (actual_frame.header.final ? "" : "not ") << "final";
return false;
}
- if (actual_chunk.header) {
- if (actual_chunk.header->final !=
- (expected_chunk.header.final == FINAL_FRAME)) {
- *listener << "the frame is marked as "
- << (actual_chunk.header->final ? "" : "not ") << "final";
- return false;
- }
- if (actual_chunk.header->opcode != expected_chunk.header.opcode) {
- *listener << "the opcode is " << actual_chunk.header->opcode;
- return false;
- }
- if (actual_chunk.header->masked !=
- (expected_chunk.header.masked == MASKED)) {
- *listener << "the frame is "
- << (actual_chunk.header->masked ? "masked" : "not masked");
- return false;
- }
- if (actual_chunk.header->payload_length !=
- expected_chunk.header.payload_length) {
- *listener << "the payload length is "
- << actual_chunk.header->payload_length;
- return false;
- }
+ if (actual_frame.header.opcode != expected_frame.opcode) {
+ *listener << "the opcode is " << actual_frame.header.opcode;
+ return false;
}
- if (actual_chunk.final_chunk !=
- (expected_chunk.final_chunk == FINAL_CHUNK)) {
- *listener << "the chunk is marked as "
- << (actual_chunk.final_chunk ? "" : "not ") << "final";
+ if (actual_frame.header.masked != (expected_frame.masked == MASKED)) {
+ *listener << "the frame is "
+ << (actual_frame.header.masked ? "masked" : "not masked");
return false;
}
- if (actual_chunk.data->size() !=
- base::checked_numeric_cast<int>(strlen(expected_chunk.data))) {
- *listener << "the data size is " << actual_chunk.data->size();
+ const size_t expected_length =
+ expected_frame.data ? strlen(expected_frame.data) : 0;
+ if (actual_frame.header.payload_length != expected_length) {
+ *listener << "the payload length is "
+ << actual_frame.header.payload_length;
return false;
}
- if (memcmp(actual_chunk.data->data(),
- expected_chunk.data,
- actual_chunk.data->size()) != 0) {
+ if (expected_length != 0 &&
+ memcmp(actual_frame.data->data(),
+ expected_frame.data,
+ actual_frame.header.payload_length) != 0) {
*listener << "the data content differs";
return false;
}
@@ -390,23 +334,23 @@ class EqualsChunksMatcher
}
virtual void DescribeTo(std::ostream* os) const {
- *os << "matches " << *expect_chunks_;
+ *os << "matches " << *expect_frames_;
}
virtual void DescribeNegationTo(std::ostream* os) const {
- *os << "does not match " << *expect_chunks_;
+ *os << "does not match " << *expect_frames_;
}
private:
- const InitFrameChunk (*expect_chunks_)[N];
+ const InitFrame (*expect_frames_)[N];
};
-// The definition of EqualsChunks GoogleMock matcher. Unlike the ReturnChunks
+// The definition of EqualsFrames GoogleMock matcher. Unlike the ReturnFrames
// action, this can take the array by reference.
template <size_t N>
-::testing::Matcher<ScopedVector<WebSocketFrameChunk>*> EqualsChunks(
- const InitFrameChunk (&chunks)[N]) {
- return ::testing::MakeMatcher(new EqualsChunksMatcher<N>(&chunks));
+::testing::Matcher<ScopedVector<WebSocketFrame>*> EqualsFrames(
+ const InitFrame (&frames)[N]) {
+ return ::testing::MakeMatcher(new EqualsFramesMatcher<N>(&frames));
}
// A FakeWebSocketStream whose ReadFrames() function returns data.
@@ -427,39 +371,38 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream {
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
+ // Prepares a fake response. 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().
+ // the callback in the asynchronous case. |frames| will be converted to a
+ // ScopedVector<WebSocketFrame> 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)));
+ const InitFrame (&frames)[N]) {
+ responses_.push_back(new Response(async, error, CreateFrameVector(frames)));
}
// An alternate version of PrepareReadFrames for when we need to construct
// the frames manually.
void PrepareRawReadFrames(IsSync async,
int error,
- ScopedVector<WebSocketFrameChunk> chunks) {
- responses_.push_back(new Response(async, error, chunks.Pass()));
+ ScopedVector<WebSocketFrame> frames) {
+ responses_.push_back(new Response(async, error, frames.Pass()));
}
// 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>()));
+ new Response(async, error, ScopedVector<WebSocketFrame>()));
}
- virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
CHECK(!read_frames_pending_);
if (index_ >= responses_.size())
@@ -470,34 +413,34 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream {
FROM_HERE,
base::Bind(&ReadableFakeWebSocketStream::DoCallback,
base::Unretained(this),
- frame_chunks,
+ frames,
callback));
return ERR_IO_PENDING;
} else {
- frame_chunks->swap(responses_[index_]->chunks);
+ frames->swap(responses_[index_]->frames);
return responses_[index_++]->error;
}
}
private:
- void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ void DoCallback(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) {
read_frames_pending_ = false;
- frame_chunks->swap(responses_[index_]->chunks);
+ frames->swap(responses_[index_]->frames);
callback.Run(responses_[index_++]->error);
return;
}
struct Response {
- Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks)
- : async(async), error(error), chunks(chunks.Pass()) {}
+ Response(IsSync async, int error, ScopedVector<WebSocketFrame> frames)
+ : async(async), error(error), frames(frames.Pass()) {}
IsSync async;
int error;
- ScopedVector<WebSocketFrameChunk> chunks;
+ ScopedVector<WebSocketFrame> frames;
private:
- // Bad things will happen if we attempt to copy or assign "chunks".
+ // Bad things will happen if we attempt to copy or assign |frames|.
DISALLOW_COPY_AND_ASSIGN(Response);
};
ScopedVector<Response> responses_;
@@ -516,7 +459,7 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream {
// synchronously.
class WriteableFakeWebSocketStream : public FakeWebSocketStream {
public:
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
return OK;
}
@@ -525,7 +468,7 @@ class WriteableFakeWebSocketStream : public FakeWebSocketStream {
// A FakeWebSocketStream where writes always fail.
class UnWriteableFakeWebSocketStream : public FakeWebSocketStream {
public:
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
return ERR_CONNECTION_RESET;
}
@@ -539,23 +482,22 @@ class UnWriteableFakeWebSocketStream : public FakeWebSocketStream {
// otherwise the ReadFrames() callback will never be called.
class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
public:
- EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {}
+ EchoeyFakeWebSocketStream() : read_frames_(NULL), done_(false) {}
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
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();
+ stored_frames_.insert(stored_frames_.end(), frames->begin(), frames->end());
+ frames->weak_clear();
PostCallback();
return OK;
}
- virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
read_callback_ = callback;
- read_frame_chunks_ = frame_chunks;
+ read_frames_ = frames;
if (done_)
PostCallback();
return ERR_IO_PENDING;
@@ -572,36 +514,34 @@ class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
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;
+ } else if (!stored_frames_.empty()) {
+ done_ = MoveFrames(read_frames_);
+ read_frames_ = NULL;
read_callback_.Run(OK);
}
}
- // Copy the chunks stored in stored_frame_chunks_ to |out|, while clearing the
+ // Copy the frames stored in stored_frames_ to |out|, while clearing the
// "masked" header bit. Returns true if a Close Frame was seen, false
// otherwise.
- bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* out) {
+ bool MoveFrames(ScopedVector<WebSocketFrame>* out) {
bool seen_close = false;
- *out = stored_frame_chunks_.Pass();
- for (ScopedVector<WebSocketFrameChunk>::iterator it = out->begin();
+ *out = stored_frames_.Pass();
+ for (ScopedVector<WebSocketFrame>::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;
- }
+ WebSocketFrameHeader& header = (*it)->header;
+ header.masked = false;
+ if (header.opcode == WebSocketFrameHeader::kOpCodeClose)
+ seen_close = true;
}
return seen_close;
}
- ScopedVector<WebSocketFrameChunk> stored_frame_chunks_;
+ ScopedVector<WebSocketFrame> stored_frames_;
CompletionCallback read_callback_;
// Owned by the caller of ReadFrames().
- ScopedVector<WebSocketFrameChunk>* read_frame_chunks_;
+ ScopedVector<WebSocketFrame>* read_frames_;
// True if we should close the connection.
bool done_;
};
@@ -612,7 +552,7 @@ class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
// run the message loop.
class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream {
public:
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, ERR_CONNECTION_RESET));
@@ -621,7 +561,7 @@ class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream {
return ERR_IO_PENDING;
}
- virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) OVERRIDE {
read_callback_ = callback;
return ERR_IO_PENDING;
@@ -636,10 +576,10 @@ class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream {
class MockWebSocketStream : public WebSocketStream {
public:
MOCK_METHOD2(ReadFrames,
- int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ int(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback));
MOCK_METHOD2(WriteFrames,
- int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ int(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback));
MOCK_METHOD0(Close, void());
MOCK_CONST_METHOD0(GetSubProtocol, std::string());
@@ -846,8 +786,8 @@ TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) {
// AddressSanitizer.
TEST_F(WebSocketChannelDeletingTest, DeletingFromOnAddChannelResponseWorks) {
CreateChannelAndConnect();
- connect_data_.factory.connect_delegate
- ->OnFailure(kWebSocketErrorNoStatusReceived);
+ connect_data_.factory.connect_delegate->OnFailure(
+ kWebSocketErrorNoStatusReceived);
EXPECT_EQ(NULL, channel_.get());
}
@@ -869,8 +809,8 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) {
CreateChannelAndConnect();
- connect_data_.factory.connect_delegate
- ->OnFailure(kWebSocketErrorNoStatusReceived);
+ connect_data_.factory.connect_delegate->OnFailure(
+ kWebSocketErrorNoStatusReceived);
}
TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
@@ -889,10 +829,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
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);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
set_stream(stream.Pass());
{
InSequence s;
@@ -912,10 +851,10 @@ TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) {
TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23},
- FINAL_CHUNK, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
ERR_CONNECTION_CLOSED);
set_stream(stream.Pass());
@@ -954,13 +893,12 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) {
TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
- FINAL_CHUNK, "HELLO"}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "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);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames);
set_stream(stream.Pass());
{
InSequence s;
@@ -985,14 +923,12 @@ TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) {
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);
+ static const InitFrame frames1[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}};
+ static const InitFrame frames2[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "WORLD"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames2);
set_stream(stream.Pass());
{
InSequence s;
@@ -1012,31 +948,29 @@ TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) {
base::MessageLoop::current()->RunUntilIdle();
}
-// Data frames that arrive in fragments are turned into individual frames
-TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) {
+// Data frames are delivered the same regardless of how many reads they arrive
+// as.
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedMessage) {
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);
+ // Here we have one message which arrived in five frames split across three
+ // reads. It may have been reframed on arrival, but this class doesn't care
+ // about that.
+ static const InitFrame frames1[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "THREE"},
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ NOT_MASKED, " "}};
+ static const InitFrame frames2[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ NOT_MASKED, "SMALL"}};
+ static const InitFrame frames3[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ NOT_MASKED, " "},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ NOT_MASKED, "FRAMES"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames3);
set_stream(stream.Pass());
{
InSequence s;
@@ -1068,112 +1002,16 @@ TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) {
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, CLOSE_DATA(NORMAL_CLOSURE, "")}};
- 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();
-}
-
-// The payload of a control frame is not permitted to exceed 125 bytes. RFC6455
-// 5.5 "All control frames MUST have a payload length of 125 bytes or less"
-TEST_F(WebSocketChannelEventInterfaceTest, OversizeControlMessageIsRejected) {
- scoped_ptr<ReadableFakeWebSocketStream> stream(
- new ReadableFakeWebSocketStream);
- static const size_t kPayloadLen = 126;
- char payload[kPayloadLen + 1]; // allow space for trailing NUL
- std::fill(payload, payload + kPayloadLen, 'A');
- payload[kPayloadLen] = '\0';
- // Not static because "payload" is constructed at runtime.
- const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED,
- kPayloadLen},
- FINAL_CHUNK, payload}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
- set_stream(stream.Pass());
-
- EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
- EXPECT_CALL(*event_interface_, OnFlowControl(_));
- EXPECT_CALL(*event_interface_,
- OnDropChannel(kWebSocketErrorProtocolError, _));
-
- CreateChannelAndConnectSuccessfully();
-}
-
// A control frame is not permitted to be split into multiple frames. RFC6455
// 5.5 "All control frames ... MUST NOT be fragmented."
TEST_F(WebSocketChannelEventInterfaceTest, MultiFrameControlMessageIsRejected) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
- static const InitFrameChunk chunks[] = {
- {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 2},
- FINAL_CHUNK, "Pi"},
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 2},
- FINAL_CHUNK, "ng"}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ static const InitFrame frames[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, "Pi"},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ NOT_MASKED, "ng"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames);
set_stream(stream.Pass());
{
InSequence s;
@@ -1225,39 +1063,14 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) {
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, CLOSE_DATA(NORMAL_CLOSURE, "")}};
-
- 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"}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames);
set_stream(stream.Pass());
{
InSequence s;
@@ -1276,10 +1089,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) {
TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, 4, NOT_MASKED, 5}, FINAL_CHUNK, "HELLO"}};
+ static const InitFrame frames[] = {{FINAL_FRAME, 4, NOT_MASKED, "HELLO"}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames);
set_stream(stream.Pass());
{
InSequence s;
@@ -1300,18 +1112,17 @@ TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) {
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);
+ static const InitFrame frames1[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText,
+ NOT_MASKED, "SPLIT "}};
+ static const InitFrame frames2[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, ""}};
+ static const InitFrame frames3[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ NOT_MASKED, "MESSAGE"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames3);
set_stream(stream.Pass());
{
InSequence s;
@@ -1331,17 +1142,16 @@ TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) {
base::MessageLoop::current()->RunUntilIdle();
}
-// If a chunk has an invalid header, then the connection is closed and
-// subsequent chunks must not trigger events.
-TEST_F(WebSocketChannelEventInterfaceTest, HeaderlessChunkAfterInvalidChunk) {
+// If a frame has an invalid header, then the connection is closed and
+// subsequent frames must not trigger events.
+TEST_F(WebSocketChannelEventInterfaceTest, FrameAfterInvalidFrame) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 11},
- NOT_FINAL_CHUNK, "HELLO"},
- {{NO_HEADER}, FINAL_CHUNK, " WORLD"}};
+ static const InitFrame frames[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, " WORLD"}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames);
set_stream(stream.Pass());
{
InSequence s;
@@ -1501,10 +1311,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, OnDropChannelCalledOnce) {
TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) {
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0},
- FINAL_CHUNK, ""}};
- stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, ""}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames);
stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
ERR_CONNECTION_CLOSED);
set_stream(stream.Pass());
@@ -1517,16 +1326,50 @@ TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) {
CreateChannelAndConnectSuccessfully();
}
+// If ReadFrames() returns ERR_WS_PROTOCOL_ERROR, then
+// kWebSocketErrorProtocolError must be sent to the renderer.
+TEST_F(WebSocketChannelEventInterfaceTest, SyncProtocolErrorGivesStatus1002) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_WS_PROTOCOL_ERROR);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// Async version of above test.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncProtocolErrorGivesStatus1002) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_WS_PROTOCOL_ERROR);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+
+ CreateChannelAndConnectSuccessfully();
+ 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) {
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 13},
- FINAL_CHUNK, "NEEDS MASKING"}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText,
+ MASKED, "NEEDS MASKING"}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
CreateChannelAndConnectSuccessfully();
@@ -1537,12 +1380,12 @@ TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) {
// RFC6455 5.5.1 "The application MUST NOT send any more data frames after
// sending a Close frame."
TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) {
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 9},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Success")}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Success")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
CreateChannelAndConnectSuccessfully();
@@ -1554,17 +1397,17 @@ TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) {
// RFC6455 5.5.1 "If an endpoint receives a Close frame and did not previously
// send a Close frame, the endpoint MUST send a Close frame in response."
TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) {
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
- .WillOnce(ReturnChunks(&chunks))
+ .WillOnce(ReturnFrames(&frames))
.WillRepeatedly(Return(ERR_IO_PENDING));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
CreateChannelAndConnectSuccessfully();
@@ -1573,17 +1416,17 @@ TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) {
// The converse of the above case; after sending a Close frame, we should not
// send another one.
TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) {
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ static const InitFrame frames_init[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
// We store the parameters that were passed to ReadFrames() so that we can
// call them explicitly later.
CompletionCallback read_callback;
- ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL;
+ ScopedVector<WebSocketFrame>* frames = NULL;
// Use a checkpoint to make the ordering of events clearer.
MockFunction<void(int)> checkpoint;
@@ -1591,11 +1434,11 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) {
InSequence s;
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
- .WillOnce(DoAll(SaveArg<0>(&frame_chunks),
+ .WillOnce(DoAll(SaveArg<0>(&frames),
SaveArg<1>(&read_callback),
Return(ERR_IO_PENDING)));
EXPECT_CALL(checkpoint, Call(1));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
@@ -1610,7 +1453,7 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) {
channel_->StartClosingHandshake(kWebSocketNormalClosure, "Close");
checkpoint.Call(2);
- *frame_chunks = CreateFrameChunkVector(chunks);
+ *frames = CreateFrameVector(frames_init);
read_callback.Run(OK);
checkpoint.Call(3);
}
@@ -1621,17 +1464,15 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) {
// CloseWithNoPayloadGivesStatus1005, above, for confirmation that code 1005 is
// correctly generated internally.
TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) {
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0},
- FINAL_CHUNK, ""}};
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 0},
- FINAL_CHUNK, ""}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, ""}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, ""}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
- .WillOnce(ReturnChunks(&chunks))
+ .WillOnce(ReturnFrames(&frames))
.WillRepeatedly(Return(ERR_IO_PENDING));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
CreateChannelAndConnectSuccessfully();
@@ -1643,58 +1484,57 @@ TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) {
// "Application data" as found in the message body of the Ping frame being
// replied to."
TEST_F(WebSocketChannelStreamTest, PingRepliedWithPong) {
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16},
- FINAL_CHUNK, "Application data"}};
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16},
- FINAL_CHUNK, "Application data"}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing,
+ NOT_MASKED, "Application data"}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong,
+ MASKED, "Application data"}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
- .WillOnce(ReturnChunks(&chunks))
+ .WillOnce(ReturnFrames(&frames))
.WillRepeatedly(Return(ERR_IO_PENDING));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
CreateChannelAndConnectSuccessfully();
}
TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) {
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16},
- FINAL_CHUNK, "Application data"}};
- static const InitFrameChunk expected1[] = {
- {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6},
- FINAL_CHUNK, "Hello "}};
- static const InitFrameChunk expected2[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16},
- FINAL_CHUNK, "Application data"}};
- static const InitFrameChunk expected3[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, MASKED, 5},
- FINAL_CHUNK, "World"}};
- ScopedVector<WebSocketFrameChunk>* read_chunks;
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing,
+ NOT_MASKED, "Application data"}};
+ static const InitFrame expected1[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "Hello "}};
+ static const InitFrame expected2[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong,
+ MASKED, "Application data"}};
+ static const InitFrame expected3[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation,
+ MASKED, "World"}};
+ ScopedVector<WebSocketFrame>* read_frames;
CompletionCallback read_callback;
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
- .WillOnce(DoAll(SaveArg<0>(&read_chunks),
+ .WillOnce(DoAll(SaveArg<0>(&read_frames),
SaveArg<1>(&read_callback),
Return(ERR_IO_PENDING)))
.WillRepeatedly(Return(ERR_IO_PENDING));
{
InSequence s;
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _))
.WillOnce(Return(OK));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _))
.WillOnce(Return(OK));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected3), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected3), _))
.WillOnce(Return(OK));
}
CreateChannelAndConnectSuccessfully();
channel_->SendFrame(
false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello "));
- *read_chunks = CreateFrameChunkVector(chunks);
+ *read_frames = CreateFrameVector(frames);
read_callback.Run(OK);
channel_->SendFrame(
true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("World"));
@@ -1703,12 +1543,10 @@ TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) {
// WriteFrames() may not be called until the previous write has completed.
// WebSocketChannel must buffer writes that happen in the meantime.
TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) {
- static const InitFrameChunk expected1[] = {
- {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6},
- FINAL_CHUNK, "Hello "}};
- static const InitFrameChunk expected2[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK,
- "World"}};
+ static const InitFrame expected1[] = {
+ {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "Hello "}};
+ static const InitFrame expected2[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "World"}};
CompletionCallback write_callback;
MockFunction<void(int)> checkpoint;
@@ -1717,10 +1555,10 @@ TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) {
{
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _))
.WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING)));
EXPECT_CALL(checkpoint, Call(2));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _))
.WillOnce(Return(ERR_IO_PENDING));
EXPECT_CALL(checkpoint, Call(3));
}
@@ -1741,27 +1579,22 @@ TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) {
// important to get good throughput in the "many small messages" case.
TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) {
static const char input_letters[] = "Hello";
- static const InitFrameChunk expected1[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
- "H"}};
- static const InitFrameChunk expected2[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
- "e"},
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
- "l"},
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
- "l"},
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
- "o"}};
+ static const InitFrame expected1[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "H"}};
+ static const InitFrame expected2[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "e"},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "l"},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "l"},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "o"}};
CompletionCallback write_callback;
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
{
InSequence s;
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _))
.WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING)));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _))
.WillOnce(Return(ERR_IO_PENDING));
}
@@ -1781,12 +1614,12 @@ TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) {
// even be using a different extension which uses that code to mean something
// else.
TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) {
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 16},
- FINAL_CHUNK, CLOSE_DATA(GOING_AWAY, "Internal Error")}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(GOING_AWAY, "Internal Error")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
EXPECT_CALL(*mock_stream_, Close());
@@ -1800,45 +1633,41 @@ TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) {
// protocol also has Binary frames and those need to be 8-bit clean. For the
// sake of completeness, this test verifies that they are.
TEST_F(WebSocketChannelStreamTest, WrittenBinaryFramesAre8BitClean) {
- ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL;
+ ScopedVector<WebSocketFrame>* frames = NULL;
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
EXPECT_CALL(*mock_stream_, WriteFrames(_, _))
- .WillOnce(DoAll(SaveArg<0>(&frame_chunks), Return(ERR_IO_PENDING)));
+ .WillOnce(DoAll(SaveArg<0>(&frames), Return(ERR_IO_PENDING)));
CreateChannelAndConnectSuccessfully();
channel_->SendFrame(
true,
WebSocketFrameHeader::kOpCodeBinary,
std::vector<char>(kBinaryBlob, kBinaryBlob + kBinaryBlobSize));
- ASSERT_TRUE(frame_chunks != NULL);
- ASSERT_EQ(1U, frame_chunks->size());
- const WebSocketFrameChunk* out_chunk = (*frame_chunks)[0];
- ASSERT_TRUE(out_chunk->header);
- EXPECT_EQ(kBinaryBlobSize, out_chunk->header->payload_length);
- ASSERT_TRUE(out_chunk->data);
- EXPECT_EQ(kBinaryBlobSize, static_cast<size_t>(out_chunk->data->size()));
- EXPECT_EQ(0, memcmp(kBinaryBlob, out_chunk->data->data(), kBinaryBlobSize));
+ ASSERT_TRUE(frames != NULL);
+ ASSERT_EQ(1U, frames->size());
+ const WebSocketFrame* out_frame = (*frames)[0];
+ EXPECT_EQ(kBinaryBlobSize, out_frame->header.payload_length);
+ ASSERT_TRUE(out_frame->data);
+ EXPECT_EQ(0, memcmp(kBinaryBlob, out_frame->data->data(), kBinaryBlobSize));
}
// Test the read path for 8-bit cleanliness as well.
TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) {
- scoped_ptr<WebSocketFrameHeader> frame_header(
- new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeBinary));
- frame_header->final = true;
- frame_header->payload_length = kBinaryBlobSize;
- scoped_ptr<WebSocketFrameChunk> frame_chunk(new WebSocketFrameChunk);
- frame_chunk->header = frame_header.Pass();
- frame_chunk->final_chunk = true;
- frame_chunk->data = new IOBufferWithSize(kBinaryBlobSize);
- memcpy(frame_chunk->data->data(), kBinaryBlob, kBinaryBlobSize);
- ScopedVector<WebSocketFrameChunk> chunks;
- chunks.push_back(frame_chunk.release());
+ scoped_ptr<WebSocketFrame> frame(
+ new WebSocketFrame(WebSocketFrameHeader::kOpCodeBinary));
+ WebSocketFrameHeader& frame_header = frame->header;
+ frame_header.final = true;
+ frame_header.payload_length = kBinaryBlobSize;
+ frame->data = new IOBuffer(kBinaryBlobSize);
+ memcpy(frame->data->data(), kBinaryBlob, kBinaryBlobSize);
+ ScopedVector<WebSocketFrame> frames;
+ frames.push_back(frame.release());
scoped_ptr<ReadableFakeWebSocketStream> stream(
new ReadableFakeWebSocketStream);
stream->PrepareRawReadFrames(
- ReadableFakeWebSocketStream::SYNC, OK, chunks.Pass());
+ ReadableFakeWebSocketStream::SYNC, OK, frames.Pass());
set_stream(stream.Pass());
EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
EXPECT_CALL(*event_interface_, OnFlowControl(_));
@@ -1856,17 +1685,17 @@ TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) {
// but the current implementation fails the connection. Since a Close has
// already been sent, this just means closing the connection.
TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) {
- static const InitFrameChunk chunks[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 4},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")},
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 9},
- FINAL_CHUNK, "Ping body"}};
- static const InitFrameChunk expected[] = {
- {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 4},
- FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ static const InitFrame frames[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")},
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing,
+ NOT_MASKED, "Ping body"}};
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
- .WillOnce(ReturnChunks(&chunks))
+ .WillOnce(ReturnFrames(&frames))
.WillRepeatedly(Return(ERR_IO_PENDING));
{
// We only need to verify the relative order of WriteFrames() and
@@ -1874,7 +1703,7 @@ TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) {
// frame before calling ReadFrames() again, but that is an implementation
// detail and better not to consider required behaviour.
InSequence s;
- EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
.WillOnce(Return(OK));
EXPECT_CALL(*mock_stream_, Close()).Times(1);
}
@@ -1882,5 +1711,21 @@ TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) {
CreateChannelAndConnectSuccessfully();
}
+// A protocol error from the remote server should result in a close frame with
+// status 1002, followed by the connection closing.
+TEST_F(WebSocketChannelStreamTest, ProtocolError) {
+ static const InitFrame expected[] = {
+ {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose,
+ MASKED, CLOSE_DATA(PROTOCOL_ERROR, "WebSocket Protocol Error")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(Return(ERR_WS_PROTOCOL_ERROR));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close());
+
+ CreateChannelAndConnectSuccessfully();
+}
+
} // namespace
} // namespace net
diff --git a/net/websockets/websocket_frame.cc b/net/websockets/websocket_frame.cc
index f05b5b7..763712a 100644
--- a/net/websockets/websocket_frame.cc
+++ b/net/websockets/websocket_frame.cc
@@ -41,18 +41,27 @@ inline void MaskWebSocketFramePayloadByBytes(
namespace net {
-scoped_ptr<WebSocketFrameHeader> WebSocketFrameHeader::Clone() {
+scoped_ptr<WebSocketFrameHeader> WebSocketFrameHeader::Clone() const {
scoped_ptr<WebSocketFrameHeader> ret(new WebSocketFrameHeader(opcode));
- ret->final = final;
- ret->reserved1 = reserved1;
- ret->reserved2 = reserved2;
- ret->reserved3 = reserved3;
- ret->opcode = opcode;
- ret->masked = masked;
- ret->payload_length = payload_length;
+ ret->CopyFrom(*this);
return ret.Pass();
}
+void WebSocketFrameHeader::CopyFrom(const WebSocketFrameHeader& source) {
+ final = source.final;
+ reserved1 = source.reserved1;
+ reserved2 = source.reserved2;
+ reserved3 = source.reserved3;
+ opcode = source.opcode;
+ masked = source.masked;
+ payload_length = source.payload_length;
+}
+
+WebSocketFrame::WebSocketFrame(WebSocketFrameHeader::OpCode opcode)
+ : header(opcode) {}
+
+WebSocketFrame::~WebSocketFrame() {}
+
WebSocketFrameChunk::WebSocketFrameChunk() : final_chunk(false) {}
WebSocketFrameChunk::~WebSocketFrameChunk() {}
@@ -195,7 +204,7 @@ void MaskWebSocketFramePayload(const WebSocketMaskingKey& masking_key,
// Create a version of the mask which is rotated by the appropriate offset
// for our alignment. The "trick" here is that 0 XORed with the mask will
// give the value of the mask for the appropriate byte.
- char realigned_mask[kMaskingKeyLength] = { 0 };
+ char realigned_mask[kMaskingKeyLength] = {};
MaskWebSocketFramePayloadByBytes(
masking_key,
(frame_offset + aligned_begin - data) % kMaskingKeyLength,
diff --git a/net/websockets/websocket_frame.h b/net/websockets/websocket_frame.h
index 8c04f7d..c9d8b97 100644
--- a/net/websockets/websocket_frame.h
+++ b/net/websockets/websocket_frame.h
@@ -14,6 +14,7 @@
namespace net {
+class IOBuffer;
class IOBufferWithSize;
// Represents a WebSocket frame header.
@@ -69,7 +70,10 @@ struct NET_EXPORT WebSocketFrameHeader {
payload_length(0) {}
// Create a clone of this object on the heap.
- scoped_ptr<WebSocketFrameHeader> Clone();
+ scoped_ptr<WebSocketFrameHeader> Clone() const;
+
+ // Overwrite this object with the fields from |source|.
+ void CopyFrom(const WebSocketFrameHeader& source);
// Members below correspond to each item in WebSocket frame header.
// See <http://tools.ietf.org/html/rfc6455#section-5.2> for details.
@@ -85,16 +89,32 @@ struct NET_EXPORT WebSocketFrameHeader {
DISALLOW_COPY_AND_ASSIGN(WebSocketFrameHeader);
};
-// Contains payload data of part of a WebSocket frame.
+// Contains an entire WebSocket frame including payload. This is used by APIs
+// that are not concerned about retaining the original frame boundaries (because
+// frames may need to be split in order for the data to fit in memory).
+struct NET_EXPORT_PRIVATE WebSocketFrame {
+ // A frame must always have an opcode, so this parameter is compulsory.
+ explicit WebSocketFrame(WebSocketFrameHeader::OpCode opcode);
+ ~WebSocketFrame();
+
+ // |header| is always present.
+ WebSocketFrameHeader header;
+
+ // |data| is always unmasked even if the frame is masked. The size of |data|
+ // is given by |header.payload_length|.
+ scoped_refptr<IOBuffer> data;
+};
+
+// Structure describing one chunk of a WebSocket frame.
//
-// Payload of a WebSocket frame may be divided into multiple chunks.
+// The payload of a WebSocket frame may be divided into multiple chunks.
// You need to look at |final_chunk| member variable to detect the end of a
// series of chunk objects of a WebSocket frame.
//
-// Frame dissection is necessary to handle WebSocket frame stream containing
-// abritrarily large frames in the browser process. Because the server may send
-// a huge frame that doesn't fit in the memory, we cannot store the entire
-// payload data in the memory.
+// Frame dissection is necessary to handle frames that are too large to store in
+// the browser memory without losing information about the frame boundaries. In
+// practice, most code does not need to worry about the original frame
+// boundaries and can use the WebSocketFrame type declared above.
//
// Users of this struct should treat WebSocket frames as a data stream; it's
// important to keep the frame data flowing, especially in the browser process.
diff --git a/net/websockets/websocket_stream.h b/net/websockets/websocket_stream.h
index 4885bbe..ca29f57 100644
--- a/net/websockets/websocket_stream.h
+++ b/net/websockets/websocket_stream.h
@@ -24,7 +24,7 @@ class BoundNetLog;
class HttpRequestHeaders;
class HttpResponseInfo;
class URLRequestContext;
-struct WebSocketFrameChunk;
+struct WebSocketFrame;
// WebSocketStreamRequest is the caller's handle to the process of creation of a
// WebSocketStream. Deleting the object before the OnSuccess or OnFailure
@@ -91,10 +91,10 @@ class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase {
virtual ~WebSocketStream();
// Reads WebSocket frame data. This operation finishes when new frame data
- // becomes available. Each frame message might be chopped off in the middle
- // as specified in the description of the WebSocketFrameChunk struct.
- // |frame_chunks| remains owned by the caller and must be valid until the
- // operation completes or Close() is called. |frame_chunks| must be empty on
+ // becomes available.
+ //
+ // |frames| remains owned by the caller and must be valid until the
+ // operation completes or Close() is called. |frames| must be empty on
// calling.
//
// This function should not be called while the previous call of ReadFrames()
@@ -102,20 +102,18 @@ class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase {
//
// Returns net::OK or one of the net::ERR_* codes.
//
- // frame_chunks->size() >= 1 if the result is OK.
+ // frames->size() >= 1 if the result is OK.
//
- // A frame with an incomplete header will never be inserted into
- // |frame_chunks|. If the currently available bytes of a new frame do not form
- // a complete frame header, then the implementation will buffer them until all
- // the fields in the WebSocketFrameHeader object can be filled. If
- // ReadFrames() is freshly called in this situation, it will return
- // ERR_IO_PENDING exactly as if no data was available.
+ // Only frames with complete header information are inserted into |frames|. If
+ // the currently available bytes of a new frame do not form a complete frame
+ // header, then the implementation will buffer them until all the fields in
+ // the WebSocketFrameHeader object can be filled. If ReadFrames() is freshly
+ // called in this situation, it will return ERR_IO_PENDING exactly as if no
+ // data was available.
//
- // Every WebSocketFrameChunk in the vector except the first and last is
- // guaranteed to be a complete frame. The first chunk may be the final part
- // of the previous frame. The last chunk may be the first part of a new
- // frame. If there is only one chunk, then it may consist of data from the
- // middle part of a frame.
+ // Original frame boundaries are not preserved. In particular, if only part of
+ // a frame is available, then the frame will be split, and the available data
+ // will be returned immediately.
//
// When the socket is closed on the remote side, this method will return
// ERR_CONNECTION_CLOSED. It will not return OK with an empty vector.
@@ -124,33 +122,23 @@ class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase {
// ReadFrames may discard the incomplete frame. Since the renderer will
// discard any incomplete messages when the connection is closed, this makes
// no difference to the overall semantics.
- virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) = 0;
- // Writes WebSocket frame data. |frame_chunks| must only contain complete
- // frames. Every chunk must have a non-NULL |header| and the |final_chunk|
- // boolean set to true.
- //
- // The |frame_chunks| pointer must remain valid until the operation completes
- // or Close() is called. WriteFrames() will modify the contents of
- // |frame_chunks| in the process of sending the message. After WriteFrames()
- // has completed it is safe to clear and then re-use the vector, but other
- // than that the caller should make no assumptions about its contents.
+ // Writes WebSocket frame data.
//
- // This function should not be called while a previous call to WriteFrames()
- // on the same stream is pending.
+ // |frames| must be valid until the operation completes or Close() is called.
//
- // Frame boundaries may not be preserved. Frames may be split or
- // coalesced. Message boundaries are preserved (as required by WebSocket API
- // semantics).
+ // This function must not be called while a previous call of WriteFrames() is
+ // still pending.
//
// This method will only return OK if all frames were written completely.
// Otherwise it will return an appropriate net error code.
- virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames,
const CompletionCallback& callback) = 0;
// Closes the stream. All pending I/O operations (if any) are cancelled
- // at this point, so |frame_chunks| can be freed.
+ // at this point, so |frames| can be freed.
virtual void Close() = 0;
// The subprotocol that was negotiated for the stream. If no protocol was