(original) (raw)
//---------------------------------------------------------------------------------------------------------------------- // SaneCppSocket.h - Sane C++ Socket Library (single file build) //---------------------------------------------------------------------------------------------------------------------- // Dependencies: SaneCppFoundation.h // Version: release/2025/11 (cf7313e5) // LOC header: 123 (code) + 189 (comments) // LOC implementation: 608 (code) + 141 (comments) // Documentation: https://pagghiu.github.io/SaneCppLibraries // Source Code: https://github.com/pagghiu/SaneCppLibraries //---------------------------------------------------------------------------------------------------------------------- // All copyrights and SPDX information for this library (each amalgamated section has its own copyright attributions): // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT //---------------------------------------------------------------------------------------------------------------------- #include "SaneCppFoundation.h" #if !defined(SANE_CPP_SOCKET_HEADER) #define SANE_CPP_SOCKET_HEADER 1 //---------------------------------------------------------------------------------------------------------------------- // Socket/Socket.h //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT namespace SC { //! @addtogroup group_socket //! @{ namespace detail { /// @brief Definition for SocketDescriptor #if SC_PLATFORM_WINDOWS struct SocketDescriptorDefinition { using Handle = size_t; // SOCKET static Result releaseHandle(Handle& handle); static constexpr Handle Invalid = ~static_cast(0); // INVALID_SOCKET }; #else struct SocketDescriptorDefinition { using Handle = int; // fd static Result releaseHandle(Handle& handle); static constexpr Handle Invalid = -1; // invalid fd }; #endif } // namespace detail /// @brief Flags for SocketDescriptor (Blocking / Inheritable, IPVx, SocketType) struct SocketFlags { /// @brief Sets the socket as blocking / nonblocking mode enum BlockingType { NonBlocking, ///< SocketDescriptor is in non-blocking mode Blocking ///< SocketDescriptor is in blocking mode }; /// @brief Sets the socket inheritable behaviour for child processes enum InheritableType { NonInheritable, ///< SocketDescriptor will not be inherited by child processes Inheritable ///< SocketDescriptor will be inherited by child processes }; /// @brief Sets the address family of an IP Address (IPv4 or IPV6) enum AddressFamily { AddressFamilyIPV4, ///< IP Address is IPV4 AddressFamilyIPV6, ///< IP Address is IPV6 }; /// @brief Sets the socket type, if it's a Datagram (for UDP) or Streaming (for TCP and others) enum SocketType { SocketStream, ///< Sets the socket type as Streaming type (for TCP and others) SocketDgram ///< Sets the socket type as Streaming type (for UDP) }; /// @brief Sets the socket protocol type enum ProtocolType { ProtocolTcp, ///< The protocol is TCP ProtocolUdp, ///< The protocol is UDP }; /// @brief Sets the type of shutdown to perform enum ShutdownType { ShutdownBoth ///< Shuts down the socket for both reading and writing }; private: friend struct SocketDescriptor; friend struct SocketIPAddressInternal; [[nodiscard]] static AddressFamily AddressFamilyFromInt(int value); [[nodiscard]] static unsigned char toNative(AddressFamily family); [[nodiscard]] static int toNative(SocketType family); [[nodiscard]] static int toNative(ProtocolType family); }; /// @brief Native representation of an IP Address. /// /// Example: /// @snippet Tests/Libraries/Socket/SocketTest.cpp socketIpAddressSnippet struct SocketIPAddress { /// @brief Maximum length of the ASCII representation of an IP Address static constexpr int MAX_ASCII_STRING_LENGTH = 46; /// @brief Buffer for storing the ASCII representation of an IP Address using AsciiBuffer = char[MAX_ASCII_STRING_LENGTH]; /// @brief Constructs an ip address from a given family (IPV4 or IPV6) /// @param addressFamily The address family SocketIPAddress(SocketFlags::AddressFamily addressFamily = SocketFlags::AddressFamilyIPV4); /// @brief Get Address family of this ip address (IPV4 or IPV6) /// @return The Ip Address Family of the given SocketDescriptor [[nodiscard]] SocketFlags::AddressFamily getAddressFamily() const; /// @brief Get port of this ip address /// @return The port of the given SocketIPAddress [[nodiscard]] uint16_t getPort() const; /// @brief Builds this SocketIPAddress parsing given address string and port /// @param interfaceAddress A valid IPV4 or IPV6 address expressed as an ASCII string /// @param port The port to connect to /// @return A valid Result if the address has been parsed successfully Result fromAddressPort(StringSpan interfaceAddress, uint16_t port); /// @brief Size of the native IP Address representation [[nodiscard]] uint32_t sizeOfHandle() const; /// @brief Checks if this is a valid IPV4 or IPV6 address [[nodiscard]] bool isValid() const; /// @brief Returns the text representation of this SocketIPAddress /// @param inputSpan Buffer to store the ASCII representation of the IP Address /// @param outputSpan A sub-Span of `inputSpan` that has the length of actually written bytes /// @note The buffer must be at least `MAX_ASCII_STRING_LENGTH` bytes long [[nodiscard]] bool toString(Span inputSpan, StringSpan& outputSpan) const; /// @brief Handle to native OS representation of the IP Address AlignedStorage<28> handle = {}; private: friend struct SocketServer; friend struct SocketClient; struct Internal; }; /// @brief Low-level OS socket handle. /// It also allow querying inheritability and changing it (and blocking mode) /// @n /// Example (extracted from unit test): /// @snippet Tests/Libraries/Socket/SocketTest.cpp socketCreateSnippet struct SC_COMPILER_EXPORT SocketDescriptor : public UniqueHandle{ /// @brief Creates a new SocketDescriptor Descriptor of given family, type, protocol /// @param addressFamily Address family (IPV4 / IPV6) /// @param socketType SocketDescriptor type (Stream or Dgram) /// @param protocol Protocol (TCP or UDP) /// @param blocking If the socket should be created in blocking mode /// @param inheritable If the socket should be inheritable by child processes /// @return Valid Result if a socket with the requested options has been successfully created Result create(SocketFlags::AddressFamily addressFamily, SocketFlags::SocketType socketType = SocketFlags::SocketStream, SocketFlags::ProtocolType protocol = SocketFlags::ProtocolTcp, SocketFlags::BlockingType blocking = SocketFlags::Blocking, SocketFlags::InheritableType inheritable = SocketFlags::NonInheritable); /// @brief Check if socket is inheritable by child processes /// @param[out] value if set to `true` indicates that this socket is inheritable by child processes /// @return Valid Result if the inheritable status for this socket has been queried successfully Result isInheritable(bool& value) const; /// @brief Changes the inheritable flag for this socket /// @param value `true` if this socket should be made inheritable, `false` for non-inheritable /// @return Valid Result if it has been possible changing the inheritable status of this socket Result setInheritable(bool value); /// @brief Changes the blocking flag for this socket (if IO reads / writes should be blocking or not) /// @param value `true` if this socket should be made blocking, `false` for non-blocking /// @return Valid Result if it has been possible changing the blocking status of this socket Result setBlocking(bool value); /// @brief Get address family (IPV4 / IPV6) of this socket /// @param[out] addressFamily The address family of this socket (if Result is valid) /// @return Valid Result the address family for this socket has been queried successfully Result getAddressFamily(SocketFlags::AddressFamily& addressFamily) const; /// @brief Shuts down the socket for reading, writing, or both /// @param shutdownType The type of shutdown to perform /// @return Valid Result if the socket has been successfully shut down Result shutdown(SocketFlags::ShutdownType shutdownType); }; /// @brief Use a SocketDescriptor as a Server (example TCP or UDP Socket Server). /// /// Example: /// @snippet Tests/Libraries/Socket/SocketTest.cpp socketServerSnippet struct SocketServer { /// @brief Build a SocketServer from a SocketDescriptor (already created with SocketDescriptor::create) /// @param socket A socket descriptor created with SocketDescriptor::create to be used as server SocketServer(SocketDescriptor& socket) : socket(socket) {} /// @brief Calls SocketDescriptor::close /// @return The Result of SocketDescriptor::close Result close(); /// @brief Binds this socket to a given address / port combination /// @param nativeAddress The interface ip address and port to start listening to /// @return Valid Result if this socket has successfully been bound Result bind(SocketIPAddress nativeAddress); /// @brief Start listening for incoming connections at a specific address / port combination (after bind) /// @param numberOfWaitingConnections How many connections can be queued before `accept` /// @return Valid Result if this socket has successfully been put in listening mode /// @note UDP socket cannot be listened. TCP socket need a successful SocketServer::bind before SocketServer::listen Result listen(uint32_t numberOfWaitingConnections); /// @brief Accepts a new client, blocking while waiting for it /// @param[in] addressFamily The address family of the SocketDescriptor that will be created /// @param[out] newClient The SocketDescriptor that will be accepted /// @return Valid Result if the socket has been successfully accepted Result accept(SocketFlags::AddressFamily addressFamily, SocketDescriptor& newClient); private: SocketDescriptor& socket; }; /// @brief Use a SocketDescriptor as a client (example a TCP or UDP socket client). /// /// The socket client can be obtained via SC::SocketServer::accept or connected to an endpoint /// through SC::SocketClient::connect. /// /// Example (accepted client from server, doing a synchronous read): /// @snippet Tests/Libraries/Socket/SocketTest.cpp socketClientAcceptSnippet /// /// Example (connecting client to server, doing two synchronous writes): /// @snippet Tests/Libraries/Socket/SocketTest.cpp socketClientConnectSnippet struct SocketClient { /// @brief Constructs this SocketClient from a SocketDescriptor (already created with SocketDescriptor::create) /// @param socket A socket descriptor created with SocketDescriptor::create to be used as client SocketClient(const SocketDescriptor& socket) : socket(socket) {} /// @brief Connect to a given address and port combination /// @param address Address as ASCII encoded string /// @param port Port to start listening to /// @return Valid Result if this client successfully connected to the specified address and port /// @note Socket descriptor MUST have already been created with SocketDescriptor::create Result connect(StringSpan address, uint16_t port); /// @brief Connect to a given address and port combination /// @param ipAddress Address and port to connect to /// @return Valid Result if this client successfully connected to the specified address and port Result connect(SocketIPAddress ipAddress); /// @brief Writes bytes to this socket /// @param data Bytes to write to this socket /// @return Valid Result if bytes have been written successfully Result write(Span data); /// @brief Read bytes from this socket blocking until they're actually received /// @param[in] data Span of memory pointing at a buffer that will receive the read data /// @param[out] readData A sub-Span of `data` that has the length of actually read bytes /// @return Valid Result if bytes have been read successfully Result read(Span data, Span& readData); /// @brief Read bytes from this socket blocking until they're actually received or timeout occurs /// @param[in] data Span of memory pointing at a buffer that will receive the read data /// @param[out] readData A sub-Span of `data` that has the length of actually read bytes /// @param[in] timeout For how many milliseconds the read should wait before timing out /// @return Valid Result if bytes have been read successfully and timeout didn't occur Result readWithTimeout(Span data, Span& readData, int64_t timeout); private: const SocketDescriptor& socket; }; /// @brief Synchronous DNS Resolution /// /// Example: /// @snippet Tests/Libraries/Socket/SocketTest.cpp resolveDNSSnippet struct SocketDNS { /// @brief Resolve an host string to an ip address (blocking until DNS response arrives) /// @param[in] host The ASCII encoded host string (example.com) /// @param[out] ipAddress Host ip address (ASCII encoded and null-terminated) /// @return Valid Result if ip address for the passed host has been successfully resolved /// /// Example: /// @snippet Tests/Libraries/Socket/SocketTest.cpp resolveDNSSnippet [[nodiscard]] static Result resolveDNS(StringSpan host, Span& ipAddress); }; /// @brief Networking globals initialization (Winsock2 WSAStartup) struct SocketNetworking { /// @brief Initializes Winsock2 on Windows (WSAStartup) /// @return Valid Result if Winsock2 has been successfully initialized [[nodiscard]] static Result initNetworking(); /// @brief Shutdowns Winsock2 on Windows (WSAStartup) static void shutdownNetworking(); /// @brief Check if initNetworking has been previously called /// @return `true` if initNetworking has been previously called [[nodiscard]] static bool isNetworkingInited(); private: struct Internal; }; //! @} } // namespace SC #endif // SANE_CPP_SOCKET_HEADER #if defined(SANE_CPP_IMPLEMENTATION) && !defined(SANE_CPP_SOCKET_IMPLEMENTATION) #define SANE_CPP_SOCKET_IMPLEMENTATION 1 //---------------------------------------------------------------------------------------------------------------------- // Socket/Socket.cpp //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #if SC_PLATFORM_WINDOWS //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketDescriptorWindows.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #include #include // sockadd_in6 using socklen_t = int; #define WIN32_LEAN_AND_MEAN #include #pragma comment(lib, "Ws2_32.lib") #if SC_COMPILER_CLANG #include #endif SC::Result SC::detail::SocketDescriptorDefinition::releaseHandle(Handle& handle) { const int res = ::closesocket(handle); handle = SocketDescriptor::Invalid; return Result(res != -1); } SC::Result SC::SocketDescriptor::setInheritable(bool inheritable) { BOOL res = ::SetHandleInformation(reinterpret_cast(handle), HANDLE_FLAG_INHERIT, inheritable ? TRUE : FALSE); return res == FALSE ? Result::Error("SetHandleInformation failed") : Result(true); } SC::Result SC::SocketDescriptor::setBlocking(bool blocking) { ULONG enable = blocking ? 0 : 1; if (::ioctlsocket(handle, FIONBIO, &enable) == SOCKET_ERROR) { return Result::Error("ioctlsocket failed"); } return Result(true); } SC::Result SC::SocketDescriptor::isInheritable(bool& hasValue) const { DWORD flags; if (::GetHandleInformation(reinterpret_cast(handle), &flags) == FALSE) { return Result::Error("GetHandleInformation failed"); } hasValue = (flags & HANDLE_FLAG_INHERIT) != 0; return Result(true); } SC::Result SC::SocketDescriptor::shutdown(SocketFlags::ShutdownType shutdownType) { int how = 0; switch (shutdownType) { case SocketFlags::ShutdownBoth: how = SD_BOTH; break; default: return Result::Error("Invalid shutdown type"); } if (::shutdown(handle, how) == 0) { return Result(true); } return Result::Error("Failed to shutdown socket"); } SC::Result SC::SocketDescriptor::create(SocketFlags::AddressFamily addressFamily, SocketFlags::SocketType socketType, SocketFlags::ProtocolType protocol, SocketFlags::BlockingType blocking, SocketFlags::InheritableType inheritable) { SC_TRY(SocketNetworking::isNetworkingInited()); SC_TRUST_RESULT(close()); DWORD flags = WSA_FLAG_OVERLAPPED; if (inheritable == SocketFlags::NonInheritable) { flags |= WSA_FLAG_NO_HANDLE_INHERIT; } handle = ::WSASocketW(SocketFlags::toNative(addressFamily), SocketFlags::toNative(socketType), SocketFlags::toNative(protocol), nullptr, 0, flags); if (!isValid()) { return Result::Error("WSASocketW failed"); } SC_TRY(setBlocking(blocking == SocketFlags::Blocking)); return Result(isValid()); } struct SC::SocketNetworking::Internal { #if SC_COMPILER_MSVC volatile long networkingInited = 0; #elif SC_COMPILER_CLANG _Atomic bool networkingInited = false; #elif SC_COMPILER_GCC volatile bool networkingInited = false; __attribute__((always_inline)) inline bool load() { return __atomic_load_n(&networkingInited, __ATOMIC_SEQ_CST); } __attribute__((always_inline)) inline void store(bool value) { __atomic_store_n(&networkingInited, value, __ATOMIC_SEQ_CST); } #endif static Internal& get() { static Internal internal; return internal; } }; bool SC::SocketNetworking::isNetworkingInited() { #if SC_COMPILER_MSVC return InterlockedCompareExchange(&Internal::get().networkingInited, 0, 0) != 0; #elif SC_COMPILER_CLANG return atomic_load(&Internal::get().networkingInited); #elif SC_COMPILER_GCC return Internal::get().load(); #endif } SC::Result SC::SocketNetworking::initNetworking() { if (isNetworkingInited() == false) { WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { return Result::Error("WSAStartup failed"); } #if SC_COMPILER_MSVC InterlockedExchange(&Internal::get().networkingInited, 1); #elif SC_COMPILER_CLANG atomic_store(&Internal::get().networkingInited, true); #elif SC_COMPILER_GCC Internal::get().store(true); #endif } return Result(true); } void SC::SocketNetworking::shutdownNetworking() { WSACleanup(); #if SC_COMPILER_MSVC InterlockedExchange(&Internal::get().networkingInited, 0); #elif SC_COMPILER_CLANG atomic_store(&Internal::get().networkingInited, false); #elif SC_COMPILER_GCC Internal::get().store(false); #endif } #else //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketDescriptorPosix.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #include // inet_pton #include // errno #include // fcntl #include // AF_INET / IPPROTO_TCP / AF_UNSPEC #include // close namespace { static SC::Result getFileFlags(int flagRead, const int fileDescriptor, int& outFlags) { do { outFlags = ::fcntl(fileDescriptor, flagRead); } while (outFlags == -1 && errno == EINTR); SC_TRY_MSG(outFlags != -1, "fcntl getFlag failed"); return SC::Result(true); } static SC::Result setFileFlags(int flagRead, int flagWrite, const int fileDescriptor, const bool setFlag, const int flag) { int oldFlags; do { oldFlags = ::fcntl(fileDescriptor, flagRead); } while (oldFlags == -1 && errno == EINTR); SC_TRY_MSG(oldFlags != -1, "fcntl getFlag failed"); const int newFlags = setFlag ? oldFlags | flag : oldFlags & (~flag); if (newFlags != oldFlags) { int res; do { res = ::fcntl(fileDescriptor, flagWrite, newFlags); } while (res == -1 && errno == EINTR); SC_TRY_MSG(res == 0, "fcntl setFlag failed"); } return SC::Result(true); } template static SC::Result hasFileDescriptorFlags(int fileDescriptor, bool& hasFlag) { static_assert(flag == FD_CLOEXEC, "hasFileDescriptorFlags invalid value"); int flags = 0; SC_TRY(getFileFlags(F_GETFD, fileDescriptor, flags)); hasFlag = (flags & flag) != 0; return SC::Result(true); } template static SC::Result setFileDescriptorFlags(int fileDescriptor, bool setFlag) { static_assert(flag == FD_CLOEXEC, "setFileDescriptorFlags invalid value"); return setFileFlags(F_GETFD, F_SETFD, fileDescriptor, setFlag, flag); } template static SC::Result hasFileStatusFlags(int fileDescriptor, bool& hasFlag) { static_assert(flag == O_NONBLOCK, "hasFileStatusFlags invalid value"); int flags = 0; SC_TRY(getFileFlags(F_GETFL, fileDescriptor, flags)); hasFlag = (flags & flag) != 0; return SC::Result(true); } template static SC::Result setFileStatusFlags(int fileDescriptor, bool setFlag) { static_assert(flag == O_NONBLOCK, "setFileStatusFlags invalid value"); return setFileFlags(F_GETFL, F_SETFL, fileDescriptor, setFlag, flag); } } // namespace SC::Result SC::detail::SocketDescriptorDefinition::releaseHandle(Handle& handle) { ::close(handle); handle = Invalid; return Result(true); } SC::Result SC::SocketDescriptor::setInheritable(bool inheritable) { // On POSIX, inheritable = false means set FD_CLOEXEC return setFileDescriptorFlags(handle, !inheritable); } SC::Result SC::SocketDescriptor::setBlocking(bool blocking) { // On POSIX, blocking = false means set O_NONBLOCK return setFileStatusFlags(handle, !blocking); } SC::Result SC::SocketDescriptor::isInheritable(bool& hasValue) const { bool cloexec = false; SC_TRY(hasFileDescriptorFlags(handle, cloexec)); hasValue = !cloexec; return SC::Result(true); } SC::Result SC::SocketDescriptor::shutdown(SocketFlags::ShutdownType shutdownType) { SC_TRY_MSG(shutdownType == SocketFlags::ShutdownBoth, "Invalid shutdown type"); int how = 0; SC_TRY_MSG(::shutdown(handle, how) == 0, "shutdown failed"); return Result(true); } SC::Result SC::SocketDescriptor::create(SocketFlags::AddressFamily addressFamily, SocketFlags::SocketType socketType, SocketFlags::ProtocolType protocol, SocketFlags::BlockingType blocking, SocketFlags::InheritableType inheritable) { SC_TRY(SocketNetworking::isNetworkingInited()); SC_TRUST_RESULT(close()); int typeWithAdditions = SocketFlags::toNative(socketType); #if defined(SOCK_NONBLOCK) if (blocking == SocketFlags::NonBlocking) { typeWithAdditions |= SOCK_NONBLOCK; } #endif // defined(SOCK_NONBLOCK) #if defined(SOCK_CLOEXEC) if (inheritable == SocketFlags::NonInheritable) { typeWithAdditions |= SOCK_CLOEXEC; } #endif // defined(SOCK_CLOEXEC) do { handle = ::socket(SocketFlags::toNative(addressFamily), typeWithAdditions, SocketFlags::toNative(protocol)); } while (handle == -1 and errno == EINTR); #if !defined(SOCK_CLOEXEC) if (inheritable == SocketFlags::NonInheritable) { SC_TRY(setInheritable(false)); } #endif // !defined(SOCK_CLOEXEC) #if !defined(SOCK_NONBLOCK) if (blocking == SocketFlags::NonBlocking) { SC_TRY(setBlocking(false)); } #endif // !defined(SOCK_NONBLOCK) #if defined(SO_NOSIGPIPE) { int active = 1; ::setsockopt(handle, SOL_SOCKET, SO_NOSIGPIPE, &active, sizeof(active)); } #endif // defined(SO_NOSIGPIPE) return Result(isValid()); } SC::Result SC::SocketNetworking::initNetworking() { return Result(true); } void SC::SocketNetworking::shutdownNetworking() {} bool SC::SocketNetworking::isNetworkingInited() { return Result(true); } #endif //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketDNS.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketInternal.h //---------------------------------------------------------------------------------------------------------------------- namespace SC { namespace detail { template [[nodiscard]] bool writeNullTerminatedToBuffer(Span source, char (&destination)[N]) { SC_TRY(N >= source.sizeInBytes() + 1); ::memcpy(destination, source.data(), source.sizeInBytes()); destination[source.sizeInBytes()] = 0; return true; } /// @brief Bitwise copies contents of this Span over another (non-overlapping) template [[nodiscard]] bool copyFromTo(const Span source, Span& other) { SC_TRY(other.sizeInBytes() >= source.sizeInBytes()); ::memcpy(other.data(), source.data(), source.sizeInBytes()); other = {other.data(), source.sizeInBytes() / sizeof(U)}; return true; } } // namespace detail #if !SC_PLATFORM_WINDOWS constexpr int SOCKET_ERROR = -1; #endif } // namespace SC #if !SC_PLATFORM_WINDOWS #include // inet_ntop #include // AF_INET / IPPROTO_TCP / AF_UNSPEC #endif #include SC::Result SC::SocketDNS::resolveDNS(StringSpan host, Span& ipAddress) { struct addrinfo hints, *res, *p; // Setup hints structure memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // Use either IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; // Use SOCK_STREAM for TCP char nullTerminated[256] = {0}; SC_TRY_MSG(host.getEncoding() == StringEncoding::Ascii, "Only ASCII encoding is supported"); SC_TRY_MSG(detail::writeNullTerminatedToBuffer(host.toCharSpan(), nullTerminated), "host is too big"); // Get address information SC_TRY_MSG(::getaddrinfo(nullTerminated, NULL, &hints, &res) == 0, "getaddrinfo error"); // Loop through results and print IP addresses for (p = res; p != NULL; p = p->ai_next) { void* addr; if (p->ai_family == AF_INET) { addr = &reinterpret_cast(p->ai_addr)->sin_addr; } else { addr = &reinterpret_cast(p->ai_addr)->sin6_addr; } if (p->ai_next == NULL) // take the last { // Convert IP address to a readable string char ipstr[INET6_ADDRSTRLEN + 1] = {0}; ::inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); Span ipOut = {ipstr, ::strnlen(ipstr, sizeof(ipstr) - 1)}; SC_TRY_MSG(detail::copyFromTo(ipOut, ipAddress), "ipAddress is insufficient"); } } ::freeaddrinfo(res); // Free the linked list return Result(true); } //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketFlags.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #if !SC_PLATFORM_WINDOWS #include // AF_INET / IPPROTO_TCP / AF_UNSPEC #endif SC::SocketFlags::AddressFamily SC::SocketFlags::AddressFamilyFromInt(int value) { return value == AF_INET ? SocketFlags::AddressFamilyIPV4 : SocketFlags::AddressFamilyIPV6; } unsigned char SC::SocketFlags::toNative(SocketFlags::AddressFamily type) { return type == SocketFlags::AddressFamilyIPV4 ? AF_INET : AF_INET6; } int SC::SocketFlags::toNative(SocketType type) { return type == SocketStream ? SOCK_STREAM : SOCK_DGRAM; } int SC::SocketFlags::toNative(ProtocolType family) { return family == ProtocolTcp ? IPPROTO_TCP : IPPROTO_UDP; } //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketIPAddress.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #if !SC_PLATFORM_WINDOWS #include // inet_pton #include // AF_INET / IPPROTO_TCP / AF_UNSPEC #endif namespace SC { struct SocketIPAddressInternal; } struct SC::SocketIPAddressInternal { [[nodiscard]] static Result parseIPV4(StringSpan ipAddress, uint16_t port, struct sockaddr_in& inaddr) { char buffer[64] = {0}; SC_TRY_MSG(detail::writeNullTerminatedToBuffer(ipAddress.toCharSpan(), buffer), "ipAddress too long"); memset(&inaddr, 0, sizeof(inaddr)); inaddr.sin_port = htons(port); inaddr.sin_family = SocketFlags::toNative(SocketFlags::AddressFamilyIPV4); const auto res = ::inet_pton(inaddr.sin_family, buffer, &inaddr.sin_addr); if (res == 0 or res == -1) { return Result::Error("inet_pton Invalid IPV4 Address"); } return Result(true); } [[nodiscard]] static Result parseIPV6(StringSpan ipAddress, uint16_t port, struct sockaddr_in6& inaddr) { char buffer[64] = {0}; SC_TRY_MSG(detail::writeNullTerminatedToBuffer(ipAddress.toCharSpan(), buffer), "ipAddress too long"); memset(&inaddr, 0, sizeof(inaddr)); inaddr.sin6_port = htons(port); inaddr.sin6_family = SocketFlags::toNative(SocketFlags::AddressFamilyIPV6); const auto res = ::inet_pton(inaddr.sin6_family, buffer, &inaddr.sin6_addr); if (res == 0 or res == -1) { return Result::Error("inet_pton Invalid IPV6 Address"); } return Result(true); } }; SC::SocketIPAddress::SocketIPAddress(SocketFlags::AddressFamily addressFamily) { switch (addressFamily) { case SocketFlags::AddressFamilyIPV4: { sockaddr_in& sa = handle.reinterpret_as(); sa.sin_family = AF_INET; } break; case SocketFlags::AddressFamilyIPV6: { sockaddr_in6& sa = handle.reinterpret_as(); sa.sin6_family = AF_INET6; } break; } } SC::SocketFlags::AddressFamily SC::SocketIPAddress::getAddressFamily() const { const sockaddr_in* sa = &handle.reinterpret_as(); if (sa->sin_family == AF_INET) { return SocketFlags::AddressFamilyIPV4; } else { SC_ASSERT_RELEASE(sa->sin_family == AF_INET6); return SocketFlags::AddressFamilyIPV6; } } SC::uint16_t SC::SocketIPAddress::getPort() const { const sockaddr_in* sa = &handle.reinterpret_as(); if (sa->sin_family == AF_INET) { const sockaddr_in* sa_in = (struct sockaddr_in*)sa; return ntohs(sa_in->sin_port); } else { SC_ASSERT_RELEASE(sa->sin_family == AF_INET6); const sockaddr_in6* sa_in6 = (struct sockaddr_in6*)sa; return ntohs(sa_in6->sin6_port); } } SC::uint32_t SC::SocketIPAddress::sizeOfHandle() const { return getAddressFamily() == SocketFlags::AddressFamilyIPV4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); } bool SC::SocketIPAddress::isValid() const { static_assert(MAX_ASCII_STRING_LENGTH <= INET6_ADDRSTRLEN, "MAX_ASCII_STRING_LENGTH"); char ipstr[INET6_ADDRSTRLEN]; StringSpan outSpan; return toString(ipstr, outSpan); } bool SC::SocketIPAddress::toString(Span inputSpan, StringSpan& outputSpan) const { const sockaddr* sa = &handle.reinterpret_as(); SC_TRY(inputSpan.sizeInBytes() >= MAX_ASCII_STRING_LENGTH); char* ipstr = inputSpan.data(); if (sa->sa_family == AF_INET) { const struct sockaddr_in* sa_in = &handle.reinterpret_as(); SC_TRY(::inet_ntop(AF_INET, &(sa_in->sin_addr), ipstr, (socklen_t)inputSpan.sizeInBytes()) != 0); outputSpan = StringSpan({ipstr, ::strlen(ipstr)}, true, StringEncoding::Ascii); return true; } else if (sa->sa_family == AF_INET6) { const struct sockaddr_in6* sa_in6 = &handle.reinterpret_as(); SC_TRY(::inet_ntop(AF_INET6, &(sa_in6->sin6_addr), ipstr, (socklen_t)inputSpan.sizeInBytes()) != 0); outputSpan = StringSpan({ipstr, ::strlen(ipstr)}, true, StringEncoding::Ascii); return true; } return false; } SC::Result SC::SocketIPAddress::fromAddressPort(StringSpan interfaceAddress, uint16_t port) { static_assert(sizeof(sockaddr_in6) >= sizeof(sockaddr_in), "size"); static_assert(alignof(sockaddr_in6) >= alignof(sockaddr_in), "size"); SC_TRY_MSG(interfaceAddress.getEncoding() == StringEncoding::Ascii, "Only ASCII encoding is supported"); Result res = SocketIPAddressInternal::parseIPV4(interfaceAddress, port, handle.reinterpret_as()); if (not res) { res = SocketIPAddressInternal::parseIPV6(interfaceAddress, port, handle.reinterpret_as()); } return res; } //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketClient.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #include // errno #if !SC_PLATFORM_WINDOWS #include // sockaddr_in #include // select #endif SC::Result SC::SocketClient::connect(StringSpan address, uint16_t port) { SocketIPAddress nativeAddress; SC_TRY(nativeAddress.fromAddressPort(address, port)); return connect(nativeAddress); } SC::Result SC::SocketClient::connect(SocketIPAddress ipAddress) { SC_TRY(SocketNetworking::isNetworkingInited()); SocketDescriptor::Handle openedSocket; SC_TRUST_RESULT(socket.get(openedSocket, Result::Error("invalid connect socket"))); socklen_t nativeSize = ipAddress.sizeOfHandle(); int res; do { res = ::connect(openedSocket, &ipAddress.handle.reinterpret_as(), nativeSize); } while (res == SOCKET_ERROR and errno == EINTR); SC_TRY_MSG(res != SOCKET_ERROR, "connect failed"); return Result(true); } SC::Result SC::SocketClient::write(Span data) { SocketDescriptor::Handle nativeSocket; SC_TRY(socket.get(nativeSocket, Result::Error("Invalid socket"))); #if SC_PLATFORM_WINDOWS const int sizeInBytes = static_cast(data.sizeInBytes()); #else const auto sizeInBytes = data.sizeInBytes(); #endif const auto written = ::send(nativeSocket, data.data(), sizeInBytes, 0); SC_TRY_MSG(written >= 0, "send error"); SC_TRY_MSG(static_cast<decltype(data.sizeinbytes())>(written) == data.sizeInBytes(), "send didn't write all bytes"); return Result(true); } SC::Result SC::SocketClient::read(Span data, Span& readData) { SocketDescriptor::Handle nativeSocket; SC_TRY(socket.get(nativeSocket, Result::Error("Invalid socket"))); #if SC_PLATFORM_WINDOWS const int sizeInBytes = static_cast(data.sizeInBytes()); #else const auto sizeInBytes = data.sizeInBytes(); #endif const auto recvSize = ::recv(nativeSocket, data.data(), sizeInBytes, 0); SC_TRY_MSG(recvSize >= 0, "recv error"); readData = {data.data(), static_cast(recvSize)}; return Result(true); } SC::Result SC::SocketClient::readWithTimeout(Span data, Span& readData, int64_t timeout) { SocketDescriptor::Handle nativeSocket; SC_TRY(socket.get(nativeSocket, Result::Error("Invalid socket"))); fd_set fds; FD_ZERO(&fds); FD_SET(nativeSocket, &fds); struct timeval tv; tv.tv_sec = static_cast(timeout / 1000); tv.tv_usec = (int)((timeout % 1000) * 1000); #if SC_PLATFORM_WINDOWS int maxFd = -1; #else int maxFd = nativeSocket; #endif const auto result = ::select(maxFd + 1, &fds, nullptr, nullptr, &tv); SC_TRY_MSG(result != SOCKET_ERROR, "select failed"); return FD_ISSET(nativeSocket, &fds) ? read(data, readData) : Result(false); } //---------------------------------------------------------------------------------------------------------------------- // Socket/Internal/SocketServer.inl //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #if !SC_PLATFORM_WINDOWS #include // bind #endif SC::Result SC::SocketServer::close() { return socket.close(); } // TODO: Add EINTR checks for all SocketServer/SocketClient os calls. SC::Result SC::SocketServer::bind(SocketIPAddress nativeAddress) { SC_TRY(SocketNetworking::isNetworkingInited()); SC_TRY_MSG(socket.isValid(), "Invalid socket"); SocketDescriptor::Handle listenSocket; SC_TRUST_RESULT(socket.get(listenSocket, Result::Error("invalid listen socket"))); // TODO: Expose SO_REUSEADDR as an option? int value = 1; #if SC_PLATFORM_WINDOWS ::setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&value), sizeof(value)); #elif !SC_PLATFORM_EMSCRIPTEN ::setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); #else SC_COMPILER_UNUSED(value); #endif const struct sockaddr* sa = &nativeAddress.handle.reinterpret_as(); const socklen_t saSize = nativeAddress.sizeOfHandle(); SC_TRY_MSG(::bind(listenSocket, sa, saSize) != SOCKET_ERROR, "Could not bind socket to port"); return Result(true); } SC::Result SC::SocketServer::listen(uint32_t numberOfWaitingConnections) { SC_TRY(SocketNetworking::isNetworkingInited()); SC_TRY_MSG(socket.isValid(), "Invalid socket"); SocketDescriptor::Handle listenSocket; SC_TRUST_RESULT(socket.get(listenSocket, Result::Error("invalid listen socket"))); SC_TRY_MSG(::listen(listenSocket, static_cast(numberOfWaitingConnections)) != SOCKET_ERROR, "listen failed"); return Result(true); } SC::Result SC::SocketServer::accept(SocketFlags::AddressFamily addressFamily, SocketDescriptor& newClient) { SC_TRY_MSG(not newClient.isValid(), "destination socket already in use"); SocketDescriptor::Handle listenDescriptor; SC_TRY(socket.get(listenDescriptor, Result::Error("Invalid socket"))); SocketIPAddress nativeAddress(addressFamily); socklen_t nativeSize = nativeAddress.sizeOfHandle(); SocketDescriptor::Handle acceptedClient = ::accept(listenDescriptor, &nativeAddress.handle.reinterpret_as(), &nativeSize); SC_TRY_MSG(acceptedClient != SocketDescriptor::Invalid, "accept failed"); return newClient.assign(acceptedClient); } SC::Result SC::SocketDescriptor::getAddressFamily(SocketFlags::AddressFamily& addressFamily) const { struct sockaddr_in6 socketInfo; socklen_t socketInfoLen = sizeof(socketInfo); if (::getsockname(handle, reinterpret_cast(&socketInfo), &socketInfoLen) == SOCKET_ERROR) { return Result::Error("getsockname failed"); } addressFamily = SocketFlags::AddressFamilyFromInt(socketInfo.sin6_family); return Result(true); } #endif // SANE_CPP_SOCKET_IMPLEMENTATION</decltype(data.sizeinbytes())>