Implement Cast Streaming mirroring, DLNA casting, daemon+GUI, and breadd integration
Some checks failed
dev release / build (push) Failing after 12s

Builds out the full v1 scope: a vendored+patched openscreen subset for
low-latency Cast Streaming (Mirroring receiver 0F5096E8) alongside the
existing Cast V2/HLS and new DLNA/AVTransport casting paths, breadcastd's
Idle/Casting state machine with a private IPC socket, the breadcast GTK4
popup as a thin IPC client, and bread.cast.*/bread.command.cast.* breadd
integration (device discovery, start/stop, mirroring lifecycle events).
Also adds bakery/systemd/Forgejo CI packaging.

Validated end-to-end against a real Chromecast/Google TV: negotiated
Cast Streaming session, live pipeline playback, and daemon+GUI click-to-cast/
stop through the actual popup.
This commit is contained in:
Breadway 2026-08-03 09:07:21 +08:00
parent 887c29002f
commit 8c745d18e0
283 changed files with 36788 additions and 0 deletions

View file

@ -0,0 +1,32 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_LOGGING_H_
#define PLATFORM_IMPL_LOGGING_H_
#include <string>
#include "util/osp_logging.h"
namespace openscreen {
// Append logging output to a named FIFO having the given `filename`. If the
// file does not exist, an attempt is made to auto-create it. If unsuccessful,
// abort the program. If this is never called, logging continues to output to
// the default destination (stderr).
void SetLogFifoOrDie(const char* filename);
// Set the global logging level. If this is never called, kWarning is the
// default.
void SetLogLevel(LogLevel level);
// Returns the current global logging level.
LogLevel GetLogLevel();
// Log a trace message. Used by the text trace logging platform.
void LogTraceMessage(const std::string& message);
} // namespace openscreen
#endif // PLATFORM_IMPL_LOGGING_H_

View file

@ -0,0 +1,161 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include "build/build_config.h"
#include "platform/impl/logging.h"
#include "platform/impl/logging_test.h"
#include "util/trace_logging.h"
#if OSP_DCHECK_IS_ON()
#include <execinfo.h>
#include <array>
#endif
namespace openscreen {
namespace {
int g_log_fd = STDERR_FILENO;
LogLevel g_log_level = LogLevel::kWarning;
std::vector<std::string>* g_log_messages_for_test = nullptr;
std::ostream& operator<<(std::ostream& os, const LogLevel& level) {
const char* level_string = "";
switch (level) {
case LogLevel::kVerbose:
level_string = "VERBOSE";
break;
case LogLevel::kInfo:
level_string = "INFO";
break;
case LogLevel::kWarning:
level_string = "WARNING";
break;
case LogLevel::kError:
level_string = "ERROR";
break;
case LogLevel::kFatal:
level_string = "FATAL";
break;
}
os << level_string;
return os;
}
} // namespace
void SetLogFifoOrDie(const char* filename) {
if (g_log_fd != STDERR_FILENO) {
close(g_log_fd);
g_log_fd = STDERR_FILENO;
}
// Note: The use of OSP_CHECK/OSP_LOG_* here will log to stderr.
struct stat st {};
int open_result = -1;
if (stat(filename, &st) == -1 && errno == ENOENT) {
if (mkfifo(filename, 0644) == 0) {
open_result = open(filename, O_WRONLY);
OSP_CHECK_NE(open_result, -1)
<< "open(" << filename << ") failed: " << strerror(errno);
} else {
OSP_LOG_FATAL << "mkfifo(" << filename << ") failed: " << strerror(errno);
}
} else if (S_ISFIFO(st.st_mode)) {
open_result = open(filename, O_WRONLY);
OSP_CHECK_NE(open_result, -1)
<< "open(" << filename << ") failed: " << strerror(errno);
} else {
OSP_LOG_FATAL << "not a FIFO special file: " << filename;
}
// Direct all logging to the opened FIFO file.
g_log_fd = open_result;
}
void SetLogLevel(LogLevel level) {
g_log_level = level;
}
LogLevel GetLogLevel() {
return g_log_level;
}
bool IsLoggingOn(LogLevel level, const std::string_view file) {
// Possible future enhancement: Use glob patterns passed on the command-line
// to use a different logging level for certain files, like in Chromium.
return level >= g_log_level;
}
void LogWithLevel(LogLevel level,
const char* file,
int line,
std::stringstream message) {
if (level < g_log_level)
return;
std::stringstream ss;
ss << "[" << level << ":" << file << "(" << line << "):T" << std::hex
<< TRACE_CURRENT_ID << "] " << message.rdbuf() << std::endl;
// NOTE: backtrace() is only supported in modern versions of Android (33+), so
// it is just disabled here.
#if OSP_DCHECK_IS_ON() && !BUILDFLAG(IS_ANDROID)
if (level == LogLevel::kFatal) {
constexpr size_t kMaxCallstackSize = 128;
std::array<void*, kMaxCallstackSize> callstack = {};
// Get the return addresses and attempt to symbolize them.
const int num_frames = backtrace(callstack.data(), callstack.size());
char** strs = backtrace_symbols(callstack.data(), num_frames);
if (num_frames > 0) {
ss << "Debug stack trace for fatal error:" << std::endl;
for (int i = 0; i < num_frames; ++i) {
ss << strs[i] << std::endl;
}
}
free(strs);
}
#endif
const auto ss_str = ss.str();
const auto bytes_written = write(g_log_fd, ss_str.c_str(), ss_str.size());
OSP_CHECK(bytes_written);
if (g_log_messages_for_test) {
g_log_messages_for_test->push_back(ss_str);
}
}
void LogTraceMessage(const std::string& message) {
const std::string to_write = message + '\n';
const auto bytes_written = write(g_log_fd, to_write.c_str(), to_write.size());
OSP_CHECK(bytes_written);
}
[[noreturn]] void Break() {
// Generally this will just resolve to an abort anyways, but gives the
// compiler a chance to perform a more appropriate, target specific trap
// as appropriate.
#if defined(_DEBUG)
__builtin_trap();
#else
std::abort();
#endif
}
void SetLogBufferForTest(std::vector<std::string>* messages) {
g_log_messages_for_test = messages;
}
} // namespace openscreen

View file

@ -0,0 +1,24 @@
// 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.
#ifndef PLATFORM_IMPL_LOGGING_TEST_H_
#define PLATFORM_IMPL_LOGGING_TEST_H_
#include <string>
#include <vector>
// These functions should only be called from logging unittests.
namespace openscreen {
// Append logging output to `messages`. Each log entry will be written as a new
// element including a newline. Pass nullptr to stop appending output. Calling
// this does not affect the behavior of SetLogFifoOrDie(). Normally this should
// only be called for tests as it creates an append-only buffer of log messages
// in memory.
void SetLogBufferForTest(std::vector<std::string>* messages);
} // namespace openscreen
#endif // PLATFORM_IMPL_LOGGING_TEST_H_

View file

@ -0,0 +1,31 @@
// 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 "platform/impl/network_interface.h"
#include "platform/base/ip_address.h"
#include "util/std_util.h"
namespace openscreen {
// Returns an InterfaceInfo associated with the system's loopback interface.
std::optional<InterfaceInfo> GetLoopbackInterfaceForTesting() {
const std::vector<InterfaceInfo> interfaces = GetNetworkInterfaces();
auto it = std::find_if(
interfaces.begin(), interfaces.end(), [](const InterfaceInfo& info) {
return info.type == InterfaceInfo::Type::kLoopback &&
ContainsIf(info.addresses, [](const IPSubnet& subnet) {
return subnet.address == IPAddress::kV4LoopbackAddress() ||
subnet.address == IPAddress::kV6LoopbackAddress();
});
});
if (it == interfaces.end()) {
return std::nullopt;
} else {
return *it;
}
}
} // namespace openscreen

View file

@ -0,0 +1,23 @@
// 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.
#ifndef PLATFORM_IMPL_NETWORK_INTERFACE_H_
#define PLATFORM_IMPL_NETWORK_INTERFACE_H_
#include <optional>
#include <vector>
#include "platform/base/interface_info.h"
namespace openscreen {
// Implements the platform API.
std::vector<InterfaceInfo> GetNetworkInterfaces();
// Returns the system's loopback interface. Used for unit tests.
std::optional<InterfaceInfo> GetLoopbackInterfaceForTesting();
} // namespace openscreen
#endif // PLATFORM_IMPL_NETWORK_INTERFACE_H_

View file

