97.56% Lines (40/41) 100.00% Functions (10/10)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP 10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP 11   #define BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP
12   12  
13   #include <boost/corosio/detail/native_handle.hpp> 13   #include <boost/corosio/detail/native_handle.hpp>
14   #include <boost/corosio/endpoint.hpp> 14   #include <boost/corosio/endpoint.hpp>
15   #include <boost/corosio/native/detail/endpoint_convert.hpp> 15   #include <boost/corosio/native/detail/endpoint_convert.hpp>
16   #include <boost/corosio/native/detail/make_err.hpp> 16   #include <boost/corosio/native/detail/make_err.hpp>
17   17  
18   #include <memory> 18   #include <memory>
19   #include <system_error> 19   #include <system_error>
20   20  
21   #include <errno.h> 21   #include <errno.h>
22   #include <sys/socket.h> 22   #include <sys/socket.h>
23   23  
24   /* 24   /*
25   Readiness/completion-agnostic socket base for the POSIX-fd backends. 25   Readiness/completion-agnostic socket base for the POSIX-fd backends.
26   26  
27   Holds the part of a socket impl that does not care whether the backend 27   Holds the part of a socket impl that does not care whether the backend
28   is readiness-based (epoll/kqueue/select, which park ops on a 28   is readiness-based (epoll/kqueue/select, which park ops on a
29   descriptor_state) or completion-based (io_uring, which submits SQEs): 29   descriptor_state) or completion-based (io_uring, which submits SQEs):
30   the file descriptor, the cached family and local endpoint, and the 30   the file descriptor, the cached family and local endpoint, and the
31   synchronous accessors (native_handle / options / bind). The impl 31   synchronous accessors (native_handle / options / bind). The impl
32   lifecycle (the intrusive refcount + the owning service's recycling 32   lifecycle (the intrusive refcount + the owning service's recycling
33   pool) is carried by `io_object::implementation`, not by this base. 33   pool) is carried by `io_object::implementation`, not by this base.
34   34  
35   reactor_basic_socket derives from this and adds the readiness machinery 35   reactor_basic_socket derives from this and adds the readiness machinery
36   (descriptor_state, register_op, the cancel/close that deregister from 36   (descriptor_state, register_op, the cancel/close that deregister from
37   the reactor). io_uring's socket impls derive from it and add their op 37   the reactor). io_uring's socket impls derive from it and add their op
38   slots + SQE submission. This is the socket-layer analogue of io_uring 38   slots + SQE submission. This is the socket-layer analogue of io_uring
39   deriving from reactor_scheduler: io_uring sockets share the reactor's 39   deriving from reactor_scheduler: io_uring sockets share the reactor's
40   readiness-agnostic socket surface, while the on-EAGAIN action (park vs 40   readiness-agnostic socket surface, while the on-EAGAIN action (park vs
41   submit-SQE) and the op model stay backend-specific. 41   submit-SQE) and the op model stay backend-specific.
42   42  
43   @tparam Derived The concrete socket type (CRTP). 43   @tparam Derived The concrete socket type (CRTP).
44   @tparam ImplBase The public vtable base (tcp_socket::implementation, 44   @tparam ImplBase The public vtable base (tcp_socket::implementation,
45   udp_socket::implementation, ...). 45   udp_socket::implementation, ...).
46   @tparam Endpoint The endpoint type (endpoint or local_endpoint). 46   @tparam Endpoint The endpoint type (endpoint or local_endpoint).
47   */ 47   */
48   48  
49   namespace boost::corosio::detail { 49   namespace boost::corosio::detail {
50   50  
51   template<class Derived, class ImplBase, class Endpoint = endpoint> 51   template<class Derived, class ImplBase, class Endpoint = endpoint>
52   class native_socket_base 52   class native_socket_base
53   : public ImplBase 53   : public ImplBase
54   { 54   {
55   protected: 55   protected:
56   // CRTP base: not publicly constructible. The check's preferred fix 56   // CRTP base: not publicly constructible. The check's preferred fix
57   // (private ctor + `friend Derived`) is infeasible here — the reactor 57   // (private ctor + `friend Derived`) is infeasible here — the reactor
58   // sockets reach this base through intermediate templates 58   // sockets reach this base through intermediate templates
59   // (reactor_stream_socket -> reactor_basic_socket) that are not `Derived`, 59   // (reactor_stream_socket -> reactor_basic_socket) that are not `Derived`,
60   // so a private ctor would stop those intermediates from constructing it. 60   // so a private ctor would stop those intermediates from constructing it.
61   // Protected is the correct access; suppress the private-only suggestion. 61   // Protected is the correct access; suppress the private-only suggestion.
62   // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility) 62   // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
HITCBC 63   5799 native_socket_base() = default; 63   5694 native_socket_base() = default;
64   64  
65   int fd_ = -1; 65   int fd_ = -1;
66   // Option calls, connects and datagram sends all need the family. 66   // Option calls, connects and datagram sends all need the family.
67   // Backends seed it where they know it and clear it whenever the 67   // Backends seed it where they know it and clear it whenever the
68   // descriptor goes; otherwise it is looked up once, on first use. 68   // descriptor goes; otherwise it is looked up once, on first use.
69   mutable int family_ = AF_UNSPEC; 69   mutable int family_ = AF_UNSPEC;
70   // mutable so a derived const local_endpoint() override can lazily fill 70   // mutable so a derived const local_endpoint() override can lazily fill
71   // it via getsockname() on first read (io_uring's lazy_pending state). 71   // it via getsockname() on first read (io_uring's lazy_pending state).
72   mutable Endpoint local_endpoint_; 72   mutable Endpoint local_endpoint_;
73   73  
74   public: 74   public:
HITCBC 75   5799 ~native_socket_base() override = default; 75   5694 ~native_socket_base() override = default;
76   76  
77   /// Return the underlying file descriptor. 77   /// Return the underlying file descriptor.
HITCBC 78   91118 native_handle_type native_handle() const noexcept override 78   89950 native_handle_type native_handle() const noexcept override
79   { 79   {
HITCBC 80   91118 return fd_; 80   89950 return fd_;
81   } 81   }
82   82  
83   /// Return the socket's address family. 83   /// Return the socket's address family.
HITCBC 84   7168 corosio::family family() const noexcept override 84   6875 corosio::family family() const noexcept override
85   { 85   {
HITCBC 86   7168 return to_family(native_family()); 86   6875 return to_family(native_family());
87   } 87   }
88   88  
89   /** Return the descriptor's native address family. 89   /** Return the descriptor's native address family.
90   90  
91   @return `AF_INET`, `AF_INET6`, `AF_UNIX`, or `AF_UNSPEC` if the 91   @return `AF_INET`, `AF_INET6`, `AF_UNIX`, or `AF_UNSPEC` if the
92   socket is closed or the lookup fails. 92   socket is closed or the lookup fails.
93   */ 93   */
HITCBC 94   15797 int native_family() const noexcept 94   15210 int native_family() const noexcept
95   { 95   {
HITCBC 96   15797 if (fd_ < 0) 96   15210 if (fd_ < 0)
MISUBC 97   ✗ return AF_UNSPEC; 97   ✗ return AF_UNSPEC;
98   // A failed lookup stays uncached, so the next call retries. 98   // A failed lookup stays uncached, so the next call retries.
HITCBC 99   15797 if (family_ == AF_UNSPEC) 99   15210 if (family_ == AF_UNSPEC)
HITCBC 100   2 family_ = socket_family(fd_); 100   2 family_ = socket_family(fd_);
HITCBC 101   15797 return family_; 101   15210 return family_;
102   } 102   }
103   103  
104   /// Return the cached local endpoint. 104   /// Return the cached local endpoint.
HITCBC 105   200 Endpoint local_endpoint() const noexcept override 105   200 Endpoint local_endpoint() const noexcept override
106   { 106   {
HITCBC 107   200 return local_endpoint_; 107   200 return local_endpoint_;
108   } 108   }
109   109  
110   /// Return true if the socket has an open file descriptor. 110   /// Return true if the socket has an open file descriptor.
111   bool is_open() const noexcept 111   bool is_open() const noexcept
112   { 112   {
113   return fd_ >= 0; 113   return fd_ >= 0;
114   } 114   }
115   115  
116   /// Set a socket option. 116   /// Set a socket option.
HITCBC 117   117 std::error_code set_option( 117   117 std::error_code set_option(
118   int level, 118   int level,
119   int optname, 119   int optname,
120   void const* data, 120   void const* data,
121   std::size_t size) noexcept override 121   std::size_t size) noexcept override
122   { 122   {
HITCBC 123   117 if (::setsockopt( 123   117 if (::setsockopt(
HITCBC 124   117 fd_, level, optname, data, static_cast<socklen_t>(size)) != 0) 124   117 fd_, level, optname, data, static_cast<socklen_t>(size)) != 0)
HITCBC 125   8 return make_err(errno); 125   8 return make_err(errno);
HITCBC 126   109 return {}; 126   109 return {};
127   } 127   }
128   128  
129   /// Get a socket option. 129   /// Get a socket option.
130   std::error_code 130   std::error_code
HITCBC 131   170 get_option(int level, int optname, void* data, std::size_t* size) 131   170 get_option(int level, int optname, void* data, std::size_t* size)
132   const noexcept override 132   const noexcept override
133   { 133   {
HITCBC 134   170 socklen_t len = static_cast<socklen_t>(*size); 134   170 socklen_t len = static_cast<socklen_t>(*size);
HITCBC 135   170 if (::getsockopt(fd_, level, optname, data, &len) != 0) 135   170 if (::getsockopt(fd_, level, optname, data, &len) != 0)
HITCBC 136   13 return make_err(errno); 136   13 return make_err(errno);
HITCBC 137   157 *size = static_cast<std::size_t>(len); 137   157 *size = static_cast<std::size_t>(len);
HITCBC 138   157 return {}; 138   157 return {};
139   } 139   }
140   140  
141   /// Assign the file descriptor and, when known, its family. 141   /// Assign the file descriptor and, when known, its family.
HITCBC 142   6346 void set_socket(int fd, int family = AF_UNSPEC) noexcept 142   6378 void set_socket(int fd, int family = AF_UNSPEC) noexcept
143   { 143   {
HITCBC 144   6346 fd_ = fd; 144   6378 fd_ = fd;
HITCBC 145   6346 family_ = family; 145   6378 family_ = family;
HITCBC 146   6346 } 146   6378 }
147   147  
148   /// Cache the local endpoint. 148   /// Cache the local endpoint.
149   void set_local_endpoint(Endpoint ep) noexcept 149   void set_local_endpoint(Endpoint ep) noexcept
150   { 150   {
151   local_endpoint_ = ep; 151   local_endpoint_ = ep;
152   } 152   }
153   153  
154   /** Bind the socket to a local endpoint. 154   /** Bind the socket to a local endpoint.
155   155  
156   Calls ::bind() and caches the resulting local endpoint via 156   Calls ::bind() and caches the resulting local endpoint via
157   getsockname(). Readiness-agnostic; usable by any fd backend. 157   getsockname(). Readiness-agnostic; usable by any fd backend.
158   158  
159   @param ep The endpoint to bind to. 159   @param ep The endpoint to bind to.
160   @return Error code on failure, empty on success. 160   @return Error code on failure, empty on success.
161   */ 161   */
HITCBC 162   278 std::error_code do_bind(Endpoint const& ep) noexcept 162   278 std::error_code do_bind(Endpoint const& ep) noexcept
163   { 163   {
HITCBC 164   278 sockaddr_storage storage{}; 164   278 sockaddr_storage storage{};
HITCBC 165   278 socklen_t addrlen = to_sockaddr(ep, native_family(), storage); 165   278 socklen_t addrlen = to_sockaddr(ep, native_family(), storage);
HITCBC 166   278 if (::bind(fd_, reinterpret_cast<sockaddr*>(&storage), addrlen) != 0) 166   278 if (::bind(fd_, reinterpret_cast<sockaddr*>(&storage), addrlen) != 0)
HITCBC 167   20 return make_err(errno); 167   20 return make_err(errno);
168   168  
HITCBC 169   258 sockaddr_storage local_storage{}; 169   258 sockaddr_storage local_storage{};
HITCBC 170   258 socklen_t local_len = sizeof(local_storage); 170   258 socklen_t local_len = sizeof(local_storage);
HITCBC 171   258 if (::getsockname( 171   258 if (::getsockname(
HITCBC 172   258 fd_, reinterpret_cast<sockaddr*>(&local_storage), &local_len) == 172   258 fd_, reinterpret_cast<sockaddr*>(&local_storage), &local_len) ==
173   0) 173   0)
HITCBC 174   197 local_endpoint_ = 174   197 local_endpoint_ =
HITCBC 175   258 from_sockaddr_as(local_storage, local_len, Endpoint{}); 175   258 from_sockaddr_as(local_storage, local_len, Endpoint{});
176   176  
HITCBC 177   258 return {}; 177   258 return {};
178   } 178   }
179   }; 179   };
180   180  
181   } // namespace boost::corosio::detail 181   } // namespace boost::corosio::detail
182   182  
183   #endif // BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP 183   #endif // BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP