// 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. // // Tests for WebSocketBasicStream. Note that we do not attempt to verify that // frame parsing itself functions correctly, as that is covered by the // WebSocketFrameParser tests. #include "net/websockets/websocket_basic_stream.h" #include "base/basictypes.h" #include "base/port.h" #include "net/base/capturing_net_log.h" #include "net/base/test_completion_callback.h" #include "net/socket/socket_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { // TODO(ricea): Add tests for // - Empty frames (data & control) // - Non-NULL masking key // - A frame larger than kReadBufferSize; const char kSampleFrame[] = "\x81\x06Sample"; const size_t kSampleFrameSize = arraysize(kSampleFrame) - 1; const char kPartialLargeFrame[] = "\x81\x7F\x00\x00\x00\x00\x7F\xFF\xFF\xFF" "chromiunum ad pasco per loca insanis pullum manducat frumenti"; const size_t kPartialLargeFrameSize = arraysize(kPartialLargeFrame) - 1; const size_t kLargeFrameHeaderSize = 10; const size_t kLargeFrameDeclaredPayloadSize = 0x7FFFFFFF; const char kMultipleFrames[] = "\x81\x01X\x81\x01Y\x81\x01Z"; const size_t kMultipleFramesSize = arraysize(kMultipleFrames) - 1; // This frame encodes a payload length of 7 in two bytes, which is always // invalid. const char kInvalidFrame[] = "\x81\x7E\x00\x07Invalid"; const size_t kInvalidFrameSize = arraysize(kInvalidFrame) - 1; const char kWriteFrame[] = "\x81\x85\x00\x00\x00\x00Write"; const size_t kWriteFrameSize = arraysize(kWriteFrame) - 1; const WebSocketMaskingKey kNulMaskingKey = {{'\0', '\0', '\0', '\0'}}; // Generates a ScopedVector which will have a wire format // matching kWriteFrame. ScopedVector GenerateWriteFrame() { scoped_ptr chunk(new WebSocketFrameChunk); const size_t payload_size = kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize + WebSocketFrameHeader::kMaskingKeyLength); chunk->data = new IOBufferWithSize(payload_size); memcpy(chunk->data->data(), kWriteFrame + kWriteFrameSize - payload_size, payload_size); chunk->final_chunk = true; scoped_ptr header( new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeText)); header->final = true; header->masked = true; header->payload_length = payload_size; chunk->header = header.Pass(); ScopedVector chunks; chunks.push_back(chunk.release()); return chunks.Pass(); } // A masking key generator function which generates the identity mask, // ie. "\0\0\0\0". WebSocketMaskingKey GenerateNulMaskingKey() { return kNulMaskingKey; } // Base class for WebSocketBasicStream test fixtures. class WebSocketBasicStreamTest : public ::testing::Test { protected: scoped_ptr stream_; CapturingNetLog net_log_; }; // A fixture for tests which only perform normal socket operations. class WebSocketBasicStreamSocketTest : public WebSocketBasicStreamTest { protected: WebSocketBasicStreamSocketTest() : histograms_("a"), pool_(1, 1, &histograms_, &factory_) {} virtual ~WebSocketBasicStreamSocketTest() { // stream_ has a reference to socket_data_ (via MockTCPClientSocket) and so // should be destroyed first. stream_.reset(); } scoped_ptr MakeTransportSocket(MockRead reads[], size_t reads_count, MockWrite writes[], size_t writes_count) { socket_data_.reset( new StaticSocketDataProvider(reads, reads_count, writes, writes_count)); socket_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); factory_.AddSocketDataProvider(socket_data_.get()); scoped_ptr transport_socket(new ClientSocketHandle); scoped_refptr params; transport_socket->Init("a", params, MEDIUM, CompletionCallback(), &pool_, bound_net_log_.bound()); return transport_socket.Pass(); } void SetHttpReadBuffer(const char* data, size_t size) { http_read_buffer_ = new GrowableIOBuffer; http_read_buffer_->SetCapacity(size); memcpy(http_read_buffer_->data(), data, size); http_read_buffer_->set_offset(size); } void CreateStream(MockRead reads[], size_t reads_count, MockWrite writes[], size_t writes_count) { stream_ = WebSocketBasicStream::CreateWebSocketBasicStreamForTesting( MakeTransportSocket(reads, reads_count, writes, writes_count), http_read_buffer_, sub_protocol_, extensions_, &GenerateNulMaskingKey); } template void CreateReadOnly(MockRead (&reads)[N]) { CreateStream(reads, N, NULL, 0); } template void CreateWriteOnly(MockWrite (&writes)[N]) { CreateStream(NULL, 0, writes, N); } void CreateNullStream() { CreateStream(NULL, 0, NULL, 0); } scoped_ptr socket_data_; MockClientSocketFactory factory_; ClientSocketPoolHistograms histograms_; MockTransportClientSocketPool pool_; CapturingBoundNetLog(bound_net_log_); ScopedVector frame_chunks_; TestCompletionCallback cb_; scoped_refptr http_read_buffer_; std::string sub_protocol_; std::string extensions_; }; TEST_F(WebSocketBasicStreamSocketTest, ConstructionWorks) { CreateNullStream(); } TEST_F(WebSocketBasicStreamSocketTest, SyncReadWorks) { MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize)}; CreateReadOnly(reads); int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); EXPECT_EQ(OK, result); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); EXPECT_TRUE(frame_chunks_[0]->header->final); EXPECT_TRUE(frame_chunks_[0]->final_chunk); } TEST_F(WebSocketBasicStreamSocketTest, AsyncReadWorks) { MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kSampleFrameSize)}; CreateReadOnly(reads); int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); ASSERT_EQ(ERR_IO_PENDING, result); EXPECT_EQ(OK, cb_.WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); // Don't repeat all the tests from SyncReadWorks; just enough to be sure the // frame was really read. } // ReadFrames will not return a frame whose header has not been wholly received. TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSync) { MockRead reads[] = { MockRead(SYNCHRONOUS, kSampleFrame, 1), MockRead(SYNCHRONOUS, kSampleFrame + 1, kSampleFrameSize - 1)}; CreateReadOnly(reads); int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); ASSERT_EQ(OK, result); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); } // The same behaviour applies to asynchronous reads. TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedAsync) { MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1), MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)}; CreateReadOnly(reads); int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); ASSERT_EQ(ERR_IO_PENDING, result); EXPECT_EQ(OK, cb_.WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); } // If it receives an incomplete header in a synchronous call, then has to wait // for the rest of the frame, ReadFrames will return ERR_IO_PENDING. TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSyncAsync) { MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, 1), MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)}; CreateReadOnly(reads); int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); ASSERT_EQ(ERR_IO_PENDING, result); EXPECT_EQ(OK, cb_.WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); } // An extended header should also return ERR_IO_PENDING if it is not completely // received. TEST_F(WebSocketBasicStreamSocketTest, FragmentedLargeHeader) { MockRead reads[] = { MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize - 1), MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; CreateReadOnly(reads); EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); } // A frame that does not arrive in a single read should arrive in chunks. TEST_F(WebSocketBasicStreamSocketTest, LargeFrameFirstChunk) { MockRead reads[] = { MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize)}; CreateReadOnly(reads); EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(kLargeFrameDeclaredPayloadSize, frame_chunks_[0]->header->payload_length); EXPECT_TRUE(frame_chunks_[0]->header->final); EXPECT_FALSE(frame_chunks_[0]->final_chunk); EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize, static_cast(frame_chunks_[0]->data->size())); } // If only the header arrives, we should get a zero-byte chunk. TEST_F(WebSocketBasicStreamSocketTest, HeaderOnlyChunk) { MockRead reads[] = { MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize)}; CreateReadOnly(reads); EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); ASSERT_EQ(1U, frame_chunks_.size()); EXPECT_FALSE(frame_chunks_[0]->final_chunk); EXPECT_TRUE(frame_chunks_[0]->data.get() == NULL); } // The second and subsequent chunks of a frame have no header. TEST_F(WebSocketBasicStreamSocketTest, LargeFrameTwoChunks) { static const size_t kChunkSize = 16; MockRead reads[] = { MockRead(ASYNC, kPartialLargeFrame, kChunkSize), MockRead(ASYNC, kPartialLargeFrame + kChunkSize, kChunkSize)}; CreateReadOnly(reads); TestCompletionCallback cb[2]; ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb[0].callback())); EXPECT_EQ(OK, cb[0].WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->header); frame_chunks_.clear(); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb[1].callback())); EXPECT_EQ(OK, cb[1].WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_FALSE(frame_chunks_[0]->header); } // Only the final chunk of a frame has final_chunk set. TEST_F(WebSocketBasicStreamSocketTest, OnlyFinalChunkIsFinal) { static const size_t kFirstChunkSize = 4; MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kFirstChunkSize), MockRead(ASYNC, kSampleFrame + kFirstChunkSize, kSampleFrameSize - kFirstChunkSize)}; CreateReadOnly(reads); TestCompletionCallback cb[2]; ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb[0].callback())); EXPECT_EQ(OK, cb[0].WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_FALSE(frame_chunks_[0]->final_chunk); frame_chunks_.clear(); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb[1].callback())); EXPECT_EQ(OK, cb[1].WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->final_chunk); } // Multiple frames that arrive together should be parsed correctly. TEST_F(WebSocketBasicStreamSocketTest, ThreeFramesTogether) { MockRead reads[] = { MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize)}; CreateReadOnly(reads); ASSERT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); ASSERT_EQ(3U, frame_chunks_.size()); EXPECT_TRUE(frame_chunks_[0]->final_chunk); EXPECT_TRUE(frame_chunks_[1]->final_chunk); EXPECT_TRUE(frame_chunks_[2]->final_chunk); } // ERR_CONNECTION_CLOSED must be returned on close. TEST_F(WebSocketBasicStreamSocketTest, SyncClose) { MockRead reads[] = {MockRead(SYNCHRONOUS, "", 0)}; CreateReadOnly(reads); EXPECT_EQ(ERR_CONNECTION_CLOSED, stream_->ReadFrames(&frame_chunks_, cb_.callback())); } TEST_F(WebSocketBasicStreamSocketTest, AsyncClose) { MockRead reads[] = {MockRead(ASYNC, "", 0)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } // The result should be the same if the socket returns // ERR_CONNECTION_CLOSED. This is not expected to happen on an established // connection; a Read of size 0 is the expected behaviour. The key point of this // test is to confirm that ReadFrames() behaviour is identical in both cases. TEST_F(WebSocketBasicStreamSocketTest, SyncCloseWithErr) { MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)}; CreateReadOnly(reads); EXPECT_EQ(ERR_CONNECTION_CLOSED, stream_->ReadFrames(&frame_chunks_, cb_.callback())); } TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseWithErr) { MockRead reads[] = {MockRead(ASYNC, ERR_CONNECTION_CLOSED)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } TEST_F(WebSocketBasicStreamSocketTest, SyncErrorsPassedThrough) { // ERR_INSUFFICIENT_RESOURCES here represents an arbitrary error that // WebSocketBasicStream gives no special handling to. MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES)}; CreateReadOnly(reads); EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES, stream_->ReadFrames(&frame_chunks_, cb_.callback())); } TEST_F(WebSocketBasicStreamSocketTest, AsyncErrorsPassedThrough) { MockRead reads[] = {MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES, cb_.WaitForResult()); } // If we get a frame followed by a close, we should receive them separately. TEST_F(WebSocketBasicStreamSocketTest, CloseAfterFrame) { MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize), MockRead(SYNCHRONOUS, "", 0)}; CreateReadOnly(reads); EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(1U, frame_chunks_.size()); frame_chunks_.clear(); EXPECT_EQ(ERR_CONNECTION_CLOSED, stream_->ReadFrames(&frame_chunks_, cb_.callback())); } // Synchronous close after an async frame header is handled by a different code // path. TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseAfterIncompleteHeader) { MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1U), MockRead(SYNCHRONOUS, "", 0)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } // When Stream::Read returns ERR_CONNECTION_CLOSED we get the same result via a // slightly different code path. TEST_F(WebSocketBasicStreamSocketTest, AsyncErrCloseAfterIncompleteHeader) { MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1U), MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } // If there was a frame read at the same time as the response headers (and the // handshake succeeded), then we should parse it. TEST_F(WebSocketBasicStreamSocketTest, HttpReadBufferIsUsed) { SetHttpReadBuffer(kSampleFrame, kSampleFrameSize); CreateNullStream(); EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->data); EXPECT_EQ(6, frame_chunks_[0]->data->size()); } // Check that a frame whose header partially arrived at the end of the response // headers works correctly. TEST_F(WebSocketBasicStreamSocketTest, PartialFrameHeaderInHttpResponse) { SetHttpReadBuffer(kSampleFrame, 1); MockRead reads[] = {MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(OK, cb_.WaitForResult()); ASSERT_EQ(1U, frame_chunks_.size()); ASSERT_TRUE(frame_chunks_[0]->data); EXPECT_EQ(6, frame_chunks_[0]->data->size()); ASSERT_TRUE(frame_chunks_[0]->header); EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frame_chunks_[0]->header->opcode); } // Check that an invalid frame results in an error. TEST_F(WebSocketBasicStreamSocketTest, SyncInvalidFrame) { MockRead reads[] = {MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize)}; CreateReadOnly(reads); EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, stream_->ReadFrames(&frame_chunks_, cb_.callback())); } TEST_F(WebSocketBasicStreamSocketTest, AsyncInvalidFrame) { MockRead reads[] = {MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize)}; CreateReadOnly(reads); ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, cb_.WaitForResult()); } // Check that writing a frame all at once works. TEST_F(WebSocketBasicStreamSocketTest, WriteAtOnce) { MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, kWriteFrameSize)}; CreateWriteOnly(writes); frame_chunks_ = GenerateWriteFrame(); EXPECT_EQ(OK, stream_->WriteFrames(&frame_chunks_, cb_.callback())); } // Check that completely async writing works. TEST_F(WebSocketBasicStreamSocketTest, AsyncWriteAtOnce) { MockWrite writes[] = {MockWrite(ASYNC, kWriteFrame, kWriteFrameSize)}; CreateWriteOnly(writes); frame_chunks_ = GenerateWriteFrame(); ASSERT_EQ(ERR_IO_PENDING, stream_->WriteFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(OK, cb_.WaitForResult()); } // Check that writing a frame to an extremely full kernel buffer (so that it // ends up being sent in bits) works. The WriteFrames() callback should not be // called until all parts have been written. TEST_F(WebSocketBasicStreamSocketTest, WriteInBits) { MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, 4), MockWrite(ASYNC, kWriteFrame + 4, 4), MockWrite(ASYNC, kWriteFrame + 8, kWriteFrameSize - 8)}; CreateWriteOnly(writes); frame_chunks_ = GenerateWriteFrame(); ASSERT_EQ(ERR_IO_PENDING, stream_->WriteFrames(&frame_chunks_, cb_.callback())); EXPECT_EQ(OK, cb_.WaitForResult()); } TEST_F(WebSocketBasicStreamSocketTest, GetExtensionsWorks) { extensions_ = "inflate-uuencode"; CreateNullStream(); EXPECT_EQ("inflate-uuencode", stream_->GetExtensions()); } TEST_F(WebSocketBasicStreamSocketTest, GetSubProtocolWorks) { sub_protocol_ = "cyberchat"; CreateNullStream(); EXPECT_EQ("cyberchat", stream_->GetSubProtocol()); } } // namespace } // namespace net