1029 lines
41 KiB
C++
1029 lines
41 KiB
C++
// Copyright 2013 The Chromium Authors
|
|
// 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 <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h> // for memcpy() and memset().
|
|
|
|
#include <utility>
|
|
|
|
#include "base/big_endian.h"
|
|
#include "base/containers/span.h"
|
|
#include "base/time/time.h"
|
|
#include "net/base/io_buffer.h"
|
|
#include "net/base/privacy_mode.h"
|
|
#include "net/base/test_completion_callback.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/log/test_net_log.h"
|
|
#include "net/socket/connect_job.h"
|
|
#include "net/socket/socket_tag.h"
|
|
#include "net/socket/socket_test_util.h"
|
|
#include "net/socket/ssl_client_socket.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
|
#include "url/scheme_host_port.h"
|
|
#include "url/url_constants.h"
|
|
|
|
using net::test::IsError;
|
|
using net::test::IsOk;
|
|
|
|
namespace net {
|
|
namespace {
|
|
|
|
#define WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(name, value) \
|
|
const char k##name[] = value; \
|
|
const size_t k##name##Size = std::size(k##name) - 1
|
|
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(SampleFrame, "\x81\x06Sample");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(
|
|
PartialLargeFrame,
|
|
"\x81\x7F\x00\x00\x00\x00\x7F\xFF\xFF\xFF"
|
|
"chromiunum ad pasco per loca insanis pullum manducat frumenti");
|
|
const size_t kLargeFrameHeaderSize = 10;
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(MultipleFrames,
|
|
"\x81\x01X\x81\x01Y\x81\x01Z");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(EmptyFirstFrame, "\x01\x00");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(EmptyMiddleFrame, "\x00\x00");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(EmptyFinalTextFrame, "\x81\x00");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(EmptyFinalContinuationFrame,
|
|
"\x80\x00");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(ValidPong, "\x8A\x00");
|
|
// This frame encodes a payload length of 7 in two bytes, which is always
|
|
// invalid.
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(InvalidFrame,
|
|
"\x81\x7E\x00\x07Invalid");
|
|
// Control frames must have the FIN bit set. This one does not.
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(PingFrameWithoutFin, "\x09\x00");
|
|
// Control frames must have a payload of 125 bytes or less. This one has
|
|
// a payload of 126 bytes.
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(
|
|
126BytePong,
|
|
"\x8a\x7e\x00\x7eZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
|
"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(CloseFrame,
|
|
"\x88\x09\x03\xe8occludo");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(WriteFrame,
|
|
"\x81\x85\x00\x00\x00\x00Write");
|
|
WEBSOCKET_BASIC_STREAM_TEST_DEFINE_CONSTANT(MaskedEmptyPong,
|
|
"\x8A\x80\x00\x00\x00\x00");
|
|
const WebSocketMaskingKey kNulMaskingKey = {{'\0', '\0', '\0', '\0'}};
|
|
const WebSocketMaskingKey kNonNulMaskingKey = {
|
|
{'\x0d', '\x1b', '\x06', '\x17'}};
|
|
|
|
// A masking key generator function which generates the identity mask,
|
|
// ie. "\0\0\0\0".
|
|
WebSocketMaskingKey GenerateNulMaskingKey() { return kNulMaskingKey; }
|
|
|
|
// A masking key generation function which generates a fixed masking key with no
|
|
// nul characters.
|
|
WebSocketMaskingKey GenerateNonNulMaskingKey() { return kNonNulMaskingKey; }
|
|
|
|
// A subclass of StaticSocketDataProvider modified to require that all data
|
|
// expected to be read or written actually is.
|
|
class StrictStaticSocketDataProvider : public StaticSocketDataProvider {
|
|
public:
|
|
StrictStaticSocketDataProvider(base::span<const MockRead> reads,
|
|
base::span<const MockWrite> writes,
|
|
bool strict_mode)
|
|
: StaticSocketDataProvider(reads, writes), strict_mode_(strict_mode) {}
|
|
|
|
~StrictStaticSocketDataProvider() override {
|
|
if (strict_mode_) {
|
|
EXPECT_EQ(read_count(), read_index());
|
|
EXPECT_EQ(write_count(), write_index());
|
|
}
|
|
}
|
|
|
|
private:
|
|
const bool strict_mode_;
|
|
};
|
|
|
|
// A fixture for tests which only perform normal socket operations.
|
|
class WebSocketBasicStreamSocketTest : public TestWithTaskEnvironment {
|
|
protected:
|
|
WebSocketBasicStreamSocketTest()
|
|
: common_connect_job_params_(
|
|
&factory_,
|
|
nullptr /* host_resolver */,
|
|
nullptr /* http_auth_cache */,
|
|
nullptr /* http_auth_handler_factory */,
|
|
nullptr /* spdy_session_pool */,
|
|
nullptr /* quic_supported_versions */,
|
|
nullptr /* quic_stream_factory */,
|
|
nullptr /* proxy_delegate */,
|
|
nullptr /* http_user_agent_settings */,
|
|
nullptr /* ssl_client_context */,
|
|
nullptr /* socket_performance_watcher_factory */,
|
|
nullptr /* network_quality_estimator */,
|
|
nullptr /* net_log */,
|
|
nullptr /* websocket_endpoint_lock_manager */),
|
|
pool_(1, 1, &common_connect_job_params_),
|
|
generator_(&GenerateNulMaskingKey) {}
|
|
|
|
~WebSocketBasicStreamSocketTest() override {
|
|
// stream_ has a reference to socket_data_ (via MockTCPClientSocket) and so
|
|
// should be destroyed first.
|
|
stream_.reset();
|
|
}
|
|
|
|
std::unique_ptr<ClientSocketHandle> MakeTransportSocket(
|
|
base::span<const MockRead> reads,
|
|
base::span<const MockWrite> writes) {
|
|
socket_data_ = std::make_unique<StrictStaticSocketDataProvider>(
|
|
reads, writes, expect_all_io_to_complete_);
|
|
socket_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
|
|
factory_.AddSocketDataProvider(socket_data_.get());
|
|
|
|
auto transport_socket = std::make_unique<ClientSocketHandle>();
|
|
scoped_refptr<ClientSocketPool::SocketParams> null_params;
|
|
ClientSocketPool::GroupId group_id(
|
|
url::SchemeHostPort(url::kHttpScheme, "a", 80),
|
|
PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
|
|
SecureDnsPolicy::kAllow);
|
|
transport_socket->Init(
|
|
group_id, null_params, absl::nullopt /* proxy_annotation_tag */, MEDIUM,
|
|
SocketTag(), ClientSocketPool::RespectLimits::ENABLED,
|
|
CompletionOnceCallback(), ClientSocketPool::ProxyAuthCallback(), &pool_,
|
|
NetLogWithSource());
|
|
return transport_socket;
|
|
}
|
|
|
|
void SetHttpReadBuffer(const char* data, size_t size) {
|
|
http_read_buffer_ = base::MakeRefCounted<GrowableIOBuffer>();
|
|
http_read_buffer_->SetCapacity(size);
|
|
memcpy(http_read_buffer_->data(), data, size);
|
|
http_read_buffer_->set_offset(size);
|
|
}
|
|
|
|
void CreateStream(base::span<const MockRead> reads,
|
|
base::span<const MockWrite> writes) {
|
|
stream_ = WebSocketBasicStream::CreateWebSocketBasicStreamForTesting(
|
|
MakeTransportSocket(reads, writes), http_read_buffer_, sub_protocol_,
|
|
extensions_, net_log_, generator_);
|
|
}
|
|
|
|
std::unique_ptr<SocketDataProvider> socket_data_;
|
|
MockClientSocketFactory factory_;
|
|
const CommonConnectJobParams common_connect_job_params_;
|
|
MockTransportClientSocketPool pool_;
|
|
std::vector<std::unique_ptr<WebSocketFrame>> frames_;
|
|
TestCompletionCallback cb_;
|
|
scoped_refptr<GrowableIOBuffer> http_read_buffer_;
|
|
std::string sub_protocol_;
|
|
std::string extensions_;
|
|
NetLogWithSource net_log_;
|
|
WebSocketBasicStream::WebSocketMaskingKeyGeneratorFunction generator_;
|
|
bool expect_all_io_to_complete_ = true;
|
|
std::unique_ptr<WebSocketBasicStream> stream_;
|
|
};
|
|
|
|
// A test fixture for the common case of tests that only perform a single read.
|
|
class WebSocketBasicStreamSocketSingleReadTest
|
|
: public WebSocketBasicStreamSocketTest {
|
|
protected:
|
|
void CreateRead(const MockRead& read) {
|
|
reads_[0] = read;
|
|
CreateStream(reads_, base::span<MockWrite>());
|
|
}
|
|
|
|
MockRead reads_[1];
|
|
};
|
|
|
|
// A test fixture for tests that perform chunked reads.
|
|
class WebSocketBasicStreamSocketChunkedReadTest
|
|
: public WebSocketBasicStreamSocketTest {
|
|
protected:
|
|
// Specify the behaviour if there aren't enough chunks to use all the data. If
|
|
// LAST_FRAME_BIG is specified, then the rest of the data will be
|
|
// put in the last chunk. If LAST_FRAME_NOT_BIG is specified, then the last
|
|
// frame will be no bigger than the rest of the frames (but it can be smaller,
|
|
// if not enough data remains).
|
|
enum LastFrameBehaviour {
|
|
LAST_FRAME_BIG,
|
|
LAST_FRAME_NOT_BIG
|
|
};
|
|
|
|
// Prepares a read from |data| of |data_size|, split into |number_of_chunks|,
|
|
// each of |chunk_size| (except that the last chunk may be larger or
|
|
// smaller). All reads must be either SYNCHRONOUS or ASYNC (not a mixture),
|
|
// and errors cannot be simulated. Once data is exhausted, further reads will
|
|
// return 0 (ie. connection closed).
|
|
void CreateChunkedRead(IoMode mode,
|
|
const char data[],
|
|
size_t data_size,
|
|
int chunk_size,
|
|
size_t number_of_chunks,
|
|
LastFrameBehaviour last_frame_behaviour) {
|
|
reads_.clear();
|
|
const char* start = data;
|
|
for (size_t i = 0; i < number_of_chunks; ++i) {
|
|
int len = chunk_size;
|
|
const bool is_last_chunk = (i == number_of_chunks - 1);
|
|
if ((last_frame_behaviour == LAST_FRAME_BIG && is_last_chunk) ||
|
|
static_cast<int>(data + data_size - start) < len) {
|
|
len = static_cast<int>(data + data_size - start);
|
|
}
|
|
reads_.emplace_back(mode, start, len);
|
|
start += len;
|
|
}
|
|
CreateStream(reads_, base::span<MockWrite>());
|
|
}
|
|
|
|
std::vector<MockRead> reads_;
|
|
};
|
|
|
|
// Test fixture for write tests.
|
|
class WebSocketBasicStreamSocketWriteTest
|
|
: public WebSocketBasicStreamSocketTest {
|
|
protected:
|
|
// All write tests use the same frame, so it is easiest to create it during
|
|
// test creation.
|
|
void SetUp() override { PrepareWriteFrame(); }
|
|
|
|
// Creates a WebSocketFrame with a wire format matching kWriteFrame and adds
|
|
// it to |frames_|.
|
|
void PrepareWriteFrame() {
|
|
auto frame =
|
|
std::make_unique<WebSocketFrame>(WebSocketFrameHeader::kOpCodeText);
|
|
const size_t payload_size =
|
|
kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize +
|
|
WebSocketFrameHeader::kMaskingKeyLength);
|
|
auto buffer = base::MakeRefCounted<IOBuffer>(payload_size);
|
|
frame_buffers_.push_back(buffer);
|
|
memcpy(buffer->data(), kWriteFrame + kWriteFrameSize - payload_size,
|
|
payload_size);
|
|
frame->payload = buffer->data();
|
|
WebSocketFrameHeader& header = frame->header;
|
|
header.final = true;
|
|
header.masked = true;
|
|
header.payload_length = payload_size;
|
|
frames_.push_back(std::move(frame));
|
|
}
|
|
|
|
// TODO(yoichio): Make this type std::vector<std::string>.
|
|
std::vector<scoped_refptr<IOBuffer>> frame_buffers_;
|
|
};
|
|
|
|
// A test fixture for tests that perform read buffer size switching.
|
|
class WebSocketBasicStreamSwitchTest : public WebSocketBasicStreamSocketTest {
|
|
protected:
|
|
// This is used to specify the read start/end time.
|
|
base::TimeTicks MicrosecondsFromStart(int microseconds) {
|
|
static const base::TimeTicks kStartPoint =
|
|
base::TimeTicks::UnixEpoch() + base::Seconds(60);
|
|
return kStartPoint + base::Microseconds(microseconds);
|
|
}
|
|
|
|
WebSocketBasicStream::BufferSizeManager buffer_size_manager_;
|
|
};
|
|
|
|
TEST_F(WebSocketBasicStreamSocketTest, ConstructionWorks) {
|
|
CreateStream(base::span<MockRead>(), base::span<MockWrite>());
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncReadWorks) {
|
|
CreateRead(MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize));
|
|
int result = stream_->ReadFrames(&frames_, cb_.callback());
|
|
EXPECT_THAT(result, IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(UINT64_C(6), frames_[0]->header.payload_length);
|
|
EXPECT_TRUE(frames_[0]->header.final);
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncReadWorks) {
|
|
CreateRead(MockRead(ASYNC, kSampleFrame, kSampleFrameSize));
|
|
int result = stream_->ReadFrames(&frames_, cb_.callback());
|
|
ASSERT_THAT(result, IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(UINT64_C(6), frames_[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(WebSocketBasicStreamSocketChunkedReadTest, HeaderFragmentedSync) {
|
|
CreateChunkedRead(
|
|
SYNCHRONOUS, kSampleFrame, kSampleFrameSize, 1, 2, LAST_FRAME_BIG);
|
|
int result = stream_->ReadFrames(&frames_, cb_.callback());
|
|
EXPECT_THAT(result, IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(UINT64_C(6), frames_[0]->header.payload_length);
|
|
}
|
|
|
|
// The same behaviour applies to asynchronous reads.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, HeaderFragmentedAsync) {
|
|
CreateChunkedRead(
|
|
ASYNC, kSampleFrame, kSampleFrameSize, 1, 2, LAST_FRAME_BIG);
|
|
int result = stream_->ReadFrames(&frames_, cb_.callback());
|
|
ASSERT_THAT(result, IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(UINT64_C(6), frames_[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)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
int result = stream_->ReadFrames(&frames_, cb_.callback());
|
|
ASSERT_THAT(result, IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(UINT64_C(6), frames_[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)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
}
|
|
|
|
// A frame that does not arrive in a single read should be broken into separate
|
|
// frames.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, LargeFrameFirstChunk) {
|
|
CreateRead(MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize));
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_FALSE(frames_[0]->header.final);
|
|
EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize,
|
|
static_cast<size_t>(frames_[0]->header.payload_length));
|
|
}
|
|
|
|
// If only the header of a data frame arrives, we should receive a frame with a
|
|
// zero-size payload.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, HeaderOnlyChunk) {
|
|
CreateRead(MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize));
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(nullptr, frames_[0]->payload);
|
|
EXPECT_EQ(0U, frames_[0]->header.payload_length);
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
|
|
}
|
|
|
|
// If the header and the body of a data frame arrive seperately, we should see
|
|
// them as separate frames.
|
|
TEST_F(WebSocketBasicStreamSocketTest, HeaderBodySeparated) {
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize),
|
|
MockRead(ASYNC,
|
|
kPartialLargeFrame + kLargeFrameHeaderSize,
|
|
kPartialLargeFrameSize - kLargeFrameHeaderSize)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(nullptr, frames_[0]->payload);
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
|
|
frames_.clear();
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize,
|
|
frames_[0]->header.payload_length);
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation,
|
|
frames_[0]->header.opcode);
|
|
}
|
|
|
|
// Every frame has a header with a correct payload_length field.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, LargeFrameTwoChunks) {
|
|
const size_t kChunkSize = 16;
|
|
CreateChunkedRead(ASYNC,
|
|
kPartialLargeFrame,
|
|
kPartialLargeFrameSize,
|
|
kChunkSize,
|
|
2,
|
|
LAST_FRAME_NOT_BIG);
|
|
TestCompletionCallback cb[2];
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[0].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[0].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(kChunkSize - kLargeFrameHeaderSize,
|
|
frames_[0]->header.payload_length);
|
|
|
|
frames_.clear();
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[1].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[1].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(kChunkSize, frames_[0]->header.payload_length);
|
|
}
|
|
|
|
// Only the final frame of a fragmented message has |final| bit set.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, OnlyFinalChunkIsFinal) {
|
|
static const size_t kFirstChunkSize = 4;
|
|
CreateChunkedRead(ASYNC,
|
|
kSampleFrame,
|
|
kSampleFrameSize,
|
|
kFirstChunkSize,
|
|
2,
|
|
LAST_FRAME_BIG);
|
|
TestCompletionCallback cb[2];
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[0].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[0].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
ASSERT_FALSE(frames_[0]->header.final);
|
|
|
|
frames_.clear();
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[1].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[1].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
ASSERT_TRUE(frames_[0]->header.final);
|
|
}
|
|
|
|
// All frames after the first have their opcode changed to Continuation.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, ContinuationOpCodeUsed) {
|
|
const size_t kFirstChunkSize = 3;
|
|
const int kChunkCount = 3;
|
|
// The input data is one frame with opcode Text, which arrives in three
|
|
// separate chunks.
|
|
CreateChunkedRead(ASYNC,
|
|
kSampleFrame,
|
|
kSampleFrameSize,
|
|
kFirstChunkSize,
|
|
kChunkCount,
|
|
LAST_FRAME_BIG);
|
|
TestCompletionCallback cb[kChunkCount];
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[0].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[0].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
|
|
|
|
// This test uses a loop to verify that the opcode for every frames generated
|
|
// after the first is converted to Continuation.
|
|
for (int i = 1; i < kChunkCount; ++i) {
|
|
frames_.clear();
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[i].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[i].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation,
|
|
frames_[0]->header.opcode);
|
|
}
|
|
}
|
|
|
|
// Multiple frames that arrive together should be parsed correctly.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, ThreeFramesTogether) {
|
|
CreateRead(MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize));
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(3U, frames_.size());
|
|
EXPECT_TRUE(frames_[0]->header.final);
|
|
EXPECT_TRUE(frames_[1]->header.final);
|
|
EXPECT_TRUE(frames_[2]->header.final);
|
|
}
|
|
|
|
// ERR_CONNECTION_CLOSED must be returned on close.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncClose) {
|
|
CreateRead(MockRead(SYNCHRONOUS, "", 0));
|
|
|
|
EXPECT_EQ(ERR_CONNECTION_CLOSED,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncClose) {
|
|
CreateRead(MockRead(ASYNC, "", 0));
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_CONNECTION_CLOSED));
|
|
}
|
|
|
|
// 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(WebSocketBasicStreamSocketSingleReadTest, SyncCloseWithErr) {
|
|
CreateRead(MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED));
|
|
|
|
EXPECT_EQ(ERR_CONNECTION_CLOSED,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncCloseWithErr) {
|
|
CreateRead(MockRead(ASYNC, ERR_CONNECTION_CLOSED));
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_CONNECTION_CLOSED));
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncErrorsPassedThrough) {
|
|
// ERR_INSUFFICIENT_RESOURCES here represents an arbitrary error that
|
|
// WebSocketBasicStream gives no special handling to.
|
|
CreateRead(MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES));
|
|
|
|
EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncErrorsPassedThrough) {
|
|
CreateRead(MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES));
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_INSUFFICIENT_RESOURCES));
|
|
}
|
|
|
|
// If we get a frame followed by a close, we should receive them separately.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, CloseAfterFrame) {
|
|
// The chunk size equals the data size, so the second chunk is 0 size, closing
|
|
// the connection.
|
|
CreateChunkedRead(SYNCHRONOUS,
|
|
kSampleFrame,
|
|
kSampleFrameSize,
|
|
kSampleFrameSize,
|
|
2,
|
|
LAST_FRAME_NOT_BIG);
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
EXPECT_EQ(1U, frames_.size());
|
|
frames_.clear();
|
|
EXPECT_EQ(ERR_CONNECTION_CLOSED,
|
|
stream_->ReadFrames(&frames_, 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)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_CONNECTION_CLOSED));
|
|
}
|
|
|
|
// 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)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_CONNECTION_CLOSED));
|
|
}
|
|
|
|
// An empty first frame is not ignored.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, EmptyFirstFrame) {
|
|
CreateRead(MockRead(SYNCHRONOUS, kEmptyFirstFrame, kEmptyFirstFrameSize));
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(nullptr, frames_[0]->payload);
|
|
EXPECT_EQ(0U, frames_[0]->header.payload_length);
|
|
}
|
|
|
|
// An empty frame in the middle of a message is ignored.
|
|
TEST_F(WebSocketBasicStreamSocketTest, EmptyMiddleFrame) {
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, kEmptyFirstFrame, kEmptyFirstFrameSize),
|
|
MockRead(SYNCHRONOUS, kEmptyMiddleFrame, kEmptyMiddleFrameSize),
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
EXPECT_EQ(1U, frames_.size());
|
|
frames_.clear();
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
}
|
|
|
|
// An empty frame in the middle of a message that arrives separately is still
|
|
// ignored.
|
|
TEST_F(WebSocketBasicStreamSocketTest, EmptyMiddleFrameAsync) {
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, kEmptyFirstFrame, kEmptyFirstFrameSize),
|
|
MockRead(ASYNC, kEmptyMiddleFrame, kEmptyMiddleFrameSize),
|
|
// We include a pong message to verify the middle frame was actually
|
|
// processed.
|
|
MockRead(ASYNC, kValidPong, kValidPongSize)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
EXPECT_EQ(1U, frames_.size());
|
|
frames_.clear();
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodePong, frames_[0]->header.opcode);
|
|
}
|
|
|
|
// An empty final frame is not ignored.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, EmptyFinalFrame) {
|
|
CreateRead(
|
|
MockRead(SYNCHRONOUS, kEmptyFinalTextFrame, kEmptyFinalTextFrameSize));
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(nullptr, frames_[0]->payload);
|
|
EXPECT_EQ(0U, frames_[0]->header.payload_length);
|
|
}
|
|
|
|
// An empty middle frame is ignored with a final frame present.
|
|
TEST_F(WebSocketBasicStreamSocketTest, ThreeFrameEmptyMessage) {
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, kEmptyFirstFrame, kEmptyFirstFrameSize),
|
|
MockRead(SYNCHRONOUS, kEmptyMiddleFrame, kEmptyMiddleFrameSize),
|
|
MockRead(SYNCHRONOUS,
|
|
kEmptyFinalContinuationFrame,
|
|
kEmptyFinalContinuationFrameSize)};
|
|
CreateStream(reads, base::span<MockWrite>());
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
|
|
frames_.clear();
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_TRUE(frames_[0]->header.final);
|
|
}
|
|
|
|
// 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);
|
|
CreateStream(base::span<MockRead>(), base::span<MockWrite>());
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
ASSERT_TRUE(frames_[0]->payload);
|
|
EXPECT_EQ(UINT64_C(6), frames_[0]->header.payload_length);
|
|
}
|
|
|
|
// Check that a frame whose header partially arrived at the end of the response
|
|
// headers works correctly.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest,
|
|
PartialFrameHeaderInHttpResponse) {
|
|
SetHttpReadBuffer(kSampleFrame, 1);
|
|
CreateRead(MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1));
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
ASSERT_TRUE(frames_[0]->payload);
|
|
EXPECT_EQ(UINT64_C(6), frames_[0]->header.payload_length);
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode);
|
|
}
|
|
|
|
// Check that a control frame which partially arrives at the end of the response
|
|
// headers works correctly.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest,
|
|
PartialControlFrameInHttpResponse) {
|
|
const size_t kPartialFrameBytes = 3;
|
|
SetHttpReadBuffer(kCloseFrame, kPartialFrameBytes);
|
|
CreateRead(MockRead(ASYNC,
|
|
kCloseFrame + kPartialFrameBytes,
|
|
kCloseFrameSize - kPartialFrameBytes));
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
|
|
EXPECT_EQ(kCloseFrameSize - 2, frames_[0]->header.payload_length);
|
|
EXPECT_EQ(std::string(frames_[0]->payload, kCloseFrameSize - 2),
|
|
std::string(kCloseFrame + 2, kCloseFrameSize - 2));
|
|
}
|
|
|
|
// Check that a control frame which partially arrives at the end of the response
|
|
// headers works correctly. Synchronous version (unlikely in practice).
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest,
|
|
PartialControlFrameInHttpResponseSync) {
|
|
const size_t kPartialFrameBytes = 3;
|
|
SetHttpReadBuffer(kCloseFrame, kPartialFrameBytes);
|
|
CreateRead(MockRead(SYNCHRONOUS,
|
|
kCloseFrame + kPartialFrameBytes,
|
|
kCloseFrameSize - kPartialFrameBytes));
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
|
|
}
|
|
|
|
// Check that an invalid frame results in an error.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncInvalidFrame) {
|
|
CreateRead(MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize));
|
|
|
|
EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncInvalidFrame) {
|
|
CreateRead(MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize));
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_WS_PROTOCOL_ERROR));
|
|
}
|
|
|
|
// A control frame without a FIN flag is invalid and should not be passed
|
|
// through to higher layers. RFC6455 5.5 "All control frames ... MUST NOT be
|
|
// fragmented."
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, ControlFrameWithoutFin) {
|
|
CreateRead(
|
|
MockRead(SYNCHRONOUS, kPingFrameWithoutFin, kPingFrameWithoutFinSize));
|
|
|
|
EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
EXPECT_TRUE(frames_.empty());
|
|
}
|
|
|
|
// A control frame over 125 characters is invalid. RFC6455 5.5 "All control
|
|
// frames MUST have a payload length of 125 bytes or less". Since we use a
|
|
// 125-byte buffer to assemble fragmented control frames, we need to detect this
|
|
// error before attempting to assemble the fragments.
|
|
TEST_F(WebSocketBasicStreamSocketSingleReadTest, OverlongControlFrame) {
|
|
CreateRead(MockRead(SYNCHRONOUS, k126BytePong, k126BytePongSize));
|
|
|
|
EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
EXPECT_TRUE(frames_.empty());
|
|
}
|
|
|
|
// A control frame over 125 characters should still be rejected if it is split
|
|
// into multiple chunks.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, SplitOverlongControlFrame) {
|
|
const size_t kFirstChunkSize = 16;
|
|
expect_all_io_to_complete_ = false;
|
|
CreateChunkedRead(SYNCHRONOUS,
|
|
k126BytePong,
|
|
k126BytePongSize,
|
|
kFirstChunkSize,
|
|
2,
|
|
LAST_FRAME_BIG);
|
|
|
|
EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
|
|
stream_->ReadFrames(&frames_, cb_.callback()));
|
|
EXPECT_TRUE(frames_.empty());
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest,
|
|
AsyncSplitOverlongControlFrame) {
|
|
const size_t kFirstChunkSize = 16;
|
|
expect_all_io_to_complete_ = false;
|
|
CreateChunkedRead(ASYNC,
|
|
k126BytePong,
|
|
k126BytePongSize,
|
|
kFirstChunkSize,
|
|
2,
|
|
LAST_FRAME_BIG);
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsError(ERR_WS_PROTOCOL_ERROR));
|
|
// The caller should not call ReadFrames() again after receiving an error
|
|
// other than ERR_IO_PENDING.
|
|
EXPECT_TRUE(frames_.empty());
|
|
}
|
|
|
|
// In the synchronous case, ReadFrames assembles the whole control frame before
|
|
// returning.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, SyncControlFrameAssembly) {
|
|
const size_t kChunkSize = 3;
|
|
CreateChunkedRead(
|
|
SYNCHRONOUS, kCloseFrame, kCloseFrameSize, kChunkSize, 3, LAST_FRAME_BIG);
|
|
|
|
EXPECT_THAT(stream_->ReadFrames(&frames_, cb_.callback()), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
|
|
}
|
|
|
|
// In the asynchronous case, the callback is not called until the control frame
|
|
// has been completely assembled.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, AsyncControlFrameAssembly) {
|
|
const size_t kChunkSize = 3;
|
|
CreateChunkedRead(
|
|
ASYNC, kCloseFrame, kCloseFrameSize, kChunkSize, 3, LAST_FRAME_BIG);
|
|
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode);
|
|
}
|
|
|
|
// A frame with a 1MB payload that has to be read in chunks.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, OneMegFrame) {
|
|
// This should be equal to the definition of kSmallReadBufferFrame in
|
|
// websocket_basic_stream.cc.
|
|
const int kReadBufferSize = 1000;
|
|
const uint64_t kPayloadSize = 1 << 20;
|
|
const size_t kWireSize = kPayloadSize + kLargeFrameHeaderSize;
|
|
const size_t kExpectedFrameCount =
|
|
(kWireSize + kReadBufferSize - 1) / kReadBufferSize;
|
|
auto big_frame = std::make_unique<char[]>(kWireSize);
|
|
memcpy(big_frame.get(), "\x81\x7F", 2);
|
|
base::WriteBigEndian(big_frame.get() + 2, kPayloadSize);
|
|
memset(big_frame.get() + kLargeFrameHeaderSize, 'A', kPayloadSize);
|
|
|
|
CreateChunkedRead(ASYNC,
|
|
big_frame.get(),
|
|
kWireSize,
|
|
kReadBufferSize,
|
|
kExpectedFrameCount,
|
|
LAST_FRAME_BIG);
|
|
|
|
for (size_t frame = 0; frame < kExpectedFrameCount; ++frame) {
|
|
frames_.clear();
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
size_t expected_payload_size = kReadBufferSize;
|
|
if (frame == 0) {
|
|
expected_payload_size = kReadBufferSize - kLargeFrameHeaderSize;
|
|
} else if (frame == kExpectedFrameCount - 1) {
|
|
expected_payload_size =
|
|
kWireSize - kReadBufferSize * (kExpectedFrameCount - 1);
|
|
}
|
|
EXPECT_EQ(expected_payload_size, frames_[0]->header.payload_length);
|
|
}
|
|
}
|
|
|
|
// A frame with reserved flag(s) set that arrives in chunks should only have the
|
|
// reserved flag(s) set on the first chunk when split.
|
|
TEST_F(WebSocketBasicStreamSocketChunkedReadTest, ReservedFlagCleared) {
|
|
static const char kReservedFlagFrame[] = "\x41\x05Hello";
|
|
const size_t kReservedFlagFrameSize = std::size(kReservedFlagFrame) - 1;
|
|
const size_t kChunkSize = 5;
|
|
|
|
CreateChunkedRead(ASYNC,
|
|
kReservedFlagFrame,
|
|
kReservedFlagFrameSize,
|
|
kChunkSize,
|
|
2,
|
|
LAST_FRAME_BIG);
|
|
|
|
TestCompletionCallback cb[2];
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[0].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[0].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_TRUE(frames_[0]->header.reserved1);
|
|
|
|
frames_.clear();
|
|
ASSERT_THAT(stream_->ReadFrames(&frames_, cb[1].callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb[1].WaitForResult(), IsOk());
|
|
ASSERT_EQ(1U, frames_.size());
|
|
EXPECT_FALSE(frames_[0]->header.reserved1);
|
|
}
|
|
|
|
// Check that writing a frame all at once works.
|
|
TEST_F(WebSocketBasicStreamSocketWriteTest, WriteAtOnce) {
|
|
MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, kWriteFrameSize)};
|
|
CreateStream(base::span<MockRead>(), writes);
|
|
|
|
EXPECT_THAT(stream_->WriteFrames(&frames_, cb_.callback()), IsOk());
|
|
}
|
|
|
|
// Check that completely async writing works.
|
|
TEST_F(WebSocketBasicStreamSocketWriteTest, AsyncWriteAtOnce) {
|
|
MockWrite writes[] = {MockWrite(ASYNC, kWriteFrame, kWriteFrameSize)};
|
|
CreateStream(base::span<MockRead>(), writes);
|
|
|
|
ASSERT_THAT(stream_->WriteFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
}
|
|
|
|
// 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(WebSocketBasicStreamSocketWriteTest, WriteInBits) {
|
|
MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, 4),
|
|
MockWrite(ASYNC, kWriteFrame + 4, 4),
|
|
MockWrite(ASYNC, kWriteFrame + 8, kWriteFrameSize - 8)};
|
|
CreateStream(base::span<MockRead>(), writes);
|
|
|
|
ASSERT_THAT(stream_->WriteFrames(&frames_, cb_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(cb_.WaitForResult(), IsOk());
|
|
}
|
|
|
|
// Check that writing a Pong frame with a nullptr body works.
|
|
TEST_F(WebSocketBasicStreamSocketWriteTest, WriteNullptrPong) {
|
|
MockWrite writes[] = {
|
|
MockWrite(SYNCHRONOUS, kMaskedEmptyPong, kMaskedEmptyPongSize)};
|
|
CreateStream(base::span<MockRead>(), writes);
|
|
|
|
auto frame =
|
|
std::make_unique<WebSocketFrame>(WebSocketFrameHeader::kOpCodePong);
|
|
WebSocketFrameHeader& header = frame->header;
|
|
header.final = true;
|
|
header.masked = true;
|
|
header.payload_length = 0;
|
|
std::vector<std::unique_ptr<WebSocketFrame>> frames;
|
|
frames.push_back(std::move(frame));
|
|
EXPECT_THAT(stream_->WriteFrames(&frames, cb_.callback()), IsOk());
|
|
}
|
|
|
|
// Check that writing with a non-nullptr mask works correctly.
|
|
TEST_F(WebSocketBasicStreamSocketTest, WriteNonNulMask) {
|
|
std::string masked_frame = std::string("\x81\x88");
|
|
masked_frame += std::string(kNonNulMaskingKey.key, 4);
|
|
masked_frame += "jiggered";
|
|
MockWrite writes[] = {
|
|
MockWrite(SYNCHRONOUS, masked_frame.data(), masked_frame.size())};
|
|
generator_ = &GenerateNonNulMaskingKey;
|
|
CreateStream(base::span<MockRead>(), writes);
|
|
|
|
auto frame =
|
|
std::make_unique<WebSocketFrame>(WebSocketFrameHeader::kOpCodeText);
|
|
const std::string unmasked_payload = "graphics";
|
|
const size_t payload_size = unmasked_payload.size();
|
|
auto buffer = base::MakeRefCounted<IOBuffer>(payload_size);
|
|
memcpy(buffer->data(), unmasked_payload.data(), payload_size);
|
|
frame->payload = buffer->data();
|
|
WebSocketFrameHeader& header = frame->header;
|
|
header.final = true;
|
|
header.masked = true;
|
|
header.payload_length = payload_size;
|
|
frames_.push_back(std::move(frame));
|
|
|
|
EXPECT_THAT(stream_->WriteFrames(&frames_, cb_.callback()), IsOk());
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketTest, GetExtensionsWorks) {
|
|
extensions_ = "inflate-uuencode";
|
|
CreateStream(base::span<MockRead>(), base::span<MockWrite>());
|
|
|
|
EXPECT_EQ("inflate-uuencode", stream_->GetExtensions());
|
|
}
|
|
|
|
TEST_F(WebSocketBasicStreamSocketTest, GetSubProtocolWorks) {
|
|
sub_protocol_ = "cyberchat";
|
|
CreateStream(base::span<MockRead>(), base::span<MockWrite>());
|
|
|
|
EXPECT_EQ("cyberchat", stream_->GetSubProtocol());
|
|
}
|
|
|
|
// Check that the read buffer size initialization works correctly.
|
|
TEST_F(WebSocketBasicStreamSwitchTest, GetInitialReadBufferSize) {
|
|
EXPECT_EQ(buffer_size_manager_.buffer_size(),
|
|
WebSocketBasicStream::BufferSize::kSmall);
|
|
buffer_size_manager_.OnRead(MicrosecondsFromStart(0));
|
|
EXPECT_EQ(buffer_size_manager_.buffer_size(),
|
|
WebSocketBasicStream::BufferSize::kSmall);
|
|
}
|
|
|
|
// Check that the case where the start time and the end time are the same.
|
|
TEST_F(WebSocketBasicStreamSwitchTest, ZeroSecondRead) {
|
|
buffer_size_manager_.set_window_for_test(1);
|
|
buffer_size_manager_.OnRead(MicrosecondsFromStart(0));
|
|
buffer_size_manager_.OnReadComplete(MicrosecondsFromStart(0), 1000);
|
|
EXPECT_EQ(buffer_size_manager_.buffer_size(),
|
|
WebSocketBasicStream::BufferSize::kLarge);
|
|
}
|
|
|
|
// Check that the read buffer size is switched for high throughput connection.
|
|
TEST_F(WebSocketBasicStreamSwitchTest, CheckSwitch) {
|
|
buffer_size_manager_.set_window_for_test(4);
|
|
// It tests the case where 4000 bytes data is read in 2000 ms. In this case,
|
|
// the read buffer size should be switched to the large one.
|
|
buffer_size_manager_.OnRead(MicrosecondsFromStart(0));
|
|
buffer_size_manager_.OnReadComplete(MicrosecondsFromStart(200), 1000);
|
|
buffer_size_manager_.OnRead(MicrosecondsFromStart(800));
|
|
buffer_size_manager_.OnReadComplete(MicrosecondsFromStart(1000), 1000);
|
|
buffer_size_manager_.OnRead(MicrosecondsFromStart(1300));
|
|
buffer_size_manager_.OnReadComplete(MicrosecondsFromStart(1500), 1000);
|
|
buffer_size_manager_.OnRead(MicrosecondsFromStart(1800));
|
|
buffer_size_manager_.OnReadComplete(MicrosecondsFromStart(2000), 1000);
|
|
EXPECT_EQ(buffer_size_manager_.buffer_size(),
|
|
WebSocketBasicStream::BufferSize::kLarge);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace net
|