// 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 <memory>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/posix/unix_domain_socket.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/timer.h"
#include "chromecast/media/audio/audio_io_thread.h"
#include "chromecast/media/audio/audio_output_service/constants.h"
#include "chromecast/net/socket_util.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "net/base/net_errors.h"
#include "net/socket/unix_domain_client_socket_posix.h"
namespace chromecast {
namespace media {
namespace {
constexpr base::TimeDelta kConnectTimeout = base::Seconds(1);
constexpr char kSocketMsg[] = "socket-handle";
} // namespace
// Helper class for sending the socket descriptors to the audio output service.
class AudioSocketBroker::SocketFdConnection {
public:
SocketFdConnection(AudioSocketBroker* socket_broker,
base::ScopedFD connect_socket_fd,
base::ScopedFD pending_socket_fd,
const std::string& audio_output_service_path,
base::OnceCallback<void(base::ScopedFD)> connect_callback)
: socket_broker_(socket_broker),
socket_fd_(std::move(connect_socket_fd)),
pending_socket_fd_(std::move(pending_socket_fd)),
connect_callback_(std::move(connect_callback)) {
DCHECK(socket_broker_);
DCHECK(socket_fd_.is_valid());
DCHECK(pending_socket_fd_.is_valid());
DCHECK(connect_callback_);
connecting_socket_ = std::make_unique<net::UnixDomainClientSocket>(
audio_output_service_path, true);
}
SocketFdConnection(const SocketFdConnection&) = delete;
SocketFdConnection& operator=(const SocketFdConnection&) = delete;
~SocketFdConnection() = default;
void Connect() {
DCHECK(connecting_socket_);
int result = connecting_socket_->Connect(base::BindOnce(
&SocketFdConnection::OnConnected, base::Unretained(this)));
if (result != net::ERR_IO_PENDING) {
OnConnected(result);
return;
}
connection_timeout_.Start(FROM_HERE, kConnectTimeout, this,
&SocketFdConnection::ConnectTimeout);
}
private:
void OnConnected(int result) {
if (result != net::OK ||
!base::UnixDomainSocket::SendMsg(
connecting_socket_->ReleaseConnectedSocket(), kSocketMsg,
sizeof(kSocketMsg), {socket_fd_.get()})) {
std::move(connect_callback_).Run(base::ScopedFD());
return;
}
connecting_socket_.reset();
std::move(connect_callback_).Run(std::move(pending_socket_fd_));
}
void ConnectTimeout() {
LOG(ERROR) << "Timed out connecting to audio output service";
OnConnected(net::ERR_TIMED_OUT);
}
AudioSocketBroker* const socket_broker_;
base::ScopedFD socket_fd_;
base::ScopedFD pending_socket_fd_;
base::OnceCallback<void(base::ScopedFD)> connect_callback_;
std::unique_ptr<net::UnixDomainClientSocket> connecting_socket_;
base::OneShotTimer connection_timeout_;
};
AudioSocketBroker::PendingConnectionInfo::PendingConnectionInfo(
base::SequenceBound<SocketFdConnection> arg_socket_fd_connection,
GetSocketDescriptorCallback arg_callback)
: socket_fd_connection(std::move(arg_socket_fd_connection)),
callback(std::move(arg_callback)) {}
AudioSocketBroker::PendingConnectionInfo::PendingConnectionInfo(
PendingConnectionInfo&&) = default;
AudioSocketBroker::PendingConnectionInfo&
AudioSocketBroker::PendingConnectionInfo::operator=(PendingConnectionInfo&&) =
default;
AudioSocketBroker::PendingConnectionInfo::~PendingConnectionInfo() = default;
void AudioSocketBroker::Create(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<mojom::AudioSocketBroker> receiver) {
CHECK(render_frame_host);
// Lifecycle managed by content::DocumentService.
new AudioSocketBroker(*render_frame_host, std::move(receiver));
}
AudioSocketBroker& AudioSocketBroker::CreateForTesting(
content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<mojom::AudioSocketBroker> receiver,
const std::string& audio_output_service_path) {
return *new AudioSocketBroker(render_frame_host, std::move(receiver),
audio_output_service_path);
}
AudioSocketBroker::AudioSocketBroker(
content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<mojom::AudioSocketBroker> receiver)
: AudioSocketBroker(render_frame_host,
std::move(receiver),
audio_output_service::
kDefaultAudioOutputServiceUnixDomainSocketPath) {}
AudioSocketBroker::AudioSocketBroker(
content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<mojom::AudioSocketBroker> receiver,
const std::string& audio_output_service_path)
: DocumentService(render_frame_host, std::move(receiver)),
audio_output_service_path_(audio_output_service_path),
main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
AudioSocketBroker::~AudioSocketBroker() = default;
void AudioSocketBroker::GetSocketDescriptor(
GetSocketDescriptorCallback callback) {
base::ScopedFD socket_fd1, socket_fd2;
if (!CreateUnnamedSocketPair(&socket_fd1, &socket_fd2)) {
std::move(callback).Run(mojo::PlatformHandle(base::ScopedFD()));
return;
}
// Send one socket descriptor to audio output service first, and then the
// other to the client in the renderer.
int sock_fd1 = socket_fd1.get();
auto socket_fd_connection = base::SequenceBound<SocketFdConnection>(
AudioIoThread::Get()->task_runner(), this, std::move(socket_fd2),
std::move(socket_fd1), audio_output_service_path_,
base::BindPostTask(
main_task_runner_,
base::BindOnce(&AudioSocketBroker::OnSocketHandleSentToAudioService,
weak_factory_.GetWeakPtr(), sock_fd1)));
auto result = pending_connection_infos_.emplace(
sock_fd1, PendingConnectionInfo(std::move(socket_fd_connection),
std::move(callback)));
DCHECK(result.second);
result.first->second.socket_fd_connection.AsyncCall(
&SocketFdConnection::Connect);
}
void AudioSocketBroker::OnSocketHandleSentToAudioService(
int socket_fd,
base::ScopedFD pending_socket_fd) {
DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
auto it = pending_connection_infos_.find(socket_fd);
if (it == pending_connection_infos_.end()) {
LOG(ERROR) << "Cannot find connection: " << socket_fd;
return;
}
DCHECK(it->second.callback);
// Now that one descriptor is sent to the audio output service, send the other
// descriptor back to the client.
std::move(it->second.callback)
.Run(mojo::PlatformHandle(std::move(pending_socket_fd)));
pending_connection_infos_.erase(it);
}
} // namespace media
} // namespace chromecast