blob: 187ce1c02be64e4ead03264a9e6f03c98148f57a [file]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <openssl/evp.h>
#include <openssl/mem.h>
#include <atomic>
#include <chrono>
#include <optional>
#include "cast/common/certificate/testing/test_helpers.h"
#include "cast/common/channel/connection_namespace_handler.h"
#include "cast/common/channel/message_util.h"
#include "cast/common/channel/virtual_connection_router.h"
#include "cast/common/public/cast_socket.h"
#include "cast/common/public/trust_store.h"
#include "cast/receiver/channel/device_auth_namespace_handler.h"
#include "cast/receiver/channel/static_credentials.h"
#include "cast/receiver/public/receiver_socket_factory.h"
#include "cast/sender/public/sender_socket_factory.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "platform/api/task_runner_deleter.h"
#include "platform/api/tls_connection_factory.h"
#include "platform/base/tls_connect_options.h"
#include "platform/base/tls_credentials.h"
#include "platform/base/tls_listen_options.h"
#include "platform/impl/logging.h"
#include "platform/impl/network_interface.h"
#include "platform/impl/platform_client_posix.h"
#include "testing/util/task_util.h"
#include "util/crypto/certificate_utils.h"
#include "util/osp_logging.h"
namespace openscreen::cast {
namespace {
using ::testing::_;
using ::testing::StrictMock;
constexpr char kLogDecorator[] = "--- ";
} // namespace
class SenderSocketsClient : public SenderSocketFactory::Client,
public VirtualConnectionRouter::SocketErrorHandler {
public:
explicit SenderSocketsClient(VirtualConnectionRouter& router) // NOLINT
: router_(router) {}
~SenderSocketsClient() override = default;
CastSocket* socket() const { return socket_; }
// SenderSocketFactory::Client overrides.
void OnConnected(SenderSocketFactory* factory,
const IPEndpoint& endpoint,
std::unique_ptr<CastSocket> socket) override {
OSP_CHECK(!socket_);
OSP_LOG_INFO << kLogDecorator
<< "Sender connected to endpoint: " << endpoint;
socket_ = socket.get();
router_.TakeSocket(this, std::move(socket));
}
void OnError(SenderSocketFactory* factory,
const IPEndpoint& endpoint,
const Error& error) override {
OSP_LOG_FATAL << error;
}
// VirtualConnectionRouter::SocketErrorHandler overrides.
void OnClose(CastSocket* socket) override {
socket_ = nullptr;
OnCloseMock(socket);
}
void OnError(CastSocket* socket, const Error& error) override {
socket_ = nullptr;
OnErrorMock(socket, error);
}
MOCK_METHOD(void, OnCloseMock, (CastSocket * socket), ());
MOCK_METHOD(void, OnErrorMock, (CastSocket * socket, const Error& error), ());
private:
VirtualConnectionRouter& router_;
std::atomic<CastSocket*> socket_{nullptr};
};
class ReceiverSocketsClient
: public ReceiverSocketFactory::Client,
public VirtualConnectionRouter::SocketErrorHandler {
public:
explicit ReceiverSocketsClient(VirtualConnectionRouter* router)
: router_(router) {}
virtual ~ReceiverSocketsClient() = default;
const IPEndpoint& endpoint() const { return endpoint_; }
CastSocket* socket() const { return socket_; }
// ReceiverSocketFactory::Client overrides.
void OnConnected(ReceiverSocketFactory* factory,
const IPEndpoint& endpoint,
std::unique_ptr<CastSocket> socket) override {
OSP_CHECK(!socket_);
OSP_LOG_INFO << kLogDecorator
<< "Receiver got connection from endpoint: " << endpoint;
endpoint_ = endpoint;
socket_ = socket.get();
router_->TakeSocket(this, std::move(socket));
}
void OnError(ReceiverSocketFactory* factory, const Error& error) override {
OSP_LOG_FATAL << error;
}
// VirtualConnectionRouter::SocketErrorHandler overrides.
void OnClose(CastSocket* socket) override {
socket_ = nullptr;
OnCloseMock(socket);
}
void OnError(CastSocket* socket, const Error& error) override {
socket_ = nullptr;
OnErrorMock(socket, error);
}
MOCK_METHOD(void, OnCloseMock, (CastSocket * socket), ());
MOCK_METHOD(void, OnErrorMock, (CastSocket * socket, const Error& error), ());
private:
VirtualConnectionRouter* router_;
IPEndpoint endpoint_;
std::atomic<CastSocket*> socket_{nullptr};
};
class CastSocketE2ETest : public ::testing::Test {
public:
void SetUp() override {
PlatformClientPosix::Create(std::chrono::milliseconds(10));
task_runner_ = &PlatformClientPosix::GetInstance()->GetTaskRunner();
credentials_ =
std::move(GenerateCredentialsForTesting("Device ID").value());
sender_router_ =
TaskRunnerDeleter::MakeUnique<VirtualConnectionRouter>(*task_runner_);
sender_client_ =
std::make_unique<StrictMock<SenderSocketsClient>>(*sender_router_);
sender_factory_ = TaskRunnerDeleter::MakeUnique<SenderSocketFactory>(
*task_runner_, *sender_client_, *task_runner_,
TrustStore::CreateInstanceForTest(credentials_.root_cert_der),
CastCRLTrustStore::Create());
sender_tls_factory_ = TaskRunnerDeleter::WrapUnique(
*task_runner_,
TlsConnectionFactory::CreateFactory(*sender_factory_, *task_runner_)
.release());
sender_factory_->set_factory(sender_tls_factory_.get());
auth_handler_ = TaskRunnerDeleter::MakeUnique<DeviceAuthNamespaceHandler>(
*task_runner_, *credentials_.provider);
receiver_router_ =
TaskRunnerDeleter::MakeUnique<VirtualConnectionRouter>(*task_runner_);
receiver_router_->AddHandlerForLocalId(kPlatformReceiverId,
auth_handler_.get());
receiver_client_ = std::make_unique<StrictMock<ReceiverSocketsClient>>(
receiver_router_.get());
receiver_factory_ = TaskRunnerDeleter::MakeUnique<ReceiverSocketFactory>(
*task_runner_, *receiver_client_, *receiver_router_);
receiver_tls_factory_ = TaskRunnerDeleter::WrapUnique(
*task_runner_,
TlsConnectionFactory::CreateFactory(*receiver_factory_, *task_runner_)
.release());
}
void TearDown() override {
OSP_LOG_INFO << "Shutting down";
sender_router_.reset();
receiver_router_.reset();
receiver_tls_factory_.reset();
receiver_factory_.reset();
auth_handler_.reset();
sender_tls_factory_.reset();
sender_factory_.reset();
PlatformClientPosix::ShutDown();
}
protected:
IPAddress GetLoopbackV4Address() {
std::optional<InterfaceInfo> loopback = GetLoopbackInterfaceForTesting();
OSP_CHECK(loopback);
IPAddress address = loopback->GetIpAddressV4();
OSP_CHECK(address);
return address;
}
IPAddress GetLoopbackV6Address() {
std::optional<InterfaceInfo> loopback = GetLoopbackInterfaceForTesting();
OSP_CHECK(loopback);
IPAddress address = loopback->GetIpAddressV6();
return address;
}
void Connect(const IPAddress& address) {
uint16_t port = 65321;
OSP_LOG_INFO << kLogDecorator << "Starting socket factories";
task_runner_->PostTask([this, &address, port]() {
OSP_LOG_INFO << kLogDecorator << "Receiver TLS factory Listen()";
receiver_tls_factory_->SetListenCredentials(credentials_.tls_credentials);
receiver_tls_factory_->Listen(IPEndpoint{address, port},
TlsListenOptions{1u});
});
task_runner_->PostTask([this, &address, port]() {
OSP_LOG_INFO << kLogDecorator << "Sender CastSocket factory Connect()";
sender_factory_->Connect(IPEndpoint{address, port},
SenderSocketFactory::DeviceMediaPolicy::kNone,
sender_router_.get());
});
WaitForCondition([this]() { return sender_client_->socket(); });
}
void ConnectSocketsV4() {
OSP_LOG_INFO << "Getting loopback IPv4 address";
IPAddress loopback_address = GetLoopbackV4Address();
OSP_LOG_INFO << "Connecting CastSockets";
Connect(loopback_address);
}
template <typename SocketClient, typename PeerSocketClient>
void CloseSocketsFromOneEnd(VirtualConnectionRouter* router,
SocketClient* client,
PeerSocketClient* peer_client) {
// TODO(issuetracker.google.com/169967989): Would like to have a symmetric
// OnClose check.
EXPECT_CALL(*client, OnCloseMock(client->socket()));
// Verification of SSL_shutdown (issuetracker.google.com/169966671):
// If the peer does not call SSL_shutdown during socket closure, BoringSSL
// will report a protocol error (missing close notify) resulting in
// Error::Code::kFatalSSLError. The expectation of kSocketClosedFailure
// here explicitly verifies that SSL_shutdown was called and succeeded.
EXPECT_CALL(*peer_client, OnErrorMock(peer_client->socket(), _))
.WillOnce([](CastSocket* socket, const Error& error) {
EXPECT_EQ(error.code(), Error::Code::kSocketClosedFailure);
});
int32_t id = client->socket()->socket_id();
std::atomic_bool did_run{false};
task_runner_->PostTask([id, router, &did_run]() {
router->CloseSocket(id);
did_run = true;
});
OSP_LOG_INFO << "Waiting for socket to close";
WaitForCondition([&did_run]() { return did_run.load(); });
EXPECT_FALSE(sender_client_->socket());
EXPECT_FALSE(receiver_client_->socket());
}
TaskRunner* task_runner_;
// NOTE: Sender components.
std::unique_ptr<VirtualConnectionRouter, TaskRunnerDeleter> sender_router_;
std::unique_ptr<StrictMock<SenderSocketsClient>> sender_client_;
std::unique_ptr<SenderSocketFactory, TaskRunnerDeleter> sender_factory_;
std::unique_ptr<TlsConnectionFactory, TaskRunnerDeleter> sender_tls_factory_;
// NOTE: Receiver components.
std::unique_ptr<VirtualConnectionRouter, TaskRunnerDeleter> receiver_router_;
GeneratedCredentials credentials_;
std::unique_ptr<DeviceAuthNamespaceHandler, TaskRunnerDeleter> auth_handler_;
std::unique_ptr<StrictMock<ReceiverSocketsClient>> receiver_client_;
std::unique_ptr<ReceiverSocketFactory, TaskRunnerDeleter> receiver_factory_;
std::unique_ptr<TlsConnectionFactory, TaskRunnerDeleter>
receiver_tls_factory_;
};
// These test the most basic setup of a complete CastSocket. This means
// constructing both a SenderSocketFactory and ReceiverSocketFactory, making a
// TLS connection to a known port over the loopback device, and checking device
// authentication.
TEST_F(CastSocketE2ETest, ConnectV4) {
ConnectSocketsV4();
}
TEST_F(CastSocketE2ETest, ConnectV6) {
OSP_LOG_INFO << "Getting loopback IPv6 address";
IPAddress loopback_address = GetLoopbackV6Address();
if (loopback_address) {
OSP_LOG_INFO << "Connecting CastSockets";
Connect(loopback_address);
} else {
OSP_LOG_WARN << "Test skipped due to missing IPv6 loopback address";
}
}
TEST_F(CastSocketE2ETest, SenderClose) {
ConnectSocketsV4();
CloseSocketsFromOneEnd(sender_router_.get(), sender_client_.get(),
receiver_client_.get());
}
TEST_F(CastSocketE2ETest, ReceiverClose) {
ConnectSocketsV4();
CloseSocketsFromOneEnd(receiver_router_.get(), receiver_client_.get(),
sender_client_.get());
}
} // namespace openscreen::cast