diff options
Diffstat (limited to 'chromium/net/websockets/websocket_channel_test.cc')
-rw-r--r-- | chromium/net/websockets/websocket_channel_test.cc | 1886 |
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 |