diff options
Diffstat (limited to 'net/websockets/websocket_channel_test.cc')
-rw-r--r-- | net/websockets/websocket_channel_test.cc | 893 |
1 files changed, 369 insertions, 524 deletions
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc index 25e9cdc..361f69f 100644 --- a/net/websockets/websocket_channel_test.cc +++ b/net/websockets/websocket_channel_test.cc @@ -34,40 +34,37 @@ #define CLOSE_DATA(code, string) WEBSOCKET_CLOSE_CODE_AS_STRING_##code string #define WEBSOCKET_CLOSE_CODE_AS_STRING_NORMAL_CLOSURE "\x03\xe8" #define WEBSOCKET_CLOSE_CODE_AS_STRING_GOING_AWAY "\x03\xe9" +#define WEBSOCKET_CLOSE_CODE_AS_STRING_PROTOCOL_ERROR "\x03\xea" #define WEBSOCKET_CLOSE_CODE_AS_STRING_SERVER_ERROR "\x03\xf3" namespace net { -// Printing helpers to allow GoogleMock to print frame chunks. These are -// explicitly designed to look like the static initialisation format we use in -// these tests. They have to live in the net namespace in order to be found by +// Printing helpers to allow GoogleMock to print frames. 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") << ", " + return os << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", " << header.opcode << ", " - << (header.masked ? "MASKED" : "NOT_MASKED") << ", " - << header.payload_length << "}"; + << (header.masked ? "MASKED" : "NOT_MASKED"); } -std::ostream& operator<<(std::ostream& os, const WebSocketFrameChunk& chunk) { - os << "{"; - if (chunk.header) { - os << *chunk.header; - } else { - os << "{NO_HEADER}"; +std::ostream& operator<<(std::ostream& os, const WebSocketFrame& frame) { + os << "{" << frame.header << ", "; + if (frame.data) { + return os << "\"" << base::StringPiece(frame.data->data(), + frame.header.payload_length) + << "\"}"; } - return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK") - << ", \"" - << base::StringPiece(chunk.data->data(), chunk.data->size()) - << "\"}"; + return os << "NULL}"; } std::ostream& operator<<(std::ostream& os, - const ScopedVector<WebSocketFrameChunk>& vector) { + const ScopedVector<WebSocketFrame>& vector) { os << "{"; bool first = true; - for (ScopedVector<WebSocketFrameChunk>::const_iterator it = vector.begin(); + for (ScopedVector<WebSocketFrame>::const_iterator it = vector.begin(); it != vector.end(); ++it) { if (!first) { @@ -81,7 +78,7 @@ std::ostream& operator<<(std::ostream& os, } std::ostream& operator<<(std::ostream& os, - const ScopedVector<WebSocketFrameChunk>* vector) { + const ScopedVector<WebSocketFrame>* vector) { return os << '&' << *vector; } @@ -97,7 +94,7 @@ using ::testing::_; // A selection of characters that have traditionally been mangled in some // environment or other, for testing 8-bit cleanliness. -const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL +const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL '\0', // nul '\x7F', // DEL '\x80', '\xFF', // NOT VALID UTF-8 @@ -170,12 +167,12 @@ class FakeWebSocketStream : public WebSocketStream { return ERR_IO_PENDING; } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return ERR_IO_PENDING; } - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return ERR_IO_PENDING; } @@ -198,12 +195,7 @@ class FakeWebSocketStream : public WebSocketStream { // 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 }; @@ -213,54 +205,33 @@ enum IsMasked { 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; +// This is used to initialise a WebSocketFrame but is statically initialisable. +struct InitFrame { + IsFinal final; + // Reserved fields omitted for now. Add them if you need them. + WebSocketFrameHeader::OpCode opcode; + IsMasked masked; // 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. + // nul-terminated string for ease-of-use. |header.payload_length| is + // initialised from |strlen(data)|. This means it is not 8-bit clean, but this + // is not an issue for test data. const char* const data; }; // For GoogleMock -std::ostream& operator<<(std::ostream& os, const InitFrameChunk& chunk) { - os << "{"; - if (chunk.header.final != NO_HEADER) { - os << "{" << (chunk.header.final == FINAL_FRAME ? "FINAL_FRAME" - : "NOT_FINAL_FRAME") << ", " - << chunk.header.opcode << ", " - << (chunk.header.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", " - << chunk.header.payload_length << "}"; - - } else { - os << "{NO_HEADER}"; +std::ostream& operator<<(std::ostream& os, const InitFrame& frame) { + os << "{" << (frame.final == FINAL_FRAME ? "FINAL_FRAME" : "NOT_FINAL_FRAME") + << ", " << frame.opcode << ", " + << (frame.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", "; + if (frame.data) { + return os << "\"" << frame.data << "\"}"; } - return os << ", " << (chunk.final_chunk == FINAL_CHUNK ? "FINAL_CHUNK" - : "NOT_FINAL_CHUNK") - << ", \"" << chunk.data << "\"}"; + return os << "NULL}"; } template <size_t N> -std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) { +std::ostream& operator<<(std::ostream& os, const InitFrame (&frames)[N]) { os << "{"; bool first = true; for (size_t i = 0; i < N; ++i) { @@ -269,119 +240,92 @@ std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) { } else { first = false; } - os << chunks[i]; + os << frames[i]; } return os << "}"; } -// Convert a const array of InitFrameChunks to the format used at +// Convert a const array of InitFrame structs 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); +ScopedVector<WebSocketFrame> CreateFrameVector( + const InitFrame (&source_frames)[N]) { + ScopedVector<WebSocketFrame> result_frames; + result_frames.reserve(N); for (size_t i = 0; i < N; ++i) { - scoped_ptr<WebSocketFrameChunk> result_chunk(new WebSocketFrameChunk); - size_t chunk_length = - source_chunks[i].data ? strlen(source_chunks[i].data) : 0; - if (source_chunks[i].header.final != NO_HEADER) { - const InitFrameChunk::FrameHeader& source_header = - source_chunks[i].header; - scoped_ptr<WebSocketFrameHeader> result_header( - new WebSocketFrameHeader(source_header.opcode)); - result_header->final = (source_header.final == FINAL_FRAME); - result_header->masked = (source_header.masked == MASKED); - result_header->payload_length = source_header.payload_length; - DCHECK(chunk_length <= source_header.payload_length); - result_chunk->header.swap(result_header); - } - result_chunk->final_chunk = (source_chunks[i].final_chunk == FINAL_CHUNK); - if (source_chunks[i].data) { - result_chunk->data = new IOBufferWithSize(chunk_length); - memcpy(result_chunk->data->data(), source_chunks[i].data, chunk_length); + const InitFrame& source_frame = source_frames[i]; + scoped_ptr<WebSocketFrame> result_frame( + new WebSocketFrame(source_frame.opcode)); + size_t frame_length = source_frame.data ? strlen(source_frame.data) : 0; + WebSocketFrameHeader& result_header = result_frame->header; + result_header.final = (source_frame.final == FINAL_FRAME); + result_header.masked = (source_frame.masked == MASKED); + result_header.payload_length = frame_length; + if (source_frame.data) { + result_frame->data = new IOBuffer(frame_length); + memcpy(result_frame->data->data(), source_frame.data, frame_length); } - result_chunks.push_back(result_chunk.release()); + result_frames.push_back(result_frame.release()); } - return result_chunks.Pass(); + return result_frames.Pass(); } // A GoogleMock action which can be used to respond to call to ReadFrames with -// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(&chunks)); -// |chunks| is an array of InitFrameChunks needs to be passed by pointer because -// otherwise it will be reduced to a pointer and lose the array size -// information. -ACTION_P(ReturnChunks, source_chunks) { - *arg0 = CreateFrameChunkVector(*source_chunks); +// some frames. Use like ReadFrames(_, _).WillOnce(ReturnFrames(&frames)); +// |frames| is an array of InitFrame. |frames| needs to be passed by pointer +// because otherwise it will be treated as a pointer and the array size +// information will be lost. +ACTION_P(ReturnFrames, source_frames) { + *arg0 = CreateFrameVector(*source_frames); return OK; } // The implementation of a GoogleMock matcher which can be used to compare a -// ScopedVector<WebSocketFrameChunk>* against an expectation defined as an array -// of InitFrameChunks. Although it is possible to compose built-in GoogleMock -// matchers to check the contents of a WebSocketFrameChunk, the results are so +// ScopedVector<WebSocketFrame>* against an expectation defined as an array of +// InitFrame objects. Although it is possible to compose built-in GoogleMock +// matchers to check the contents of a WebSocketFrame, the results are so // unreadable that it is better to use this matcher. template <size_t N> -class EqualsChunksMatcher - : public ::testing::MatcherInterface<ScopedVector<WebSocketFrameChunk>*> { +class EqualsFramesMatcher + : public ::testing::MatcherInterface<ScopedVector<WebSocketFrame>*> { public: - EqualsChunksMatcher(const InitFrameChunk (*expect_chunks)[N]) - : expect_chunks_(expect_chunks) {} + EqualsFramesMatcher(const InitFrame (*expect_frames)[N]) + : expect_frames_(expect_frames) {} - virtual bool MatchAndExplain(ScopedVector<WebSocketFrameChunk>* actual_chunks, + virtual bool MatchAndExplain(ScopedVector<WebSocketFrame>* actual_frames, ::testing::MatchResultListener* listener) const { - if (actual_chunks->size() != N) { - *listener << "the vector size is " << actual_chunks->size(); + if (actual_frames->size() != N) { + *listener << "the vector size is " << actual_frames->size(); return false; } for (size_t i = 0; i < N; ++i) { - const WebSocketFrameChunk& actual_chunk = *(*actual_chunks)[i]; - const InitFrameChunk& expected_chunk = (*expect_chunks_)[i]; - // Testing that the absence or presence of a header is the same for both. - if ((!actual_chunk.header) != - (expected_chunk.header.final == NO_HEADER)) { - *listener << "the header is " - << (actual_chunk.header ? "present" : "absent"); + const WebSocketFrame& actual_frame = *(*actual_frames)[i]; + const InitFrame& expected_frame = (*expect_frames_)[i]; + if (actual_frame.header.final != (expected_frame.final == FINAL_FRAME)) { + *listener << "the frame is marked as " + << (actual_frame.header.final ? "" : "not ") << "final"; return false; } - if (actual_chunk.header) { - if (actual_chunk.header->final != - (expected_chunk.header.final == FINAL_FRAME)) { - *listener << "the frame is marked as " - << (actual_chunk.header->final ? "" : "not ") << "final"; - return false; - } - if (actual_chunk.header->opcode != expected_chunk.header.opcode) { - *listener << "the opcode is " << actual_chunk.header->opcode; - return false; - } - if (actual_chunk.header->masked != - (expected_chunk.header.masked == MASKED)) { - *listener << "the frame is " - << (actual_chunk.header->masked ? "masked" : "not masked"); - return false; - } - if (actual_chunk.header->payload_length != - expected_chunk.header.payload_length) { - *listener << "the payload length is " - << actual_chunk.header->payload_length; - return false; - } + if (actual_frame.header.opcode != expected_frame.opcode) { + *listener << "the opcode is " << actual_frame.header.opcode; + return false; } - if (actual_chunk.final_chunk != - (expected_chunk.final_chunk == FINAL_CHUNK)) { - *listener << "the chunk is marked as " - << (actual_chunk.final_chunk ? "" : "not ") << "final"; + if (actual_frame.header.masked != (expected_frame.masked == MASKED)) { + *listener << "the frame is " + << (actual_frame.header.masked ? "masked" : "not masked"); return false; } - if (actual_chunk.data->size() != - base::checked_numeric_cast<int>(strlen(expected_chunk.data))) { - *listener << "the data size is " << actual_chunk.data->size(); + const size_t expected_length = + expected_frame.data ? strlen(expected_frame.data) : 0; + if (actual_frame.header.payload_length != expected_length) { + *listener << "the payload length is " + << actual_frame.header.payload_length; return false; } - if (memcmp(actual_chunk.data->data(), - expected_chunk.data, - actual_chunk.data->size()) != 0) { + if (expected_length != 0 && + memcmp(actual_frame.data->data(), + expected_frame.data, + actual_frame.header.payload_length) != 0) { *listener << "the data content differs"; return false; } @@ -390,23 +334,23 @@ class EqualsChunksMatcher } virtual void DescribeTo(std::ostream* os) const { - *os << "matches " << *expect_chunks_; + *os << "matches " << *expect_frames_; } virtual void DescribeNegationTo(std::ostream* os) const { - *os << "does not match " << *expect_chunks_; + *os << "does not match " << *expect_frames_; } private: - const InitFrameChunk (*expect_chunks_)[N]; + const InitFrame (*expect_frames_)[N]; }; -// The definition of EqualsChunks GoogleMock matcher. Unlike the ReturnChunks +// The definition of EqualsFrames GoogleMock matcher. Unlike the ReturnFrames // action, this can take the array by reference. template <size_t N> -::testing::Matcher<ScopedVector<WebSocketFrameChunk>*> EqualsChunks( - const InitFrameChunk (&chunks)[N]) { - return ::testing::MakeMatcher(new EqualsChunksMatcher<N>(&chunks)); +::testing::Matcher<ScopedVector<WebSocketFrame>*> EqualsFrames( + const InitFrame (&frames)[N]) { + return ::testing::MakeMatcher(new EqualsFramesMatcher<N>(&frames)); } // A FakeWebSocketStream whose ReadFrames() function returns data. @@ -427,39 +371,38 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream { 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 + // Prepares a fake response. 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(). + // the callback in the asynchronous case. |frames| will be converted to a + // ScopedVector<WebSocketFrame> 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))); + const InitFrame (&frames)[N]) { + responses_.push_back(new Response(async, error, CreateFrameVector(frames))); } // An alternate version of PrepareReadFrames for when we need to construct // the frames manually. void PrepareRawReadFrames(IsSync async, int error, - ScopedVector<WebSocketFrameChunk> chunks) { - responses_.push_back(new Response(async, error, chunks.Pass())); + ScopedVector<WebSocketFrame> frames) { + responses_.push_back(new Response(async, error, frames.Pass())); } // Prepares a fake error response (ie. there is no data). void PrepareReadFramesError(IsSync async, int error) { responses_.push_back( - new Response(async, error, ScopedVector<WebSocketFrameChunk>())); + new Response(async, error, ScopedVector<WebSocketFrame>())); } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { CHECK(!read_frames_pending_); if (index_ >= responses_.size()) @@ -470,34 +413,34 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream { FROM_HERE, base::Bind(&ReadableFakeWebSocketStream::DoCallback, base::Unretained(this), - frame_chunks, + frames, callback)); return ERR_IO_PENDING; } else { - frame_chunks->swap(responses_[index_]->chunks); + frames->swap(responses_[index_]->frames); return responses_[index_++]->error; } } private: - void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks, + void DoCallback(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) { read_frames_pending_ = false; - frame_chunks->swap(responses_[index_]->chunks); + frames->swap(responses_[index_]->frames); callback.Run(responses_[index_++]->error); return; } struct Response { - Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks) - : async(async), error(error), chunks(chunks.Pass()) {} + Response(IsSync async, int error, ScopedVector<WebSocketFrame> frames) + : async(async), error(error), frames(frames.Pass()) {} IsSync async; int error; - ScopedVector<WebSocketFrameChunk> chunks; + ScopedVector<WebSocketFrame> frames; private: - // Bad things will happen if we attempt to copy or assign "chunks". + // Bad things will happen if we attempt to copy or assign |frames|. DISALLOW_COPY_AND_ASSIGN(Response); }; ScopedVector<Response> responses_; @@ -516,7 +459,7 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream { // synchronously. class WriteableFakeWebSocketStream : public FakeWebSocketStream { public: - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return OK; } @@ -525,7 +468,7 @@ class WriteableFakeWebSocketStream : public FakeWebSocketStream { // A FakeWebSocketStream where writes always fail. class UnWriteableFakeWebSocketStream : public FakeWebSocketStream { public: - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return ERR_CONNECTION_RESET; } @@ -539,23 +482,22 @@ class UnWriteableFakeWebSocketStream : public FakeWebSocketStream { // otherwise the ReadFrames() callback will never be called. class EchoeyFakeWebSocketStream : public FakeWebSocketStream { public: - EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {} + EchoeyFakeWebSocketStream() : read_frames_(NULL), done_(false) {} - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, 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(); + stored_frames_.insert(stored_frames_.end(), frames->begin(), frames->end()); + frames->weak_clear(); PostCallback(); return OK; } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { read_callback_ = callback; - read_frame_chunks_ = frame_chunks; + read_frames_ = frames; if (done_) PostCallback(); return ERR_IO_PENDING; @@ -572,36 +514,34 @@ class EchoeyFakeWebSocketStream : public FakeWebSocketStream { 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; + } else if (!stored_frames_.empty()) { + done_ = MoveFrames(read_frames_); + read_frames_ = NULL; read_callback_.Run(OK); } } - // Copy the chunks stored in stored_frame_chunks_ to |out|, while clearing the + // Copy the frames stored in stored_frames_ to |out|, while clearing the // "masked" header bit. Returns true if a Close Frame was seen, false // otherwise. - bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* out) { + bool MoveFrames(ScopedVector<WebSocketFrame>* out) { bool seen_close = false; - *out = stored_frame_chunks_.Pass(); - for (ScopedVector<WebSocketFrameChunk>::iterator it = out->begin(); + *out = stored_frames_.Pass(); + for (ScopedVector<WebSocketFrame>::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; - } + WebSocketFrameHeader& header = (*it)->header; + header.masked = false; + if (header.opcode == WebSocketFrameHeader::kOpCodeClose) + seen_close = true; } return seen_close; } - ScopedVector<WebSocketFrameChunk> stored_frame_chunks_; + ScopedVector<WebSocketFrame> stored_frames_; CompletionCallback read_callback_; // Owned by the caller of ReadFrames(). - ScopedVector<WebSocketFrameChunk>* read_frame_chunks_; + ScopedVector<WebSocketFrame>* read_frames_; // True if we should close the connection. bool done_; }; @@ -612,7 +552,7 @@ class EchoeyFakeWebSocketStream : public FakeWebSocketStream { // run the message loop. class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { public: - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(callback, ERR_CONNECTION_RESET)); @@ -621,7 +561,7 @@ class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { return ERR_IO_PENDING; } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { read_callback_ = callback; return ERR_IO_PENDING; @@ -636,10 +576,10 @@ class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { class MockWebSocketStream : public WebSocketStream { public: MOCK_METHOD2(ReadFrames, - int(ScopedVector<WebSocketFrameChunk>* frame_chunks, + int(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback)); MOCK_METHOD2(WriteFrames, - int(ScopedVector<WebSocketFrameChunk>* frame_chunks, + int(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback)); MOCK_METHOD0(Close, void()); MOCK_CONST_METHOD0(GetSubProtocol, std::string()); @@ -846,8 +786,8 @@ TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) { // AddressSanitizer. TEST_F(WebSocketChannelDeletingTest, DeletingFromOnAddChannelResponseWorks) { CreateChannelAndConnect(); - connect_data_.factory.connect_delegate - ->OnFailure(kWebSocketErrorNoStatusReceived); + connect_data_.factory.connect_delegate->OnFailure( + kWebSocketErrorNoStatusReceived); EXPECT_EQ(NULL, channel_.get()); } @@ -869,8 +809,8 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) { CreateChannelAndConnect(); - connect_data_.factory.connect_delegate - ->OnFailure(kWebSocketErrorNoStatusReceived); + connect_data_.factory.connect_delegate->OnFailure( + kWebSocketErrorNoStatusReceived); } TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) { @@ -889,10 +829,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) { 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); + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -912,10 +851,10 @@ TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) { TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23}, - FINAL_CHUNK, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames); stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, ERR_CONNECTION_CLOSED); set_stream(stream.Pass()); @@ -954,13 +893,12 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) { TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, - FINAL_CHUNK, "HELLO"}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "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); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -985,14 +923,12 @@ TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) { 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); + static const InitFrame frames1[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}}; + static const InitFrame frames2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "WORLD"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames2); set_stream(stream.Pass()); { InSequence s; @@ -1012,31 +948,29 @@ TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) { base::MessageLoop::current()->RunUntilIdle(); } -// Data frames that arrive in fragments are turned into individual frames -TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) { +// Data frames are delivered the same regardless of how many reads they arrive +// as. +TEST_F(WebSocketChannelEventInterfaceTest, FragmentedMessage) { 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); + // Here we have one message which arrived in five frames split across three + // reads. It may have been reframed on arrival, but this class doesn't care + // about that. + static const InitFrame frames1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "THREE"}, + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, " "}}; + static const InitFrame frames2[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "SMALL"}}; + static const InitFrame frames3[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, " "}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "FRAMES"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames2); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames3); set_stream(stream.Pass()); { InSequence s; @@ -1068,112 +1002,16 @@ TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) { base::MessageLoop::current()->RunUntilIdle(); } -// In the case when a single-frame message because fragmented, it must be -// correctly transformed to multiple frames. -TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - // A single-frame Text message arrives in three chunks. This should be - // delivered as three frames. - static const InitFrameChunk chunks1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12}, - NOT_FINAL_CHUNK, "TIME"}}; - static const InitFrameChunk chunks2[] = { - {{NO_HEADER}, NOT_FINAL_CHUNK, " FOR "}}; - static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); - set_stream(stream.Pass()); - { - InSequence s; - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL( - *event_interface_, - OnDataFrame( - false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME"))); - EXPECT_CALL(*event_interface_, - OnDataFrame(false, - WebSocketFrameHeader::kOpCodeContinuation, - AsVector(" FOR "))); - EXPECT_CALL( - *event_interface_, - OnDataFrame( - true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA"))); - } - - CreateChannelAndConnectSuccessfully(); - base::MessageLoop::current()->RunUntilIdle(); -} - -// If a control message is fragmented, it must be re-assembled before being -// delivered. A control message can only be fragmented at the network level; it -// is not permitted to be split into multiple frames. -TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - NOT_FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "")}}; - static const InitFrameChunk chunks2[] = { - {{NO_HEADER}, NOT_FINAL_CHUNK, "Clo"}}; - static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "se"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); - stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, - ERR_CONNECTION_CLOSED); - set_stream(stream.Pass()); - { - InSequence s; - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL(*event_interface_, OnClosingHandshake()); - EXPECT_CALL(*event_interface_, - OnDropChannel(kWebSocketNormalClosure, "Close")); - } - - CreateChannelAndConnectSuccessfully(); - base::MessageLoop::current()->RunUntilIdle(); -} - -// The payload of a control frame is not permitted to exceed 125 bytes. RFC6455 -// 5.5 "All control frames MUST have a payload length of 125 bytes or less" -TEST_F(WebSocketChannelEventInterfaceTest, OversizeControlMessageIsRejected) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - static const size_t kPayloadLen = 126; - char payload[kPayloadLen + 1]; // allow space for trailing NUL - std::fill(payload, payload + kPayloadLen, 'A'); - payload[kPayloadLen] = '\0'; - // Not static because "payload" is constructed at runtime. - const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, - kPayloadLen}, - FINAL_CHUNK, payload}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); - set_stream(stream.Pass()); - - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL(*event_interface_, - OnDropChannel(kWebSocketErrorProtocolError, _)); - - CreateChannelAndConnectSuccessfully(); -} - // A control frame is not permitted to be split into multiple frames. RFC6455 // 5.5 "All control frames ... MUST NOT be fragmented." TEST_F(WebSocketChannelEventInterfaceTest, MultiFrameControlMessageIsRejected) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 2}, - FINAL_CHUNK, "Pi"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 2}, - FINAL_CHUNK, "ng"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + static const InitFrame frames[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, "Pi"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "ng"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1225,39 +1063,14 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) { base::MessageLoop::current()->RunUntilIdle(); } -// Connection closed in the middle of a Close message (server bug, etc.) -TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - NOT_FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "")}}; - - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); - stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, - ERR_CONNECTION_CLOSED); - set_stream(stream.Pass()); - { - InSequence s; - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL(*event_interface_, - OnDropChannel(kWebSocketErrorAbnormalClosure, _)); - } - - CreateChannelAndConnectSuccessfully(); - base::MessageLoop::current()->RunUntilIdle(); -} - // RFC6455 5.1 "A client MUST close a connection if it detects a masked frame." TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK, - "HELLO"}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1276,10 +1089,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) { TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, 4, NOT_MASKED, 5}, FINAL_CHUNK, "HELLO"}}; + static const InitFrame frames[] = {{FINAL_FRAME, 4, NOT_MASKED, "HELLO"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1300,18 +1112,17 @@ TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) { 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); + static const InitFrame frames1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, + NOT_MASKED, "SPLIT "}}; + static const InitFrame frames2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, ""}}; + static const InitFrame frames3[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "MESSAGE"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames2); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames3); set_stream(stream.Pass()); { InSequence s; @@ -1331,17 +1142,16 @@ TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) { base::MessageLoop::current()->RunUntilIdle(); } -// If a chunk has an invalid header, then the connection is closed and -// subsequent chunks must not trigger events. -TEST_F(WebSocketChannelEventInterfaceTest, HeaderlessChunkAfterInvalidChunk) { +// If a frame has an invalid header, then the connection is closed and +// subsequent frames must not trigger events. +TEST_F(WebSocketChannelEventInterfaceTest, FrameAfterInvalidFrame) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 11}, - NOT_FINAL_CHUNK, "HELLO"}, - {{NO_HEADER}, FINAL_CHUNK, " WORLD"}}; + static const InitFrame frames[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, " WORLD"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1501,10 +1311,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, OnDropChannelCalledOnce) { TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0}, - FINAL_CHUNK, ""}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, ""}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames); stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, ERR_CONNECTION_CLOSED); set_stream(stream.Pass()); @@ -1517,16 +1326,50 @@ TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) { CreateChannelAndConnectSuccessfully(); } +// If ReadFrames() returns ERR_WS_PROTOCOL_ERROR, then +// kWebSocketErrorProtocolError must be sent to the renderer. +TEST_F(WebSocketChannelEventInterfaceTest, SyncProtocolErrorGivesStatus1002) { + scoped_ptr<ReadableFakeWebSocketStream> stream( + new ReadableFakeWebSocketStream); + stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, + ERR_WS_PROTOCOL_ERROR); + set_stream(stream.Pass()); + EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); + EXPECT_CALL(*event_interface_, OnFlowControl(_)); + + EXPECT_CALL(*event_interface_, + OnDropChannel(kWebSocketErrorProtocolError, _)); + + CreateChannelAndConnectSuccessfully(); +} + +// Async version of above test. +TEST_F(WebSocketChannelEventInterfaceTest, AsyncProtocolErrorGivesStatus1002) { + scoped_ptr<ReadableFakeWebSocketStream> stream( + new ReadableFakeWebSocketStream); + stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, + ERR_WS_PROTOCOL_ERROR); + set_stream(stream.Pass()); + EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); + EXPECT_CALL(*event_interface_, OnFlowControl(_)); + + EXPECT_CALL(*event_interface_, + OnDropChannel(kWebSocketErrorProtocolError, _)); + + CreateChannelAndConnectSuccessfully(); + 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) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 13}, - FINAL_CHUNK, "NEEDS MASKING"}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, + MASKED, "NEEDS MASKING"}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1537,12 +1380,12 @@ TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) { // RFC6455 5.5.1 "The application MUST NOT send any more data frames after // sending a Close frame." TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 9}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Success")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Success")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1554,17 +1397,17 @@ TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) { // RFC6455 5.5.1 "If an endpoint receives a Close frame and did not previously // send a Close frame, the endpoint MUST send a Close frame in response." TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1573,17 +1416,17 @@ TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) { // The converse of the above case; after sending a Close frame, we should not // send another one. TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame frames_init[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; // We store the parameters that were passed to ReadFrames() so that we can // call them explicitly later. CompletionCallback read_callback; - ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL; + ScopedVector<WebSocketFrame>* frames = NULL; // Use a checkpoint to make the ordering of events clearer. MockFunction<void(int)> checkpoint; @@ -1591,11 +1434,11 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { InSequence s; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(DoAll(SaveArg<0>(&frame_chunks), + .WillOnce(DoAll(SaveArg<0>(&frames), SaveArg<1>(&read_callback), Return(ERR_IO_PENDING))); EXPECT_CALL(checkpoint, Call(1)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); EXPECT_CALL(checkpoint, Call(2)); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) @@ -1610,7 +1453,7 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { channel_->StartClosingHandshake(kWebSocketNormalClosure, "Close"); checkpoint.Call(2); - *frame_chunks = CreateFrameChunkVector(chunks); + *frames = CreateFrameVector(frames_init); read_callback.Run(OK); checkpoint.Call(3); } @@ -1621,17 +1464,15 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { // CloseWithNoPayloadGivesStatus1005, above, for confirmation that code 1005 is // correctly generated internally. TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0}, - FINAL_CHUNK, ""}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 0}, - FINAL_CHUNK, ""}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, ""}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, ""}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1643,58 +1484,57 @@ TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) { // "Application data" as found in the message body of the Ping frame being // replied to." TEST_F(WebSocketChannelStreamTest, PingRepliedWithPong) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16}, - FINAL_CHUNK, "Application data"}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16}, - FINAL_CHUNK, "Application data"}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, + NOT_MASKED, "Application data"}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, + MASKED, "Application data"}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); } TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16}, - FINAL_CHUNK, "Application data"}}; - static const InitFrameChunk expected1[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6}, - FINAL_CHUNK, "Hello "}}; - static const InitFrameChunk expected2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16}, - FINAL_CHUNK, "Application data"}}; - static const InitFrameChunk expected3[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, MASKED, 5}, - FINAL_CHUNK, "World"}}; - ScopedVector<WebSocketFrameChunk>* read_chunks; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, + NOT_MASKED, "Application data"}}; + static const InitFrame expected1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "Hello "}}; + static const InitFrame expected2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, + MASKED, "Application data"}}; + static const InitFrame expected3[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + MASKED, "World"}}; + ScopedVector<WebSocketFrame>* read_frames; CompletionCallback read_callback; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(DoAll(SaveArg<0>(&read_chunks), + .WillOnce(DoAll(SaveArg<0>(&read_frames), SaveArg<1>(&read_callback), Return(ERR_IO_PENDING))) .WillRepeatedly(Return(ERR_IO_PENDING)); { InSequence s; - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _)) .WillOnce(Return(OK)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _)) .WillOnce(Return(OK)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected3), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected3), _)) .WillOnce(Return(OK)); } CreateChannelAndConnectSuccessfully(); channel_->SendFrame( false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello ")); - *read_chunks = CreateFrameChunkVector(chunks); + *read_frames = CreateFrameVector(frames); read_callback.Run(OK); channel_->SendFrame( true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("World")); @@ -1703,12 +1543,10 @@ TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) { // WriteFrames() may not be called until the previous write has completed. // WebSocketChannel must buffer writes that happen in the meantime. TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { - static const InitFrameChunk expected1[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6}, - FINAL_CHUNK, "Hello "}}; - static const InitFrameChunk expected2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK, - "World"}}; + static const InitFrame expected1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "Hello "}}; + static const InitFrame expected2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "World"}}; CompletionCallback write_callback; MockFunction<void(int)> checkpoint; @@ -1717,10 +1555,10 @@ TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { { InSequence s; EXPECT_CALL(checkpoint, Call(1)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _)) .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING))); EXPECT_CALL(checkpoint, Call(2)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _)) .WillOnce(Return(ERR_IO_PENDING)); EXPECT_CALL(checkpoint, Call(3)); } @@ -1741,27 +1579,22 @@ TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { // important to get good throughput in the "many small messages" case. TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) { static const char input_letters[] = "Hello"; - static const InitFrameChunk expected1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "H"}}; - static const InitFrameChunk expected2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "e"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "l"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "l"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "o"}}; + static const InitFrame expected1[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "H"}}; + static const InitFrame expected2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "e"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "l"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "l"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "o"}}; CompletionCallback write_callback; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); { InSequence s; - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _)) .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING))); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _)) .WillOnce(Return(ERR_IO_PENDING)); } @@ -1781,12 +1614,12 @@ TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) { // even be using a different extension which uses that code to mean something // else. TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 16}, - FINAL_CHUNK, CLOSE_DATA(GOING_AWAY, "Internal Error")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(GOING_AWAY, "Internal Error")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); EXPECT_CALL(*mock_stream_, Close()); @@ -1800,45 +1633,41 @@ TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) { // protocol also has Binary frames and those need to be 8-bit clean. For the // sake of completeness, this test verifies that they are. TEST_F(WebSocketChannelStreamTest, WrittenBinaryFramesAre8BitClean) { - ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL; + ScopedVector<WebSocketFrame>* frames = NULL; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); EXPECT_CALL(*mock_stream_, WriteFrames(_, _)) - .WillOnce(DoAll(SaveArg<0>(&frame_chunks), Return(ERR_IO_PENDING))); + .WillOnce(DoAll(SaveArg<0>(&frames), Return(ERR_IO_PENDING))); CreateChannelAndConnectSuccessfully(); channel_->SendFrame( true, WebSocketFrameHeader::kOpCodeBinary, std::vector<char>(kBinaryBlob, kBinaryBlob + kBinaryBlobSize)); - ASSERT_TRUE(frame_chunks != NULL); - ASSERT_EQ(1U, frame_chunks->size()); - const WebSocketFrameChunk* out_chunk = (*frame_chunks)[0]; - ASSERT_TRUE(out_chunk->header); - EXPECT_EQ(kBinaryBlobSize, out_chunk->header->payload_length); - ASSERT_TRUE(out_chunk->data); - EXPECT_EQ(kBinaryBlobSize, static_cast<size_t>(out_chunk->data->size())); - EXPECT_EQ(0, memcmp(kBinaryBlob, out_chunk->data->data(), kBinaryBlobSize)); + ASSERT_TRUE(frames != NULL); + ASSERT_EQ(1U, frames->size()); + const WebSocketFrame* out_frame = (*frames)[0]; + EXPECT_EQ(kBinaryBlobSize, out_frame->header.payload_length); + ASSERT_TRUE(out_frame->data); + EXPECT_EQ(0, memcmp(kBinaryBlob, out_frame->data->data(), kBinaryBlobSize)); } // Test the read path for 8-bit cleanliness as well. TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) { - scoped_ptr<WebSocketFrameHeader> frame_header( - new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeBinary)); - frame_header->final = true; - frame_header->payload_length = kBinaryBlobSize; - scoped_ptr<WebSocketFrameChunk> frame_chunk(new WebSocketFrameChunk); - frame_chunk->header = frame_header.Pass(); - frame_chunk->final_chunk = true; - frame_chunk->data = new IOBufferWithSize(kBinaryBlobSize); - memcpy(frame_chunk->data->data(), kBinaryBlob, kBinaryBlobSize); - ScopedVector<WebSocketFrameChunk> chunks; - chunks.push_back(frame_chunk.release()); + scoped_ptr<WebSocketFrame> frame( + new WebSocketFrame(WebSocketFrameHeader::kOpCodeBinary)); + WebSocketFrameHeader& frame_header = frame->header; + frame_header.final = true; + frame_header.payload_length = kBinaryBlobSize; + frame->data = new IOBuffer(kBinaryBlobSize); + memcpy(frame->data->data(), kBinaryBlob, kBinaryBlobSize); + ScopedVector<WebSocketFrame> frames; + frames.push_back(frame.release()); scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); stream->PrepareRawReadFrames( - ReadableFakeWebSocketStream::SYNC, OK, chunks.Pass()); + ReadableFakeWebSocketStream::SYNC, OK, frames.Pass()); set_stream(stream.Pass()); EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); EXPECT_CALL(*event_interface_, OnFlowControl(_)); @@ -1856,17 +1685,17 @@ TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) { // but the current implementation fails the connection. Since a Close has // already been sent, this just means closing the connection. TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 4}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 9}, - FINAL_CHUNK, "Ping body"}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 4}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, + NOT_MASKED, "Ping body"}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); { // We only need to verify the relative order of WriteFrames() and @@ -1874,7 +1703,7 @@ TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { // frame before calling ReadFrames() again, but that is an implementation // detail and better not to consider required behaviour. InSequence s; - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); EXPECT_CALL(*mock_stream_, Close()).Times(1); } @@ -1882,5 +1711,21 @@ TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { CreateChannelAndConnectSuccessfully(); } +// A protocol error from the remote server should result in a close frame with +// status 1002, followed by the connection closing. +TEST_F(WebSocketChannelStreamTest, ProtocolError) { + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(PROTOCOL_ERROR, "WebSocket Protocol Error")}}; + EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); + EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) + .WillOnce(Return(ERR_WS_PROTOCOL_ERROR)); + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) + .WillOnce(Return(OK)); + EXPECT_CALL(*mock_stream_, Close()); + + CreateChannelAndConnectSuccessfully(); +} + } // namespace } // namespace net |