summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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