// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/browser/audio_socket_broker.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "chromecast/net/socket_util.h"
#include "content/public/test/test_content_client_initializer.h"
#include "content/public/test/test_renderer_host.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "net/socket/stream_socket.h"
#include "net/socket/unix_domain_server_socket_posix.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
namespace media {
namespace {
constexpr char kTestSocket[] = "test.socket";
constexpr char kSocketMsg[] = "socket-handle";
constexpr int kListenBacklog = 1;
} // namespace
class AudioSocketBrokerTest : public content::RenderViewHostTestHarness {
public:
AudioSocketBrokerTest() = default;
~AudioSocketBrokerTest() override {
if (io_thread_) {
io_thread_->task_runner()->DeleteSoon(FROM_HERE,
std::move(accepted_socket_));
io_thread_->task_runner()->DeleteSoon(FROM_HERE,
std::move(listen_socket_));
}
}
void SetUp() override {
ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
socket_path_ = test_dir_.GetPath().Append(kTestSocket).value();
initializer_ = std::make_unique<content::TestContentClientInitializer>();
content::RenderViewHostTestHarness::SetUp();
audio_socket_broker_ = &AudioSocketBroker::CreateForTesting(
*main_rfh(), audio_socket_broker_remote_.BindNewPipeAndPassReceiver(),
socket_path_);
}
void SetupServerSocket() {
base::WaitableEvent server_setup_finished;
io_thread_ = std::make_unique<base::Thread>("test_io_thread");
io_thread_->StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
io_thread_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&AudioSocketBrokerTest::SetupServerSocketOnIoThread,
base::Unretained(this), &server_setup_finished));
server_setup_finished.Wait();
}
void SetupServerSocketOnIoThread(base::WaitableEvent* server_setup_finished) {
auto unix_socket = std::make_unique<net::UnixDomainServerSocket>(
base::BindRepeating(
[](const net::UnixDomainServerSocket::Credentials&) {
// Always accept the connection.
return true;
}),
/*use_abstract_namespace=*/true);
int result = unix_socket->BindAndListen(socket_path_, kListenBacklog);
EXPECT_EQ(result, net::OK);
listen_socket_ = std::move(unix_socket);
listen_socket_->AcceptSocketDescriptor(
&accepted_descriptor_,
base::BindRepeating(&AudioSocketBrokerTest::OnAccept,
base::Unretained(this)));
server_setup_finished->Signal();
}
void OnAccept(int result) {
EXPECT_EQ(result, net::OK);
char buffer[16];
std::vector<base::ScopedFD> fds;
const int flags = fcntl(accepted_descriptor_, F_GETFL);
ASSERT_NE(
HANDLE_EINTR(fcntl(accepted_descriptor_, F_SETFL, flags & ~O_NONBLOCK)),
-1);
EXPECT_EQ(static_cast<size_t>(base::UnixDomainSocket::RecvMsg(
accepted_descriptor_, buffer, sizeof(buffer), &fds)),
sizeof(kSocketMsg));
EXPECT_EQ(memcmp(buffer, kSocketMsg, sizeof(kSocketMsg)), 0);
EXPECT_THAT(fds, ::testing::SizeIs(1U));
accepted_socket_ = AdoptUnnamedSocketHandle(std::move(fds[0]));
}
void OnSocketDescriptor(bool expect_success, mojo::PlatformHandle handle) {
EXPECT_EQ(handle.is_valid_fd(), expect_success);
descriptor_received_ = true;
if (expect_success) {
auto stream_socket = AdoptUnnamedSocketHandle(handle.TakeFD());
EXPECT_TRUE(stream_socket->IsConnected());
}
run_loop_.Quit();
}
void RunThreadsUntilIdle() {
run_loop_.Run();
task_environment()->RunUntilIdle();
}
protected:
mojo::Remote<mojom::AudioSocketBroker> audio_socket_broker_remote_;
base::ScopedTempDir test_dir_;
std::string socket_path_;
std::unique_ptr<content::TestContentClientInitializer> initializer_;
// `AudioSocketBroker` is a `DocumentService` which manages its own
// lifecycle.
AudioSocketBroker* audio_socket_broker_ = nullptr;
bool descriptor_received_ = false;
base::RunLoop run_loop_;
std::unique_ptr<base::Thread> io_thread_;
std::unique_ptr<net::UnixDomainServerSocket> listen_socket_;
net::SocketDescriptor accepted_descriptor_;
std::unique_ptr<net::StreamSocket> accepted_socket_;
};
TEST_F(AudioSocketBrokerTest, ValidSocketHandle) {
SetupServerSocket();
audio_socket_broker_remote_->GetSocketDescriptor(
base::BindOnce(&AudioSocketBrokerTest::OnSocketDescriptor,
base::Unretained(this), true));
RunThreadsUntilIdle();
EXPECT_TRUE(descriptor_received_);
}
TEST_F(AudioSocketBrokerTest, InvalidSocketHandle) {
audio_socket_broker_remote_->GetSocketDescriptor(
base::BindOnce(&AudioSocketBrokerTest::OnSocketDescriptor,
base::Unretained(this), false));
RunThreadsUntilIdle();
EXPECT_TRUE(descriptor_received_);
}
} // namespace media
} // namespace chromecast