aboutsummaryrefslogtreecommitdiff
path: root/src/net/sys
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-04-11 02:20:31 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-04-11 02:20:31 +0800
commit98e88c01eb0e6404d1c5f49a22165d0861e6b924 (patch)
treed016d445fe476f747eea9030896214adeaacf51f /src/net/sys
parent44ad3c8b36540c6b0b3144a21ac40310e163bb19 (diff)
downloadrwebbrowser-98e88c01eb0e6404d1c5f49a22165d0861e6b924.tar.gz
rwebbrowser-98e88c01eb0e6404d1c5f49a22165d0861e6b924.tar.xz
feat: implement basic HTTP client with socket and address networking utilities
Diffstat (limited to 'src/net/sys')
-rw-r--r--src/net/sys/address.cc207
-rw-r--r--src/net/sys/file_descriptor.cc213
-rw-r--r--src/net/sys/socket.cc286
3 files changed, 706 insertions, 0 deletions
diff --git a/src/net/sys/address.cc b/src/net/sys/address.cc
new file mode 100644
index 0000000..48f22ac
--- /dev/null
+++ b/src/net/sys/address.cc
@@ -0,0 +1,207 @@
+/**
+ * ---------------------------------------------------------------------------
+ * @notice This file was adapted from the Minnow networking framework,
+ * originally developed for Stanford University's CS144 course.
+ * It has been incorporated into the Ricochet project as foundational
+ * system utility code.
+ * ---------------------------------------------------------------------------
+ */
+
+#include "ricochet/net/sys/address.hh"
+
+#include "ricochet/net/sys/exception.hh"
+
+#include <array>
+#include <cstring>
+#include <linux/if_packet.h>
+#include <memory>
+#include <netdb.h>
+#include <stdexcept>
+#include <sys/socket.h>
+#include <system_error>
+
+using namespace std;
+
+//! Converts Raw to `sockaddr *`.
+Address::Raw::operator sockaddr*()
+{
+ return reinterpret_cast<sockaddr*>( &storage ); // NOLINT(*-reinterpret-cast)
+}
+
+//! Converts Raw to `const sockaddr *`.
+Address::Raw::operator const sockaddr*() const
+{
+ return reinterpret_cast<const sockaddr*>( &storage ); // NOLINT(*-reinterpret-cast)
+}
+
+//! \param[in] addr points to a raw socket address
+//! \param[in] size is `addr`'s length
+Address::Address( const sockaddr* addr, const size_t size ) : size_( size )
+{
+ // make sure proposed sockaddr can fit
+ if ( size > sizeof( address_.storage ) ) {
+ throw runtime_error( "invalid sockaddr size" );
+ }
+
+ memcpy( &address_.storage, addr, size );
+}
+
+//! Error category for getaddrinfo and getnameinfo failures.
+class gai_error_category : public error_category
+{
+public:
+ //! The name of the wrapped error
+ const char* name() const noexcept override { return "gai_error_category"; }
+ //! \brief An error message
+ //! \param[in] return_value the error return value from [getaddrinfo(3)](\ref man3::getaddrinfo)
+ //! or [getnameinfo(3)](\ref man3::getnameinfo)
+ string message( const int return_value ) const noexcept override { return gai_strerror( return_value ); }
+};
+
+//! \param[in] node is the hostname or dotted-quad address
+//! \param[in] service is the service name or numeric string
+//! \param[in] hints are criteria for resolving the supplied name
+Address::Address( const string& node, const string& service, const addrinfo& hints ) : size_()
+{
+ // prepare for the answer
+ addrinfo* resolved_address = nullptr;
+
+ // look up the name or names
+ const int gai_ret = getaddrinfo( node.c_str(), service.c_str(), &hints, &resolved_address );
+ if ( gai_ret != 0 ) {
+ throw tagged_error( gai_error_category(), "getaddrinfo(" + node + ", " + service + ")", gai_ret );
+ }
+
+ // if success, should always have at least one entry
+ if ( resolved_address == nullptr ) {
+ throw runtime_error( "getaddrinfo returned successfully but with no results" );
+ }
+
+ // put resolved_address in a wrapper so it will get freed if we have to throw an exception
+ auto addrinfo_deleter = []( addrinfo* const x ) { freeaddrinfo( x ); };
+ unique_ptr<addrinfo, decltype( addrinfo_deleter )> wrapped_address( resolved_address, move( addrinfo_deleter ) );
+
+ // assign to our private members (making sure size fits)
+ *this = Address( wrapped_address->ai_addr, wrapped_address->ai_addrlen );
+}
+
+//! \brief Build a `struct addrinfo` containing hints for [getaddrinfo(3)](\ref man3::getaddrinfo)
+//! \param[in] ai_flags is the value of the `ai_flags` field in the [struct addrinfo](\ref man3::getaddrinfo)
+//! \param[in] ai_family is the value of the `ai_family` field in the [struct addrinfo](\ref
+//! man3::getaddrinfo)
+namespace {
+inline addrinfo make_hints( int ai_flags, int ai_family ) // NOLINT(*-swappable-parameters)
+{
+ addrinfo hints {}; // value initialized to all zeros
+ hints.ai_flags = ai_flags;
+ hints.ai_family = ai_family;
+ return hints;
+}
+} // namespace
+
+//! \param[in] hostname to resolve
+//! \param[in] service name (from `/etc/services`, e.g., "http" is port 80)
+Address::Address( const string& hostname, const string& service )
+ : Address( hostname, service, make_hints( AI_ALL, AF_INET ) )
+{}
+
+//! \param[in] ip address as a dotted quad ("1.1.1.1")
+//! \param[in] port number
+Address::Address( const string& ip, const uint16_t port )
+ // tell getaddrinfo that we don't want to resolve anything
+ : Address( ip, ::to_string( port ), make_hints( AI_NUMERICHOST | AI_NUMERICSERV, AF_INET ) )
+{}
+
+// accessors
+pair<string, uint16_t> Address::ip_port() const
+{
+ if ( address_.storage.ss_family != AF_INET and address_.storage.ss_family != AF_INET6 ) {
+ throw runtime_error( "Address::ip_port() called on non-Internet address" );
+ }
+
+ array<char, NI_MAXHOST> ip {};
+ array<char, NI_MAXSERV> port {};
+
+ const int gni_ret = getnameinfo( static_cast<const sockaddr*>( address_ ),
+ size_,
+ ip.data(),
+ ip.size(),
+ port.data(),
+ port.size(),
+ NI_NUMERICHOST | NI_NUMERICSERV );
+ if ( gni_ret != 0 ) {
+ throw tagged_error( gai_error_category(), "getnameinfo", gni_ret );
+ }
+
+ return { ip.data(), stoi( port.data() ) };
+}
+
+string Address::to_string() const
+{
+ if ( address_.storage.ss_family == AF_INET or address_.storage.ss_family == AF_INET6 ) {
+ const auto ip_and_port = ip_port();
+ return ip_and_port.first + ":" + ::to_string( ip_and_port.second );
+ }
+
+ return "(non-Internet address)";
+}
+
+uint32_t Address::ipv4_numeric() const
+{
+ if ( address_.storage.ss_family != AF_INET or size_ != sizeof( sockaddr_in ) ) {
+ throw runtime_error( "ipv4_numeric called on non-IPV4 address" );
+ }
+
+ sockaddr_in ipv4_addr {};
+ memcpy( &ipv4_addr, &address_.storage, size_ );
+
+ return be32toh( ipv4_addr.sin_addr.s_addr );
+}
+
+Address Address::from_ipv4_numeric( const uint32_t ip_address )
+{
+ sockaddr_in ipv4_addr {};
+ ipv4_addr.sin_family = AF_INET;
+ ipv4_addr.sin_addr.s_addr = htobe32( ip_address );
+
+ return { reinterpret_cast<sockaddr*>( &ipv4_addr ), sizeof( ipv4_addr ) }; // NOLINT(*-reinterpret-cast)
+}
+
+// equality
+bool Address::operator==( const Address& other ) const
+{
+ if ( size_ != other.size_ ) {
+ return false;
+ }
+
+ return 0 == memcmp( &address_, &other.address_, size_ );
+}
+
+// address families that correspond to each sockaddr type
+template<typename sockaddr_type>
+constexpr int sockaddr_family = -1;
+
+template<>
+constexpr int sockaddr_family<sockaddr_in> = AF_INET;
+
+template<>
+constexpr int sockaddr_family<sockaddr_in6> = AF_INET6;
+
+template<>
+constexpr int sockaddr_family<sockaddr_ll> = AF_PACKET;
+
+// safely cast the address to its underlying sockaddr type
+template<typename sockaddr_type>
+const sockaddr_type* Address::as() const
+{
+ const sockaddr* raw { address_ };
+ if ( sizeof( sockaddr_type ) < size() or raw->sa_family != sockaddr_family<sockaddr_type> ) {
+ throw runtime_error( "Address::as() conversion failure" );
+ }
+
+ return reinterpret_cast<const sockaddr_type*>( raw ); // NOLINT(*-reinterpret-cast)
+}
+
+template const sockaddr_in* Address::as<sockaddr_in>() const;
+template const sockaddr_in6* Address::as<sockaddr_in6>() const;
+template const sockaddr_ll* Address::as<sockaddr_ll>() const;
diff --git a/src/net/sys/file_descriptor.cc b/src/net/sys/file_descriptor.cc
new file mode 100644
index 0000000..b765308
--- /dev/null
+++ b/src/net/sys/file_descriptor.cc
@@ -0,0 +1,213 @@
+/**
+ * ---------------------------------------------------------------------------
+ * @notice This file was adapted from the Minnow networking framework,
+ * originally developed for Stanford University's CS144 course.
+ * It has been incorporated into the Ricochet project as foundational
+ * system utility code.
+ * ---------------------------------------------------------------------------
+ */
+
+#include "ricochet/net/sys/file_descriptor.hh"
+
+#include "ricochet/net/sys/exception.hh"
+
+#include <fcntl.h>
+#include <iostream>
+#include <stdexcept>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+using namespace std;
+
+size_t FileDescriptor::FDWrapper::CheckFDSystemCall( string_view what, ssize_t return_value ) const
+{
+ if ( return_value >= 0 ) {
+ return return_value;
+ }
+
+ if ( non_blocking_ and ( errno == EAGAIN or errno == EWOULDBLOCK or errno == EINPROGRESS ) ) {
+ return 0;
+ }
+
+ throw unix_error { what };
+}
+
+size_t FileDescriptor::FDWrapper::CheckRead( string_view what, ssize_t return_value )
+{
+ if ( return_value == 0 ) {
+ eof_ = true;
+ }
+
+ return CheckFDSystemCall( what, return_value );
+}
+
+size_t FileDescriptor::CheckFDSystemCall( string_view what, ssize_t return_value ) const
+{
+ if ( not internal_fd_ ) {
+ throw runtime_error( "internal error: missing internal_fd_" );
+ }
+ return internal_fd_->CheckFDSystemCall( what, return_value );
+}
+
+size_t FileDescriptor::CheckRead( string_view what, ssize_t return_value )
+{
+ if ( not internal_fd_ ) {
+ throw runtime_error( "internal error: missing internal_fd_" );
+ }
+ return internal_fd_->CheckRead( what, return_value );
+}
+
+// fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
+FileDescriptor::FDWrapper::FDWrapper( int fd ) : fd_( fd )
+{
+ if ( fd < 0 ) {
+ throw runtime_error( "invalid fd number:" + to_string( fd ) );
+ }
+
+ const int flags = CheckSystemCall( "fcntl", fcntl( fd, F_GETFL ) ); // NOLINT(*-vararg)
+ non_blocking_ = flags & O_NONBLOCK; // NOLINT(*-bitwise)
+}
+
+void FileDescriptor::FDWrapper::close()
+{
+ CheckSystemCall( "close", ::close( fd_ ) );
+ eof_ = closed_ = true;
+}
+
+FileDescriptor::FDWrapper::~FDWrapper()
+{
+ try {
+ if ( closed_ ) {
+ return;
+ }
+ close();
+ } catch ( const exception& e ) {
+ // don't throw an exception from the destructor
+ cerr << "Exception destructing FDWrapper: " << e.what() << "\n";
+ }
+}
+
+// fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
+FileDescriptor::FileDescriptor( int fd ) : internal_fd_( make_shared<FDWrapper>( fd ) ) {}
+
+// Private constructor used by duplicate()
+FileDescriptor::FileDescriptor( shared_ptr<FDWrapper> other_shared_ptr ) : internal_fd_( move( other_shared_ptr ) )
+{}
+
+// returns a copy of this FileDescriptor
+FileDescriptor FileDescriptor::duplicate() const
+{
+ return FileDescriptor { internal_fd_ };
+}
+
+// Read into a single buffer of given length (which if empty will be resized to a reasonable value before reading).
+// Afer the read, the buffer will be resized to match whatever was read.
+void FileDescriptor::read( string& buffer )
+{
+ if ( buffer.empty() ) {
+ buffer.resize( kReadBufferSize );
+ }
+
+ const size_t bytes_read = CheckRead( "read", ::read( fd_num(), buffer.data(), buffer.size() ) );
+ register_read();
+
+ if ( bytes_read > buffer.size() ) {
+ throw runtime_error( "read() read more than requested" );
+ }
+
+ buffer.resize( bytes_read );
+}
+
+// Read into a vector of buffers (if all empty, the last one will be resized to a reasonable value).
+// After the read, the buffers will be resized to match whatever was read.
+void FileDescriptor::read( vector<string>& buffers )
+{
+ if ( buffers.empty() ) {
+ throw runtime_error( "FileDescriptor::read called with no buffers" );
+ }
+
+ if ( buffers.back().empty() ) {
+ buffers.back().resize( kReadBufferSize );
+ }
+
+ static thread_local vector<iovec> iovecs;
+ const size_t total_size = to_iovecs( buffers, iovecs );
+
+ const size_t bytes_read
+ = CheckRead( "readv", readv( fd_num(), iovecs.data(), static_cast<int>( iovecs.size() ) ) );
+ register_read();
+
+ if ( bytes_read > total_size ) {
+ throw runtime_error( "read() read more than requested" );
+ }
+
+ size_t remaining_size = bytes_read;
+ for ( auto& buf : buffers ) {
+ if ( remaining_size >= buf.size() ) {
+ remaining_size -= buf.size();
+ } else {
+ buf.resize( remaining_size );
+ remaining_size = 0;
+ }
+ }
+}
+
+void FileDescriptor::write_all( string_view buffer )
+{
+ if ( not blocking() ) {
+ throw runtime_error( "write_all requires a blocking file descriptor" );
+ // otherwise, this could call write on a non-blocking fd that isn't "ready" for writing
+ }
+
+ while ( not buffer.empty() ) {
+ buffer.remove_prefix( write( buffer ) );
+ }
+}
+
+size_t FileDescriptor::write( string_view buffer )
+{
+ const size_t bytes_written = CheckFDSystemCall( "write", ::write( fd_num(), buffer.data(), buffer.size() ) );
+ register_write();
+
+ if ( bytes_written == 0 and not buffer.empty() ) {
+ throw runtime_error( "write returned 0 given non-empty input buffer" );
+ }
+
+ if ( bytes_written > buffer.size() ) {
+ throw runtime_error( "write wrote more than length of input buffer" );
+ }
+
+ return bytes_written;
+}
+
+size_t FileDescriptor::write( const std::vector<iovec>& iovecs, size_t total_size )
+{
+ const size_t bytes_written
+ = CheckFDSystemCall( "writev", ::writev( fd_num(), iovecs.data(), static_cast<int>( iovecs.size() ) ) );
+ register_write();
+
+ if ( bytes_written == 0 and total_size != 0 ) {
+ throw runtime_error( "writev returned 0 given non-empty input buffer" );
+ }
+
+ if ( bytes_written > total_size ) {
+ throw runtime_error( "writev wrote more than length of input buffer" );
+ }
+
+ return bytes_written;
+}
+
+void FileDescriptor::set_blocking( bool blocking )
+{
+ int flags = CheckSystemCall( "fcntl", fcntl( fd_num(), F_GETFL ) ); // NOLINT(*-vararg)
+ if ( blocking ) {
+ flags ^= ( flags & O_NONBLOCK ); // NOLINT(*-bitwise)
+ } else {
+ flags |= O_NONBLOCK; // NOLINT(*-bitwise)
+ }
+
+ CheckSystemCall( "fcntl", fcntl( fd_num(), F_SETFL, flags ) ); // NOLINT(*-vararg)
+
+ internal_fd_->non_blocking_ = not blocking;
+}
diff --git a/src/net/sys/socket.cc b/src/net/sys/socket.cc
new file mode 100644
index 0000000..701704f
--- /dev/null
+++ b/src/net/sys/socket.cc
@@ -0,0 +1,286 @@
+/**
+ * ---------------------------------------------------------------------------
+ * @notice This file was adapted from the Minnow networking framework,
+ * originally developed for Stanford University's CS144 course.
+ * It has been incorporated into the Ricochet project as foundational
+ * system utility code.
+ * ---------------------------------------------------------------------------
+ */
+
+#include "ricochet/net/sys/socket.hh"
+
+#include "ricochet/net/sys/exception.hh"
+
+#include <linux/if_packet.h>
+#include <stdexcept>
+#include <unistd.h>
+
+using namespace std;
+
+// default constructor for socket of (subclassed) domain and type
+//! \param[in] domain is as described in [socket(7)](\ref man7::socket), probably `AF_INET` or `AF_UNIX`
+//! \param[in] type is as described in [socket(7)](\ref man7::socket)
+Socket::Socket( const int domain, const int type, const int protocol )
+ : FileDescriptor( CheckSystemCall( "socket", socket( domain, type, protocol ) ) )
+{}
+
+// construct from file descriptor
+//! \param[in] fd is the FileDescriptor from which to construct
+//! \param[in] domain is `fd`'s domain; throws std::runtime_error if wrong value is supplied
+//! \param[in] type is `fd`'s type; throws std::runtime_error if wrong value is supplied
+//! \param[in] protocol is `fd`'s protocol; throws std::runtime_error if wrong value is supplied
+Socket::Socket( FileDescriptor&& fd, int domain, int type, int protocol ) // NOLINT(*-swappable-parameters)
+ : FileDescriptor( move( fd ) )
+{
+ int actual_value {};
+ socklen_t len {};
+
+ // verify domain
+ len = getsockopt( SOL_SOCKET, SO_DOMAIN, actual_value );
+ if ( ( len != sizeof( actual_value ) ) or ( actual_value != domain ) ) {
+ throw runtime_error( "socket domain mismatch" );
+ }
+
+ // verify type
+ len = getsockopt( SOL_SOCKET, SO_TYPE, actual_value );
+ if ( ( len != sizeof( actual_value ) ) or ( actual_value != type ) ) {
+ throw runtime_error( "socket type mismatch" );
+ }
+
+ // verify protocol
+ len = getsockopt( SOL_SOCKET, SO_PROTOCOL, actual_value );
+ if ( ( len != sizeof( actual_value ) ) or ( actual_value != protocol ) ) {
+ throw runtime_error( "socket protocol mismatch" );
+ }
+}
+
+// get the local or peer address the socket is connected to
+//! \param[in] name_of_function is the function to call (string passed to CheckSystemCall())
+//! \param[in] function is a pointer to the function
+//! \returns the requested Address
+Address Socket::get_address( const string& name_of_function,
+ const function<int( int, sockaddr*, socklen_t* )>& function ) const
+{
+ Address::Raw address;
+ socklen_t size = sizeof( address );
+
+ CheckSystemCall( name_of_function, function( fd_num(), address, &size ) );
+
+ return Address { address, size };
+}
+
+//! \returns the local Address of the socket
+Address Socket::local_address() const
+{
+ return get_address( "getsockname", getsockname );
+}
+
+//! \returns the socket's peer's Address
+Address Socket::peer_address() const
+{
+ return get_address( "getpeername", getpeername );
+}
+
+// bind socket to a specified local address (usually to listen/accept)
+//! \param[in] address is a local Address to bind
+void Socket::bind( const Address& address )
+{
+ CheckSystemCall( "bind", ::bind( fd_num(), address.raw(), address.size() ) );
+}
+
+void Socket::bind_to_device( const string_view device_name )
+{
+ setsockopt( SOL_SOCKET, SO_BINDTODEVICE, device_name );
+}
+
+// connect socket to a specified peer address
+//! \param[in] address is the peer's Address
+void Socket::connect( const Address& address )
+{
+ CheckFDSystemCall( "connect", ::connect( fd_num(), address.raw(), address.size() ) );
+}
+
+// shut down a socket in the specified way
+//! \param[in] how can be `SHUT_RD`, `SHUT_WR`, or `SHUT_RDWR`; see [shutdown(2)](\ref man2::shutdown)
+void Socket::shutdown( const int how )
+{
+ CheckSystemCall( "shutdown", ::shutdown( fd_num(), how ) );
+ switch ( how ) {
+ case SHUT_RD:
+ register_read();
+ break;
+ case SHUT_WR:
+ register_write();
+ break;
+ case SHUT_RDWR:
+ register_read();
+ register_write();
+ break;
+ default:
+ throw runtime_error( "Socket::shutdown() called with invalid `how`" );
+ }
+}
+
+void DatagramSocket::recv( Address& source_address, string& payload )
+{
+ if ( payload.empty() ) {
+ payload.resize( kReadBufferSize );
+ }
+
+ Address::Raw raw_source_address;
+ socklen_t namelen = sizeof( raw_source_address );
+ const size_t recv_len = CheckFDSystemCall(
+ "recvfrom", ::recvfrom( fd_num(), payload.data(), payload.size(), MSG_TRUNC, raw_source_address, &namelen ) );
+ register_read();
+
+ if ( recv_len > payload.size() ) {
+ throw runtime_error( "recvfrom (oversized datagram of length " + to_string( recv_len ) + ")" );
+ }
+ if ( namelen <= 0 || namelen > sizeof( raw_source_address ) ) {
+ throw runtime_error( "recvfrom gave invalid namelen" );
+ }
+
+ source_address = { raw_source_address, namelen };
+}
+
+void DatagramSocket::recv( Address& source_address, vector<string>& payloads )
+{
+ if ( payloads.empty() ) {
+ throw runtime_error( "DatagramSocket::recv called with no payload buffers" );
+ }
+
+ if ( payloads.back().empty() ) {
+ payloads.back().resize( kReadBufferSize );
+ }
+
+ // set up iovecs
+ static thread_local vector<iovec> iovecs;
+ const size_t total_size = to_iovecs( payloads, iovecs );
+
+ // set up msghdr
+ Address::Raw raw_source_address;
+ msghdr message { .msg_name = &raw_source_address,
+ .msg_namelen = sizeof( raw_source_address ),
+ .msg_iov = iovecs.data(),
+ .msg_iovlen = iovecs.size(),
+ .msg_control = nullptr,
+ .msg_controllen {},
+ .msg_flags {} };
+ const size_t recv_len = CheckFDSystemCall( "recvmsg", ::recvmsg( fd_num(), &message, MSG_TRUNC ) );
+ register_read();
+ if ( recv_len > total_size ) {
+ throw runtime_error( "recvmsg (oversized datagram of length " + to_string( recv_len ) + ")" );
+ }
+ if ( message.msg_flags & MSG_TRUNC ) {
+ throw runtime_error( "recvmsg (oversized datagram indicated only by MSG_TRUNC)" );
+ }
+ if ( message.msg_namelen <= 0 || message.msg_namelen > sizeof( raw_source_address ) ) {
+ throw runtime_error( "recvmsg gave invalid namelen" );
+ }
+ source_address = { raw_source_address, message.msg_namelen };
+
+ size_t remaining_size = recv_len;
+ for ( auto& buf : payloads ) {
+ if ( remaining_size >= buf.size() ) {
+ remaining_size -= buf.size();
+ } else {
+ buf.resize( remaining_size );
+ remaining_size = 0;
+ }
+ }
+}
+
+void DatagramSocket::send( string_view payload, const optional<Address>& destination )
+{
+ const size_t bytes_sent = CheckFDSystemCall( "sendto",
+ ::sendto( fd_num(),
+ payload.data(),
+ payload.size(),
+ 0,
+ destination.has_value() ? destination->raw() : nullptr,
+ destination.has_value() ? destination->size() : 0 ) );
+ register_write();
+ if ( bytes_sent != payload.size() ) {
+ throw runtime_error( "sendto sent some length other than that of payload" );
+ }
+}
+
+void DatagramSocket::send( vector<iovec>& iovecs, size_t total_size, const optional<Address>& destination )
+{
+ const msghdr message { .msg_name = destination.has_value() // NOLINTNEXTLINE(*-const-cast)
+ ? static_cast<void*>( const_cast<sockaddr*>( destination->raw() ) )
+ : nullptr,
+ .msg_namelen = destination.has_value() ? destination->size() : 0,
+ .msg_iov = iovecs.data(),
+ .msg_iovlen = iovecs.size(),
+ .msg_control = nullptr,
+ .msg_controllen {},
+ .msg_flags {} };
+ const size_t bytes_sent = CheckFDSystemCall( "sendmsg", ::sendmsg( fd_num(), &message, 0 ) );
+ register_write();
+ if ( bytes_sent != total_size ) {
+ throw runtime_error( "sendmsg sent some length other than that of payload" );
+ }
+}
+
+// mark the socket as listening for incoming connections
+//! \param[in] backlog is the number of waiting connections to queue (see [listen(2)](\ref man2::listen))
+void TCPSocket::listen( const int backlog )
+{
+ CheckSystemCall( "listen", ::listen( fd_num(), backlog ) );
+}
+
+// accept a new incoming connection
+//! \returns a new TCPSocket connected to the peer.
+//! \note This function blocks until a new connection is available
+TCPSocket TCPSocket::accept()
+{
+ register_read();
+ return TCPSocket( FileDescriptor( CheckSystemCall( "accept", ::accept( fd_num(), nullptr, nullptr ) ) ) );
+}
+
+// get socket option
+template<typename option_type>
+socklen_t Socket::getsockopt( const int level, const int option, option_type& option_value ) const
+{
+ socklen_t optlen = sizeof( option_value );
+ CheckSystemCall( "getsockopt", ::getsockopt( fd_num(), level, option, &option_value, &optlen ) );
+ return optlen;
+}
+
+// set socket option
+//! \param[in] level The protocol level at which the argument resides
+//! \param[in] option A single option to set
+//! \param[in] option_value The value to set
+//! \details See [setsockopt(2)](\ref man2::setsockopt) for details.
+template<typename option_type>
+void Socket::setsockopt( const int level, const int option, const option_type& option_value )
+{
+ CheckSystemCall( "setsockopt", ::setsockopt( fd_num(), level, option, &option_value, sizeof( option_value ) ) );
+}
+
+// setsockopt with size only known at runtime
+void Socket::setsockopt( const int level, const int option, const string_view option_val )
+{
+ CheckSystemCall( "setsockopt", ::setsockopt( fd_num(), level, option, option_val.data(), option_val.size() ) );
+}
+
+// allow local address to be reused sooner, at the cost of some robustness
+//! \note Using `SO_REUSEADDR` may reduce the robustness of your application
+void Socket::set_reuseaddr()
+{
+ setsockopt( SOL_SOCKET, SO_REUSEADDR, int { true } );
+}
+
+void Socket::throw_if_error() const
+{
+ int socket_error = 0;
+ const socklen_t len = getsockopt( SOL_SOCKET, SO_ERROR, socket_error );
+ if ( len != sizeof( socket_error ) ) {
+ throw runtime_error( "unexpected length from getsockopt: " + to_string( len ) );
+ }
+
+ if ( socket_error ) {
+ throw unix_error( "socket error", socket_error );
+ }
+}