File platform.hpp
File List > astutedds > platform.hpp
Go to the documentation of this file
//
// Copyright (c) 2026, Astute Systems PTY LTD
//
// This file is part of the Astute DDS developed by Astute Systems.
//
// See the commercial LICENSE file in the project root for full license details.
//
// @file platform.hpp
// @brief Platform abstraction for POSIX/Windows socket and OS APIs
//
// Provides a unified interface for BSD sockets, non-blocking I/O, process
// IDs, network interface enumeration, and poll(). All platform-specific
// details are isolated here so that the rest of the codebase can use a
// single, portable API.
//
#ifndef ASTUTEDDS_PLATFORM_HPP
#define ASTUTEDDS_PLATFORM_HPP
// ============================================================================
// Windows
// ============================================================================
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
// clang-format off
// Order matters: winsock2 before windows.h, windows.h before iphlpapi.h
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <iphlpapi.h>
// clang-format on
// Windows headers define macros that collide with C++ identifiers.
// Undefine the ones that clash with AstuteDDS symbols (e.g. LogLevel::ERROR).
#ifdef ERROR
#undef ERROR
#endif
#include <array>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
// Link libraries (MSVC pragma; MinGW uses CMake target_link_libraries)
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")
#endif
namespace astutedds::platform
{
// ---- Socket type alias ----
using socket_t = SOCKET;
static constexpr socket_t INVALID_SOCK = INVALID_SOCKET;
// ---- Winsock lifetime (call once at startup) ----
inline bool net_init()
{
WSADATA wsa;
return WSAStartup(MAKEWORD(2, 2), &wsa) == 0;
}
inline void net_cleanup()
{
WSACleanup();
}
// ---- Close a socket ----
inline int close_socket(socket_t s)
{
return ::closesocket(s);
}
// ---- Set socket non-blocking ----
inline bool set_nonblocking(socket_t s)
{
u_long mode = 1;
return ioctlsocket(s, FIONBIO, &mode) == 0;
}
// ---- poll() ----
// WSAPoll has the same signature as POSIX poll on Windows Vista+.
using pollfd_t = WSAPOLLFD;
inline int poll(pollfd_t* fds, unsigned long nfds, int timeout_ms)
{
return WSAPoll(fds, nfds, timeout_ms);
}
// ---- socklen_t (provided by ws2tcpip.h, but alias for convenience) ----
using socklen_t = int;
// ---- ssize_t ----
using ssize_t = SSIZE_T; // from <basetsd.h>, included by windows.h
// ---- MSG_NOSIGNAL (not needed on Windows — SIGPIPE doesn't exist) ----
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
// ---- SO_REUSEPORT (not available on Windows) ----
#ifndef SO_REUSEPORT
#define SO_REUSEPORT 0
#endif
// ---- Process ID ----
inline uint32_t get_process_id()
{
return static_cast<uint32_t>(GetCurrentProcessId());
}
// ---- errno for sockets (Winsock uses WSAGetLastError) ----
inline int sock_errno()
{
return WSAGetLastError();
}
inline bool is_wouldblock()
{
int e = WSAGetLastError();
return e == WSAEWOULDBLOCK || e == WSAEINPROGRESS;
}
// ---- Network interface enumeration (get first non-loopback IPv4) ----
inline std::array<uint8_t, 4> get_local_ip_address()
{
std::array<uint8_t, 4> addr{127, 0, 0, 1};
ULONG bufSize = 15000;
std::vector<uint8_t> buf(bufSize);
auto* adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buf.data());
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
DWORD ret = GetAdaptersAddresses(AF_INET, flags, nullptr, adapters, &bufSize);
if (ret == ERROR_BUFFER_OVERFLOW)
{
buf.resize(bufSize);
adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buf.data());
ret = GetAdaptersAddresses(AF_INET, flags, nullptr, adapters, &bufSize);
}
if (ret != NO_ERROR)
{
return addr;
}
for (auto* a = adapters; a != nullptr; a = a->Next)
{
if (a->OperStatus != IfOperStatusUp)
continue;
if (a->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
continue;
for (auto* ua = a->FirstUnicastAddress; ua != nullptr; ua = ua->Next)
{
if (ua->Address.lpSockaddr->sa_family != AF_INET)
continue;
auto* sin = reinterpret_cast<struct sockaddr_in*>(ua->Address.lpSockaddr);
uint32_t ip = ntohl(sin->sin_addr.s_addr);
addr[0] = (ip >> 24) & 0xFF;
addr[1] = (ip >> 16) & 0xFF;
addr[2] = (ip >> 8) & 0xFF;
addr[3] = ip & 0xFF;
return addr;
}
}
return addr;
}
// ---- setsockopt compatibility (Windows uses const char*, POSIX uses const void*) ----
template <typename T>
inline int setsockopt_portable(socket_t s, int level, int optname, const T& optval)
{
return ::setsockopt(s, level, optname, reinterpret_cast<const char*>(&optval), sizeof(optval));
}
template <typename T>
inline int getsockopt_portable(socket_t s, int level, int optname, T& optval)
{
int len = sizeof(optval);
return ::getsockopt(s, level, optname, reinterpret_cast<char*>(&optval), &len);
}
// ---- sendto / recvfrom buffer type (Windows uses const char*) ----
inline ssize_t sendto_portable(socket_t s, const void* buf, size_t len, int flags, const struct sockaddr* to, int tolen)
{
return ::sendto(s, static_cast<const char*>(buf), static_cast<int>(len), flags, to, tolen);
}
inline ssize_t recvfrom_portable(socket_t s, void* buf, size_t len, int flags, struct sockaddr* from, int* fromlen)
{
return ::recvfrom(s, static_cast<char*>(buf), static_cast<int>(len), flags, from, fromlen);
}
inline ssize_t send_portable(socket_t s, const void* buf, size_t len, int flags)
{
return ::send(s, static_cast<const char*>(buf), static_cast<int>(len), flags);
}
inline ssize_t recv_portable(socket_t s, void* buf, size_t len, int flags)
{
return ::recv(s, static_cast<char*>(buf), static_cast<int>(len), flags);
}
} // namespace astutedds::platform
// ============================================================================
// POSIX (Linux, macOS, etc.)
// ============================================================================
#else // !_WIN32
#include <arpa/inet.h>
#include <array>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <fcntl.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
namespace astutedds::platform
{
// ---- Socket type alias ----
using socket_t = int;
static constexpr socket_t INVALID_SOCK = -1;
// ---- Network lifetime (no-op on POSIX) ----
inline bool net_init()
{
return true;
}
inline void net_cleanup() {}
// ---- Close a socket ----
inline int close_socket(socket_t s)
{
return ::close(s);
}
// ---- Set socket non-blocking ----
inline bool set_nonblocking(socket_t s)
{
int flags = fcntl(s, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(s, F_SETFL, flags | O_NONBLOCK) >= 0;
}
// ---- ssize_t (native) ----
using ssize_t = ::ssize_t;
// ---- poll ----
using pollfd_t = struct pollfd;
inline int poll(pollfd_t* fds, unsigned long nfds, int timeout_ms)
{
return ::poll(fds, static_cast<nfds_t>(nfds), timeout_ms);
}
// ---- Process ID ----
inline uint32_t get_process_id()
{
return static_cast<uint32_t>(getpid());
}
// ---- errno for sockets ----
inline int sock_errno()
{
return errno;
}
inline bool is_wouldblock()
{
return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS;
}
// ---- Network interface enumeration ----
inline std::array<uint8_t, 4> get_local_ip_address()
{
std::array<uint8_t, 4> addr{127, 0, 0, 1};
struct ifaddrs* ifaddr = nullptr;
if (getifaddrs(&ifaddr) == 0)
{
for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == nullptr)
continue;
if (ifa->ifa_addr->sa_family != AF_INET)
continue;
if (ifa->ifa_flags & IFF_LOOPBACK)
continue;
if (!(ifa->ifa_flags & IFF_UP))
continue;
auto* sin = reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr);
uint32_t ip = ntohl(sin->sin_addr.s_addr);
addr[0] = (ip >> 24) & 0xFF;
addr[1] = (ip >> 16) & 0xFF;
addr[2] = (ip >> 8) & 0xFF;
addr[3] = ip & 0xFF;
break;
}
freeifaddrs(ifaddr);
}
return addr;
}
// ---- setsockopt compatibility ----
template <typename T>
inline int setsockopt_portable(socket_t s, int level, int optname, const T& optval)
{
return ::setsockopt(s, level, optname, &optval, sizeof(optval));
}
template <typename T>
inline int getsockopt_portable(socket_t s, int level, int optname, T& optval)
{
socklen_t len = sizeof(optval);
return ::getsockopt(s, level, optname, &optval, &len);
}
// ---- sendto / recvfrom (native) ----
inline ssize_t sendto_portable(socket_t s, const void* buf, size_t len, int flags, const struct sockaddr* to,
socklen_t tolen)
{
return ::sendto(s, buf, len, flags, to, tolen);
}
inline ssize_t recvfrom_portable(socket_t s, void* buf, size_t len, int flags, struct sockaddr* from,
socklen_t* fromlen)
{
return ::recvfrom(s, buf, len, flags, from, fromlen);
}
inline ssize_t send_portable(socket_t s, const void* buf, size_t len, int flags)
{
return ::send(s, buf, len, flags);
}
inline ssize_t recv_portable(socket_t s, void* buf, size_t len, int flags)
{
return ::recv(s, buf, len, flags);
}
} // namespace astutedds::platform
#endif // _WIN32
#endif // ASTUTEDDS_PLATFORM_HPP