1676 lines
64 KiB
C++
1676 lines
64 KiB
C++
// Copyright 2018 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/websockets/websocket_basic_stream_adapters.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/memory/scoped_refptr.h"
|
|
#include "base/memory/weak_ptr.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/strcat.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "net/base/host_port_pair.h"
|
|
#include "net/base/io_buffer.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/privacy_mode.h"
|
|
#include "net/base/proxy_server.h"
|
|
#include "net/base/test_completion_callback.h"
|
|
#include "net/dns/mock_host_resolver.h"
|
|
#include "net/dns/public/host_resolver_results.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/http/http_network_session.h"
|
|
#include "net/log/net_log_with_source.h"
|
|
#include "net/quic/address_utils.h"
|
|
#include "net/quic/mock_crypto_client_stream_factory.h"
|
|
#include "net/quic/mock_quic_data.h"
|
|
#include "net/quic/quic_chromium_alarm_factory.h"
|
|
#include "net/quic/quic_chromium_client_session_peer.h"
|
|
#include "net/quic/quic_chromium_connection_helper.h"
|
|
#include "net/quic/quic_context.h"
|
|
#include "net/quic/quic_server_info.h"
|
|
#include "net/quic/quic_test_packet_maker.h"
|
|
#include "net/quic/test_quic_crypto_client_config_handle.h"
|
|
#include "net/quic/test_task_runner.h"
|
|
#include "net/socket/client_socket_handle.h"
|
|
#include "net/socket/connect_job.h"
|
|
#include "net/socket/socket_tag.h"
|
|
#include "net/socket/socket_test_util.h"
|
|
#include "net/socket/transport_client_socket_pool.h"
|
|
#include "net/socket/websocket_endpoint_lock_manager.h"
|
|
#include "net/spdy/spdy_session.h"
|
|
#include "net/spdy/spdy_session_key.h"
|
|
#include "net/spdy/spdy_test_util_common.h"
|
|
#include "net/ssl/ssl_config.h"
|
|
#include "net/ssl/ssl_config_service_defaults.h"
|
|
#include "net/ssl/ssl_info.h"
|
|
#include "net/test/cert_test_util.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_data_directory.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/quic_stream_id_manager.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/mock_clock.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_connection_peer.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_session_peer.h"
|
|
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
|
#include "net/websockets/websocket_http3_handshake_stream.h"
|
|
#include "net/websockets/websocket_test_util.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 testing::_;
|
|
using testing::AnyNumber;
|
|
using testing::Invoke;
|
|
using testing::Return;
|
|
using testing::StrictMock;
|
|
using testing::Test;
|
|
|
|
namespace net::test {
|
|
|
|
class WebSocketClientSocketHandleAdapterTest : public TestWithTaskEnvironment {
|
|
protected:
|
|
WebSocketClientSocketHandleAdapterTest()
|
|
: network_session_(
|
|
SpdySessionDependencies::SpdyCreateSession(&session_deps_)),
|
|
websocket_endpoint_lock_manager_(
|
|
network_session_->websocket_endpoint_lock_manager()) {}
|
|
|
|
~WebSocketClientSocketHandleAdapterTest() override = default;
|
|
|
|
bool InitClientSocketHandle(ClientSocketHandle* connection) {
|
|
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
|
|
ssl_config_for_origin->alpn_protos = {kProtoHTTP11};
|
|
scoped_refptr<ClientSocketPool::SocketParams> socks_params =
|
|
base::MakeRefCounted<ClientSocketPool::SocketParams>(
|
|
std::move(ssl_config_for_origin),
|
|
/*ssl_config_for_proxy=*/nullptr);
|
|
TestCompletionCallback callback;
|
|
int rv = connection->Init(
|
|
ClientSocketPool::GroupId(
|
|
url::SchemeHostPort(url::kHttpsScheme, "www.example.org", 443),
|
|
PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
|
|
SecureDnsPolicy::kAllow),
|
|
socks_params, TRAFFIC_ANNOTATION_FOR_TESTS /* proxy_annotation_tag */,
|
|
MEDIUM, SocketTag(), ClientSocketPool::RespectLimits::ENABLED,
|
|
callback.callback(), ClientSocketPool::ProxyAuthCallback(),
|
|
network_session_->GetSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL,
|
|
ProxyServer::Direct()),
|
|
NetLogWithSource());
|
|
rv = callback.GetResult(rv);
|
|
return rv == OK;
|
|
}
|
|
|
|
SpdySessionDependencies session_deps_;
|
|
std::unique_ptr<HttpNetworkSession> network_session_;
|
|
raw_ptr<WebSocketEndpointLockManager> websocket_endpoint_lock_manager_;
|
|
};
|
|
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, Uninitialized) {
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_FALSE(adapter.is_initialized());
|
|
}
|
|
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, IsInitialized) {
|
|
StaticSocketDataProvider data;
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
|
|
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
ClientSocketHandle* const connection_ptr = connection.get();
|
|
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_FALSE(adapter.is_initialized());
|
|
|
|
EXPECT_TRUE(InitClientSocketHandle(connection_ptr));
|
|
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
}
|
|
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, Disconnect) {
|
|
StaticSocketDataProvider data;
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
|
|
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
EXPECT_TRUE(InitClientSocketHandle(connection.get()));
|
|
|
|
StreamSocket* const socket = connection->socket();
|
|
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
EXPECT_TRUE(socket->IsConnected());
|
|
adapter.Disconnect();
|
|
EXPECT_FALSE(socket->IsConnected());
|
|
}
|
|
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, Read) {
|
|
MockRead reads[] = {MockRead(SYNCHRONOUS, "foo"), MockRead("bar")};
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
|
|
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
EXPECT_TRUE(InitClientSocketHandle(connection.get()));
|
|
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
// Buffer larger than each MockRead.
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
int rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("foo", base::StringPiece(read_buf->data(), rv));
|
|
|
|
TestCompletionCallback callback;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("bar", base::StringPiece(read_buf->data(), rv));
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, ReadIntoSmallBuffer) {
|
|
MockRead reads[] = {MockRead(SYNCHRONOUS, "foo"), MockRead("bar")};
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
|
|
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
EXPECT_TRUE(InitClientSocketHandle(connection.get()));
|
|
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
// Buffer smaller than each MockRead.
|
|
const int kReadBufSize = 2;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
int rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(2, rv);
|
|
EXPECT_EQ("fo", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(1, rv);
|
|
EXPECT_EQ("o", base::StringPiece(read_buf->data(), rv));
|
|
|
|
TestCompletionCallback callback1;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, callback1.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback1.WaitForResult();
|
|
ASSERT_EQ(2, rv);
|
|
EXPECT_EQ("ba", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(1, rv);
|
|
EXPECT_EQ("r", base::StringPiece(read_buf->data(), rv));
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, Write) {
|
|
MockWrite writes[] = {MockWrite(SYNCHRONOUS, "foo"), MockWrite("bar")};
|
|
StaticSocketDataProvider data(base::span<MockRead>(), writes);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
|
|
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
EXPECT_TRUE(InitClientSocketHandle(connection.get()));
|
|
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
auto write_buf1 = base::MakeRefCounted<StringIOBuffer>("foo");
|
|
int rv =
|
|
adapter.Write(write_buf1.get(), write_buf1->size(),
|
|
CompletionOnceCallback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_EQ(3, rv);
|
|
|
|
auto write_buf2 = base::MakeRefCounted<StringIOBuffer>("bar");
|
|
TestCompletionCallback callback;
|
|
rv = adapter.Write(write_buf2.get(), write_buf2->size(), callback.callback(),
|
|
TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
// Test that if both Read() and Write() returns asynchronously,
|
|
// the two callbacks are handled correctly.
|
|
TEST_F(WebSocketClientSocketHandleAdapterTest, AsyncReadAndWrite) {
|
|
MockRead reads[] = {MockRead("foobar")};
|
|
MockWrite writes[] = {MockWrite("baz")};
|
|
StaticSocketDataProvider data(reads, writes);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
|
|
|
|
auto connection = std::make_unique<ClientSocketHandle>();
|
|
EXPECT_TRUE(InitClientSocketHandle(connection.get()));
|
|
|
|
WebSocketClientSocketHandleAdapter adapter(std::move(connection));
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback read_callback;
|
|
int rv = adapter.Read(read_buf.get(), kReadBufSize, read_callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
auto write_buf = base::MakeRefCounted<StringIOBuffer>("baz");
|
|
TestCompletionCallback write_callback;
|
|
rv = adapter.Write(write_buf.get(), write_buf->size(),
|
|
write_callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
rv = read_callback.WaitForResult();
|
|
ASSERT_EQ(6, rv);
|
|
EXPECT_EQ("foobar", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = write_callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
class MockDelegate : public WebSocketSpdyStreamAdapter::Delegate {
|
|
public:
|
|
~MockDelegate() override = default;
|
|
MOCK_METHOD(void, OnHeadersSent, (), (override));
|
|
MOCK_METHOD(void,
|
|
OnHeadersReceived,
|
|
(const spdy::Http2HeaderBlock&),
|
|
(override));
|
|
MOCK_METHOD(void, OnClose, (int), (override));
|
|
};
|
|
|
|
class WebSocketSpdyStreamAdapterTest : public TestWithTaskEnvironment {
|
|
protected:
|
|
WebSocketSpdyStreamAdapterTest()
|
|
: url_("wss://www.example.org/"),
|
|
key_(HostPortPair::FromURL(url_),
|
|
ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(),
|
|
NetworkAnonymizationKey(),
|
|
SecureDnsPolicy::kAllow),
|
|
session_(SpdySessionDependencies::SpdyCreateSession(&session_deps_)),
|
|
ssl_(SYNCHRONOUS, OK) {}
|
|
|
|
~WebSocketSpdyStreamAdapterTest() override = default;
|
|
|
|
static spdy::Http2HeaderBlock RequestHeaders() {
|
|
return WebSocketHttp2Request("/", "www.example.org:443",
|
|
"http://www.example.org", {});
|
|
}
|
|
|
|
static spdy::Http2HeaderBlock ResponseHeaders() {
|
|
return WebSocketHttp2Response({});
|
|
}
|
|
|
|
void AddSocketData(SocketDataProvider* data) {
|
|
session_deps_.socket_factory->AddSocketDataProvider(data);
|
|
}
|
|
|
|
void AddSSLSocketData() {
|
|
ssl_.ssl_info.cert =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem");
|
|
ASSERT_TRUE(ssl_.ssl_info.cert);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_);
|
|
}
|
|
|
|
base::WeakPtr<SpdySession> CreateSpdySession() {
|
|
return ::net::CreateSpdySession(session_.get(), key_, NetLogWithSource());
|
|
}
|
|
|
|
base::WeakPtr<SpdyStream> CreateSpdyStream(
|
|
base::WeakPtr<SpdySession> session) {
|
|
return CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session, url_,
|
|
LOWEST, NetLogWithSource());
|
|
}
|
|
|
|
SpdyTestUtil spdy_util_;
|
|
StrictMock<MockDelegate> mock_delegate_;
|
|
|
|
private:
|
|
const GURL url_;
|
|
const SpdySessionKey key_;
|
|
SpdySessionDependencies session_deps_;
|
|
std::unique_ptr<HttpNetworkSession> session_;
|
|
SSLSocketDataProvider ssl_;
|
|
};
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, Disconnect) {
|
|
MockRead reads[] = {MockRead(ASYNC, ERR_IO_PENDING, 0),
|
|
MockRead(ASYNC, 0, 1)};
|
|
SequencedSocketData data(reads, base::span<MockWrite>());
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(stream);
|
|
adapter.Disconnect();
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Read EOF.
|
|
EXPECT_TRUE(session);
|
|
data.Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, SendRequestHeadersThenDisconnect) {
|
|
MockRead reads[] = {MockRead(ASYNC, ERR_IO_PENDING, 0),
|
|
MockRead(ASYNC, 0, 3)};
|
|
spdy::SpdySerializedFrame headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
spdy::SpdySerializedFrame rst(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
|
|
MockWrite writes[] = {CreateMockWrite(headers, 1), CreateMockWrite(rst, 2)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// First read is a pause and it has lower sequence number than first write.
|
|
// Therefore writing headers does not complete while |data| is paused.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Reset the stream before writing completes.
|
|
// OnHeadersSent() will never be called.
|
|
EXPECT_TRUE(stream);
|
|
adapter.Disconnect();
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Resume |data|, finish writing headers, and read EOF.
|
|
EXPECT_TRUE(session);
|
|
data.Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, OnHeadersSentThenDisconnect) {
|
|
MockRead reads[] = {MockRead(ASYNC, 0, 2)};
|
|
spdy::SpdySerializedFrame headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
spdy::SpdySerializedFrame rst(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
|
|
MockWrite writes[] = {CreateMockWrite(headers, 0), CreateMockWrite(rst, 1)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// Finish asynchronous write of headers. This calls OnHeadersSent().
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(stream);
|
|
adapter.Disconnect();
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Read EOF.
|
|
EXPECT_TRUE(session);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, OnHeadersReceivedThenDisconnect) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
MockRead(ASYNC, 0, 3)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
spdy::SpdySerializedFrame rst(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0),
|
|
CreateMockWrite(rst, 2)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(stream);
|
|
adapter.Disconnect();
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Read EOF.
|
|
EXPECT_TRUE(session);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, ServerClosesConnection) {
|
|
MockRead reads[] = {MockRead(ASYNC, 0, 0)};
|
|
SequencedSocketData data(reads, base::span<MockWrite>());
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnClose(ERR_CONNECTION_CLOSED));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest,
|
|
SendRequestHeadersThenServerClosesConnection) {
|
|
MockRead reads[] = {MockRead(ASYNC, 0, 1)};
|
|
spdy::SpdySerializedFrame headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnClose(ERR_CONNECTION_CLOSED));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest,
|
|
OnHeadersReceivedThenServerClosesConnection) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
MockRead(ASYNC, 0, 2)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
EXPECT_CALL(mock_delegate_, OnClose(ERR_CONNECTION_CLOSED));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
// Previously we failed to detect a half-close by the server that indicated the
|
|
// stream should be closed. This test ensures a half-close is correctly
|
|
// detected. See https://crbug.com/1151393.
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, OnHeadersReceivedThenStreamEnd) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
spdy::SpdySerializedFrame stream_end(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "", true));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
CreateMockRead(stream_end, 2),
|
|
MockRead(ASYNC, ERR_IO_PENDING, 3), // pause here
|
|
MockRead(ASYNC, 0, 4)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, /* fin = */ false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
EXPECT_CALL(mock_delegate_, OnClose(ERR_CONNECTION_CLOSED));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
constexpr int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback read_callback;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, read_callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
rv = read_callback.WaitForResult();
|
|
EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
|
|
EXPECT_TRUE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Close the session.
|
|
data.Resume();
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, DetachDelegate) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
MockRead(ASYNC, 0, 2)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
// No Delegate methods shall be called after this.
|
|
adapter.DetachDelegate();
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, Read) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
// First read is the same size as the buffer, next is smaller, last is larger.
|
|
spdy::SpdySerializedFrame data_frame1(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "foo", false));
|
|
spdy::SpdySerializedFrame data_frame2(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "ba", false));
|
|
spdy::SpdySerializedFrame data_frame3(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "rbaz", true));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
CreateMockRead(data_frame1, 2),
|
|
CreateMockRead(data_frame2, 3),
|
|
CreateMockRead(data_frame3, 4), MockRead(ASYNC, 0, 5)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
const int kReadBufSize = 3;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback callback;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("foo", base::StringPiece(read_buf->data(), rv));
|
|
|
|
// Read EOF to destroy the connection and the stream.
|
|
// This calls SpdySession::Delegate::OnClose().
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Two socket reads are concatenated by WebSocketSpdyStreamAdapter.
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("bar", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("baz", base::StringPiece(read_buf->data(), rv));
|
|
|
|
// Even though connection and stream are already closed,
|
|
// WebSocketSpdyStreamAdapter::Delegate::OnClose() is only called after all
|
|
// buffered data are read.
|
|
EXPECT_CALL(mock_delegate_, OnClose(ERR_CONNECTION_CLOSED));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, CallDelegateOnCloseShouldNotCrash) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
spdy::SpdySerializedFrame data_frame1(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "foo", false));
|
|
spdy::SpdySerializedFrame data_frame2(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "bar", false));
|
|
spdy::SpdySerializedFrame rst(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
CreateMockRead(data_frame1, 2),
|
|
CreateMockRead(data_frame2, 3), CreateMockRead(rst, 4),
|
|
MockRead(ASYNC, 0, 5)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// Buffer larger than each MockRead.
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback callback;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("foo", base::StringPiece(read_buf->data(), rv));
|
|
|
|
// Read RST_STREAM to destroy the stream.
|
|
// This calls SpdySession::Delegate::OnClose().
|
|
EXPECT_TRUE(session);
|
|
EXPECT_TRUE(stream);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
// Read remaining buffered data. This will PostTask CallDelegateOnClose().
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("bar", base::StringPiece(read_buf->data(), rv));
|
|
|
|
adapter.DetachDelegate();
|
|
|
|
// Run CallDelegateOnClose(), which should not crash
|
|
// even if |delegate_| is null.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, Write) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
MockRead(ASYNC, 0, 3)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
spdy::SpdySerializedFrame data_frame(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "foo", false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0),
|
|
CreateMockWrite(data_frame, 2)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, nullptr, NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
auto write_buf = base::MakeRefCounted<StringIOBuffer>("foo");
|
|
TestCompletionCallback callback;
|
|
rv = adapter.Write(write_buf.get(), write_buf->size(), callback.callback(),
|
|
TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
|
|
// Read EOF.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
// Test that if both Read() and Write() returns asynchronously,
|
|
// the two callbacks are handled correctly.
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, AsyncReadAndWrite) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
spdy::SpdySerializedFrame read_data_frame(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "foobar", true));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
CreateMockRead(read_data_frame, 3),
|
|
MockRead(ASYNC, 0, 4)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
spdy::SpdySerializedFrame write_data_frame(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "baz", false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0),
|
|
CreateMockWrite(write_data_frame, 2)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, nullptr, NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback read_callback;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, read_callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
auto write_buf = base::MakeRefCounted<StringIOBuffer>("baz");
|
|
TestCompletionCallback write_callback;
|
|
rv = adapter.Write(write_buf.get(), write_buf->size(),
|
|
write_callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
rv = read_callback.WaitForResult();
|
|
ASSERT_EQ(6, rv);
|
|
EXPECT_EQ("foobar", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = write_callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
|
|
// Read EOF.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
// A helper class that will delete |adapter| when the callback is invoked.
|
|
class KillerCallback : public TestCompletionCallbackBase {
|
|
public:
|
|
explicit KillerCallback(std::unique_ptr<WebSocketSpdyStreamAdapter> adapter)
|
|
: adapter_(std::move(adapter)) {}
|
|
|
|
~KillerCallback() override = default;
|
|
|
|
CompletionOnceCallback callback() {
|
|
return base::BindOnce(&KillerCallback::OnComplete, base::Unretained(this));
|
|
}
|
|
|
|
private:
|
|
void OnComplete(int result) {
|
|
adapter_.reset();
|
|
SetResult(result);
|
|
}
|
|
|
|
std::unique_ptr<WebSocketSpdyStreamAdapter> adapter_;
|
|
};
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, ReadCallbackDestroysAdapter) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
MockRead(ASYNC, ERR_IO_PENDING, 2),
|
|
MockRead(ASYNC, 0, 3)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
auto adapter = std::make_unique<WebSocketSpdyStreamAdapter>(
|
|
stream, &mock_delegate_, NetLogWithSource());
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// Send headers.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
WebSocketSpdyStreamAdapter* adapter_raw = adapter.get();
|
|
KillerCallback callback(std::move(adapter));
|
|
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
rv = adapter_raw->Read(read_buf.get(), kReadBufSize, callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// Read EOF while read is pending. WebSocketSpdyStreamAdapter::OnClose()
|
|
// should not crash if read callback destroys |adapter|.
|
|
data.Resume();
|
|
rv = callback.WaitForResult();
|
|
EXPECT_THAT(rv, IsError(ERR_CONNECTION_CLOSED));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest, WriteCallbackDestroysAdapter) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
MockRead(ASYNC, ERR_IO_PENDING, 2),
|
|
MockRead(ASYNC, 0, 3)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
auto adapter = std::make_unique<WebSocketSpdyStreamAdapter>(
|
|
stream, &mock_delegate_, NetLogWithSource());
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// Send headers.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
WebSocketSpdyStreamAdapter* adapter_raw = adapter.get();
|
|
KillerCallback callback(std::move(adapter));
|
|
|
|
auto write_buf = base::MakeRefCounted<StringIOBuffer>("foo");
|
|
rv = adapter_raw->Write(write_buf.get(), write_buf->size(),
|
|
callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
// Read EOF while write is pending. WebSocketSpdyStreamAdapter::OnClose()
|
|
// should not crash if write callback destroys |adapter|.
|
|
data.Resume();
|
|
rv = callback.WaitForResult();
|
|
EXPECT_THAT(rv, IsError(ERR_CONNECTION_CLOSED));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(stream);
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_F(WebSocketSpdyStreamAdapterTest,
|
|
OnCloseOkShouldBeTranslatedToConnectionClose) {
|
|
spdy::SpdySerializedFrame response_headers(
|
|
spdy_util_.ConstructSpdyResponseHeaders(1, ResponseHeaders(), false));
|
|
spdy::SpdySerializedFrame close(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_NO_ERROR));
|
|
MockRead reads[] = {CreateMockRead(response_headers, 1),
|
|
CreateMockRead(close, 2), MockRead(ASYNC, 0, 3)};
|
|
spdy::SpdySerializedFrame request_headers(spdy_util_.ConstructSpdyHeaders(
|
|
1, RequestHeaders(), DEFAULT_PRIORITY, false));
|
|
MockWrite writes[] = {CreateMockWrite(request_headers, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
AddSocketData(&data);
|
|
AddSSLSocketData();
|
|
|
|
EXPECT_CALL(mock_delegate_, OnHeadersSent());
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_));
|
|
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession();
|
|
base::WeakPtr<SpdyStream> stream = CreateSpdyStream(session);
|
|
WebSocketSpdyStreamAdapter adapter(stream, &mock_delegate_,
|
|
NetLogWithSource());
|
|
EXPECT_TRUE(adapter.is_initialized());
|
|
|
|
EXPECT_CALL(mock_delegate_, OnClose(ERR_CONNECTION_CLOSED));
|
|
|
|
int rv = stream->SendRequestHeaders(RequestHeaders(), MORE_DATA_TO_SEND);
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback callback;
|
|
rv = adapter.Read(read_buf.get(), kReadBufSize, callback.callback());
|
|
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
|
|
rv = callback.WaitForResult();
|
|
ASSERT_EQ(ERR_CONNECTION_CLOSED, rv);
|
|
}
|
|
|
|
class MockQuicDelegate : public WebSocketQuicStreamAdapter::Delegate {
|
|
public:
|
|
~MockQuicDelegate() override = default;
|
|
MOCK_METHOD(void, OnHeadersSent, (), (override));
|
|
MOCK_METHOD(void,
|
|
OnHeadersReceived,
|
|
(const spdy::Http2HeaderBlock&),
|
|
(override));
|
|
MOCK_METHOD(void, OnClose, (int), (override));
|
|
};
|
|
|
|
class WebSocketQuicStreamAdapterTest
|
|
: public TestWithTaskEnvironment,
|
|
public ::testing::WithParamInterface<quic::ParsedQuicVersion> {
|
|
protected:
|
|
static spdy::Http2HeaderBlock RequestHeaders() {
|
|
return WebSocketHttp2Request("/", "www.example.org:443",
|
|
"http://www.example.org", {});
|
|
}
|
|
WebSocketQuicStreamAdapterTest()
|
|
: version_(GetParam()),
|
|
mock_quic_data_(version_),
|
|
client_data_stream_id1_(quic::QuicUtils::GetFirstBidirectionalStreamId(
|
|
version_.transport_version,
|
|
quic::Perspective::IS_CLIENT)),
|
|
crypto_config_(
|
|
quic::test::crypto_test_utils::ProofVerifierForTesting()),
|
|
connection_id_(quic::test::TestConnectionId(2)),
|
|
client_maker_(version_,
|
|
connection_id_,
|
|
&clock_,
|
|
"mail.example.org",
|
|
quic::Perspective::IS_CLIENT),
|
|
server_maker_(version_,
|
|
connection_id_,
|
|
&clock_,
|
|
"mail.example.org",
|
|
quic::Perspective::IS_SERVER),
|
|
peer_addr_(IPAddress(192, 0, 2, 23), 443),
|
|
destination_endpoint_(url::kHttpsScheme, "mail.example.org", 80) {}
|
|
|
|
~WebSocketQuicStreamAdapterTest() override = default;
|
|
|
|
void SetUp() override {
|
|
FLAGS_quic_enable_http3_grease_randomness = false;
|
|
clock_.AdvanceTime(quic::QuicTime::Delta::FromMilliseconds(20));
|
|
quic::QuicEnableVersion(version_);
|
|
}
|
|
|
|
void TearDown() override {
|
|
EXPECT_TRUE(mock_quic_data_.AllReadDataConsumed());
|
|
EXPECT_TRUE(mock_quic_data_.AllWriteDataConsumed());
|
|
}
|
|
|
|
net::QuicChromiumClientSession::Handle* GetQuicSessionHandle() {
|
|
return session_handle_.get();
|
|
}
|
|
|
|
// Helper functions for constructing packets sent by the client
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructSettingsPacket(
|
|
uint64_t packet_number) {
|
|
return client_maker_.MakeInitialSettingsPacket(packet_number);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructServerDataPacket(
|
|
uint64_t packet_number,
|
|
base::StringPiece data) {
|
|
DCHECK(version_.HasIetfQuicFrames());
|
|
quiche::QuicheBuffer buffer = quic::HttpEncoder::SerializeDataFrameHeader(
|
|
data.size(), quiche::SimpleBufferAllocator::Get());
|
|
return server_maker_.MakeDataPacket(
|
|
packet_number, client_data_stream_id1_,
|
|
/*should_include_version=*/false, /*fin=*/false,
|
|
base::StrCat({base::StringPiece(buffer.data(), buffer.size()), data}));
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructRstPacket(
|
|
uint64_t packet_number,
|
|
quic::QuicRstStreamErrorCode error_code) {
|
|
return client_maker_.MakeRstPacket(packet_number, /*include_version=*/true,
|
|
client_data_stream_id1_, error_code,
|
|
/*include_stop_sending_if_v99=*/true);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicEncryptedPacket> ConstructClientAckPacket(
|
|
uint64_t packet_number,
|
|
uint64_t largest_received,
|
|
uint64_t smallest_received) {
|
|
return client_maker_.MakeAckPacket(packet_number, largest_received,
|
|
smallest_received);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructAckAndRstPacket(
|
|
uint64_t packet_number,
|
|
quic::QuicRstStreamErrorCode error_code,
|
|
uint64_t largest_received,
|
|
uint64_t smallest_received) {
|
|
return client_maker_.MakeAckAndRstPacket(
|
|
packet_number, /*include_version=*/false, client_data_stream_id1_,
|
|
error_code, largest_received, smallest_received,
|
|
/*include_stop_sending_if_v99=*/true);
|
|
}
|
|
|
|
void Initialize() {
|
|
auto socket = std::make_unique<MockUDPClientSocket>(
|
|
mock_quic_data_.InitializeAndGetSequencedSocketData(), NetLog::Get());
|
|
socket->Connect(peer_addr_);
|
|
|
|
runner_ = base::MakeRefCounted<TestTaskRunner>(&clock_);
|
|
helper_ = std::make_unique<QuicChromiumConnectionHelper>(
|
|
&clock_, &random_generator_);
|
|
alarm_factory_ =
|
|
std::make_unique<QuicChromiumAlarmFactory>(runner_.get(), &clock_);
|
|
// Ownership of 'writer' is passed to 'QuicConnection'.
|
|
QuicChromiumPacketWriter* writer = new QuicChromiumPacketWriter(
|
|
socket.get(), base::SingleThreadTaskRunner::GetCurrentDefault().get());
|
|
quic::QuicConnection* connection = new quic::QuicConnection(
|
|
connection_id_, quic::QuicSocketAddress(),
|
|
net::ToQuicSocketAddress(peer_addr_), helper_.get(),
|
|
alarm_factory_.get(), writer, true /* owns_writer */,
|
|
quic::Perspective::IS_CLIENT, quic::test::SupportedVersions(version_),
|
|
connection_id_generator_);
|
|
connection->set_visitor(&visitor_);
|
|
|
|
// Load a certificate that is valid for *.example.org
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
|
|
EXPECT_TRUE(test_cert.get());
|
|
|
|
verify_details_.cert_verify_result.verified_cert = test_cert;
|
|
verify_details_.cert_verify_result.is_issued_by_known_root = true;
|
|
crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details_);
|
|
|
|
base::TimeTicks dns_end = base::TimeTicks::Now();
|
|
base::TimeTicks dns_start = dns_end - base::Milliseconds(1);
|
|
|
|
session_ = std::make_unique<QuicChromiumClientSession>(
|
|
connection, std::move(socket),
|
|
/*stream_factory=*/nullptr, &crypto_client_stream_factory_, &clock_,
|
|
&transport_security_state_, &ssl_config_service_,
|
|
/*server_info=*/nullptr,
|
|
QuicSessionKey("mail.example.org", 80, PRIVACY_MODE_DISABLED,
|
|
SocketTag(), NetworkAnonymizationKey(),
|
|
SecureDnsPolicy::kAllow,
|
|
/*require_dns_https_alpn=*/false),
|
|
/*require_confirmation=*/false,
|
|
/*migrate_session_early_v2=*/false,
|
|
/*migrate_session_on_network_change_v2=*/false,
|
|
/*default_network=*/handles::kInvalidNetworkHandle,
|
|
quic::QuicTime::Delta::FromMilliseconds(
|
|
kDefaultRetransmittableOnWireTimeout.InMilliseconds()),
|
|
/*migrate_idle_session=*/true, /*allow_port_migration=*/false,
|
|
kDefaultIdleSessionMigrationPeriod, kMaxTimeOnNonDefaultNetwork,
|
|
kMaxMigrationsToNonDefaultNetworkOnWriteError,
|
|
kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
|
|
kQuicYieldAfterPacketsRead,
|
|
quic::QuicTime::Delta::FromMilliseconds(
|
|
kQuicYieldAfterDurationMilliseconds),
|
|
/*cert_verify_flags=*/0, quic::test::DefaultQuicConfig(),
|
|
std::make_unique<TestQuicCryptoClientConfigHandle>(&crypto_config_),
|
|
"CONNECTION_UNKNOWN", dns_start, dns_end,
|
|
std::make_unique<quic::QuicClientPushPromiseIndex>(), nullptr,
|
|
base::DefaultTickClock::GetInstance(),
|
|
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
|
|
/*socket_performance_watcher=*/nullptr, HostResolverEndpointResult(),
|
|
NetLog::Get());
|
|
|
|
session_->Initialize();
|
|
|
|
// Blackhole QPACK decoder stream instead of constructing mock writes.
|
|
session_->qpack_decoder()->set_qpack_stream_sender_delegate(
|
|
&noop_qpack_stream_sender_delegate_);
|
|
TestCompletionCallback callback;
|
|
EXPECT_THAT(session_->CryptoConnect(callback.callback()), IsOk());
|
|
EXPECT_TRUE(session_->OneRttKeysAvailable());
|
|
session_handle_ = session_->CreateHandle(
|
|
url::SchemeHostPort(url::kHttpsScheme, "mail.example.org", 80));
|
|
}
|
|
|
|
const quic::ParsedQuicVersion version_;
|
|
MockQuicData mock_quic_data_;
|
|
StrictMock<MockQuicDelegate> mock_delegate_;
|
|
const quic::QuicStreamId client_data_stream_id1_;
|
|
|
|
private:
|
|
quic::QuicCryptoClientConfig crypto_config_;
|
|
const quic::QuicConnectionId connection_id_;
|
|
|
|
protected:
|
|
QuicTestPacketMaker client_maker_;
|
|
QuicTestPacketMaker server_maker_;
|
|
std::unique_ptr<QuicChromiumClientSession> session_;
|
|
|
|
private:
|
|
quic::MockClock clock_;
|
|
std::unique_ptr<QuicChromiumClientSession::Handle> session_handle_;
|
|
scoped_refptr<TestTaskRunner> runner_;
|
|
ProofVerifyDetailsChromium verify_details_;
|
|
MockCryptoClientStreamFactory crypto_client_stream_factory_;
|
|
SSLConfigServiceDefaults ssl_config_service_;
|
|
quic::test::MockConnectionIdGenerator connection_id_generator_;
|
|
std::unique_ptr<QuicChromiumConnectionHelper> helper_;
|
|
std::unique_ptr<QuicChromiumAlarmFactory> alarm_factory_;
|
|
testing::StrictMock<quic::test::MockQuicConnectionVisitor> visitor_;
|
|
TransportSecurityState transport_security_state_;
|
|
IPAddress ip_;
|
|
IPEndPoint peer_addr_;
|
|
quic::test::MockRandom random_generator_{0};
|
|
url::SchemeHostPort destination_endpoint_;
|
|
quic::test::NoopQpackStreamSenderDelegate noop_qpack_stream_sender_delegate_;
|
|
};
|
|
|
|
// Like net::TestCompletionCallback, but for a callback that takes an unbound
|
|
// parameter of type WebSocketQuicStreamAdapter.
|
|
struct WebSocketQuicStreamAdapterIsPendingHelper {
|
|
bool operator()(
|
|
const std::unique_ptr<WebSocketQuicStreamAdapter>& adapter) const {
|
|
return !adapter;
|
|
}
|
|
};
|
|
|
|
using TestWebSocketQuicStreamAdapterCompletionCallbackBase =
|
|
net::internal::TestCompletionCallbackTemplate<
|
|
std::unique_ptr<WebSocketQuicStreamAdapter>,
|
|
WebSocketQuicStreamAdapterIsPendingHelper>;
|
|
|
|
class TestWebSocketQuicStreamAdapterCompletionCallback
|
|
: public TestWebSocketQuicStreamAdapterCompletionCallbackBase {
|
|
public:
|
|
base::OnceCallback<void(std::unique_ptr<WebSocketQuicStreamAdapter>)>
|
|
callback();
|
|
};
|
|
|
|
base::OnceCallback<void(std::unique_ptr<WebSocketQuicStreamAdapter>)>
|
|
TestWebSocketQuicStreamAdapterCompletionCallback::callback() {
|
|
return base::BindOnce(
|
|
&TestWebSocketQuicStreamAdapterCompletionCallback::SetResult,
|
|
base::Unretained(this));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(QuicVersion,
|
|
WebSocketQuicStreamAdapterTest,
|
|
::testing::ValuesIn(AllSupportedQuicVersions()),
|
|
::testing::PrintToStringParamName());
|
|
|
|
TEST_P(WebSocketQuicStreamAdapterTest, Disconnect) {
|
|
int packet_number = 1;
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
ConstructSettingsPacket(packet_number++));
|
|
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS,
|
|
ConstructRstPacket(packet_number++, quic::QUIC_STREAM_CANCELLED));
|
|
|
|
Initialize();
|
|
|
|
net::QuicChromiumClientSession::Handle* session_handle =
|
|
GetQuicSessionHandle();
|
|
ASSERT_TRUE(session_handle);
|
|
|
|
TestWebSocketQuicStreamAdapterCompletionCallback callback;
|
|
std::unique_ptr<WebSocketQuicStreamAdapter> adapter =
|
|
session_handle->CreateWebSocketQuicStreamAdapter(
|
|
&mock_delegate_, callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_TRUE(adapter);
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
adapter->Disconnect();
|
|
// TODO(momoka): Add tests to test both destruction orders.
|
|
}
|
|
|
|
TEST_P(WebSocketQuicStreamAdapterTest, AsyncAdapterCreation) {
|
|
const size_t kMaxOpenStreams = 50;
|
|
|
|
int packet_number = 1;
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
ConstructSettingsPacket(packet_number++));
|
|
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
client_maker_.MakeStreamsBlockedPacket(
|
|
packet_number++, true, kMaxOpenStreams,
|
|
/* unidirectional = */ false));
|
|
|
|
mock_quic_data_.AddRead(
|
|
ASYNC, server_maker_.MakeMaxStreamsPacket(1, true, kMaxOpenStreams + 2,
|
|
/* unidirectional = */ false));
|
|
|
|
mock_quic_data_.AddRead(ASYNC, ERR_IO_PENDING);
|
|
mock_quic_data_.AddRead(ASYNC, ERR_CONNECTION_CLOSED);
|
|
|
|
Initialize();
|
|
|
|
std::vector<QuicChromiumClientStream*> streams;
|
|
|
|
for (size_t i = 0; i < kMaxOpenStreams; i++) {
|
|
QuicChromiumClientStream* stream =
|
|
QuicChromiumClientSessionPeer::CreateOutgoingStream(session_.get());
|
|
ASSERT_TRUE(stream);
|
|
streams.push_back(stream);
|
|
EXPECT_EQ(i + 1, session_->GetNumActiveStreams());
|
|
}
|
|
|
|
net::QuicChromiumClientSession::Handle* session_handle =
|
|
GetQuicSessionHandle();
|
|
ASSERT_TRUE(session_handle);
|
|
|
|
// Creating an adapter should fail because of the stream limit.
|
|
TestWebSocketQuicStreamAdapterCompletionCallback callback;
|
|
std::unique_ptr<WebSocketQuicStreamAdapter> adapter =
|
|
session_handle->CreateWebSocketQuicStreamAdapter(
|
|
&mock_delegate_, callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_EQ(adapter, nullptr);
|
|
EXPECT_FALSE(callback.have_result());
|
|
EXPECT_EQ(kMaxOpenStreams, session_->GetNumActiveStreams());
|
|
|
|
// Read MAX_STREAMS frame that makes it possible to open WebSocket stream.
|
|
session_->StartReading();
|
|
callback.WaitForResult();
|
|
EXPECT_EQ(kMaxOpenStreams + 1, session_->GetNumActiveStreams());
|
|
|
|
// Close connection.
|
|
mock_quic_data_.Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
}
|
|
|
|
TEST_P(WebSocketQuicStreamAdapterTest, SendRequestHeadersThenDisconnect) {
|
|
int packet_number = 1;
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
ConstructSettingsPacket(packet_number++));
|
|
SpdyTestUtil spdy_util;
|
|
spdy::Http2HeaderBlock request_header_block = WebSocketHttp2Request(
|
|
"/", "www.example.org:443", "http://www.example.org", {});
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS,
|
|
client_maker_.MakeRequestHeadersPacket(
|
|
packet_number++, client_data_stream_id1_,
|
|
/*should_include_version=*/true,
|
|
/*fin=*/false, ConvertRequestPriorityToQuicPriority(LOWEST),
|
|
std::move(request_header_block), nullptr));
|
|
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS,
|
|
ConstructRstPacket(packet_number++, quic::QUIC_STREAM_CANCELLED));
|
|
|
|
Initialize();
|
|
|
|
net::QuicChromiumClientSession::Handle* session_handle =
|
|
GetQuicSessionHandle();
|
|
ASSERT_TRUE(session_handle);
|
|
TestWebSocketQuicStreamAdapterCompletionCallback callback;
|
|
std::unique_ptr<WebSocketQuicStreamAdapter> adapter =
|
|
session_handle->CreateWebSocketQuicStreamAdapter(
|
|
&mock_delegate_, callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_TRUE(adapter);
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
|
|
adapter->WriteHeaders(RequestHeaders(), false);
|
|
|
|
adapter->Disconnect();
|
|
}
|
|
|
|
TEST_P(WebSocketQuicStreamAdapterTest, OnHeadersReceivedThenDisconnect) {
|
|
int packet_number = 1;
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
ConstructSettingsPacket(packet_number++));
|
|
|
|
SpdyTestUtil spdy_util;
|
|
spdy::Http2HeaderBlock request_header_block = WebSocketHttp2Request(
|
|
"/", "www.example.org:443", "http://www.example.org", {});
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS,
|
|
client_maker_.MakeRequestHeadersPacket(
|
|
packet_number++, client_data_stream_id1_,
|
|
/*should_include_version=*/true,
|
|
/*fin=*/false, ConvertRequestPriorityToQuicPriority(LOWEST),
|
|
std::move(request_header_block), nullptr));
|
|
|
|
spdy::Http2HeaderBlock response_header_block = WebSocketHttp2Response({});
|
|
mock_quic_data_.AddRead(ASYNC,
|
|
server_maker_.MakeResponseHeadersPacket(
|
|
/*packet_number=*/1, client_data_stream_id1_,
|
|
/*should_include_version=*/false, /*fin=*/false,
|
|
std::move(response_header_block),
|
|
/*spdy_headers_frame_length=*/nullptr));
|
|
mock_quic_data_.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS, ConstructAckAndRstPacket(packet_number++,
|
|
quic::QUIC_STREAM_CANCELLED, 1, 0));
|
|
base::RunLoop run_loop;
|
|
auto quit_closure = run_loop.QuitClosure();
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_)).WillOnce(Invoke([&]() {
|
|
std::move(quit_closure).Run();
|
|
}));
|
|
|
|
Initialize();
|
|
|
|
net::QuicChromiumClientSession::Handle* session_handle =
|
|
GetQuicSessionHandle();
|
|
ASSERT_TRUE(session_handle);
|
|
|
|
TestWebSocketQuicStreamAdapterCompletionCallback callback;
|
|
std::unique_ptr<WebSocketQuicStreamAdapter> adapter =
|
|
session_handle->CreateWebSocketQuicStreamAdapter(
|
|
&mock_delegate_, callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_TRUE(adapter);
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
|
|
adapter->WriteHeaders(RequestHeaders(), false);
|
|
|
|
session_->StartReading();
|
|
run_loop.Run();
|
|
|
|
adapter->Disconnect();
|
|
}
|
|
|
|
TEST_P(WebSocketQuicStreamAdapterTest, Read) {
|
|
int packet_number = 1;
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
ConstructSettingsPacket(packet_number++));
|
|
|
|
SpdyTestUtil spdy_util;
|
|
spdy::Http2HeaderBlock request_header_block = WebSocketHttp2Request(
|
|
"/", "www.example.org:443", "http://www.example.org", {});
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS,
|
|
client_maker_.MakeRequestHeadersPacket(
|
|
packet_number++, client_data_stream_id1_,
|
|
/*should_include_version=*/true,
|
|
/*fin=*/false, ConvertRequestPriorityToQuicPriority(LOWEST),
|
|
std::move(request_header_block), nullptr));
|
|
|
|
spdy::Http2HeaderBlock response_header_block = WebSocketHttp2Response({});
|
|
mock_quic_data_.AddRead(ASYNC,
|
|
server_maker_.MakeResponseHeadersPacket(
|
|
/*packet_number=*/1, client_data_stream_id1_,
|
|
/*should_include_version=*/false, /*fin=*/false,
|
|
std::move(response_header_block),
|
|
/*spdy_headers_frame_length=*/nullptr));
|
|
mock_quic_data_.AddRead(ASYNC, ERR_IO_PENDING);
|
|
|
|
mock_quic_data_.AddRead(ASYNC, ConstructServerDataPacket(2, "foo"));
|
|
mock_quic_data_.AddRead(SYNCHRONOUS,
|
|
ConstructServerDataPacket(3, "hogehoge"));
|
|
mock_quic_data_.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
|
|
|
|
mock_quic_data_.AddWrite(ASYNC,
|
|
ConstructClientAckPacket(packet_number++, 2, 0));
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS, ConstructAckAndRstPacket(packet_number++,
|
|
quic::QUIC_STREAM_CANCELLED, 3, 0));
|
|
|
|
base::RunLoop run_loop;
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_)).WillOnce(Invoke([&]() {
|
|
run_loop.Quit();
|
|
}));
|
|
|
|
Initialize();
|
|
|
|
net::QuicChromiumClientSession::Handle* session_handle =
|
|
GetQuicSessionHandle();
|
|
ASSERT_TRUE(session_handle);
|
|
|
|
TestWebSocketQuicStreamAdapterCompletionCallback callback;
|
|
std::unique_ptr<WebSocketQuicStreamAdapter> adapter =
|
|
session_handle->CreateWebSocketQuicStreamAdapter(
|
|
&mock_delegate_, callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_TRUE(adapter);
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
|
|
adapter->WriteHeaders(RequestHeaders(), false);
|
|
|
|
session_->StartReading();
|
|
run_loop.Run();
|
|
|
|
// Buffer larger than each MockRead.
|
|
const int kReadBufSize = 1024;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback read_callback;
|
|
|
|
int rv =
|
|
adapter->Read(read_buf.get(), kReadBufSize, read_callback.callback());
|
|
|
|
ASSERT_EQ(ERR_IO_PENDING, rv);
|
|
|
|
mock_quic_data_.GetSequencedSocketData()->Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
rv = read_callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("foo", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = adapter->Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(8, rv);
|
|
EXPECT_EQ("hogehoge", base::StringPiece(read_buf->data(), rv));
|
|
|
|
adapter->Disconnect();
|
|
|
|
EXPECT_TRUE(mock_quic_data_.AllReadDataConsumed());
|
|
EXPECT_TRUE(mock_quic_data_.AllWriteDataConsumed());
|
|
}
|
|
|
|
TEST_P(WebSocketQuicStreamAdapterTest, ReadIntoSmallBuffer) {
|
|
int packet_number = 1;
|
|
mock_quic_data_.AddWrite(SYNCHRONOUS,
|
|
ConstructSettingsPacket(packet_number++));
|
|
|
|
SpdyTestUtil spdy_util;
|
|
spdy::Http2HeaderBlock request_header_block = WebSocketHttp2Request(
|
|
"/", "www.example.org:443", "http://www.example.org", {});
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS,
|
|
client_maker_.MakeRequestHeadersPacket(
|
|
packet_number++, client_data_stream_id1_,
|
|
/*should_include_version=*/true,
|
|
/*fin=*/false, ConvertRequestPriorityToQuicPriority(LOWEST),
|
|
std::move(request_header_block), nullptr));
|
|
|
|
spdy::Http2HeaderBlock response_header_block = WebSocketHttp2Response({});
|
|
mock_quic_data_.AddRead(ASYNC,
|
|
server_maker_.MakeResponseHeadersPacket(
|
|
/*packet_number=*/1, client_data_stream_id1_,
|
|
/*should_include_version=*/false, /*fin=*/false,
|
|
std::move(response_header_block),
|
|
/*spdy_headers_frame_length=*/nullptr));
|
|
mock_quic_data_.AddRead(ASYNC, ERR_IO_PENDING);
|
|
// First read is the same size as the buffer, next is smaller, last is larger.
|
|
mock_quic_data_.AddRead(ASYNC, ConstructServerDataPacket(2, "abc"));
|
|
mock_quic_data_.AddRead(SYNCHRONOUS, ConstructServerDataPacket(3, "12"));
|
|
mock_quic_data_.AddRead(SYNCHRONOUS, ConstructServerDataPacket(4, "ABCD"));
|
|
mock_quic_data_.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
|
|
|
|
mock_quic_data_.AddWrite(ASYNC,
|
|
ConstructClientAckPacket(packet_number++, 2, 0));
|
|
mock_quic_data_.AddWrite(
|
|
SYNCHRONOUS, ConstructAckAndRstPacket(packet_number++,
|
|
quic::QUIC_STREAM_CANCELLED, 4, 0));
|
|
|
|
base::RunLoop run_loop;
|
|
EXPECT_CALL(mock_delegate_, OnHeadersReceived(_)).WillOnce(Invoke([&]() {
|
|
run_loop.Quit();
|
|
}));
|
|
|
|
Initialize();
|
|
|
|
net::QuicChromiumClientSession::Handle* session_handle =
|
|
GetQuicSessionHandle();
|
|
ASSERT_TRUE(session_handle);
|
|
TestWebSocketQuicStreamAdapterCompletionCallback callback;
|
|
std::unique_ptr<WebSocketQuicStreamAdapter> adapter =
|
|
session_handle->CreateWebSocketQuicStreamAdapter(
|
|
&mock_delegate_, callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ASSERT_TRUE(adapter);
|
|
EXPECT_TRUE(adapter->is_initialized());
|
|
|
|
adapter->WriteHeaders(RequestHeaders(), false);
|
|
|
|
session_->StartReading();
|
|
run_loop.Run();
|
|
|
|
const int kReadBufSize = 3;
|
|
auto read_buf = base::MakeRefCounted<IOBuffer>(kReadBufSize);
|
|
TestCompletionCallback read_callback;
|
|
|
|
int rv =
|
|
adapter->Read(read_buf.get(), kReadBufSize, read_callback.callback());
|
|
|
|
ASSERT_EQ(ERR_IO_PENDING, rv);
|
|
|
|
mock_quic_data_.GetSequencedSocketData()->Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
rv = read_callback.WaitForResult();
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("abc", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = adapter->Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("12A", base::StringPiece(read_buf->data(), rv));
|
|
|
|
rv = adapter->Read(read_buf.get(), kReadBufSize, CompletionOnceCallback());
|
|
ASSERT_EQ(3, rv);
|
|
EXPECT_EQ("BCD", base::StringPiece(read_buf->data(), rv));
|
|
|
|
adapter->Disconnect();
|
|
|
|
EXPECT_TRUE(mock_quic_data_.AllReadDataConsumed());
|
|
EXPECT_TRUE(mock_quic_data_.AllWriteDataConsumed());
|
|
}
|
|
|
|
} // namespace net::test
|