@ -0,0 +1,384 @@
// 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.
// clang-format: off
#include <net/if.h>
#include <sys/socket.h>
// clang-format: on
#include <linux/ethtool.h>
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sockios.h>
#include <linux/wireless.h>
#include <netinet/ip.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <cstring>
#include <optional>
#include <string_view>
#include "platform/api/network_interface.h"
#include "platform/base/ip_address.h"
#include "platform/base/span.h"
#include "platform/impl/network_interface.h"
#include "platform/impl/scoped_pipe.h"
#include "util/osp_logging.h"
namespace openscreen {
namespace {
constexpr int kNetlinkRecvmsgBufSize = 8192;
// Safely reads the system name for the interface from the (probably)
// null-terminated string `kernel_name` and returns a std::string.
std::string GetInterfaceName(std::string_view kernel_name) {
OSP_CHECK_LT(kernel_name.length(), IFNAMSIZ);
return std::string(kernel_name);
}
// Returns the type of the interface identified by the name `ifname`, if it can
// be determined, otherwise returns InterfaceInfo::Type::kOther.
InterfaceInfo::Type GetInterfaceType(const std::string& ifname) {
// Determine type after name has been set.
ScopedFd s(socket(AF_INET6, SOCK_DGRAM, 0));
if (!s) {
s = ScopedFd(socket(AF_INET, SOCK_DGRAM, 0));
if (!s)
return InterfaceInfo::Type::kOther;
}
// Note: This uses Wireless Extensions to test the interface, which is
// deprecated. However, it's much easier than using the new nl80211
// interface for this purpose. If Wireless Extensions are ever actually
// removed though, this will need to use nl80211.
struct iwreq wr {};
static_assert(sizeof(wr.ifr_name) == IFNAMSIZ,
"expected size of interface name fields");
OSP_CHECK_LT(ifname.size(), IFNAMSIZ);
wr.ifr_name[IFNAMSIZ - 1] = 0;
strncpy(wr.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
if (ioctl(s.get(), SIOCGIWNAME, &wr) != -1)
return InterfaceInfo::Type::kWifi;
struct ethtool_cmd ecmd {};
ecmd.cmd = ETHTOOL_GSET;
struct ifreq ifr {};
static_assert(sizeof(ifr.ifr_name) == IFNAMSIZ,
"expected size of interface name fields");
OSP_CHECK_LT(ifname.size(), IFNAMSIZ);
wr.ifr_name[IFNAMSIZ - 1] = 0;
strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
ifr.ifr_data = reinterpret_cast<char*>(&ecmd);
if (ioctl(s.get(), SIOCETHTOOL, &ifr) != -1) {
return InterfaceInfo::Type::kEthernet;
}
return InterfaceInfo::Type::kOther;
}
// Reads an interface's name, hardware address, and type from `rta` and places
// the results in `info`. `rta` is the first attribute structure returned as
// part of an RTM_NEWLINK message. `attrlen` is the total length of the buffer
// pointed to by `rta`.
void GetInterfaceAttributes(struct rtattr* rta,
unsigned int attrlen,
bool is_loopback,
InterfaceInfo* info) {
for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
if (rta->rta_type == IFLA_IFNAME) {
info->name =
GetInterfaceName(reinterpret_cast<const char*>(RTA_DATA(rta)));
} else if (rta->rta_type == IFLA_ADDRESS) {
ByteView address_bytes(reinterpret_cast<uint8_t*>(RTA_DATA(rta)),
RTA_PAYLOAD(rta));
info->hardware_address.assign(address_bytes.begin(), address_bytes.end());
}
}
if (is_loopback) {
info->type = InterfaceInfo::Type::kLoopback;
} else {
info->type = GetInterfaceType(info->name);
}
}
// Reads the IPv4 or IPv6 address that comes from an RTM_NEWADDR message and
// places the result in `address`. `rta` is the first attribute structure
// returned by the message and `attrlen` is the total length of the buffer
// pointed to by `rta`. `ifname` is the name of the interface to which we
// believe the address belongs based on interface index matching. It is only
// used for sanity checking.
std::optional<IPAddress> GetIPAddressOrNull(struct rtattr* rta,
unsigned int attrlen,
IPAddress::Version version,
const std::string& ifname) {
const size_t expected_address_size = version == IPAddress::Version::kV4
? IPAddress::kV4Size
: IPAddress::kV6Size;
bool have_local = false;
IPAddress address;
IPAddress local;
for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
if (rta->rta_type == IFA_LABEL) {
const char* const label = reinterpret_cast<const char*>(RTA_DATA(rta));
if (ifname != label) {
OSP_LOG_ERROR << "Interface label mismatch! Expected: " << ifname
<< ", Have: " << label;
return std::nullopt;
}
} else if (rta->rta_type == IFA_ADDRESS) {
OSP_CHECK_EQ(expected_address_size, RTA_PAYLOAD(rta));
address =
IPAddress(version, std::span(static_cast<uint8_t*>(RTA_DATA(rta)),
expected_address_size));
} else if (rta->rta_type == IFA_LOCAL) {
OSP_CHECK_EQ(expected_address_size, RTA_PAYLOAD(rta));
have_local = true;
local = IPAddress(version, std::span(static_cast<uint8_t*>(RTA_DATA(rta)),
expected_address_size));
}
}
return have_local ? local : address;
}
std::vector<InterfaceInfo> GetLinkInfo() {
ScopedFd fd(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
if (!fd) {
OSP_LOG_WARN << "netlink socket() failed: " << errno << " - "
<< strerror(errno);
return {};
}
{
// nl_pid = 0 for the kernel.
struct sockaddr_nl peer {};
peer.nl_family = AF_NETLINK;
struct {
struct nlmsghdr header {};
struct ifinfomsg msg {};
} request;
request.header.nlmsg_len = sizeof(request);
request.header.nlmsg_type = RTM_GETLINK;
request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
request.header.nlmsg_seq = 0;
request.header.nlmsg_pid = 0;
request.msg.ifi_family = AF_UNSPEC;
struct iovec iov {
&request, request.header.nlmsg_len
};
struct msghdr msg {};
msg.msg_name = &peer;
msg.msg_namelen = sizeof(peer);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
if (sendmsg(fd.get(), &msg, 0) < 0) {
OSP_LOG_ERROR << "netlink sendmsg() failed: " << errno << " - "
<< strerror(errno);
return {};
}
}
std::vector<InterfaceInfo> info_list;
{
char buf[kNetlinkRecvmsgBufSize]{};
struct iovec iov {
buf, sizeof(buf)
};
struct sockaddr_nl source_address {};
struct msghdr msg {};
struct nlmsghdr* netlink_header = nullptr;
msg.msg_name = &source_address;
msg.msg_namelen = sizeof(source_address);
msg.msg_iov = &iov;
msg.msg_iovlen = 1, msg.msg_control = nullptr, msg.msg_controllen = 0,
msg.msg_flags = 0;
bool done = false;
while (!done) {
size_t len = recvmsg(fd.get(), &msg, 0);
for (netlink_header = reinterpret_cast<struct nlmsghdr*>(buf);
NLMSG_OK(netlink_header, len);
netlink_header = NLMSG_NEXT(netlink_header, len)) {
// The end of multipart message.
if (netlink_header->nlmsg_type == NLMSG_DONE) {
done = true;
break;
} else if (netlink_header->nlmsg_type == NLMSG_ERROR) {
done = true;
OSP_LOG_ERROR << "netlink error msg: "
<< reinterpret_cast<struct nlmsgerr*>(
NLMSG_DATA(netlink_header))
->error;
continue;
} else if ((netlink_header->nlmsg_flags & NLM_F_MULTI) == 0) {
// If this is not a multi-part message, we don't need to wait for an
// NLMSG_DONE message; this is the only message.
done = true;
}
// RTM_NEWLINK messages describe existing network links on the host.
if (netlink_header->nlmsg_type != RTM_NEWLINK)
continue;
struct ifinfomsg* interface_info =
static_cast<struct ifinfomsg*>(NLMSG_DATA(netlink_header));
// Only process interfaces which are active (up).
if (!(interface_info->ifi_flags & IFF_UP)) {
continue;
}
info_list.emplace_back();
InterfaceInfo& info = info_list.back();
info.index = interface_info->ifi_index;
GetInterfaceAttributes(IFLA_RTA(interface_info),
IFLA_PAYLOAD(netlink_header),
interface_info->ifi_flags & IFF_LOOPBACK, &info);
}
}
}
return info_list;
}
void PopulateSubnetsOrClearList(std::vector<InterfaceInfo>& info_list) {
ScopedFd fd(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
if (!fd) {
OSP_LOG_ERROR << "netlink socket() failed: " << errno << " - "
<< strerror(errno);
info_list.clear();
return;
}
{
// nl_pid = 0 for the kernel.
struct sockaddr_nl peer {};
peer.nl_family = AF_NETLINK;
struct {
struct nlmsghdr header {};
struct ifaddrmsg msg {};
} request;
request.header.nlmsg_len = sizeof(request);
request.header.nlmsg_type = RTM_GETADDR;
request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
request.header.nlmsg_seq = 1;
request.header.nlmsg_pid = 0;
request.msg.ifa_family = AF_UNSPEC;
struct iovec iov {
&request, request.header.nlmsg_len
};
struct msghdr msg {};
msg.msg_name = &peer;
msg.msg_namelen = sizeof(peer);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
if (sendmsg(fd.get(), &msg, 0) < 0) {
OSP_LOG_ERROR << "sendmsg failed: " << errno << " - " << strerror(errno);
info_list.clear();
return;
}
}
{
char buf[kNetlinkRecvmsgBufSize]{};
struct iovec iov {
buf, sizeof(buf)
};
struct sockaddr_nl source_address {};
struct msghdr msg {};
struct nlmsghdr* netlink_header = nullptr;
msg.msg_name = &source_address;
msg.msg_namelen = sizeof(source_address);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
bool done = false;
while (!done) {
size_t len = recvmsg(fd.get(), &msg, 0);
for (netlink_header = reinterpret_cast<struct nlmsghdr*>(buf);
NLMSG_OK(netlink_header, len);
netlink_header = NLMSG_NEXT(netlink_header, len)) {
if (netlink_header->nlmsg_type == NLMSG_DONE) {
done = true;
break;
} else if (netlink_header->nlmsg_type == NLMSG_ERROR) {
done = true;
OSP_LOG_ERROR << "netlink error msg: "
<< reinterpret_cast<struct nlmsgerr*>(
NLMSG_DATA(netlink_header))
->error;
continue;
} else if ((netlink_header->nlmsg_flags & NLM_F_MULTI) == 0) {
// If this is not a multi-part message, we don't need to wait for an
// NLMSG_DONE message; this is the only message.
done = true;
}
if (netlink_header->nlmsg_type != RTM_NEWADDR)
continue;
struct ifaddrmsg* interface_address =
static_cast<struct ifaddrmsg*>(NLMSG_DATA(netlink_header));
const auto it = std::find_if(
info_list.begin(), info_list.end(),
[index = interface_address->ifa_index](const InterfaceInfo& info) {
return info.index == index;
});
if (it == info_list.end()) {
OSP_DVLOG << "skipping address for interface "
<< interface_address->ifa_index;
continue;
}
if (interface_address->ifa_family == AF_INET ||
interface_address->ifa_family == AF_INET6) {
const auto address_or_null = GetIPAddressOrNull(
IFA_RTA(interface_address), IFA_PAYLOAD(netlink_header),
interface_address->ifa_family == AF_INET
? IPAddress::Version::kV4
: IPAddress::Version::kV6,
it->name);
if (address_or_null) {
it->addresses.emplace_back(*address_or_null,
interface_address->ifa_prefixlen);
}
} else {
OSP_LOG_ERROR << "Unknown address family: "
<< interface_address->ifa_family;
}
}
}
}
}
} // namespace
std::vector<InterfaceInfo> GetNetworkInterfaces() {
std::vector<InterfaceInfo> interfaces = GetLinkInfo();
PopulateSubnetsOrClearList(interfaces);
return interfaces;
}
} // namespace openscreen

View file

@ -0,0 +1,137 @@
// Copyright (c) 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/platform_client_posix.h"
#include <chrono>
#include <functional>
#include <utility>
#include <vector>
#include "platform/base/trivial_clock_traits.h"
#include "platform/impl/udp_socket_reader_posix.h"
namespace openscreen {
using clock_operators::operator<<;
// static
PlatformClientPosix* PlatformClientPosix::instance_ = nullptr;
// static
void PlatformClientPosix::Create(Clock::duration networking_operation_timeout,
std::unique_ptr<TaskRunnerImpl> task_runner) {
SetInstance(new PlatformClientPosix(networking_operation_timeout,
std::move(task_runner)));
}
// static
void PlatformClientPosix::Create(Clock::duration networking_operation_timeout) {
SetInstance(new PlatformClientPosix(networking_operation_timeout));
}
// static
void PlatformClientPosix::ShutDown() {
OSP_CHECK(instance_);
delete instance_;
instance_ = nullptr;
}
UdpSocketReaderPosix* PlatformClientPosix::udp_socket_reader() {
std::call_once(udp_socket_reader_initialization_, [this]() {
udp_socket_reader_ =
std::make_unique<UdpSocketReaderPosix>(*socket_handle_waiter());
});
return udp_socket_reader_.get();
}
TaskRunner& PlatformClientPosix::GetTaskRunner() {
return *task_runner_;
}
PlatformClientPosix::~PlatformClientPosix() {
OSP_DVLOG << "Shutting down the Task Runner...";
task_runner_->RequestStopSoon();
if (task_runner_thread_ && task_runner_thread_->joinable()) {
task_runner_thread_->join();
OSP_DVLOG << "\tTask Runner shutdown complete!";
}
OSP_DVLOG << "Shutting down network operations...";
networking_loop_running_.store(false);
networking_loop_thread_.join();
OSP_DVLOG << "\tNetwork operation shutdown complete!";
}
// static
void PlatformClientPosix::SetInstance(PlatformClientPosix* instance) {
OSP_CHECK(!instance_);
instance_ = instance;
}
PlatformClientPosix::PlatformClientPosix(
Clock::duration networking_operation_timeout)
: task_runner_(new TaskRunnerImpl(Clock::now)),
networking_loop_timeout_(networking_operation_timeout),
networking_loop_thread_(&PlatformClientPosix::RunNetworkLoopUntilStopped,
this),
task_runner_thread_(
std::thread(&TaskRunnerImpl::RunUntilStopped, task_runner_.get())) {}
PlatformClientPosix::PlatformClientPosix(
Clock::duration networking_operation_timeout,
std::unique_ptr<TaskRunnerImpl> task_runner)
: task_runner_(std::move(task_runner)),
networking_loop_timeout_(networking_operation_timeout),
networking_loop_thread_(&PlatformClientPosix::RunNetworkLoopUntilStopped,
this) {}
SocketHandleWaiterPosix* PlatformClientPosix::socket_handle_waiter() {
std::call_once(waiter_initialization_, [this]() {
waiter_ = std::make_unique<SocketHandleWaiterPosix>(&Clock::now);
waiter_created_.store(true);
});
return waiter_.get();
}
void PlatformClientPosix::RunNetworkLoopUntilStopped() {
#if OSP_DCHECK_IS_ON()
Clock::time_point last_time = Clock::now();
int iterations = 0;
#endif
while (networking_loop_running_.load()) {
#if OSP_DCHECK_IS_ON()
++iterations;
const Clock::time_point current_time = Clock::now();
const Clock::duration delta = current_time - last_time;
if (delta > std::chrono::seconds(1)) {
OSP_DCHECK_GT(iterations, 0);
OSP_VLOG << "network loop execution time averaged "
<< (delta / iterations) << " over the last second.";
last_time = current_time;
iterations = 0;
}
#endif
if (!waiter_created_.load()) {
std::this_thread::sleep_for(networking_loop_timeout_);
continue;
}
const Error process_error =
socket_handle_waiter()->ProcessHandles(networking_loop_timeout_);
// We may receive an "again" error code if there were no sockets to process.
if (process_error.code() == Error::Code::kAgain) {
std::this_thread::sleep_for(networking_loop_timeout_);
continue;
// If there is a socket error it should be handled elsewhere. Just log
// the error here.
} else if (!process_error.ok()) {
OSP_LOG_ERROR << "error occurred while processing handles. error="
<< process_error;
}
}
}
} // namespace openscreen

View file

@ -0,0 +1,130 @@
// Copyright (c) 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_PLATFORM_CLIENT_POSIX_H_
#define PLATFORM_IMPL_PLATFORM_CLIENT_POSIX_H_
#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <vector>
#include "platform/api/time.h"
#include "platform/impl/socket_handle_waiter_posix.h"
#include "platform/impl/task_runner.h"
// LOCAL PATCH (breadcast): upstream also wires up a TlsDataRouterPosix here
// for TlsConnectionFactory support. breadcast only uses this vendored subset
// of openscreen for the Cast Streaming UDP RTP/RTCP data path -- the TLS
// CASTV2 control channel is handled by the existing rust_cast-based Rust
// code -- so the TLS data router (and the BoringSSL-flavored util/crypto/*
// helpers it pulls in) has been stripped entirely rather than ported. See
// vendor/openscreen/PATCHES.md.
namespace openscreen {
class UdpSocketReaderPosix;
// Creates and provides access to singletons used by the default platform
// implementation. An instance must be created before an application uses any
// public modules in the Open Screen Library.
//
// ShutDown() should be called to destroy the PlatformClientPosix's singletons
// and TaskRunner to save resources when library APIs are not in use.
// ShutDown() calls TaskRunner::RunUntilStopped() to run any pending cleanup
// tasks.
//
// Create and ShutDown must be called in the same sequence.
//
// FIXME: Remove Create and Shutdown and use the ctor/dtor directly.
class PlatformClientPosix {
public:
// Initializes the platform implementation.
//
// `networking_loop_interval` sets the minimum amount of time that should pass
// between iterations of the loop used to handle networking operations. Higher
// values will result in less time being spent on these operations, but also
// less performant networking operations. Be careful setting values larger
// than a few hundred microseconds.
//
// `networking_operation_timeout` sets how much time may be spent on a
// single networking operation type.
//
// `task_runner` is a client-provided TaskRunner implementation.
static void Create(Clock::duration networking_operation_timeout,
std::unique_ptr<TaskRunnerImpl> task_runner);
// Initializes the platform implementation and creates a new TaskRunner (which
// starts a new thread).
static void Create(Clock::duration networking_operation_timeout);
// Shuts down and deletes the PlatformClient instance currently stored as a
// singleton. This method is expected to be called before program exit. After
// calling this method, if the client wishes to continue using the platform
// library, Create() must be called again.
static void ShutDown();
static PlatformClientPosix* GetInstance() { return instance_; }
PlatformClientPosix(const PlatformClientPosix&) = delete;
PlatformClientPosix(PlatformClientPosix&&) noexcept = delete;
PlatformClientPosix& operator=(const PlatformClientPosix&) = delete;
PlatformClientPosix& operator=(PlatformClientPosix&&) = delete;
// This method is thread-safe.
// FIXME: Rename to GetUdpSocketReader()
UdpSocketReaderPosix* udp_socket_reader();
// Returns the TaskRunner associated with this PlatformClient.
// NOTE: This method is expected to be thread safe.
TaskRunner& GetTaskRunner();
protected:
// Called by ShutDown().
~PlatformClientPosix();
static void SetInstance(PlatformClientPosix* client);
private:
explicit PlatformClientPosix(Clock::duration networking_operation_timeout);
PlatformClientPosix(Clock::duration networking_operation_timeout,
std::unique_ptr<TaskRunnerImpl> task_runner);
// This method is thread-safe.
SocketHandleWaiterPosix* socket_handle_waiter();
void RunNetworkLoopUntilStopped();
std::unique_ptr<TaskRunnerImpl> task_runner_;
// Track whether the associated instance variable has been created yet.
std::atomic_bool waiter_created_{false};
// Parameters for networking loop.
std::atomic_bool networking_loop_running_{true};
Clock::duration networking_loop_timeout_;
// Flags used to ensure that initialization of below instance objects occurs
// only once across all threads.
std::once_flag waiter_initialization_;
std::once_flag udp_socket_reader_initialization_;
// Instance objects are created at runtime when they are first needed.
std::unique_ptr<SocketHandleWaiterPosix> waiter_;
std::unique_ptr<UdpSocketReaderPosix> udp_socket_reader_;
// Threads for running TaskRunner and OperationLoop instances.
// NOTE: These must be declared last to avoid nondterministic failures.
std::thread networking_loop_thread_;
std::optional<std::thread> task_runner_thread_;
static PlatformClientPosix* instance_;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_PLATFORM_CLIENT_POSIX_H_

View file

@ -0,0 +1,71 @@
// 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.
#ifndef PLATFORM_IMPL_SCOPED_PIPE_H_
#define PLATFORM_IMPL_SCOPED_PIPE_H_
#include <unistd.h>
#include <utility>
namespace openscreen {
struct IntFdTraits {
using PipeType = int;
static constexpr int kInvalidValue = -1;
static void Close(PipeType pipe) { close(pipe); }
};
// This class wraps file descriptor and uses RAII to ensure it is closed
// properly when control leaves its scope. It is parameterized by a traits type
// which defines the value type of the file descriptor, an invalid value, and a
// closing function.
//
// This class is move-only as it represents ownership of the wrapped file
// descriptor. It is not thread-safe.
template <typename Traits>
class ScopedPipe {
public:
using PipeType = typename Traits::PipeType;
ScopedPipe() : pipe_(Traits::kInvalidValue) {}
explicit ScopedPipe(PipeType pipe) : pipe_(pipe) {}
ScopedPipe(const ScopedPipe&) = delete;
ScopedPipe(ScopedPipe&& other) : pipe_(other.release()) {}
~ScopedPipe() {
if (pipe_ != Traits::kInvalidValue)
Traits::Close(release());
}
ScopedPipe& operator=(ScopedPipe&& other) {
if (pipe_ != Traits::kInvalidValue)
Traits::Close(release());
pipe_ = other.release();
return *this;
}
PipeType get() const { return pipe_; }
PipeType release() {
PipeType pipe = pipe_;
pipe_ = Traits::kInvalidValue;
return pipe;
}
bool operator==(const ScopedPipe& other) const {
return pipe_ == other.pipe_;
}
bool operator!=(const ScopedPipe& other) const { return !(*this == other); }
explicit operator bool() const { return pipe_ != Traits::kInvalidValue; }
private:
PipeType pipe_;
};
using ScopedFd = ScopedPipe<IntFdTraits>;
} // namespace openscreen
#endif // PLATFORM_IMPL_SCOPED_PIPE_H_

View file

@ -0,0 +1,129 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/socket_address_posix.h"
#include <algorithm>
#include <vector>
#include "util/osp_logging.h"
namespace openscreen {
SocketAddressPosix::SocketAddressPosix(const struct sockaddr& address) {
if (address.sa_family == AF_INET) {
std::copy_n(reinterpret_cast<const uint8_t*>(&address),
sizeof(struct sockaddr_in),
reinterpret_cast<uint8_t*>(&internal_address_.v4));
RecomputeEndpoint(IPAddress::Version::kV4);
} else if (address.sa_family == AF_INET6) {
std::copy_n(reinterpret_cast<const uint8_t*>(&address),
sizeof(struct sockaddr_in6),
reinterpret_cast<uint8_t*>(&internal_address_.v6));
RecomputeEndpoint(IPAddress::Version::kV6);
} else {
// Not IPv4 or IPv6.
OSP_NOTREACHED();
}
}
SocketAddressPosix::SocketAddressPosix(const IPEndpoint& endpoint)
: endpoint_(endpoint) {
if (endpoint.address.IsV4()) {
internal_address_.v4 = ToSockAddrIn(endpoint);
} else {
internal_address_.v6 = ToSockAddrIn6(endpoint);
}
}
struct sockaddr* SocketAddressPosix::address() {
switch (version()) {
case IPAddress::Version::kV4:
return reinterpret_cast<struct sockaddr*>(&internal_address_.v4);
case IPAddress::Version::kV6:
return reinterpret_cast<struct sockaddr*>(&internal_address_.v6);
default:
OSP_NOTREACHED();
}
}
const struct sockaddr* SocketAddressPosix::address() const {
switch (version()) {
case IPAddress::Version::kV4:
return reinterpret_cast<const struct sockaddr*>(&internal_address_.v4);
case IPAddress::Version::kV6:
return reinterpret_cast<const struct sockaddr*>(&internal_address_.v6);
default:
OSP_NOTREACHED();
}
}
socklen_t SocketAddressPosix::size() const {
switch (version()) {
case IPAddress::Version::kV4:
return sizeof(struct sockaddr_in);
case IPAddress::Version::kV6:
return sizeof(struct sockaddr_in6);
default:
OSP_NOTREACHED();
}
}
void SocketAddressPosix::RecomputeEndpoint() {
RecomputeEndpoint(endpoint_.address.version());
}
void SocketAddressPosix::RecomputeEndpoint(IPAddress::Version version) {
switch (version) {
case IPAddress::Version::kV4:
endpoint_.address = GetIPAddressFromSockAddr(internal_address_.v4);
endpoint_.port = ntohs(internal_address_.v4.sin_port);
break;
case IPAddress::Version::kV6:
endpoint_.address = GetIPAddressFromSockAddr(internal_address_.v6);
endpoint_.port = ntohs(internal_address_.v6.sin6_port);
break;
}
}
IPAddress GetIPAddressFromSockAddr(const struct sockaddr_in& sa) {
static_assert(IPAddress::kV4Size == sizeof(sa.sin_addr.s_addr),
"IPv4 address size mismatch.");
return IPAddress(
IPAddress::Version::kV4,
std::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(&sa.sin_addr.s_addr), 4));
}
IPAddress GetIPAddressFromSockAddr(const struct sockaddr_in6& sa) {
return IPAddress(std::span<const uint8_t, 16>(sa.sin6_addr.s6_addr, 16),
sa.sin6_scope_id);
}
struct sockaddr_in ToSockAddrIn(const IPEndpoint& endpoint) {
OSP_CHECK(endpoint.address.IsV4());
struct sockaddr_in out{};
out.sin_family = AF_INET;
out.sin_port = htons(endpoint.port);
endpoint.address.CopyTo(
std::span<uint8_t>(reinterpret_cast<uint8_t*>(&out.sin_addr.s_addr), 4));
return out;
}
struct sockaddr_in6 ToSockAddrIn6(const IPEndpoint& endpoint) {
OSP_CHECK(endpoint.address.IsV6());
struct sockaddr_in6 out{};
out.sin6_family = AF_INET6;
out.sin6_flowinfo = 0;
out.sin6_scope_id = 0;
if (endpoint.address.IsLinkLocal() && endpoint.address.GetScopeId() != 0) {
out.sin6_scope_id = endpoint.address.GetScopeId();
}
out.sin6_port = htons(endpoint.port);
endpoint.address.CopyTo(
std::span<uint8_t>(reinterpret_cast<uint8_t*>(&out.sin6_addr), 16));
return out;
}
} // namespace openscreen

View file

@ -0,0 +1,66 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_SOCKET_ADDRESS_POSIX_H_
#define PLATFORM_IMPL_SOCKET_ADDRESS_POSIX_H_
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include "platform/base/ip_address.h"
namespace openscreen {
class SocketAddressPosix {
public:
explicit SocketAddressPosix(const struct sockaddr& address);
explicit SocketAddressPosix(const IPEndpoint& endpoint);
SocketAddressPosix(const SocketAddressPosix&) = default;
SocketAddressPosix(SocketAddressPosix&&) noexcept = default;
SocketAddressPosix& operator=(const SocketAddressPosix&) = default;
SocketAddressPosix& operator=(SocketAddressPosix&&) noexcept = default;
struct sockaddr* address();
const struct sockaddr* address() const;
socklen_t size() const;
IPAddress::Version version() const { return endpoint_.address.version(); }
IPEndpoint endpoint() const { return endpoint_; }
// Recomputes `endpoint_` if `internal_address_` is written to directly, e.g.
// by a system call.
void RecomputeEndpoint();
private:
void RecomputeEndpoint(IPAddress::Version version);
// The way the sockaddr_* family works in POSIX is pretty unintuitive. The
// sockaddr_in and sockaddr_in6 structs can be reinterpreted as type
// sockaddr, however they don't have a common parent--the types are unrelated.
// Our solution for this is to wrap sockaddr_in* in a union, so that our code
// can be simplified since most platform APIs just take a sockaddr.
union SocketAddressIn {
struct sockaddr_in v4;
struct sockaddr_in6 v6;
};
SocketAddressIn internal_address_;
IPEndpoint endpoint_;
};
IPAddress GetIPAddressFromSockAddr(const struct sockaddr_in& sa);
IPAddress GetIPAddressFromSockAddr(const struct sockaddr_in6& sa);
struct sockaddr_in ToSockAddrIn(const IPEndpoint& endpoint);
struct sockaddr_in6 ToSockAddrIn6(const IPEndpoint& endpoint);
} // namespace openscreen
#endif // PLATFORM_IMPL_SOCKET_ADDRESS_POSIX_H_

View file

@ -0,0 +1,27 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_SOCKET_HANDLE_H_
#define PLATFORM_IMPL_SOCKET_HANDLE_H_
#include <cstdlib>
namespace openscreen {
// A SocketHandle is the handle used to access a Socket by the underlying
// platform.
struct SocketHandle;
struct SocketHandleHash {
size_t operator()(const SocketHandle& handle) const;
};
bool operator==(const SocketHandle& lhs, const SocketHandle& rhs);
inline bool operator!=(const SocketHandle& lhs, const SocketHandle& rhs) {
return !(lhs == rhs);
}
} // namespace openscreen
#endif // PLATFORM_IMPL_SOCKET_HANDLE_H_

View file

@ -0,0 +1,22 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/socket_handle_posix.h"
#include <cstdlib>
#include <functional>
namespace openscreen {
SocketHandle::SocketHandle(int descriptor) : fd(descriptor) {}
bool operator==(const SocketHandle& lhs, const SocketHandle& rhs) {
return lhs.fd == rhs.fd;
}
size_t SocketHandleHash::operator()(const SocketHandle& handle) const {
return std::hash<int>()(handle.fd);
}
} // namespace openscreen

View file

@ -0,0 +1,19 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_SOCKET_HANDLE_POSIX_H_
#define PLATFORM_IMPL_SOCKET_HANDLE_POSIX_H_
#include "platform/impl/socket_handle.h"
namespace openscreen {
struct SocketHandle {
explicit SocketHandle(int descriptor);
int fd;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_SOCKET_HANDLE_POSIX_H_

View file

@ -0,0 +1,170 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/socket_handle_waiter.h"
#include <algorithm>
#include <atomic>
#include "platform/impl/socket_handle_posix.h"
#include "util/osp_logging.h"
#include "util/std_util.h"
namespace openscreen {
SocketHandleWaiter::SocketHandleWaiter(ClockNowFunctionPtr now_function)
: now_function_(now_function) {}
SocketHandleWaiter::Subscriber::~Subscriber() = default;
SocketHandleWaiter::~SocketHandleWaiter() = default;
void SocketHandleWaiter::Subscribe(Subscriber* subscriber,
SocketHandleRef handle,
uint32_t flags) {
std::lock_guard<std::mutex> lock(mutex_);
if (handle_mappings_.find(handle) == handle_mappings_.end()) {
handle_mappings_.emplace(handle, SocketSubscription{subscriber, flags});
}
}
void SocketHandleWaiter::Unsubscribe(Subscriber* subscriber,
SocketHandleRef handle) {
std::lock_guard<std::mutex> lock(mutex_);
auto iterator = handle_mappings_.find(handle);
if (handle_mappings_.find(handle) != handle_mappings_.end()) {
handle_mappings_.erase(iterator);
}
}
void SocketHandleWaiter::UnsubscribeAll(Subscriber* subscriber) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = handle_mappings_.begin(); it != handle_mappings_.end();) {
if (it->second.subscriber == subscriber) {
it = handle_mappings_.erase(it);
} else {
it++;
}
}
}
void SocketHandleWaiter::OnHandleDeletion(
Subscriber* subscriber,
SocketHandleRef handle,
bool disable_locking_for_testing) OSP_NO_THREAD_SAFETY_ANALYSIS {
std::unique_lock<std::mutex> lock(mutex_);
auto it = handle_mappings_.find(handle);
if (it != handle_mappings_.end()) {
handle_mappings_.erase(it);
if (!disable_locking_for_testing) {
handles_being_deleted_.push_back(handle);
OSP_DVLOG << "Starting to block for handle deletion";
// This code will allow us to block completion of the socket destructor
// (and subsequent invalidation of pointers to this socket) until we no
// longer are waiting on a SELECT(...) call to it, since we only signal
// this condition variable's wait(...) to proceed outside of SELECT(...).
while (Contains(handles_being_deleted_, handle)) {
handle_deletion_block_.wait(lock);
}
OSP_DVLOG << "\tDone blocking for handle deletion!";
}
}
}
void SocketHandleWaiter::ProcessReadyHandles(
std::vector<HandleWithSubscription>* handles,
Clock::duration timeout) {
if (handles->empty()) {
return;
}
Clock::time_point start_time = now_function_();
// Process the stalest handles one by one until we hit our timeout.
do {
Clock::time_point oldest_time = Clock::time_point::max();
HandleWithSubscription& oldest_handle = handles->at(0);
for (HandleWithSubscription& handle : *handles) {
// Skip already processed handles.
if (handle.subscription->last_updated >= start_time) {
continue;
}
// Select the oldest handle.
if (handle.subscription->last_updated < oldest_time) {
oldest_time = handle.subscription->last_updated;
oldest_handle = handle;
}
}
// Already processed all handles.
if (oldest_time == Clock::time_point::max()) {
return;
}
// Process the oldest handle.
oldest_handle.subscription->last_updated = now_function_();
oldest_handle.subscription->subscriber->ProcessReadyHandle(
oldest_handle.ready_handle.handle, oldest_handle.ready_handle.flags);
} while (now_function_() - start_time <= timeout);
}
Error SocketHandleWaiter::ProcessHandles(Clock::duration timeout) {
Clock::time_point start_time = now_function_();
std::vector<HandleWithFlags> handles;
{
std::lock_guard<std::mutex> lock(mutex_);
handles_being_deleted_.clear();
handle_deletion_block_.notify_all();
handles.reserve(handle_mappings_.size());
for (auto& pair : handle_mappings_) {
uint32_t flags = pair.second.flags;
// Remove the write flag if there is no pending write.
if (flags & kWritable) {
const bool has_pending_write =
pair.second.subscriber->HasPendingWrite(pair.first);
if (!has_pending_write) {
flags &= ~kWritable;
}
}
handles.push_back(HandleWithFlags{.handle = pair.first, .flags = flags});
}
}
if (handles.empty()) {
return Error::Code::kAgain;
}
Clock::time_point current_time = now_function_();
Clock::duration remaining_timeout = timeout - (current_time - start_time);
ErrorOr<std::vector<HandleWithFlags>> changed_handles =
AwaitSocketsReady(handles, remaining_timeout);
std::vector<HandleWithSubscription> ready_handles;
{
std::lock_guard<std::mutex> lock(mutex_);
handles_being_deleted_.clear();
handle_deletion_block_.notify_all();
if (changed_handles) {
auto& ch = changed_handles.value();
ready_handles.reserve(ch.size());
for (const auto& handle : ch) {
auto mapping_it = handle_mappings_.find(handle.handle);
if (mapping_it != handle_mappings_.end()) {
ready_handles.push_back(
HandleWithSubscription{handle, &(mapping_it->second)});
}
}
}
if (changed_handles.is_error()) {
return changed_handles.error();
}
current_time = now_function_();
remaining_timeout = timeout - (current_time - start_time);
ProcessReadyHandles(&ready_handles, remaining_timeout);
}
return Error::None();
}
} // namespace openscreen

View file

@ -0,0 +1,151 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_SOCKET_HANDLE_WAITER_H_
#define PLATFORM_IMPL_SOCKET_HANDLE_WAITER_H_
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "platform/api/time.h"
#include "platform/base/error.h"
#include "platform/impl/socket_handle.h"
#include "util/raw_ptr.h"
#include "util/thread_annotations.h"
namespace openscreen {
// The class responsible for calling platform-level method to watch UDP sockets
// for available read data. Reading from these sockets is handled at a higher
// layer.
class SocketHandleWaiter {
public:
using SocketHandleRef = std::reference_wrapper<const SocketHandle>;
// Used to manage what types of events subscribers are subscribed to.
enum Flags {
kReadable = 1 << 0,
kWritable = 1 << 1,
};
// Common flag configurations.
static inline constexpr uint32_t kReadWriteFlags =
Flags::kReadable | Flags::kWritable;
class Subscriber {
public:
virtual ~Subscriber();
// Provides a socket handle to the subscriber which has data waiting to be
// processed.
virtual void ProcessReadyHandle(SocketHandleRef handle, uint32_t flags) = 0;
// Method used to optimize event notifications. Generally speaking,
// sockets are ready for writing very often, causing the network event
// loop to be really busy -- a select() call may complete as frequently as
// every few nanoseconds -- so we really only want to be notified that a
// socket is ready for writing when we actually have something to write.
//
// NOTE: this is only used if the subscriber is subscribed to write events.
virtual bool HasPendingWrite(SocketHandleRef handle) = 0;
};
explicit SocketHandleWaiter(ClockNowFunctionPtr now_function);
SocketHandleWaiter(const SocketHandleWaiter&) = delete;
SocketHandleWaiter(SocketHandleWaiter&&) noexcept = delete;
SocketHandleWaiter& operator=(const SocketHandleWaiter&) = delete;
SocketHandleWaiter& operator=(SocketHandleWaiter&&) = delete;
virtual ~SocketHandleWaiter();
// Start notifying `subscriber` whenever `handle` has an event. May be called
// multiple times, to be notified for multiple handles, but should not be
// called multiple times for the same handle.
void Subscribe(Subscriber* subscriber,
SocketHandleRef handle,
uint32_t flags);
// Stop receiving notifications for one of the handles currently subscribed
// to.
void Unsubscribe(Subscriber* subscriber, SocketHandleRef handle);
// Stop receiving notifications for all handles currently subscribed to, or
// no-op if there are no subscriptions.
void UnsubscribeAll(Subscriber* subscriber);
// Called when a handle will be deleted to ensure that deletion can proceed
// safely.
void OnHandleDeletion(Subscriber* subscriber,
SocketHandleRef handle,
bool disable_locking_for_testing = false)
OSP_NO_THREAD_SAFETY_ANALYSIS;
// Gets all socket handles to process, checks them for readable data, and
// handles any changes that have occurred.
Error ProcessHandles(Clock::duration timeout);
protected:
struct HandleWithFlags {
SocketHandleRef handle;
uint32_t flags;
};
// Waits until data is available in one of the provided sockets or the
// provided timeout has passed - whichever is first. If any sockets have data
// available, they are returned.
//
// NOTE: The handle `flags` are checked against the subscriber's
// HasPendingWrite() method to ensure that the kWritable flag is only passed
// if there is a pending write before this method is called. The subscriber
// may be deleted while this method is being invoked, however the handle
// itself is guaranteed to not be deleted until the invocation of this method
// has been completed.
virtual ErrorOr<std::vector<HandleWithFlags>> AwaitSocketsReady(
const std::vector<HandleWithFlags>& sockets,
const Clock::duration& timeout) = 0;
private:
struct SocketSubscription {
raw_ptr<Subscriber> subscriber = nullptr;
// Subscribers are only informed of flags that they are interested in.
uint32_t flags = 0;
Clock::time_point last_updated = Clock::time_point::min();
};
struct HandleWithSubscription {
HandleWithFlags ready_handle;
// Reference to the original subscription in the unordered map, so
// we can keep track of when we updated this socket handle.
raw_ptr<SocketSubscription> subscription;
};
// Call the subscriber associated with each changed handle. Handles are only
// processed until `timeout` is exceeded. Must be called with `mutex_` held.
void ProcessReadyHandles(std::vector<HandleWithSubscription>* handles,
Clock::duration timeout);
// Guards against concurrent access to all other class data members.
std::mutex mutex_;
// Blocks deletion of handles until they are no longer being watched.
std::condition_variable handle_deletion_block_;
// Set of handles currently being deleted, for ensuring handle_deletion_block_
// does not exit prematurely.
std::vector<SocketHandleRef> handles_being_deleted_ OSP_GUARDED_BY(mutex_);
// Set of all socket handles currently being watched, mapped to the subscriber
// that is watching them.
std::unordered_map<SocketHandleRef, SocketSubscription, SocketHandleHash>
handle_mappings_ OSP_GUARDED_BY(mutex_);
const ClockNowFunctionPtr now_function_;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_SOCKET_HANDLE_WAITER_H_

View file

@ -0,0 +1,102 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/socket_handle_waiter_posix.h"
#include <time.h>
#include <algorithm>
#include <vector>
#include "platform/base/error.h"
#include "platform/impl/socket_handle_posix.h"
#include "platform/impl/timeval_posix.h"
#include "platform/impl/udp_socket_posix.h"
#include "util/osp_logging.h"
namespace openscreen {
SocketHandleWaiterPosix::SocketHandleWaiterPosix(
ClockNowFunctionPtr now_function)
: SocketHandleWaiter(now_function) {}
SocketHandleWaiterPosix::~SocketHandleWaiterPosix() = default;
ErrorOr<std::vector<SocketHandleWaiterPosix::HandleWithFlags>>
SocketHandleWaiterPosix::AwaitSocketsReady(
const std::vector<SocketHandleWaiterPosix::HandleWithFlags>& sockets,
const Clock::duration& timeout) {
int max_fd = -1;
fd_set read_handles{};
fd_set write_handles{};
FD_ZERO(&read_handles);
FD_ZERO(&write_handles);
for (const HandleWithFlags& hwf : sockets) {
if (hwf.flags & Flags::kReadable) {
FD_SET(hwf.handle.get().fd, &read_handles);
}
// Only add the socket to the write_handles list if it is configured for
// write events and also has a pending write. This keeps us from polling
// select every few nanoseconds.
if (hwf.flags & Flags::kWritable) {
FD_SET(hwf.handle.get().fd, &write_handles);
}
max_fd = std::max(max_fd, hwf.handle.get().fd);
}
if (max_fd < 0) {
return Error::Code::kIOFailure;
}
struct timeval tv {
ToTimeval(timeout)
};
// This value is set to 'max_fd + 1' by convention. Also, select() is
// level-triggered so incomplete reads/writes by the caller are fine and will
// be picked up again on the next select() call. For more information, see:
// http://man7.org/linux/man-pages/man2/select.2.html
const int max_fd_to_watch = max_fd + 1;
const int rv =
select(max_fd_to_watch, &read_handles, &write_handles, nullptr, &tv);
if (rv == -1) {
// This is the case when an error condition is hit within the select(...)
// command.
return Error::Code::kIOFailure;
} else if (rv == 0) {
// This occurs when no sockets have a pending read.
return Error::Code::kAgain;
}
std::vector<HandleWithFlags> changed_handles;
for (const HandleWithFlags& hwf : sockets) {
uint32_t flags = 0;
if (FD_ISSET(hwf.handle.get().fd, &read_handles)) {
flags |= Flags::kReadable;
}
if (FD_ISSET(hwf.handle.get().fd, &write_handles)) {
flags |= Flags::kWritable;
}
if (flags) {
changed_handles.push_back({hwf.handle, flags});
}
}
return changed_handles;
}
void SocketHandleWaiterPosix::RunUntilStopped() {
const bool was_running = is_running_.exchange(true);
OSP_CHECK(!was_running);
constexpr Clock::duration kHandleReadyTimeout = std::chrono::milliseconds(50);
while (is_running_) {
ProcessHandles(kHandleReadyTimeout);
}
}
void SocketHandleWaiterPosix::RequestStopSoon() {
is_running_.store(false);
}
} // namespace openscreen

View file

@ -0,0 +1,45 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_SOCKET_HANDLE_WAITER_POSIX_H_
#define PLATFORM_IMPL_SOCKET_HANDLE_WAITER_POSIX_H_
#include <unistd.h>
#include <atomic>
#include <mutex>
#include <vector>
#include "platform/impl/socket_handle_waiter.h"
namespace openscreen {
class SocketHandleWaiterPosix : public SocketHandleWaiter {
public:
using SocketHandleRef = SocketHandleWaiter::SocketHandleRef;
using HandleWithFlags = SocketHandleWaiter::HandleWithFlags;
explicit SocketHandleWaiterPosix(ClockNowFunctionPtr now_function);
~SocketHandleWaiterPosix() override;
// Runs the Wait function in a loop until the below RequestStopSoon function
// is called.
void RunUntilStopped();
// Signals for the RunUntilStopped loop to cease running.
void RequestStopSoon();
protected:
ErrorOr<std::vector<HandleWithFlags>> AwaitSocketsReady(
const std::vector<HandleWithFlags>& sockets,
const Clock::duration& timeout) override;
private:
// Atomic so that we can perform atomic exchanges.
std::atomic_bool is_running_;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_SOCKET_HANDLE_WAITER_POSIX_H_

View file

@ -0,0 +1,37 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_SOCKET_STATE_H_
#define PLATFORM_IMPL_SOCKET_STATE_H_
#include <cstdint>
#include <memory>
#include <string>
namespace openscreen {
// TcpSocketState should be used by TCP and TLS sockets for indicating
// current state. NOTE: socket state transitions should only happen in
// the listed order. New states should be added in appropriate order.
enum class TcpSocketState {
// Socket is not connected.
kNotConnected = 0,
// Socket is actively listening for incoming connections.
kListening,
// Socket is currently being connected.
kConnecting,
// Socket is actively connected to a remote address.
kConnected,
// The socket connection has been terminated, either by Close() or
// by the remote side.
kClosed
};
} // namespace openscreen
#endif // PLATFORM_IMPL_SOCKET_STATE_H_

View file

@ -0,0 +1,197 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/task_runner.h"
#include <csignal>
#include <thread>
#include "util/osp_logging.h"
namespace openscreen {
namespace {
// This is mutated by the signal handler installed by RunUntilSignaled(), and is
// checked by RunUntilStopped().
//
// Per the C++14 spec, passing visible changes to memory between a signal
// handler and a program thread must be done through a volatile variable.
volatile enum {
kNotRunning,
kNotSignaled,
kSignaled
} g_signal_state = kNotRunning;
void OnReceivedSignal(int signal) {
g_signal_state = kSignaled;
}
} // namespace
TaskRunnerImpl::TaskWaiter::~TaskWaiter() = default;
TaskRunnerImpl::TaskRunnerImpl(ClockNowFunctionPtr now_function,
TaskWaiter* event_waiter,
Clock::duration waiter_timeout)
: now_function_(now_function),
is_running_(false),
task_waiter_(event_waiter),
waiter_timeout_(waiter_timeout) {}
TaskRunnerImpl::~TaskRunnerImpl() {
// Ensure no thread is currently executing inside RunUntilStopped().
OSP_CHECK_EQ(task_runner_thread_id_, std::thread::id());
}
void TaskRunnerImpl::PostPackagedTask(Task task) {
std::lock_guard<std::mutex> lock(task_mutex_);
tasks_.emplace_back(std::move(task));
if (task_waiter_) {
task_waiter_->OnTaskPosted();
} else {
run_loop_wakeup_.notify_one();
}
}
void TaskRunnerImpl::PostPackagedTaskWithDelay(Task task,
Clock::duration delay) {
std::lock_guard<std::mutex> lock(task_mutex_);
if (delay <= Clock::duration::zero()) {
tasks_.emplace_back(std::move(task));
} else {
delayed_tasks_.emplace(
std::make_pair(now_function_() + delay, std::move(task)));
}
if (task_waiter_) {
task_waiter_->OnTaskPosted();
} else {
run_loop_wakeup_.notify_one();
}
}
bool TaskRunnerImpl::IsRunningOnTaskRunner() {
return task_runner_thread_id_ == std::this_thread::get_id();
}
void TaskRunnerImpl::RunUntilStopped() {
OSP_CHECK(!is_running_);
task_runner_thread_id_ = std::this_thread::get_id();
is_running_ = true;
OSP_DVLOG << "Running tasks until stopped...";
// Main loop: Run until the `is_running_` flag is set back to false by the
// "quit task" posted by RequestStopSoon(), or the process received a
// termination signal.
while (is_running_) {
ScheduleDelayedTasks();
if (GrabMoreRunnableTasks()) {
RunRunnableTasks();
}
if (g_signal_state == kSignaled) {
is_running_ = false;
}
}
OSP_DVLOG << "Finished running, entering flushing phase...";
// Flushing phase: Ensure all immediately-runnable tasks are run before
// returning. Since running some tasks might cause more immediately-runnable
// tasks to be posted, loop until there is no more work.
//
// If there is bad code that posts tasks indefinitely, this loop will never
// break. However, that also means there is a code path spinning a CPU core at
// 100% all the time. Rather than mitigate this problem scenario, purposely
// let it manifest here in the hopes that unit testing will reveal it (e.g., a
// unit test that never finishes running).
while (GrabMoreRunnableTasks()) {
RunRunnableTasks();
}
OSP_DVLOG << "Finished flushing...";
task_runner_thread_id_ = std::thread::id();
}
void TaskRunnerImpl::RunUntilSignaled() {
OSP_CHECK_EQ(g_signal_state, kNotRunning)
<< __func__ << " may not be invoked concurrently.";
g_signal_state = kNotSignaled;
const auto old_sigint_handler = std::signal(SIGINT, &OnReceivedSignal);
const auto old_sigterm_handler = std::signal(SIGTERM, &OnReceivedSignal);
#if defined(SIGHUP)
const auto old_sighup_handler = std::signal(SIGHUP, &OnReceivedSignal);
#endif
RunUntilStopped();
std::signal(SIGINT, old_sigint_handler);
std::signal(SIGTERM, old_sigterm_handler);
#if defined(SIGHUP)
std::signal(SIGHUP, old_sighup_handler);
#endif
OSP_DVLOG << "Received signal, setting state to not running...";
g_signal_state = kNotRunning;
}
void TaskRunnerImpl::RequestStopSoon() {
PostTask([this]() { is_running_ = false; });
}
void TaskRunnerImpl::RunRunnableTasks() {
for (TaskWithMetadata& running_task : running_tasks_) {
// Move the task to the stack so that its bound state is freed immediately
// after being run.
TaskWithMetadata task = std::move(running_task);
task();
}
running_tasks_.clear();
}
void TaskRunnerImpl::ScheduleDelayedTasks() {
std::lock_guard<std::mutex> lock(task_mutex_);
// Getting the time can be expensive on some platforms, so only get it once.
const auto current_time = now_function_();
const auto end_of_range = delayed_tasks_.upper_bound(current_time);
for (auto it = delayed_tasks_.begin(); it != end_of_range; ++it) {
tasks_.push_back(std::move(it->second));
}
delayed_tasks_.erase(delayed_tasks_.begin(), end_of_range);
}
bool TaskRunnerImpl::GrabMoreRunnableTasks() OSP_NO_THREAD_SAFETY_ANALYSIS {
OSP_CHECK(running_tasks_.empty());
std::unique_lock<std::mutex> lock(task_mutex_);
if (!tasks_.empty()) {
running_tasks_.swap(tasks_);
return true;
}
if (!is_running_) {
return false; // Stop was requested. Don't wait for more tasks.
}
if (task_waiter_) {
Clock::duration timeout = waiter_timeout_;
if (!delayed_tasks_.empty()) {
Clock::duration next_task_delta =
delayed_tasks_.begin()->first - now_function_();
if (next_task_delta < timeout) {
timeout = next_task_delta;
}
}
lock.unlock();
task_waiter_->WaitForTaskToBePosted(timeout);
return false;
}
if (delayed_tasks_.empty()) {
run_loop_wakeup_.wait(lock);
} else {
run_loop_wakeup_.wait_for(lock,
delayed_tasks_.begin()->first - now_function_());
}
return false;
}
} // namespace openscreen

View file

@ -0,0 +1,150 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_TASK_RUNNER_H_
#define PLATFORM_IMPL_TASK_RUNNER_H_
#include <condition_variable> // NOLINT
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
#include "platform/api/task_runner.h"
#include "platform/api/time.h"
#include "platform/base/error.h"
#include "util/raw_ptr.h"
#include "util/thread_annotations.h"
#include "util/trace_logging.h"
namespace openscreen {
class TaskRunnerImpl : public TaskRunner {
public:
using Task = TaskRunner::Task;
class TaskWaiter {
public:
virtual ~TaskWaiter();
// These calls should be thread-safe. The absolute minimum is that
// OnTaskPosted must be safe to call from another thread while this is
// inside WaitForTaskToBePosted. NOTE: There may be spurious wakeups from
// WaitForTaskToBePosted depending on whether the specific implementation
// chooses to clear queued WakeUps before entering WaitForTaskToBePosted.
// Blocks until some event occurs, which means new tasks may have been
// posted. Wait may only block up to `timeout` where 0 means don't block at
// all (not block forever).
virtual Error WaitForTaskToBePosted(Clock::duration timeout) = 0;
// If a WaitForTaskToBePosted call is currently blocking, unblock it
// immediately.
virtual void OnTaskPosted() = 0;
};
explicit TaskRunnerImpl(
ClockNowFunctionPtr now_function,
TaskWaiter* event_waiter = nullptr,
Clock::duration waiter_timeout = std::chrono::milliseconds(100));
TaskRunnerImpl(const TaskRunnerImpl&) = delete;
TaskRunnerImpl(TaskRunnerImpl&&) noexcept = delete;
TaskRunnerImpl& operator=(const TaskRunnerImpl&) = delete;
TaskRunnerImpl& operator=(TaskRunnerImpl&&) = delete;
// TaskRunner overrides
~TaskRunnerImpl() override;
void PostPackagedTask(Task task) override;
void PostPackagedTaskWithDelay(Task task, Clock::duration delay) override;
bool IsRunningOnTaskRunner() override;
// Blocks the current thread, executing tasks from the queue with the desired
// timing; and does not return until some time after RequestStopSoon() is
// called.
virtual void RunUntilStopped();
// Blocks the current thread, executing tasks from the queue with the desired
// timing; and does not return until some time after the current process is
// signaled with SIGINT or SIGTERM, or after RequestStopSoon() is called.
virtual void RunUntilSignaled();
// Thread-safe method for requesting the TaskRunner to stop running after all
// non-delayed tasks in the queue have run. This behavior allows final
// clean-up tasks to be executed before the TaskRunner stops.
//
// If any non-delayed tasks post additional non-delayed tasks, those will be
// run as well before returning.
virtual void RequestStopSoon();
private:
#if defined(ENABLE_TRACE_LOGGING)
// Wrapper around a Task used to store the TraceId Metadata along with the
// task itself, and to set the current TraceIdHierarchy before executing the
// task.
class TaskWithMetadata {
public:
// NOTE: 'explicit' keyword omitted so that conversion construtor can be
// used. This simplifies switching between 'Task' and 'TaskWithMetadata'
// based on the compilation flag.
TaskWithMetadata(Task task) // NOLINT
: task_(std::move(task)), trace_ids_(TRACE_HIERARCHY) {}
void operator()() {
TRACE_SET_HIERARCHY(trace_ids_);
std::move(task_)();
}
private:
Task task_;
TraceIdHierarchy trace_ids_;
};
#else // !defined(ENABLE_TRACE_LOGGING)
using TaskWithMetadata = Task;
#endif // defined(ENABLE_TRACE_LOGGING)
// Helper that runs all tasks in `running_tasks_` and then clears it.
void RunRunnableTasks();
// Look at all tasks in the delayed task queue, then schedule them if the
// minimum delay time has elapsed.
void ScheduleDelayedTasks();
// Transfers all ready-to-run tasks from `tasks_` to `running_tasks_`. If
// there are no ready-to-run tasks, and `is_running_` is true, this method
// will block waiting for new tasks. Returns true if any tasks were
// transferred.
bool GrabMoreRunnableTasks() OSP_NO_THREAD_SAFETY_ANALYSIS;
const ClockNowFunctionPtr now_function_;
// Flag that indicates whether the task runner loop should continue. This is
// only meant to be read/written on the thread executing RunUntilStopped().
bool is_running_;
// This mutex is used for `tasks_` and `delayed_tasks_`, and also for
// notifying the run loop to wake up when it is waiting for a task to be added
// to the queue in `run_loop_wakeup_`.
std::mutex task_mutex_;
std::vector<TaskWithMetadata> tasks_ OSP_GUARDED_BY(task_mutex_);
std::multimap<Clock::time_point, TaskWithMetadata> delayed_tasks_ OSP_GUARDED_BY(task_mutex_);
// When `task_waiter_` is nullptr, `run_loop_wakeup_` is used for sleeping the
// task runner. Otherwise, `run_loop_wakeup_` isn't used and `task_waiter_`
// is used instead (along with `waiter_timeout_`).
std::condition_variable run_loop_wakeup_;
const raw_ptr<TaskWaiter> task_waiter_;
Clock::duration waiter_timeout_;
// To prevent excessive re-allocation of the underlying array of the `tasks_`
// vector, use an A/B vector-swap mechanism. `running_tasks_` starts out
// empty, and is swapped with `tasks_` when it is time to run the Tasks.
std::vector<TaskWithMetadata> running_tasks_;
std::thread::id task_runner_thread_id_;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_TASK_RUNNER_H_

View file

@ -0,0 +1,72 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/text_trace_logging_platform.h"
#include <limits>
#include <sstream>
#include "platform/impl/logging.h"
#include "util/chrono_helpers.h"
#include "util/osp_logging.h"
namespace openscreen {
using clock_operators::operator<<;
bool TextTraceLoggingPlatform::IsTraceLoggingEnabled(TraceCategory category) {
return true;
}
TextTraceLoggingPlatform::TextTraceLoggingPlatform() {
StartTracing(this);
}
TextTraceLoggingPlatform::~TextTraceLoggingPlatform() {
StopTracing();
}
void TextTraceLoggingPlatform::LogTrace(TraceEvent event,
Clock::time_point end_time) {
const auto total_runtime = (end_time - event.start_time);
std::stringstream ss;
ss << "[TRACE" << " (" << std::dec << total_runtime << ")] " << event;
LogTraceMessage(ss.str());
}
void TextTraceLoggingPlatform::LogAsyncStart(TraceEvent event) {
std::stringstream ss;
ss << "[ASYNC TRACE START] " << event;
LogTraceMessage(ss.str());
}
void TextTraceLoggingPlatform::LogAsyncEnd(TraceEvent event) {
std::stringstream ss;
ss << "[ASYNC TRACE END] " << event;
LogTraceMessage(ss.str());
}
void TextTraceLoggingPlatform::LogFlow(TraceEvent event, FlowType type) {
std::stringstream ss;
ss << "[FLOW";
if (!event.flow_ids.empty()) {
ss << " #" << std::hex << event.flow_ids[0] << std::dec;
}
switch (type) {
case FlowType::kFlowBegin:
ss << " BEGIN";
break;
case FlowType::kFlowStep:
ss << " STEP";
break;
case FlowType::kFlowEnd:
ss << " END";
break;
}
ss << "] " << event.ToString();
LogTraceMessage(ss.str());
}
} // namespace openscreen

View file

@ -0,0 +1,30 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_TEXT_TRACE_LOGGING_PLATFORM_H_
#define PLATFORM_IMPL_TEXT_TRACE_LOGGING_PLATFORM_H_
#include "platform/api/trace_logging_platform.h"
namespace openscreen {
class TextTraceLoggingPlatform : public TraceLoggingPlatform {
public:
TextTraceLoggingPlatform();
~TextTraceLoggingPlatform() override;
bool IsTraceLoggingEnabled(TraceCategory category) override;
void LogTrace(TraceEvent event, Clock::time_point end_time) override;
void LogAsyncStart(TraceEvent event) override;
void LogAsyncEnd(TraceEvent event) override;
void LogFlow(TraceEvent event, FlowType type) override;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_TEXT_TRACE_LOGGING_PLATFORM_H_

View file

@ -0,0 +1,49 @@
// 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 "platform/api/time.h"
#include <chrono>
#include <ctime>
#include <ratio>
#include "util/chrono_helpers.h"
#include "util/osp_logging.h"
using std::chrono::high_resolution_clock;
using std::chrono::steady_clock;
using std::chrono::system_clock;
namespace openscreen {
Clock::time_point Clock::now() noexcept {
constexpr bool kSteadyIsGoodEnough =
std::ratio_less_equal_v<steady_clock::period, Clock::kRequiredResolution>;
constexpr bool kHighResIsGoodEnough =
std::ratio_less_equal_v<high_resolution_clock::period,
Clock::kRequiredResolution> &&
high_resolution_clock::is_steady;
static_assert(kSteadyIsGoodEnough || kHighResIsGoodEnough,
"No suitable default clock (steady + high enough resolution) "
"on this platform");
// 'if constexpr' guarantees compile-time branching.
// We prefer steady_clock if it meets the requirements (usually cheaper).
if constexpr (kSteadyIsGoodEnough) {
return Clock::time_point(
Clock::to_duration(steady_clock::now().time_since_epoch()));
} else {
return Clock::time_point(
Clock::to_duration(high_resolution_clock::now().time_since_epoch()));
}
}
std::chrono::seconds GetWallTimeSinceUnixEpoch() noexcept {
// C++20 guarantees that system_clock uses the Unix Epoch (1970-01-01).
// Use floor to truncate sub-second precision safely.
return std::chrono::floor<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
}
} // namespace openscreen

View file

@ -0,0 +1,22 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/timeval_posix.h"
#include <chrono>
#include "util/chrono_helpers.h"
namespace openscreen {
struct timeval ToTimeval(const Clock::duration& timeout) {
struct timeval tv {};
const auto whole_seconds = to_seconds(timeout);
tv.tv_sec = whole_seconds.count();
tv.tv_usec = to_microseconds(timeout - whole_seconds).count();
return tv;
}
} // namespace openscreen

View file

@ -0,0 +1,18 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_TIMEVAL_POSIX_H_
#define PLATFORM_IMPL_TIMEVAL_POSIX_H_
#include <sys/time.h> // timeval
#include "platform/api/time.h"
namespace openscreen {
struct timeval ToTimeval(const Clock::duration& timeout);
} // namespace openscreen
#endif // PLATFORM_IMPL_TIMEVAL_POSIX_H_

View file

@ -0,0 +1,665 @@
// 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 "platform/impl/udp_socket_posix.h"
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <cstring>
#include <memory>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "build/build_config.h"
#include "platform/api/network_interface.h"
#include "platform/api/task_runner.h"
#include "platform/base/error.h"
#include "platform/impl/socket_address_posix.h"
#include "platform/impl/udp_socket_reader_posix.h"
#include "util/osp_logging.h"
namespace openscreen {
namespace {
// 64 KB is the maximum possible UDP datagram size.
constexpr int kMaxUdpBufferSize = 64 << 10;
constexpr bool IsPowerOf2(uint32_t x) {
return (x > 0) && ((x & (x - 1)) == 0);
}
static_assert(IsPowerOf2(alignof(struct cmsghdr)),
"std::align requires power-of-2 alignment");
using IPv4NetworkInterfaceIndex = decltype(ip_mreqn().imr_ifindex);
using IPv6NetworkInterfaceIndex = decltype(ipv6_mreq().ipv6mr_interface);
ErrorOr<int> CreateNonBlockingUdpSocket(int domain) {
int fd = socket(domain, SOCK_DGRAM, 0);
if (fd == -1) {
return Error(Error::Code::kInitializationFailure, strerror(errno));
}
// On non-Linux, the SOCK_NONBLOCK option is not available, so use the
// more-portable method of calling fcntl() to set this behavior.
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1) {
close(fd);
return Error(Error::Code::kInitializationFailure, strerror(errno));
}
return fd;
}
} // namespace
UdpSocketPosix::UdpSocketPosix(TaskRunner& task_runner,
Client* client,
SocketHandle handle,
const IPEndpoint& local_endpoint,
PlatformClientPosix* platform_client)
: task_runner_(task_runner),
client_(client),
handle_(handle),
local_endpoint_(local_endpoint),
platform_client_(platform_client) {
if (handle_.fd >= 0) {
if (platform_client_) {
platform_client_->udp_socket_reader()->OnCreate(this);
}
}
}
UdpSocketPosix::~UdpSocketPosix() {
Close();
}
const SocketHandle& UdpSocketPosix::GetHandle() const {
return handle_;
}
// static
ErrorOr<std::unique_ptr<UdpSocket>> UdpSocket::Create(
TaskRunner& task_runner,
Client* client,
const IPEndpoint& endpoint) {
static std::atomic_bool in_create{false};
const bool in_create_local = in_create.exchange(true);
OSP_CHECK(!in_create_local)
<< "Another UdpSocket::Create call is in progress. Calls to this method "
"must be seralized.";
if (in_create_local) {
return Error::Code::kAgain;
}
int domain;
switch (endpoint.address.version()) {
case Version::kV4:
domain = AF_INET;
break;
case Version::kV6:
domain = AF_INET6;
break;
}
const ErrorOr<int> fd = CreateNonBlockingUdpSocket(domain);
if (!fd) {
in_create = false;
return fd.error();
}
std::unique_ptr<UdpSocket> socket = std::make_unique<UdpSocketPosix>(
task_runner, client, SocketHandle(fd.value()), endpoint);
in_create = false;
return socket;
}
bool UdpSocketPosix::IsIPv4() const {
return local_endpoint_.address.IsV4();
}
bool UdpSocketPosix::IsIPv6() const {
return local_endpoint_.address.IsV6();
}
IPEndpoint UdpSocketPosix::GetLocalEndpoint() const {
if (local_endpoint_.port == 0) {
// Note: If the getsockname() call fails, just assume that's because the
// socket isn't bound yet. In this case, leave the original value in-place.
switch (local_endpoint_.address.version()) {
case UdpSocket::Version::kV4: {
struct sockaddr_in address {};
socklen_t address_len = sizeof(address);
if (getsockname(handle_.fd,
reinterpret_cast<struct sockaddr*>(&address),
&address_len) == 0) {
OSP_CHECK_EQ(address.sin_family, AF_INET);
local_endpoint_.address = GetIPAddressFromSockAddr(address);
local_endpoint_.port = ntohs(address.sin_port);
}
break;
}
case UdpSocket::Version::kV6: {
struct sockaddr_in6 address {};
socklen_t address_len = sizeof(address);
if (getsockname(handle_.fd,
reinterpret_cast<struct sockaddr*>(&address),
&address_len) == 0) {
OSP_CHECK_EQ(address.sin6_family, AF_INET6);
local_endpoint_.address = GetIPAddressFromSockAddr(address);
local_endpoint_.port = ntohs(address.sin6_port);
}
break;
}
}
}
return local_endpoint_;
}
void UdpSocketPosix::Bind() {
OSP_CHECK(task_runner_->IsRunningOnTaskRunner());
if (is_closed()) {
OnError(Error::Code::kSocketClosedFailure);
return;
}
// This is effectively a boolean passed to setsockopt() to allow a future
// bind() on the same socket to succeed, even if the address is already in
// use. This is pretty much universally the desired behavior.
constexpr int reuse_addr = 1;
if (setsockopt(handle_.fd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,
sizeof(reuse_addr)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
}
#if BUILDFLAG(IS_APPLE)
// On Mac, SO_REUSEADDR is not enough to allow a bind() on a reusable
// multicast socket. We need to also set the option SO_REUSEPORT.
constexpr int reuse_port = 1;
if (setsockopt(handle_.fd, SOL_SOCKET, SO_REUSEPORT, &reuse_port,
sizeof(reuse_port)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
}
#endif // BUILDFLAG(IS_APPLE)
bool is_bound = false;
switch (local_endpoint_.address.version()) {
case UdpSocket::Version::kV4: {
struct sockaddr_in address = ToSockAddrIn(local_endpoint_);
if (bind(handle_.fd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address)) != -1) {
is_bound = true;
}
} break;
case UdpSocket::Version::kV6: {
struct sockaddr_in6 address = ToSockAddrIn6(local_endpoint_);
if (bind(handle_.fd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address)) != -1) {
is_bound = true;
}
} break;
}
if (is_bound) {
client_->OnBound(this);
} else {
OnError(Error::Code::kSocketBindFailure);
}
}
void UdpSocketPosix::SetMulticastOutboundInterface(
NetworkInterfaceIndex ifindex) {
OSP_CHECK(task_runner_->IsRunningOnTaskRunner());
if (is_closed()) {
OnError(Error::Code::kSocketClosedFailure);
return;
}
switch (local_endpoint_.address.version()) {
case UdpSocket::Version::kV4: {
struct ip_mreqn multicast_properties {};
// Appropriate address is set based on `imr_ifindex` when set.
multicast_properties.imr_address.s_addr = INADDR_ANY;
multicast_properties.imr_multiaddr.s_addr = INADDR_ANY;
multicast_properties.imr_ifindex =
static_cast<IPv4NetworkInterfaceIndex>(ifindex);
if (setsockopt(handle_.fd, IPPROTO_IP, IP_MULTICAST_IF,
&multicast_properties,
sizeof(multicast_properties)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
}
return;
}
case UdpSocket::Version::kV6: {
const auto index = static_cast<IPv6NetworkInterfaceIndex>(ifindex);
if (setsockopt(handle_.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index,
sizeof(index)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
}
return;
}
}
OSP_NOTREACHED();
}
void UdpSocketPosix::JoinMulticastGroup(const IPAddress& address,
NetworkInterfaceIndex ifindex) {
OSP_CHECK(task_runner_->IsRunningOnTaskRunner());
if (is_closed()) {
OnError(Error::Code::kSocketClosedFailure);
return;
}
switch (local_endpoint_.address.version()) {
case UdpSocket::Version::kV4: {
// Passed as data to setsockopt(). 1 means return IP_PKTINFO control data
// in recvmsg() calls.
const int enable_pktinfo = 1;
if (setsockopt(handle_.fd, IPPROTO_IP, IP_PKTINFO, &enable_pktinfo,
sizeof(enable_pktinfo)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
return;
}
struct ip_mreqn multicast_properties {};
// Appropriate address is set based on `imr_ifindex` when set.
multicast_properties.imr_address.s_addr = INADDR_ANY;
multicast_properties.imr_ifindex =
static_cast<IPv4NetworkInterfaceIndex>(ifindex);
#if BUILDFLAG(IS_APPLE)
// On macOS, we must specify the interface address, not just the index,
// because it ignores imr_ifindex in ip_mreqn (interpreting it as
// ip_mreq).
const std::vector<InterfaceInfo> interfaces = GetNetworkInterfaces();
const auto it = std::find_if(interfaces.begin(), interfaces.end(),
[ifindex](const InterfaceInfo& info) {
return info.index == ifindex;
});
if (it != interfaces.end()) {
for (const auto& ip_net : it->addresses) {
if (ip_net.address.version() == IPAddress::Version::kV4) {
ip_net.address.CopyToV4(
reinterpret_cast<uint8_t*>(&multicast_properties.imr_address));
break;
}
}
}
#endif
static_assert(sizeof(multicast_properties.imr_multiaddr) == 4u,
"IPv4 address requires exactly 4 bytes");
address.CopyTo(std::span<uint8_t>(
reinterpret_cast<uint8_t*>(&multicast_properties.imr_multiaddr), 4));
if (setsockopt(handle_.fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&multicast_properties,
sizeof(multicast_properties)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
}
return;
}
case UdpSocket::Version::kV6: {
// Passed as data to setsockopt(). 1 means return IPV6_PKTINFO control
// data in recvmsg() calls.
const int enable_pktinfo = 1;
if (setsockopt(handle_.fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
&enable_pktinfo, sizeof(enable_pktinfo)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
return;
}
struct ipv6_mreq multicast_properties = {
{/* filled-in below */},
static_cast<IPv6NetworkInterfaceIndex>(ifindex),
};
static_assert(sizeof(multicast_properties.ipv6mr_multiaddr) == 16u,
"IPv6 address requires exactly 16 bytes");
address.CopyTo(std::span<uint8_t>(
reinterpret_cast<uint8_t*>(&multicast_properties.ipv6mr_multiaddr),
16));
// Portability note: All platforms support IPV6_JOIN_GROUP, which is
// synonymous with IPV6_ADD_MEMBERSHIP.
if (setsockopt(handle_.fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&multicast_properties,
sizeof(multicast_properties)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
}
return;
}
}
OSP_NOTREACHED();
}
namespace {
// Examine `posix_errno` to determine whether the specific cause of a failure
// was transient or hard, and return the appropriate error response.
Error ChooseError(decltype(errno) posix_errno, Error::Code hard_error_code) {
if (posix_errno == EAGAIN || posix_errno == EWOULDBLOCK ||
posix_errno == ENOBUFS) {
return Error(Error::Code::kAgain, strerror(errno));
}
return Error(hard_error_code, strerror(errno));
}
IPAddress GetIPAddressFromPktInfo(const in_pktinfo& pktinfo) {
static_assert(IPAddress::kV4Size == sizeof(pktinfo.ipi_addr),
"IPv4 address size mismatch.");
return IPAddress(IPAddress::Version::kV4,
std::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(&pktinfo.ipi_addr), 4));
}
uint16_t GetPortFromFromSockAddr(const sockaddr_in& sa) {
return ntohs(sa.sin_port);
}
IPAddress GetIPAddressFromPktInfo(const in6_pktinfo& pktinfo) {
return IPAddress(std::span<const uint8_t, 16>(pktinfo.ipi6_addr.s6_addr, 16),
pktinfo.ipi6_ifindex);
}
uint16_t GetPortFromFromSockAddr(const sockaddr_in6& sa) {
return ntohs(sa.sin6_port);
}
template <class PktInfoType>
bool IsPacketInfo(cmsghdr* cmh);
template <>
bool IsPacketInfo<in_pktinfo>(cmsghdr* cmh) {
return cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO;
}
template <>
bool IsPacketInfo<in6_pktinfo>(cmsghdr* cmh) {
return cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO;
}
template <class SockAddrType, class PktInfoType>
ErrorOr<UdpPacket> ReceiveMessageInternal(int fd) {
// Try to determine the size of the incoming packet. If we cannot,
// it's not a fatal error, we will just allocate kMaxUdpBufferSize
// and shrink-to-fit below.
int upper_bound_bytes = -1;
#if BUILDFLAG(IS_LINUX)
// Returns the exact size of the datagram, or -1 on error.
upper_bound_bytes = recv(fd, nullptr, 0, MSG_PEEK | MSG_TRUNC);
#elif BUILDFLAG(IS_APPLE)
// Can't use recv(MSG_TRUNC) (not supported). Can't use ioctl(FIONREAD)
// (returns size in socket queue instead next message size). Use
// getsocktopt(...NREAD...) to get the datagram size if possible.
// Ref: https://www.unix.com/man-page/mojave/2/getsockopt/
socklen_t optlen = sizeof(upper_bound_bytes);
if (getsockopt(fd, SOL_SOCKET, SO_NREAD, &upper_bound_bytes, &optlen) == -1) {
upper_bound_bytes = -1;
}
#endif // BUILDFLAG(IS_LINUX)
if (upper_bound_bytes > 0) {
upper_bound_bytes = std::min(upper_bound_bytes, kMaxUdpBufferSize);
} else {
upper_bound_bytes = kMaxUdpBufferSize;
}
UdpPacket packet(upper_bound_bytes);
struct msghdr msg {};
SockAddrType sa{};
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
iovec iov = {packet.data(), packet.size()};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
// Although we don't do anything with the control buffer, on Linux
// it is required for the message to be properly read.
#if BUILDFLAG(IS_LINUX)
alignas(alignof(cmsghdr)) uint8_t control_buffer[2048];
msg.msg_control = control_buffer;
msg.msg_controllen = sizeof(control_buffer);
#endif // BUILDFLAG(IS_LINUX)
const ssize_t bytes_received = recvmsg(fd, &msg, 0);
if (bytes_received == -1) {
OSP_DVLOG << "Failed to read from socket.";
return ChooseError(errno, Error::Code::kSocketReadFailure);
}
// We may not populate the entire packet.
OSP_CHECK_LE(static_cast<size_t>(bytes_received), packet.size());
packet.resize(bytes_received);
IPEndpoint source_endpoint = {.address = GetIPAddressFromSockAddr(sa),
.port = GetPortFromFromSockAddr(sa)};
packet.set_source(std::move(source_endpoint));
// For multicast sockets, the packet's original destination address may be
// the host address (since we called bind()) but it may also be a
// multicast address. This may be relevant for handling multicast data;
// specifically, mDNSResponder requires this information to work properly.
socklen_t sa_len = sizeof(sa);
if (((msg.msg_flags & MSG_CTRUNC) != 0)) {
return Error(Error::Code::kSocketReadFailure, "Packet was truncated");
}
if ((getsockname(fd, reinterpret_cast<sockaddr*>(&sa), &sa_len) == -1)) {
return Error(Error::Code::kSocketReadFailure, "Failed to get socket name");
}
for (cmsghdr* cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) {
if (IsPacketInfo<PktInfoType>(cmh)) {
PktInfoType* pktinfo = reinterpret_cast<PktInfoType*>(CMSG_DATA(cmh));
IPEndpoint destination_endpoint = {
.address = GetIPAddressFromPktInfo(*pktinfo),
.port = GetPortFromFromSockAddr(sa)};
packet.set_destination(std::move(destination_endpoint));
break;
}
}
return std::move(packet);
}
} // namespace
void UdpSocketPosix::ReceiveMessage() {
// WARNING: This method may be called on a different thread from the thread
// calling into all the other methods.
if (is_closed()) {
task_runner_->PostTask([weak_this = weak_factory_.GetWeakPtr()] {
if (auto* self = weak_this.get()) {
if (auto* client = self->client_.get()) {
client->OnRead(self, Error::Code::kSocketClosedFailure);
}
}
});
return;
}
ErrorOr<UdpPacket> read_result = Error::Code::kUnknownError;
switch (local_endpoint_.address.version()) {
case UdpSocket::Version::kV4: {
read_result = ReceiveMessageInternal<sockaddr_in, in_pktinfo>(handle_.fd);
break;
}
case UdpSocket::Version::kV6: {
read_result =
ReceiveMessageInternal<sockaddr_in6, in6_pktinfo>(handle_.fd);
break;
}
default: {
OSP_NOTREACHED();
}
}
task_runner_->PostTask([weak_this = weak_factory_.GetWeakPtr(),
result = std::move(read_result)]() mutable {
if (auto* self = weak_this.get()) {
if (auto* client = self->client_.get()) {
client->OnRead(self, std::move(result));
}
}
});
}
void UdpSocketPosix::SendMessage(ByteView data, const IPEndpoint& dest) {
OSP_CHECK(task_runner_->IsRunningOnTaskRunner());
if (is_closed()) {
if (client_) {
client_->OnSendError(this, Error::Code::kSocketClosedFailure);
}
return;
}
struct iovec iov = {
reinterpret_cast<void*>(const_cast<uint8_t*>(data.data())), data.size()};
struct msghdr msg {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
ssize_t num_bytes_sent = -2;
switch (dest.address.version()) {
case UdpSocket::Version::kV4: {
struct sockaddr_in sa {};
sa.sin_family = AF_INET;
sa.sin_port = htons(dest.port);
dest.address.CopyTo(std::span<uint8_t>(
reinterpret_cast<uint8_t*>(&sa.sin_addr.s_addr), 4));
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
num_bytes_sent = sendmsg(handle_.fd, &msg, 0);
break;
}
case UdpSocket::Version::kV6: {
struct sockaddr_in6 sa {};
sa.sin6_family = AF_INET6;
sa.sin6_port = htons(dest.port);
dest.address.CopyTo(std::span<uint8_t>(
reinterpret_cast<uint8_t*>(&sa.sin6_addr.s6_addr), 16));
if (dest.address.IsLinkLocal() && dest.address.GetScopeId() != 0) {
sa.sin6_scope_id = dest.address.GetScopeId();
}
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
num_bytes_sent = sendmsg(handle_.fd, &msg, 0);
break;
}
}
if (num_bytes_sent == -1) {
if (client_) {
client_->OnSendError(this,
ChooseError(errno, Error::Code::kSocketSendFailure));
}
return;
}
// Sanity-check: UDP datagram sendmsg() is all or nothing.
OSP_CHECK_EQ(static_cast<size_t>(num_bytes_sent), data.size());
}
void UdpSocketPosix::SetDscp(UdpSocket::DscpMode mode) {
OSP_CHECK(task_runner_->IsRunningOnTaskRunner());
if (is_closed()) {
OnError(Error::Code::kSocketClosedFailure);
return;
}
int level;
int option;
switch (local_endpoint_.address.version()) {
case UdpSocket::Version::kV4:
level = IPPROTO_IP;
option = IP_TOS;
break;
case UdpSocket::Version::kV6:
level = IPPROTO_IPV6;
option = IPV6_TCLASS;
break;
}
// The DSCP value is a 6-bit field, while the IP_TOS and IPV6_TCLASS fields
// are 8-bit fields that expect the DSCP value in the six most significant
// digits.
const int value = static_cast<int>(mode) << 2;
if (setsockopt(handle_.fd, level, option, &value, sizeof(value)) == -1) {
OnError(Error::Code::kSocketOptionSettingFailure);
return;
}
OSP_DVLOG << __func__ << ": successfully set DSCP to "
<< static_cast<int>(mode);
}
void UdpSocketPosix::OnError(Error::Code error_code) {
// The call to Close() may change `errno`, so save it here.
const auto original_errno = errno;
// Close the socket unless the error code represents a transient condition.
if (error_code != Error::Code::kNone && error_code != Error::Code::kAgain) {
Close();
}
if (client_) {
// Call the thread-safe strerror_r() to get the human-readable form of
// `errno`. This is a real mess: 1. Since there seems to be no constant
// defined for the maximum buffer size in the standard library, 1024 is
// used, as suggested by the man page for strerror_r(). 2. There are two
// possible versions of this function: The POSIX one returns int(0) on
// success, while the legacy GNU-specific one will provide a non-null char
// pointer (that may or may not be within the `buffer`).
char buffer[1024];
const auto result = strerror_r(original_errno, buffer, sizeof(buffer));
const char* errno_str;
if (std::is_convertible<decltype(result), int>::value &&
!result) { // Case 1: POSIX strerror_r() success.
errno_str = buffer;
} else if (std::is_convertible<decltype(result), const char*>::value &&
result) { // Case 2: GNU strerror_r() success.
errno_str = reinterpret_cast<const char*>(result);
} else { // Case 3: strerror_r() failed (either version).
buffer[0] = '\0';
errno_str = buffer;
}
std::stringstream stream;
stream << "endpoint: " << local_endpoint_ << ", error: " << errno_str;
client_->OnError(this, Error(error_code, stream.str()));
}
}
void UdpSocketPosix::Close() {
if (handle_.fd < 0) {
return;
}
// Notify the UdpSocketReaderPosix that the socket handle is about to be
// closed.
if (platform_client_) {
platform_client_->udp_socket_reader()->OnDestroy(this);
}
// It's now safe to close the socket, since no other thread (e.g., from
// UdpSocketReaderPosix) should be inside ReceiveMessage() at this point.
close(handle_.fd);
handle_.fd = -1;
}
} // namespace openscreen

View file

@ -0,0 +1,90 @@
// 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.
#ifndef PLATFORM_IMPL_UDP_SOCKET_POSIX_H_
#define PLATFORM_IMPL_UDP_SOCKET_POSIX_H_
#include "platform/api/udp_socket.h"
#include "platform/impl/platform_client_posix.h"
#include "platform/impl/socket_handle_posix.h"
#include "util/raw_ptr.h"
#include "util/raw_ref.h"
#include "util/weak_ptr.h"
namespace openscreen {
class UdpSocketReaderPosix;
// Threading: All public methods must be called on the same thread--the one
// executing the TaskRunner. All non-public methods, except ReceiveMessage(),
// are also assumed to be called on that thread.
class UdpSocketPosix : public UdpSocket {
public:
// Creates a new UdpSocketPosix. The provided client and task_runner must
// exist for the duration of this socket's lifetime.
UdpSocketPosix(TaskRunner& task_runner,
Client* client,
SocketHandle handle,
const IPEndpoint& local_endpoint,
PlatformClientPosix* platform_client =
PlatformClientPosix::GetInstance());
UdpSocketPosix(const UdpSocketPosix&) = delete;
UdpSocketPosix(UdpSocketPosix&&) noexcept = delete;
UdpSocketPosix& operator=(const UdpSocketPosix&) = delete;
UdpSocketPosix& operator=(UdpSocketPosix&&) = delete;
~UdpSocketPosix() override;
// Implementations of UdpSocket methods.
bool IsIPv4() const override;
bool IsIPv6() const override;
IPEndpoint GetLocalEndpoint() const override;
void Bind() override;
void SetMulticastOutboundInterface(NetworkInterfaceIndex ifindex) override;
void JoinMulticastGroup(const IPAddress& address,
NetworkInterfaceIndex ifindex) override;
void SendMessage(ByteView data, const IPEndpoint& dest) override;
void SetDscp(DscpMode mode) override;
const SocketHandle& GetHandle() const;
protected:
friend class UdpSocketReaderPosix;
// Called by UdpSocketReaderPosix to perform a non-blocking read on the socket
// and then dispatch the packet to this socket's Client. This method is the
// only one in this class possibly being called from another thread.
void ReceiveMessage();
private:
// Helper to close the socket if `error` is fatal, in addition to dispatching
// an Error to the `client_`.
void OnError(Error::Code error);
bool is_closed() const { return handle_.fd < 0; }
void Close();
// Task runner to use for queuing `client_` callbacks.
const raw_ref<TaskRunner> task_runner_;
// Client to use for callbacks. This can be nullptr if the user does not want
// any callbacks (for example, in the send-only case).
const raw_ptr<Client> client_;
// Holds the POSIX file descriptor, or -1 if the socket is closed.
SocketHandle handle_;
// Cached value of current local endpoint. This can change (e.g., when the
// operating system auto-assigns a free local port when Bind() is called). If
// the port is zero, getsockname() is called to try to resolve it. Once the
// port is non-zero, it is assumed never to change again.
mutable IPEndpoint local_endpoint_;
WeakPtrFactory<UdpSocketPosix> weak_factory_{this};
const raw_ptr<PlatformClientPosix> platform_client_;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_UDP_SOCKET_POSIX_H_

View file

@ -0,0 +1,78 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/udp_socket_reader_posix.h"
#include <chrono>
#include <functional>
#include "platform/impl/socket_handle_posix.h"
#include "platform/impl/udp_socket_posix.h"
#include "util/osp_logging.h"
#include "util/std_util.h"
namespace openscreen {
UdpSocketReaderPosix::UdpSocketReaderPosix(SocketHandleWaiter& waiter)
: waiter_(waiter) {}
UdpSocketReaderPosix::~UdpSocketReaderPosix() {
waiter_->UnsubscribeAll(this);
}
void UdpSocketReaderPosix::ProcessReadyHandle(SocketHandleRef handle,
uint32_t flags) {
OSP_CHECK(flags & SocketHandleWaiter::Flags::kReadable);
std::lock_guard<std::mutex> lock(mutex_);
// NOTE: Because sockets_ is expected to remain small, the performance here
// is better than using an unordered_set.
for (UdpSocketPosix* socket : sockets_) {
if (socket->GetHandle() == handle) {
socket->ReceiveMessage();
break;
}
}
}
bool UdpSocketReaderPosix::HasPendingWrite(SocketHandleRef handle) {
OSP_NOTREACHED();
}
void UdpSocketReaderPosix::OnCreate(UdpSocket* socket) {
UdpSocketPosix* read_socket = static_cast<UdpSocketPosix*>(socket);
{
std::lock_guard<std::mutex> lock(mutex_);
sockets_.push_back(read_socket);
}
// We only care about read events.
waiter_->Subscribe(this, std::cref(read_socket->GetHandle()),
SocketHandleWaiter::kReadable);
}
void UdpSocketReaderPosix::OnDestroy(UdpSocket* socket) {
UdpSocketPosix* destroyed_socket = static_cast<UdpSocketPosix*>(socket);
OnDelete(destroyed_socket);
}
void UdpSocketReaderPosix::OnDelete(UdpSocketPosix* socket,
bool disable_locking_for_testing) {
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = std::find(sockets_.begin(), sockets_.end(), socket);
if (it != sockets_.end()) {
sockets_.erase(it);
}
}
waiter_->OnHandleDeletion(this, std::cref(socket->GetHandle()),
disable_locking_for_testing);
}
bool UdpSocketReaderPosix::IsMappedReadForTesting(
UdpSocketPosix* socket) const {
std::lock_guard<std::mutex> lock(mutex_);
return Contains(sockets_, socket);
}
} // namespace openscreen

View file

@ -0,0 +1,82 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PLATFORM_IMPL_UDP_SOCKET_READER_POSIX_H_
#define PLATFORM_IMPL_UDP_SOCKET_READER_POSIX_H_
#include <map>
#include <mutex>
#include <vector>
#include "platform/api/task_runner.h"
#include "platform/api/time.h"
#include "platform/impl/socket_handle.h"
#include "platform/impl/socket_handle_waiter.h"
#include "platform/impl/udp_socket_posix.h"
#include "util/raw_ptr.h"
#include "util/raw_ref.h"
#include "util/thread_annotations.h"
namespace openscreen {
// This is the class responsible for watching sockets for readable data, then
// calling the function associated with these sockets once that data is read.
// NOTE: This class will only function as intended while its RunUntilStopped
// method is running.
class UdpSocketReaderPosix : public SocketHandleWaiter::Subscriber {
public:
using SocketHandleRef = SocketHandleWaiter::SocketHandleRef;
// Creates a new instance of this object.
// NOTE: The provided NetworkWaiter must outlive this object.
explicit UdpSocketReaderPosix(SocketHandleWaiter& waiter);
UdpSocketReaderPosix(const UdpSocketReaderPosix&) = delete;
UdpSocketReaderPosix(UdpSocketReaderPosix&&) noexcept = delete;
UdpSocketReaderPosix& operator=(const UdpSocketReaderPosix&) = delete;
UdpSocketReaderPosix& operator=(UdpSocketReaderPosix&&) = delete;
~UdpSocketReaderPosix() override;
// Waits for `socket` to be readable and then calls the socket's
// RecieveMessage(...) method to process the available packet.
// NOTE: The first read on any newly watched socket may be delayed up to 50
// ms.
void OnCreate(UdpSocket* socket);
// Cancels any pending wait on reading `socket`. Following this call, any
// pending reads will proceed but their associated callbacks will not fire.
// NOTE: This method will block until a delete is safe.
// NOTE: If a socket callback is removed in the middle of a wait call, data
// may be read on this socket and but the callback may not be called. If a
// socket callback is added in the middle of a wait call, the new socket may
// not be watched until after this wait call ends.
virtual void OnDestroy(UdpSocket* socket);
// SocketHandleWaiter::Subscriber overrides.
void ProcessReadyHandle(SocketHandleRef handle, uint32_t flags) override;
// NOTE: we don't subscribe to write events from the socket handle waiter.
bool HasPendingWrite(SocketHandleRef handle) override;
protected:
bool IsMappedReadForTesting(UdpSocketPosix* socket) const;
private:
// Helper method to allow for OnDestroy calls without blocking.
void OnDelete(UdpSocketPosix* socket,
bool disable_locking_for_testing = false);
// The set of all sockets that are being read from
std::vector<raw_ptr<UdpSocketPosix>> sockets_ OSP_GUARDED_BY(mutex_);
// Mutex to protect against concurrent modification of socket info.
mutable std::mutex mutex_;
// NetworkWaiter watching this NetworkReader.
const raw_ref<SocketHandleWaiter> waiter_;
friend class TestingUdpSocketReader;
};
} // namespace openscreen
#endif // PLATFORM_IMPL_UDP_SOCKET_READER_POSIX_H_