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..25e20efd1baf12f36aee6b034d5fb061fbcab7c6 |
--- /dev/null |
+++ b/net/websockets/websocket_channel_test.cc |
@@ -0,0 +1,1177 @@ |
+// 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. 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()) << "\"}"; |
+} |
+ |
+namespace { |
+ |
+using ::testing::AnyNumber; |
+using ::testing::Field; |
+using ::testing::InSequence; |
+using ::testing::MockFunction; |
+using ::testing::Pointee; |
+using ::testing::Return; |
+using ::testing::StrictMock; |
+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_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; |
+}; |
+ |
+// 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->opcode = source_header.opcode; |
+ 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)); |
+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() : 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))); |
+ } |
+ |
+ // 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_; |
+}; |
+ |
+// 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_; |
+}; |
+ |
+// 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); |
+} |
+ |
+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, "\x03\xf3Internal 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, "\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); |
+ 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(); |
+} |
+ |
+// 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, "\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 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(); |
+} |
+ |
+// 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) { |
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); |
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); |
+ EXPECT_CALL( |
+ *mock_stream_, |
+ 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 |