summaryrefslogtreecommitdiff
path: root/chromium/net/websockets/websocket_channel_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/websockets/websocket_channel_test.cc')
-rw-r--r--chromium/net/websockets/websocket_channel_test.cc1886
1 files changed, 1886 insertions, 0 deletions
diff --git a/chromium/net/websockets/websocket_channel_test.cc b/chromium/net/websockets/websocket_channel_test.cc
new file mode 100644
index 00000000000..25e9cdc0508
--- /dev/null
+++ b/chromium/net/websockets/websocket_channel_test.cc
@@ -0,0 +1,1886 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_channel.h"
+
+#include <string.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/safe_numerics.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_mux.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// Hacky macros to construct the body of a Close message from a code and a
+// string, while ensuring the result is a compile-time constant string.
+// Use like CLOSE_DATA(NORMAL_CLOSURE, "Explanation String")
+#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_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
+// GoogleMock; a nested anonymous namespace will not work.
+
+std::ostream& operator<<(std::ostream& os, const WebSocketFrameHeader& header) {
+ return os << "{" << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", "
+ << header.opcode << ", "
+ << (header.masked ? "MASKED" : "NOT_MASKED") << ", "
+ << header.payload_length << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const WebSocketFrameChunk& chunk) {
+ os << "{";
+ if (chunk.header) {
+ os << *chunk.header;
+ } else {
+ os << "{NO_HEADER}";
+ }
+ return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK")
+ << ", \""
+ << base::StringPiece(chunk.data->data(), chunk.data->size())
+ << "\"}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const ScopedVector<WebSocketFrameChunk>& vector) {
+ os << "{";
+ bool first = true;
+ for (ScopedVector<WebSocketFrameChunk>::const_iterator it = vector.begin();
+ it != vector.end();
+ ++it) {
+ if (!first) {
+ os << ",\n";
+ } else {
+ first = false;
+ }
+ os << **it;
+ }
+ return os << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const ScopedVector<WebSocketFrameChunk>* vector) {
+ return os << '&' << *vector;
+}
+
+namespace {
+
+using ::testing::AnyNumber;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+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
+ '\0', // nul
+ '\x7F', // DEL
+ '\x80', '\xFF', // NOT VALID UTF-8
+ '\x1A', // Control-Z, EOF on DOS
+ '\x03', // Control-C
+ '\x04', // EOT, special for Unix terms
+ '\x1B', // ESC, often special
+ '\b', // backspace
+ '\'', // single-quote, special in PHP
+};
+const size_t kBinaryBlobSize = arraysize(kBinaryBlob);
+
+// The amount of quota a new connection gets by default.
+// TODO(ricea): If kDefaultSendQuotaHighWaterMark changes, then this value will
+// need to be updated.
+const size_t kDefaultInitialQuota = 1 << 17;
+// The amount of bytes we need to send after the initial connection to trigger a
+// quota refresh. TODO(ricea): Change this if kDefaultSendQuotaHighWaterMark or
+// kDefaultSendQuotaLowWaterMark change.
+const size_t kDefaultQuotaRefreshTrigger = (1 << 16) + 1;
+
+// This mock is for testing expectations about how the EventInterface is used.
+class MockWebSocketEventInterface : public WebSocketEventInterface {
+ public:
+ MOCK_METHOD2(OnAddChannelResponse, void(bool, const std::string&));
+ MOCK_METHOD3(OnDataFrame,
+ void(bool, WebSocketMessageType, const std::vector<char>&));
+ MOCK_METHOD1(OnFlowControl, void(int64));
+ MOCK_METHOD0(OnClosingHandshake, void(void));
+ MOCK_METHOD2(OnDropChannel, void(uint16, const std::string&));
+};
+
+// This fake EventInterface is for tests which need a WebSocketEventInterface
+// implementation but are not verifying how it is used.
+class FakeWebSocketEventInterface : public WebSocketEventInterface {
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_protocol) OVERRIDE {}
+ virtual void OnDataFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) OVERRIDE {}
+ virtual void OnFlowControl(int64 quota) OVERRIDE {}
+ virtual void OnClosingHandshake() OVERRIDE {}
+ virtual void OnDropChannel(uint16 code, const std::string& reason) OVERRIDE {}
+};
+
+// This fake WebSocketStream is for tests that require a WebSocketStream but are
+// not testing the way it is used. It has minimal functionality to return
+// the |protocol| and |extensions| that it was constructed with.
+class FakeWebSocketStream : public WebSocketStream {
+ public:
+ // Constructs with empty protocol and extensions.
+ FakeWebSocketStream() {}
+
+ // Constructs with specified protocol and extensions.
+ FakeWebSocketStream(const std::string& protocol,
+ const std::string& extensions)
+ : protocol_(protocol), extensions_(extensions) {}
+
+ virtual int SendHandshakeRequest(
+ const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadHandshakeResponse(
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual void Close() OVERRIDE {}
+
+ // Returns the string passed to the constructor.
+ virtual std::string GetSubProtocol() const OVERRIDE { return protocol_; }
+
+ // Returns the string passed to the constructor.
+ virtual std::string GetExtensions() const OVERRIDE { return extensions_; }
+
+ private:
+ // The string to return from GetSubProtocol().
+ std::string protocol_;
+
+ // The string to return from GetExtensions().
+ std::string extensions_;
+};
+
+// To make the static initialisers easier to read, we use enums rather than
+// bools.
+
+// NO_HEADER means there shouldn't be a header included in the generated
+// WebSocketFrameChunk. The static initialiser always has a header, but we can
+// avoid specifying the rest of the fields.
+enum IsFinal {
+ NO_HEADER,
+ NOT_FINAL_FRAME,
+ FINAL_FRAME
+};
+
+enum IsMasked {
+ NOT_MASKED,
+ MASKED
+};
+
+enum IsFinalChunk {
+ NOT_FINAL_CHUNK,
+ FINAL_CHUNK
+};
+
+// This is used to initialise a WebSocketFrameChunk but is statically
+// initialisable.
+struct InitFrameChunk {
+ struct FrameHeader {
+ IsFinal final;
+ // Reserved fields omitted for now. Add them if you need them.
+ WebSocketFrameHeader::OpCode opcode;
+ IsMasked masked;
+ // payload_length is the length of the whole frame. The length of the data
+ // members from every chunk in the frame must add up to the payload_length.
+ uint64 payload_length;
+ };
+ FrameHeader header;
+
+ // Directly equivalent to WebSocketFrameChunk::final_chunk
+ IsFinalChunk final_chunk;
+
+ // Will be used to create the IOBuffer member. Can be NULL for NULL data. Is a
+ // nul-terminated string for ease-of-use. This means it is not 8-bit clean,
+ // but this is not an issue for test data.
+ const char* const data;
+};
+
+// 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}";
+ }
+ return os << ", " << (chunk.final_chunk == FINAL_CHUNK ? "FINAL_CHUNK"
+ : "NOT_FINAL_CHUNK")
+ << ", \"" << chunk.data << "\"}";
+}
+
+template <size_t N>
+std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) {
+ os << "{";
+ bool first = true;
+ for (size_t i = 0; i < N; ++i) {
+ if (!first) {
+ os << ",\n";
+ } else {
+ first = false;
+ }
+ os << chunks[i];
+ }
+ return os << "}";
+}
+
+// Convert a const array of InitFrameChunks to the format used at
+// runtime. Templated on the size of the array to save typing.
+template <size_t N>
+ScopedVector<WebSocketFrameChunk> CreateFrameChunkVector(
+ const InitFrameChunk (&source_chunks)[N]) {
+ ScopedVector<WebSocketFrameChunk> result_chunks;
+ result_chunks.reserve(N);
+ for (size_t i = 0; i < N; ++i) {
+ scoped_ptr<WebSocketFrameChunk> result_chunk(new WebSocketFrameChunk);
+ size_t chunk_length =
+ source_chunks[i].data ? strlen(source_chunks[i].data) : 0;
+ if (source_chunks[i].header.final != NO_HEADER) {
+ const InitFrameChunk::FrameHeader& source_header =
+ source_chunks[i].header;
+ scoped_ptr<WebSocketFrameHeader> result_header(
+ new WebSocketFrameHeader(source_header.opcode));
+ result_header->final = (source_header.final == FINAL_FRAME);
+ result_header->masked = (source_header.masked == MASKED);
+ result_header->payload_length = source_header.payload_length;
+ DCHECK(chunk_length <= source_header.payload_length);
+ result_chunk->header.swap(result_header);
+ }
+ result_chunk->final_chunk = (source_chunks[i].final_chunk == FINAL_CHUNK);
+ if (source_chunks[i].data) {
+ result_chunk->data = new IOBufferWithSize(chunk_length);
+ memcpy(result_chunk->data->data(), source_chunks[i].data, chunk_length);
+ }
+ result_chunks.push_back(result_chunk.release());
+ }
+ return result_chunks.Pass();
+}
+
+// A GoogleMock action which can be used to respond to call to ReadFrames with
+// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(&chunks));
+// |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);
+ 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
+// unreadable that it is better to use this matcher.
+template <size_t N>
+class EqualsChunksMatcher
+ : public ::testing::MatcherInterface<ScopedVector<WebSocketFrameChunk>*> {
+ public:
+ EqualsChunksMatcher(const InitFrameChunk (*expect_chunks)[N])
+ : expect_chunks_(expect_chunks) {}
+
+ virtual bool MatchAndExplain(ScopedVector<WebSocketFrameChunk>* actual_chunks,
+ ::testing::MatchResultListener* listener) const {
+ if (actual_chunks->size() != N) {
+ *listener << "the vector size is " << actual_chunks->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");
+ 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_chunk.final_chunk !=
+ (expected_chunk.final_chunk == FINAL_CHUNK)) {
+ *listener << "the chunk is marked as "
+ << (actual_chunk.final_chunk ? "" : "not ") << "final";
+ 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();
+ return false;
+ }
+ if (memcmp(actual_chunk.data->data(),
+ expected_chunk.data,
+ actual_chunk.data->size()) != 0) {
+ *listener << "the data content differs";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ virtual void DescribeTo(std::ostream* os) const {
+ *os << "matches " << *expect_chunks_;
+ }
+
+ virtual void DescribeNegationTo(std::ostream* os) const {
+ *os << "does not match " << *expect_chunks_;
+ }
+
+ private:
+ const InitFrameChunk (*expect_chunks_)[N];
+};
+
+// The definition of EqualsChunks GoogleMock matcher. Unlike the ReturnChunks
+// 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));
+}
+
+// A FakeWebSocketStream whose ReadFrames() function returns data.
+class ReadableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ enum IsSync {
+ SYNC,
+ ASYNC
+ };
+
+ // After constructing the object, call PrepareReadFrames() once for each
+ // time you wish it to return from the test.
+ ReadableFakeWebSocketStream() : index_(0), read_frames_pending_(false) {}
+
+ // Check that all the prepared responses have been consumed.
+ virtual ~ReadableFakeWebSocketStream() {
+ CHECK(index_ >= responses_.size());
+ CHECK(!read_frames_pending_);
+ }
+
+ // Prepares a fake responses. Fake responses will be returned from
+ // ReadFrames() in the same order they were prepared with PrepareReadFrames()
+ // and PrepareReadFramesError(). If |async| is ASYNC, then ReadFrames() will
+ // return ERR_IO_PENDING and the callback will be scheduled to run on the
+ // message loop. This requires the test case to run the message loop. If
+ // |async| is SYNC, the response will be returned synchronously. |error| is
+ // returned directly from ReadFrames() in the synchronous case, or passed to
+ // the callback in the asynchronous case. |chunks| will be converted to a
+ // ScopedVector<WebSocketFrameChunks> and copied to the pointer that was
+ // passed to ReadFrames().
+ template <size_t N>
+ void PrepareReadFrames(IsSync async,
+ int error,
+ const InitFrameChunk (&chunks)[N]) {
+ responses_.push_back(
+ new Response(async, error, CreateFrameChunkVector(chunks)));
+ }
+
+ // 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()));
+ }
+
+ // Prepares a fake error response (ie. there is no data).
+ void PrepareReadFramesError(IsSync async, int error) {
+ responses_.push_back(
+ new Response(async, error, ScopedVector<WebSocketFrameChunk>()));
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ CHECK(!read_frames_pending_);
+ if (index_ >= responses_.size())
+ return ERR_IO_PENDING;
+ if (responses_[index_]->async == ASYNC) {
+ read_frames_pending_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ReadableFakeWebSocketStream::DoCallback,
+ base::Unretained(this),
+ frame_chunks,
+ callback));
+ return ERR_IO_PENDING;
+ } else {
+ frame_chunks->swap(responses_[index_]->chunks);
+ return responses_[index_++]->error;
+ }
+ }
+
+ private:
+ void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ read_frames_pending_ = false;
+ frame_chunks->swap(responses_[index_]->chunks);
+ callback.Run(responses_[index_++]->error);
+ return;
+ }
+
+ struct Response {
+ Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks)
+ : async(async), error(error), chunks(chunks.Pass()) {}
+
+ IsSync async;
+ int error;
+ ScopedVector<WebSocketFrameChunk> chunks;
+
+ private:
+ // Bad things will happen if we attempt to copy or assign "chunks".
+ DISALLOW_COPY_AND_ASSIGN(Response);
+ };
+ ScopedVector<Response> responses_;
+
+ // The index into the responses_ array of the next response to be returned.
+ size_t index_;
+
+ // True when an async response from ReadFrames() is pending. This only applies
+ // to "real" async responses. Once all the prepared responses have been
+ // returned, ReadFrames() returns ERR_IO_PENDING but read_frames_pending_ is
+ // not set to true.
+ bool read_frames_pending_;
+};
+
+// A FakeWebSocketStream where writes always complete successfully and
+// synchronously.
+class WriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+};
+
+// A FakeWebSocketStream where writes always fail.
+class UnWriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_CONNECTION_RESET;
+ }
+};
+
+// A FakeWebSocketStream which echoes any frames written back. Clears the
+// "masked" header bit, but makes no other checks for validity. Tests using this
+// must run the MessageLoop to receive the callback(s). If a message with opcode
+// Close is echoed, then an ERR_CONNECTION_CLOSED is returned in the next
+// callback. The test must do something to cause WriteFrames() to be called,
+// otherwise the ReadFrames() callback will never be called.
+class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {}
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ // Users of WebSocketStream will not expect the ReadFrames() callback to be
+ // called from within WriteFrames(), so post it to the message loop instead.
+ stored_frame_chunks_.insert(
+ stored_frame_chunks_.end(), frame_chunks->begin(), frame_chunks->end());
+ frame_chunks->weak_clear();
+ PostCallback();
+ return OK;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ read_callback_ = callback;
+ read_frame_chunks_ = frame_chunks;
+ if (done_)
+ PostCallback();
+ return ERR_IO_PENDING;
+ }
+
+ private:
+ void PostCallback() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&EchoeyFakeWebSocketStream::DoCallback,
+ base::Unretained(this)));
+ }
+
+ void DoCallback() {
+ if (done_) {
+ read_callback_.Run(ERR_CONNECTION_CLOSED);
+ } else if (!stored_frame_chunks_.empty()) {
+ done_ = MoveFrameChunks(read_frame_chunks_);
+ read_frame_chunks_ = NULL;
+ read_callback_.Run(OK);
+ }
+ }
+
+ // Copy the chunks stored in stored_frame_chunks_ to |out|, while clearing the
+ // "masked" header bit. Returns true if a Close Frame was seen, false
+ // otherwise.
+ bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* out) {
+ bool seen_close = false;
+ *out = stored_frame_chunks_.Pass();
+ for (ScopedVector<WebSocketFrameChunk>::iterator it = out->begin();
+ it != out->end();
+ ++it) {
+ WebSocketFrameHeader* header = (*it)->header.get();
+ if (header) {
+ header->masked = false;
+ if (header->opcode == WebSocketFrameHeader::kOpCodeClose)
+ seen_close = true;
+ }
+ }
+ return seen_close;
+ }
+
+ ScopedVector<WebSocketFrameChunk> stored_frame_chunks_;
+ CompletionCallback read_callback_;
+ // Owned by the caller of ReadFrames().
+ ScopedVector<WebSocketFrameChunk>* read_frame_chunks_;
+ // True if we should close the connection.
+ bool done_;
+};
+
+// A FakeWebSocketStream where writes trigger a connection reset.
+// This differs from UnWriteableFakeWebSocketStream in that it is asynchronous
+// and triggers ReadFrames to return a reset as well. Tests using this need to
+// run the message loop.
+class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, ERR_CONNECTION_RESET));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(read_callback_, ERR_CONNECTION_RESET));
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ read_callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+
+ private:
+ CompletionCallback read_callback_;
+};
+
+// This mock is for verifying that WebSocket protocol semantics are obeyed (to
+// the extent that they are implemented in WebSocketCommon).
+class MockWebSocketStream : public WebSocketStream {
+ public:
+ MOCK_METHOD2(ReadFrames,
+ int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback));
+ MOCK_METHOD2(WriteFrames,
+ int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback));
+ MOCK_METHOD0(Close, void());
+ MOCK_CONST_METHOD0(GetSubProtocol, std::string());
+ MOCK_CONST_METHOD0(GetExtensions, std::string());
+ MOCK_METHOD0(AsWebSocketStream, WebSocketStream*());
+ MOCK_METHOD4(SendHandshakeRequest,
+ int(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback));
+ MOCK_METHOD1(ReadHandshakeResponse, int(const CompletionCallback& callback));
+};
+
+struct ArgumentCopyingWebSocketFactory {
+ scoped_ptr<WebSocketStreamRequest> Factory(
+ const GURL& socket_url,
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const BoundNetLog& net_log,
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate) {
+ this->socket_url = socket_url;
+ this->requested_subprotocols = requested_subprotocols;
+ this->origin = origin;
+ this->url_request_context = url_request_context;
+ this->net_log = net_log;
+ this->connect_delegate = connect_delegate.Pass();
+ return make_scoped_ptr(new WebSocketStreamRequest);
+ }
+
+ GURL socket_url;
+ GURL origin;
+ std::vector<std::string> requested_subprotocols;
+ URLRequestContext* url_request_context;
+ BoundNetLog net_log;
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate;
+};
+
+// Converts a std::string to a std::vector<char>. For test purposes, it is
+// convenient to be able to specify data as a string, but the
+// WebSocketEventInterface requires the vector<char> type.
+std::vector<char> AsVector(const std::string& s) {
+ return std::vector<char>(s.begin(), s.end());
+}
+
+// Base class for all test fixtures.
+class WebSocketChannelTest : public ::testing::Test {
+ protected:
+ WebSocketChannelTest() : stream_(new FakeWebSocketStream) {}
+
+ // Creates a new WebSocketChannel and connects it, using the settings stored
+ // in |connect_data_|.
+ void CreateChannelAndConnect() {
+ channel_.reset(
+ new WebSocketChannel(connect_data_.url, CreateEventInterface()));
+ channel_->SendAddChannelRequestForTesting(
+ connect_data_.requested_subprotocols,
+ connect_data_.origin,
+ &connect_data_.url_request_context,
+ base::Bind(&ArgumentCopyingWebSocketFactory::Factory,
+ base::Unretained(&connect_data_.factory)));
+ }
+
+ // Same as CreateChannelAndConnect(), but calls the on_success callback as
+ // well. This method is virtual so that subclasses can also set the stream.
+ virtual void CreateChannelAndConnectSuccessfully() {
+ CreateChannelAndConnect();
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+ }
+
+ // Returns a WebSocketEventInterface to be passed to the WebSocketChannel.
+ // This implementation returns a newly-created fake. Subclasses may return a
+ // mock instead.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() {
+ return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface);
+ }
+
+ // This method serves no other purpose than to provide a nice syntax for
+ // assigning to stream_. class T must be a subclass of WebSocketStream or you
+ // will have unpleasant compile errors.
+ template <class T>
+ void set_stream(scoped_ptr<T> stream) {
+ // Since the definition of "PassAs" depends on the type T, the C++ standard
+ // requires the "template" keyword to indicate that "PassAs" should be
+ // parsed as a template method.
+ stream_ = stream.template PassAs<WebSocketStream>();
+ }
+
+ // A struct containing the data that will be used to connect the channel.
+ struct ConnectData {
+ // URL to (pretend to) connect to.
+ GURL url;
+ // Origin of the request
+ GURL origin;
+ // Requested protocols for the request.
+ std::vector<std::string> requested_subprotocols;
+ // URLRequestContext object.
+ URLRequestContext url_request_context;
+ // A fake WebSocketFactory that just records its arguments.
+ ArgumentCopyingWebSocketFactory factory;
+ };
+ ConnectData connect_data_;
+
+ // The channel we are testing. Not initialised until SetChannel() is called.
+ scoped_ptr<WebSocketChannel> channel_;
+
+ // A mock or fake stream for tests that need one.
+ scoped_ptr<WebSocketStream> stream_;
+};
+
+class WebSocketChannelDeletingTest : public WebSocketChannelTest {
+ public:
+ void ResetChannel() { channel_.reset(); }
+
+ protected:
+ // Create a ChannelDeletingFakeWebSocketEventInterface. Defined out-of-line to
+ // avoid circular dependency.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() OVERRIDE;
+};
+
+// A FakeWebSocketEventInterface that deletes the WebSocketChannel on failure to
+// connect.
+class ChannelDeletingFakeWebSocketEventInterface
+ : public FakeWebSocketEventInterface {
+ public:
+ ChannelDeletingFakeWebSocketEventInterface(
+ WebSocketChannelDeletingTest* fixture)
+ : fixture_(fixture) {}
+
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_protocol) OVERRIDE {
+ if (fail) {
+ fixture_->ResetChannel();
+ }
+ }
+
+ private:
+ // A pointer to the test fixture. Owned by the test harness; this object will
+ // be deleted before it is.
+ WebSocketChannelDeletingTest* fixture_;
+};
+
+scoped_ptr<WebSocketEventInterface>
+WebSocketChannelDeletingTest::CreateEventInterface() {
+ return scoped_ptr<WebSocketEventInterface>(
+ new ChannelDeletingFakeWebSocketEventInterface(this));
+}
+
+// Base class for tests which verify that EventInterface methods are called
+// appropriately.
+class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelEventInterfaceTest()
+ : event_interface_(new StrictMock<MockWebSocketEventInterface>) {}
+
+ // Tests using this fixture must set expectations on the event_interface_ mock
+ // object before calling CreateChannelAndConnect() or
+ // CreateChannelAndConnectSuccessfully(). This will only work once per test
+ // case, but once should be enough.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() OVERRIDE {
+ return scoped_ptr<WebSocketEventInterface>(event_interface_.release());
+ }
+
+ scoped_ptr<MockWebSocketEventInterface> event_interface_;
+};
+
+// Base class for tests which verify that WebSocketStream methods are called
+// appropriately by using a MockWebSocketStream.
+class WebSocketChannelStreamTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelStreamTest()
+ : mock_stream_(new StrictMock<MockWebSocketStream>) {}
+
+ virtual void CreateChannelAndConnectSuccessfully() OVERRIDE {
+ set_stream(mock_stream_.Pass());
+ WebSocketChannelTest::CreateChannelAndConnectSuccessfully();
+ }
+
+ scoped_ptr<MockWebSocketStream> mock_stream_;
+};
+
+// Simple test that everything that should be passed to the factory function is
+// passed to the factory function.
+TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) {
+ connect_data_.url = GURL("ws://example.com/test");
+ connect_data_.origin = GURL("http://example.com/test");
+ connect_data_.requested_subprotocols.push_back("Sinbad");
+
+ CreateChannelAndConnect();
+
+ EXPECT_EQ(connect_data_.url, connect_data_.factory.socket_url);
+ EXPECT_EQ(connect_data_.origin, connect_data_.factory.origin);
+ EXPECT_EQ(connect_data_.requested_subprotocols,
+ connect_data_.factory.requested_subprotocols);
+ EXPECT_EQ(&connect_data_.url_request_context,
+ connect_data_.factory.url_request_context);
+}
+
+// The documentation for WebSocketEventInterface::OnAddChannelResponse() says
+// that if the first argument is true, ie. the connection failed, then we can
+// safely synchronously delete the WebSocketChannel. This test will only
+// reliably find problems if run with a memory debugger such as
+// AddressSanitizer.
+TEST_F(WebSocketChannelDeletingTest, DeletingFromOnAddChannelResponseWorks) {
+ CreateChannelAndConnect();
+ connect_data_.factory.connect_delegate
+ ->OnFailure(kWebSocketErrorNoStatusReceived);
+ EXPECT_EQ(NULL, channel_.get());
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) {
+ // false means success.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, ""));
+ // OnFlowControl is always called immediately after connect to provide initial
+ // quota to the renderer.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) {
+ // true means failure.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, ""));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate
+ ->OnFailure(kWebSocketErrorNoStatusReceived);
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "Bob"));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate->OnSuccess(
+ scoped_ptr<WebSocketStream>(new FakeWebSocketStream("Bob", "")));
+}
+
+// The first frames from the server can arrive together with the handshake, in
+// which case they will be available as soon as ReadFrames() is called the first
+// time.
+TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could accept the handshake, but then immediately send a
+// Close frame.
+TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23},
+ FINAL_CHUNK, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorInternalServerError,
+ "Internal Server Error"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could close the connection immediately after sending the
+// handshake response (most likely a bug in the server).
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+ // We use this checkpoint object to verify that the callback isn't called
+ // until we expect it to be.
+ MockFunction<void(int)> checkpoint;
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ base::MessageLoop::current()->RunUntilIdle();
+ checkpoint.Call(2);
+}
+
+// Extra data can arrive while a read is being processed, resulting in the next
+// read completing synchronously.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "WORLD"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks2);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("WORLD")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Data frames that arrive in fragments are turned into individual frames
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // Here we have one message split into 3 frames which arrive in 3 chunks. The
+ // first frame is entirely in the first chunk, the second frame is split
+ // across all the chunks, and the final frame is entirely in the final
+ // chunk. The frame fragments are converted to separate frames so that they
+ // can be delivered immediatedly. So the EventInterface should see a Text
+ // message with 5 frames.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "THREE"},
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED,
+ 7},
+ NOT_FINAL_CHUNK, " "}};
+ static const InitFrameChunk chunks2[] = {
+ {{NO_HEADER}, NOT_FINAL_CHUNK, "SMALL"}};
+ static const InitFrameChunk chunks3[] = {
+ {{NO_HEADER}, FINAL_CHUNK, " "},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 6},
+ FINAL_CHUNK, "FRAMES"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("THREE")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("SMALL")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("FRAMES")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// In the case when a single-frame message because fragmented, it must be
+// correctly transformed to multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // A single-frame Text message arrives in three chunks. This should be
+ // delivered as three frames.
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12},
+ NOT_FINAL_CHUNK, "TIME"}};
+ static const InitFrameChunk chunks2[] = {
+ {{NO_HEADER}, NOT_FINAL_CHUNK, " FOR "}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector(" FOR ")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If a control message is fragmented, it must be re-assembled before being
+// delivered. A control message can only be fragmented at the network level; it
+// is not permitted to be split into multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, 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);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed by the remote host without a closing handshake.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncAbnormalClosure) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// A connection reset should produce the same event as an unexpected closure.
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_RESET);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed in the middle of a Close message (server bug, etc.)
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, 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"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.2 "If an unknown opcode is received, the receiving endpoint MUST
+// _Fail the WebSocket Connection_."
+TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, 4, NOT_MASKED, 5}, FINAL_CHUNK, "HELLO"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.4 "Control frames ... MAY be injected in the middle of a
+// fragmented message."
+TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // We have one message of type Text split into two frames. In the middle is a
+ // control message of type Pong.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 6},
+ FINAL_CHUNK, "SPLIT "}};
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, 0},
+ FINAL_CHUNK, ""}};
+ static const InitFrameChunk chunks3[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 7},
+ FINAL_CHUNK, "MESSAGE"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("SPLIT ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("MESSAGE")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If a chunk has an invalid header, then the connection is closed and
+// subsequent chunks must not trigger events.
+TEST_F(WebSocketChannelEventInterfaceTest, HeaderlessChunkAfterInvalidChunk) {
+ 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"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If the renderer sends lots of small writes, we don't want to update the quota
+// for each one.
+TEST_F(WebSocketChannelEventInterfaceTest, SmallWriteDoesntUpdateQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("B"));
+}
+
+// If we send enough to go below send_quota_low_water_mask_ we should get our
+// quota refreshed.
+TEST_F(WebSocketChannelEventInterfaceTest, LargeWriteUpdatesQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ // We use this checkpoint object to verify that the quota update comes after
+ // the write.
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultInitialQuota, 'B'));
+ checkpoint.Call(2);
+}
+
+// Verify that our quota actually is refreshed when we are told it is.
+TEST_F(WebSocketChannelEventInterfaceTest, QuotaReallyIsRefreshed) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ // If quota was not really refreshed, we would get an OnDropChannel()
+ // message.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(3));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultQuotaRefreshTrigger, 'D'));
+ checkpoint.Call(2);
+ // We should have received more quota at this point.
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultQuotaRefreshTrigger, 'E'));
+ checkpoint.Call(3);
+}
+
+// If we send more than the available quota then the connection will be closed
+// with an error.
+TEST_F(WebSocketChannelEventInterfaceTest, WriteOverQuotaIsRejected) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(kDefaultInitialQuota));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultInitialQuota + 1, 'C'));
+}
+
+// If a write fails, the channel is dropped.
+TEST_F(WebSocketChannelEventInterfaceTest, FailedWrite) {
+ set_stream(make_scoped_ptr(new UnWriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("H"));
+ checkpoint.Call(2);
+}
+
+// OnDropChannel() is called exactly once when StartClosingHandshake() is used.
+TEST_F(WebSocketChannelEventInterfaceTest, SendCloseDropsChannel) {
+ set_stream(make_scoped_ptr(new EchoeyFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketNormalClosure, "Fred"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "Fred");
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// OnDropChannel() is only called once when a write() on the socket triggers a
+// connection reset.
+TEST_F(WebSocketChannelEventInterfaceTest, OnDropChannelCalledOnce) {
+ set_stream(make_scoped_ptr(new ResetOnWriteFakeWebSocketStream));
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, "Abnormal Closure"))
+ .Times(1);
+
+ CreateChannelAndConnectSuccessfully();
+
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("yt?"));
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// When the remote server sends a Close frame with an empty payload,
+// WebSocketChannel should report code 1005, kWebSocketErrorNoStatusReceived.
+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);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorNoStatusReceived, _));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// 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"}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("NEEDS MASKING"));
+}
+
+// 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")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->StartClosingHandshake(1000, "Success");
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("SHOULD BE IGNORED"));
+}
+
+// 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")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// 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")}};
+
+ // 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;
+
+ // Use a checkpoint to make the ordering of events clearer.
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&frame_chunks),
+ SaveArg<1>(&read_callback),
+ Return(ERR_IO_PENDING)));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(checkpoint, Call(2));
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(checkpoint, Call(3));
+ // WriteFrames() must not be called again. GoogleMock will ensure that the
+ // test fails if it is.
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "Close");
+ checkpoint.Call(2);
+
+ *frame_chunks = CreateFrameChunkVector(chunks);
+ read_callback.Run(OK);
+ checkpoint.Call(3);
+}
+
+// We generate code 1005, kWebSocketErrorNoStatusReceived, when there is no
+// status in the Close message from the other side. Code 1005 is not allowed to
+// appear on the wire, so we should not echo it back. See test
+// 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, ""}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// RFC6455 5.5.2 "Upon receipt of a Ping frame, an endpoint MUST send a Pong
+// frame in response"
+// 5.5.3 "A Pong frame sent in response to a Ping frame must have identical
+// "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"}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(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;
+ CompletionCallback read_callback;
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&read_chunks),
+ SaveArg<1>(&read_callback),
+ Return(ERR_IO_PENDING)))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ {
+ InSequence s;
+
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected3), _))
+ .WillOnce(Return(OK));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello "));
+ *read_chunks = CreateFrameChunkVector(chunks);
+ read_callback.Run(OK);
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("World"));
+}
+
+// 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"}};
+ CompletionCallback write_callback;
+ MockFunction<void(int)> checkpoint;
+
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ {
+ InSequence s;
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING)));
+ EXPECT_CALL(checkpoint, Call(2));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(checkpoint, Call(3));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->SendFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello "));
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("World"));
+ checkpoint.Call(2);
+ write_callback.Run(OK);
+ checkpoint.Call(3);
+}
+
+// WebSocketChannel must buffer frames while it is waiting for a write to
+// complete, and then send them in a single batch. The batching behaviour is
+// 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"}};
+ 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), _))
+ .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING)));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ for (size_t i = 0; i < strlen(input_letters); ++i) {
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(1, input_letters[i]));
+ }
+ write_callback.Run(OK);
+}
+
+// When the renderer sends more on a channel than it has quota for, then we send
+// a kWebSocketMuxErrorSendQuotaViolation status code (from the draft websocket
+// mux specification) back to the renderer. This should not be sent to the
+// remote server, which may not even implement the mux specification, and could
+// 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")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close());
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultInitialQuota + 1, 'C'));
+}
+
+// For convenience, most of these tests use Text frames. However, the WebSocket
+// 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;
+
+ 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)));
+
+ 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));
+}
+
+// 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<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareRawReadFrames(
+ ReadableFakeWebSocketStream::SYNC, OK, chunks.Pass());
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeBinary,
+ std::vector<char>(kBinaryBlob,
+ kBinaryBlob + kBinaryBlobSize)));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// If we receive another frame after Close, it is not valid. It is not
+// completely clear what behaviour is required from the standard in this case,
+// 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")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ {
+ // We only need to verify the relative order of WriteFrames() and
+ // Close(). The current implementation calls WriteFrames() for the Close
+ // 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), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close()).Times(1);
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+} // namespace
+} // namespace net