| Index: net/websockets/websocket_channel_test.cc
|
| diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d9ef937f76f8aeed66d6e4d662f4b346e3da78c3
|
| --- /dev/null
|
| +++ b/net/websockets/websocket_channel_test.cc
|
| @@ -0,0 +1,1123 @@
|
| +// 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 <iostream>
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/callback.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/memory/scoped_vector.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/strings/string_piece.h"
|
| +#include "googleurl/src/gurl.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"
|
| +
|
| +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. Static to reduce the risk of linker clashes.
|
| +
|
| +static 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 << "}";
|
| +}
|
| +
|
| +static 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()) << "\"}";
|
| +}
|
| +
|
| +namespace {
|
| +
|
| +using ::testing::AnyNumber;
|
| +using ::testing::Field;
|
| +using ::testing::InSequence;
|
| +using ::testing::MockFunction;
|
| +using ::testing::Pointee;
|
| +using ::testing::Return;
|
| +using ::testing::SetArgPointee;
|
| +using ::testing::_;
|
| +
|
| +// 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(OnSendFrame,
|
| + void(bool, WebSocketMessageType, const std::vector<char>&));
|
| + MOCK_METHOD1(OnFlowControl, void(int64));
|
| + MOCK_METHOD2(OnDropChannel, void(unsigned short, const std::string&));
|
| +};
|
| +
|
| +// This fake EventInterface is for tests which need an 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 OnSendFrame(bool fin,
|
| + WebSocketMessageType type,
|
| + const std::vector<char>& data) OVERRIDE {}
|
| + virtual void OnFlowControl(int64 quota) OVERRIDE {}
|
| + virtual void OnDropChannel(unsigned short reason,
|
| + const std::string& reason_text) OVERRIDE {}
|
| +};
|
| +
|
| +// This fake WebSocketStream is for tests that require a WebSocketStream but are
|
| +// 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:
|
| + // Construct with empty protocol and extensions.
|
| + FakeWebSocketStream() : WebSocketStream(), protocol_(), extensions_() {}
|
| +
|
| + // Construct with specified protocol and extensions.
|
| + FakeWebSocketStream(const std::string& protocol,
|
| + const std::string& extensions)
|
| + : WebSocketStream(), 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;
|
| + 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.
|
| + const char *const data;
|
| +};
|
| +
|
| +// 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 (&chunks)[N]) {
|
| + ScopedVector<WebSocketFrameChunk> vector;
|
| + vector.reserve(N);
|
| + for (size_t i = 0; i < N; ++i) {
|
| + scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk);
|
| + if (chunks[i].header.final != NO_HEADER) {
|
| + const InitFrameChunk::FrameHeader& in_header = chunks[i].header;
|
| + scoped_ptr<WebSocketFrameHeader> out_header(
|
| + new WebSocketFrameHeader(in_header.opcode));
|
| + out_header->final = in_header.final == FINAL_FRAME;
|
| + out_header->opcode = in_header.opcode;
|
| + out_header->masked = in_header.masked == MASKED;
|
| + out_header->payload_length = in_header.payload_length;
|
| + chunk->header.swap(out_header);
|
| + }
|
| + chunk->final_chunk = chunks[i].final_chunk == FINAL_CHUNK;
|
| + chunk->data = new IOBufferWithSize(strlen(chunks[i].data));
|
| + memcpy(chunk->data->data(), chunks[i].data, strlen(chunks[i].data));
|
| + vector.push_back(chunk.release());
|
| + }
|
| + return vector.Pass();
|
| +}
|
| +
|
| +// A Google Mock action which can be used to respond to call to ReadFrames with
|
| +// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(chunks));
|
| +ACTION_P(ReturnChunks, chunks) {
|
| + *arg0 = CreateFrameChunkVector(chunks);
|
| + return OK;
|
| +}
|
| +
|
| +// 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()
|
| + : FakeWebSocketStream(), responses_(), index_(0) {}
|
| +
|
| + // Call PrepareReadFrames() to prepare the fake responses, in order. If
|
| + // |async| is true, 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 false, 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)));
|
| + }
|
| +
|
| + // Prepare 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 {
|
| + if (index_ >= responses_.size()) {
|
| + return ERR_IO_PENDING;
|
| + }
|
| + if (responses_[index_]->async == ASYNC) {
|
| + 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) {
|
| + 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_;
|
| + size_t index_;
|
| +};
|
| +
|
| +// 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. It unset the
|
| +// "masked" header bit, but makes no other checks for validity. Tests using this
|
| +// must run the MessageLoop to receive the callback.
|
| +class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
|
| + public:
|
| + EchoeyFakeWebSocketStream()
|
| + : stored_frame_chunks_(), read_callback_(), read_frame_chunks_(NULL) {}
|
| +
|
| + 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();
|
| + base::MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&EchoeyFakeWebSocketStream::DoCallback,
|
| + base::Unretained(this)));
|
| + return OK;
|
| + }
|
| +
|
| + virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
|
| + const CompletionCallback& callback) OVERRIDE {
|
| + read_callback_ = callback;
|
| + read_frame_chunks_ = frame_chunks;
|
| + return ERR_IO_PENDING;
|
| + }
|
| +
|
| + private:
|
| + void DoCallback() {
|
| + if (!stored_frame_chunks_.empty()) {
|
| + MoveFrameChunks(read_frame_chunks_);
|
| + read_frame_chunks_ = NULL;
|
| + read_callback_.Run(OK);
|
| + read_callback_.Reset();
|
| + }
|
| + }
|
| +
|
| + void MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* to) {
|
| + to->assign(stored_frame_chunks_.begin(), stored_frame_chunks_.end());
|
| + for (ScopedVector<WebSocketFrameChunk>::iterator it = to->begin();
|
| + it != to->end();
|
| + ++it) {
|
| + if ((*it)->header && ((*it)->header->masked)) {
|
| + (*it)->header->masked = false;
|
| + }
|
| + }
|
| + stored_frame_chunks_.weak_clear();
|
| + }
|
| +
|
| + ScopedVector<WebSocketFrameChunk> stored_frame_chunks_;
|
| + CompletionCallback read_callback_;
|
| + // Owned by the caller of ReadFrames().
|
| + ScopedVector<WebSocketFrameChunk>* read_frame_chunks_;
|
| +};
|
| +
|
| +// 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;
|
| +};
|
| +
|
| +// The WebSocketEventInterface handles data as vector<char>. For testing
|
| +// purposes, this function allows a vector<char> to be easily created from a
|
| +// string.
|
| +std::vector<char> AsVector(const std::string& s) {
|
| + return std::vector<char>(s.begin(), s.end());
|
| +}
|
| +
|
| +// Base class for WebSocketChannelTests.
|
| +class WebSocketChannelTest : public ::testing::Test {
|
| + protected:
|
| + WebSocketChannelTest()
|
| + : data_(), channel_(), stream_(new FakeWebSocketStream) {}
|
| +
|
| + // Create a new WebSocketChannel and connect it, using the settings stored in
|
| + // data_.
|
| + void CreateChannelAndConnect() {
|
| + channel_.reset(
|
| + new WebSocketChannel(data_.url, EventInterface()));
|
| + channel_->SendAddChannelRequestWithFactory(
|
| + data_.requested_subprotocols,
|
| + data_.origin,
|
| + &data_.url_request_context,
|
| + base::Bind(&ArgumentCopyingWebSocketFactory::Factory,
|
| + base::Unretained(&data_.factory)));
|
| + }
|
| +
|
| + // Same as CreateChannelAndConnect(), but call the on_success callback as
|
| + // well. This method is virtual so that WebSocketChannelStreamTest can set the
|
| + // stream as well.
|
| + virtual void CreateChannelAndConnectSuccessfully() {
|
| + CreateChannelAndConnect();
|
| + data_.factory.connect_delegate->OnSuccess(stream_.Pass());
|
| + }
|
| +
|
| + // Return 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> EventInterface() {
|
| + return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface);
|
| + }
|
| +
|
| + // Downcasting and moving a scoped_ptr in one operation doesn't seem to work
|
| + // (bug?). This function works around that and simplifies initialising stream_
|
| + // from a subclass.
|
| + template<class T>
|
| + void set_stream(scoped_ptr<T> stream) {
|
| + stream_.reset(stream.release());
|
| + }
|
| +
|
| + // 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 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_;
|
| +};
|
| +
|
| +// Base class for tests which verify that EventInterface methods are called
|
| +// appropriately.
|
| +class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest {
|
| + protected:
|
| + WebSocketChannelEventInterfaceTest()
|
| + : WebSocketChannelTest(),
|
| + event_interface_(new MockWebSocketEventInterface) {}
|
| +
|
| + virtual scoped_ptr<WebSocketEventInterface> EventInterface() 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()
|
| + : WebSocketChannelTest(),
|
| + mock_(new MockWebSocketStream) {}
|
| +
|
| + virtual void CreateChannelAndConnectSuccessfully() OVERRIDE {
|
| + set_stream(mock_.Pass());
|
| + WebSocketChannelTest::CreateChannelAndConnectSuccessfully();
|
| + }
|
| +
|
| + scoped_ptr<MockWebSocketStream> mock_;
|
| +};
|
| +
|
| +// Simple test that everything that should be passed to the factory function is
|
| +// passed to the factory function.
|
| +TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) {
|
| + data_.url = GURL("ws://example.com/test");
|
| + data_.origin = GURL("http://example.com/test");
|
| + data_.requested_subprotocols.push_back("Sinbad");
|
| +
|
| + CreateChannelAndConnect();
|
| +
|
| + EXPECT_EQ(data_.url, data_.factory.socket_url);
|
| + EXPECT_EQ(data_.origin, data_.factory.origin);
|
| + EXPECT_EQ(data_.requested_subprotocols, data_.factory.requested_subprotocols);
|
| + EXPECT_EQ(&data_.url_request_context, data_.factory.url_request_context);
|
| +}
|
| +
|
| +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();
|
| +
|
| + data_.factory.connect_delegate->OnSuccess(stream_.Pass());
|
| +}
|
| +
|
| +TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) {
|
| + // true means failure.
|
| + EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, ""));
|
| +
|
| + CreateChannelAndConnect();
|
| +
|
| + data_.factory.connect_delegate->OnFailure(kWebSocketErrorNoStatusReceived);
|
| +}
|
| +
|
| +TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
|
| + EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "Bob"));
|
| + EXPECT_CALL(*event_interface_, OnFlowControl(_));
|
| +
|
| + CreateChannelAndConnect();
|
| +
|
| + 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_,
|
| + OnSendFrame(
|
| + 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, "\x03\xf3Internal Server Error"},
|
| + };
|
| + 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_,
|
| + 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,
|
| + "Abnormal Closure"));
|
| + }
|
| +
|
| + 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_,
|
| + OnSendFrame(
|
| + 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_,
|
| + OnSendFrame(
|
| + true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
|
| + EXPECT_CALL(
|
| + *event_interface_,
|
| + OnSendFrame(
|
| + 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. This should be delivered as 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_,
|
| + OnSendFrame(
|
| + false, WebSocketFrameHeader::kOpCodeText, AsVector("THREE")));
|
| + EXPECT_CALL(
|
| + *event_interface_,
|
| + OnSendFrame(
|
| + false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
|
| + EXPECT_CALL(*event_interface_,
|
| + OnSendFrame(false,
|
| + WebSocketFrameHeader::kOpCodeContinuation,
|
| + AsVector("SMALL")));
|
| + EXPECT_CALL(
|
| + *event_interface_,
|
| + OnSendFrame(
|
| + false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
|
| + EXPECT_CALL(*event_interface_,
|
| + OnSendFrame(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);
|
| + // 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. This should be delivered as 5 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_,
|
| + OnSendFrame(
|
| + false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME")));
|
| + EXPECT_CALL(*event_interface_,
|
| + OnSendFrame(false,
|
| + WebSocketFrameHeader::kOpCodeContinuation,
|
| + AsVector(" FOR ")));
|
| + EXPECT_CALL(
|
| + *event_interface_,
|
| + OnSendFrame(
|
| + 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, "\x03\xe8"},
|
| + };
|
| + 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);
|
| + set_stream(stream.Pass());
|
| + {
|
| + InSequence s;
|
| + EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
|
| + EXPECT_CALL(*event_interface_, OnFlowControl(_));
|
| + EXPECT_CALL(*event_interface_,
|
| + OnDropChannel(kWebSocketNormalClosure, "Close"));
|
| + }
|
| +
|
| + CreateChannelAndConnectSuccessfully();
|
| + base::MessageLoop::current()->RunUntilIdle();
|
| +}
|
| +
|
| +// Connection closed unexpectedly.
|
| +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();
|
| +}
|
| +
|
| +// Connection reset.
|
| +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, "\x03\xe8"},
|
| + };
|
| +
|
| + 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_,
|
| + OnSendFrame(
|
| + false, WebSocketFrameHeader::kOpCodeText, AsVector("SPLIT ")));
|
| + EXPECT_CALL(*event_interface_,
|
| + OnSendFrame(true,
|
| + WebSocketFrameHeader::kOpCodeContinuation,
|
| + AsVector("MESSAGE")));
|
| + }
|
| +
|
| + 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);
|
| + // TODO(ricea): If kDefaultSendQuotaHighWaterMark changes, then this value
|
| + // will need to be updated.
|
| + channel_->SendFrame(
|
| + true, WebSocketFrameHeader::kOpCodeText, std::vector<char>(1 << 17, '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);
|
| + // TODO(ricea): If kDefaultSendQuotaLowWaterMark and/or
|
| + // kDefaultSendQuotaHighWaterMark change, then this value will need to be
|
| + // updated.
|
| + channel_->SendFrame(true,
|
| + WebSocketFrameHeader::kOpCodeText,
|
| + std::vector<char>((1 << 16) + 1, 'D'));
|
| + checkpoint.Call(2);
|
| + // We should have received more quota at this point.
|
| + channel_->SendFrame(true,
|
| + WebSocketFrameHeader::kOpCodeText,
|
| + std::vector<char>((1 << 16) + 1, '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, _));
|
| + // TODO(ricea): Change this if kDefaultSendQuotaHighWaterMark changes.
|
| + EXPECT_CALL(*event_interface_, OnFlowControl(1 << 17));
|
| + EXPECT_CALL(*event_interface_,
|
| + OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _));
|
| + }
|
| +
|
| + CreateChannelAndConnectSuccessfully();
|
| + channel_->SendFrame(true,
|
| + WebSocketFrameHeader::kOpCodeText,
|
| + std::vector<char>((1 << 17) + 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 SendDropChannel() 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_->SendDropChannel(kWebSocketNormalClosure, "Fred");
|
| + base::MessageLoop::current()->RunUntilIdle();
|
| +}
|
| +
|
| +// RGC6455 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) {
|
| + EXPECT_CALL(*mock_, GetSubProtocol()).Times(AnyNumber());
|
| + EXPECT_CALL(*mock_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
|
| + EXPECT_CALL(*mock_,
|
| + WriteFrames(Pointee(
|
| + ElementsAre(Pointee(Field(
|
| + &WebSocketFrameChunk::header,
|
| + Pointee(Field(&WebSocketFrameHeader::masked, true)))))),
|
| + _)).WillOnce(Return(ERR_IO_PENDING));
|
| +
|
| + CreateChannelAndConnectSuccessfully();
|
| + channel_->SendFrame(
|
| + true, WebSocketFrameHeader::kOpCodeText, AsVector("NEEDS MASKING"));
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace net
|
|
|