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 | |||||