Chromium Code Reviews| 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..fe82874b96fd0cdc647f1dd2c567d3ab91d3d2ea |
| --- /dev/null |
| +++ b/net/websockets/websocket_channel_test.cc |
| @@ -0,0 +1,1124 @@ |
| +// 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; |
|
tyoshino (SeeGerritForStatus)
2013/07/02 03:26:42
not used
Adam Rice
2013/07/02 04:51:09
Removed.
|
| +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(OnDataFrame, |
| + 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 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 OnDropChannel(unsigned short 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() : WebSocketStream(), protocol_(), extensions_() {} |
| + |
| + // Constructs 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 (&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); |
| + 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->opcode = source_header.opcode; |
| + result_header->masked = source_header.masked == MASKED; |
| + result_header->payload_length = source_header.payload_length; |
| + result_chunk->header.swap(result_header); |
| + } |
| + result_chunk->final_chunk = source_chunks[i].final_chunk == FINAL_CHUNK; |
| + result_chunk->data = new IOBufferWithSize(strlen(source_chunks[i].data)); |
| + memcpy(result_chunk->data->data(), |
| + source_chunks[i].data, |
| + strlen(source_chunks[i].data)); |
| + result_chunks.push_back(result_chunk.release()); |
| + } |
| + return result_chunks.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, source_chunks) { |
| + *arg0 = CreateFrameChunkVector(source_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) {} |
| + |
| + // 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 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))); |
| + } |
| + |
| + // 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 { |
| + 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; |
| +}; |
| + |
| +// 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 WebSocketChannelTests. |
| +class WebSocketChannelTest : public ::testing::Test { |
| + protected: |
| + WebSocketChannelTest() |
| + : data_(), channel_(), stream_(new FakeWebSocketStream) {} |
| + |
| + // Creates a new WebSocketChannel and connects 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 calls the on_success callback as |
| + // well. This method is virtual so that WebSocketChannelStreamTest can also |
| + // set the stream. |
| + virtual void CreateChannelAndConnectSuccessfully() { |
| + CreateChannelAndConnect(); |
| + 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> EventInterface() { |
| + return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface); |
| + } |
| + |
| + // Writing scoped_ptr<Base> base = derived_scoped_ptr.Pass() 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_, |
| + 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, "\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_, |
| + 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. 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_, |
| + 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); |
| + // 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_, |
| + 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, "\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_, |
| + OnDataFrame( |
| + false, WebSocketFrameHeader::kOpCodeText, AsVector("SPLIT "))); |
| + EXPECT_CALL(*event_interface_, |
| + OnDataFrame(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 |