96.88% Lines (31/32)
100.00% Functions (8/8)
| 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 local endpoint, the impl lifecycle | 30 | the file descriptor, the cached local endpoint, the impl lifecycle | |||||
| 31 | bases (enable_shared_from_this + the service's intrusive tracking node), | 31 | bases (enable_shared_from_this + the service's intrusive tracking node), | |||||
| 32 | and the synchronous accessors (native_handle / options / bind). | 32 | and the synchronous accessors (native_handle / options / bind). | |||||
| 33 | 33 | |||||||
| 34 | reactor_basic_socket derives from this and adds the readiness machinery | 34 | reactor_basic_socket derives from this and adds the readiness machinery | |||||
| 35 | (descriptor_state, register_op, the cancel/close that deregister from | 35 | (descriptor_state, register_op, the cancel/close that deregister from | |||||
| 36 | the reactor). io_uring's socket impls derive from it and add their op | 36 | the reactor). io_uring's socket impls derive from it and add their op | |||||
| 37 | slots + SQE submission. This is the socket-layer analogue of io_uring | 37 | slots + SQE submission. This is the socket-layer analogue of io_uring | |||||
| 38 | deriving from reactor_scheduler: io_uring sockets share the reactor's | 38 | deriving from reactor_scheduler: io_uring sockets share the reactor's | |||||
| 39 | readiness-agnostic socket surface, while the on-EAGAIN action (park vs | 39 | readiness-agnostic socket surface, while the on-EAGAIN action (park vs | |||||
| 40 | submit-SQE) and the op model stay backend-specific. | 40 | submit-SQE) and the op model stay backend-specific. | |||||
| 41 | 41 | |||||||
| 42 | @tparam Derived The concrete socket type (CRTP, for shared_from_this | 42 | @tparam Derived The concrete socket type (CRTP, for shared_from_this | |||||
| 43 | and the intrusive node). | 43 | and the intrusive node). | |||||
| 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 | , public std::enable_shared_from_this<Derived> | 54 | , public std::enable_shared_from_this<Derived> | |||||
| 55 | { | 55 | { | |||||
| 56 | protected: | 56 | protected: | |||||
| 57 | // CRTP base: not publicly constructible. The check's preferred fix | 57 | // CRTP base: not publicly constructible. The check's preferred fix | |||||
| 58 | // (private ctor + `friend Derived`) is infeasible here — the reactor | 58 | // (private ctor + `friend Derived`) is infeasible here — the reactor | |||||
| 59 | // sockets reach this base through intermediate templates | 59 | // sockets reach this base through intermediate templates | |||||
| 60 | // (reactor_stream_socket -> reactor_basic_socket) that are not `Derived`, | 60 | // (reactor_stream_socket -> reactor_basic_socket) that are not `Derived`, | |||||
| 61 | // so a private ctor would stop those intermediates from constructing it. | 61 | // so a private ctor would stop those intermediates from constructing it. | |||||
| 62 | // Protected is the correct access; suppress the private-only suggestion. | 62 | // Protected is the correct access; suppress the private-only suggestion. | |||||
| HITCBC | 63 | 19711 | native_socket_base() = default; // NOLINT(bugprone-crtp-constructor-accessibility) | 63 | 26270 | native_socket_base() = default; // NOLINT(bugprone-crtp-constructor-accessibility) | ||
| 64 | 64 | |||||||
| 65 | int fd_ = -1; | 65 | int fd_ = -1; | |||||
| 66 | // mutable so a derived const local_endpoint() override can lazily fill | 66 | // mutable so a derived const local_endpoint() override can lazily fill | |||||
| 67 | // it via getsockname() on first read (io_uring's lazy_pending state). | 67 | // it via getsockname() on first read (io_uring's lazy_pending state). | |||||
| 68 | mutable Endpoint local_endpoint_; | 68 | mutable Endpoint local_endpoint_; | |||||
| 69 | 69 | |||||||
| 70 | public: | 70 | public: | |||||
| HITCBC | 71 | 19711 | ~native_socket_base() override = default; | 71 | 26270 | ~native_socket_base() override = default; | ||
| 72 | 72 | |||||||
| 73 | /// Return the underlying file descriptor. | 73 | /// Return the underlying file descriptor. | |||||
| HITCBC | 74 | 60018 | native_handle_type native_handle() const noexcept override | 74 | 79695 | native_handle_type native_handle() const noexcept override | ||
| 75 | { | 75 | { | |||||
| HITCBC | 76 | 60018 | return fd_; | 76 | 79695 | return fd_; | ||
| 77 | } | 77 | } | |||||
| 78 | 78 | |||||||
| 79 | /// Return the cached local endpoint. | 79 | /// Return the cached local endpoint. | |||||
| HITCBC | 80 | 108 | Endpoint local_endpoint() const noexcept override | 80 | 106 | Endpoint local_endpoint() const noexcept override | ||
| 81 | { | 81 | { | |||||
| HITCBC | 82 | 108 | return local_endpoint_; | 82 | 106 | return local_endpoint_; | ||
| 83 | } | 83 | } | |||||
| 84 | 84 | |||||||
| 85 | /// Return true if the socket has an open file descriptor. | 85 | /// Return true if the socket has an open file descriptor. | |||||
| 86 | bool is_open() const noexcept | 86 | bool is_open() const noexcept | |||||
| 87 | { | 87 | { | |||||
| 88 | return fd_ >= 0; | 88 | return fd_ >= 0; | |||||
| 89 | } | 89 | } | |||||
| 90 | 90 | |||||||
| 91 | /// Set a socket option. | 91 | /// Set a socket option. | |||||
| HITCBC | 92 | 66 | std::error_code set_option( | 92 | 66 | std::error_code set_option( | ||
| 93 | int level, int optname, void const* data, std::size_t size) | 93 | int level, int optname, void const* data, std::size_t size) | |||||
| 94 | noexcept override | 94 | noexcept override | |||||
| 95 | { | 95 | { | |||||
| HITCBC | 96 | 66 | if (::setsockopt( | 96 | 66 | if (::setsockopt( | ||
| HITCBC | 97 | 66 | fd_, level, optname, data, static_cast<socklen_t>(size)) != 0) | 97 | 66 | fd_, level, optname, data, static_cast<socklen_t>(size)) != 0) | ||
| HITCBC | 98 | 2 | return make_err(errno); | 98 | 2 | return make_err(errno); | ||
| HITCBC | 99 | 64 | return {}; | 99 | 64 | return {}; | ||
| 100 | } | 100 | } | |||||
| 101 | 101 | |||||||
| 102 | /// Get a socket option. | 102 | /// Get a socket option. | |||||
| HITCBC | 103 | 102 | std::error_code get_option( | 103 | 102 | std::error_code get_option( | ||
| 104 | int level, int optname, void* data, std::size_t* size) | 104 | int level, int optname, void* data, std::size_t* size) | |||||
| 105 | const noexcept override | 105 | const noexcept override | |||||
| 106 | { | 106 | { | |||||
| HITCBC | 107 | 102 | socklen_t len = static_cast<socklen_t>(*size); | 107 | 102 | socklen_t len = static_cast<socklen_t>(*size); | ||
| HITCBC | 108 | 102 | if (::getsockopt(fd_, level, optname, data, &len) != 0) | 108 | 102 | if (::getsockopt(fd_, level, optname, data, &len) != 0) | ||
| MISUBC | 109 | ✗ | return make_err(errno); | 109 | ✗ | return make_err(errno); | ||
| HITCBC | 110 | 102 | *size = static_cast<std::size_t>(len); | 110 | 102 | *size = static_cast<std::size_t>(len); | ||
| HITCBC | 111 | 102 | return {}; | 111 | 102 | return {}; | ||
| 112 | } | 112 | } | |||||
| 113 | 113 | |||||||
| 114 | /// Assign the file descriptor. | 114 | /// Assign the file descriptor. | |||||
| HITCBC | 115 | 6387 | void set_socket(int fd) noexcept | 115 | 8574 | void set_socket(int fd) noexcept | ||
| 116 | { | 116 | { | |||||
| HITCBC | 117 | 6387 | fd_ = fd; | 117 | 8574 | fd_ = fd; | ||
| HITCBC | 118 | 6387 | } | 118 | 8574 | } | ||
| 119 | 119 | |||||||
| 120 | /// Cache the local endpoint. | 120 | /// Cache the local endpoint. | |||||
| 121 | void set_local_endpoint(Endpoint ep) noexcept | 121 | void set_local_endpoint(Endpoint ep) noexcept | |||||
| 122 | { | 122 | { | |||||
| 123 | local_endpoint_ = ep; | 123 | local_endpoint_ = ep; | |||||
| 124 | } | 124 | } | |||||
| 125 | 125 | |||||||
| 126 | /** Bind the socket to a local endpoint. | 126 | /** Bind the socket to a local endpoint. | |||||
| 127 | 127 | |||||||
| 128 | Calls ::bind() and caches the resulting local endpoint via | 128 | Calls ::bind() and caches the resulting local endpoint via | |||||
| 129 | getsockname(). Readiness-agnostic; usable by any fd backend. | 129 | getsockname(). Readiness-agnostic; usable by any fd backend. | |||||
| 130 | 130 | |||||||
| 131 | @param ep The endpoint to bind to. | 131 | @param ep The endpoint to bind to. | |||||
| 132 | @return Error code on failure, empty on success. | 132 | @return Error code on failure, empty on success. | |||||
| 133 | */ | 133 | */ | |||||
| HITCBC | 134 | 136 | std::error_code do_bind(Endpoint const& ep) noexcept | 134 | 136 | std::error_code do_bind(Endpoint const& ep) noexcept | ||
| 135 | { | 135 | { | |||||
| HITCBC | 136 | 136 | sockaddr_storage storage{}; | 136 | 136 | sockaddr_storage storage{}; | ||
| HITCBC | 137 | 136 | socklen_t addrlen = to_sockaddr(ep, socket_family(fd_), storage); | 137 | 136 | socklen_t addrlen = to_sockaddr(ep, socket_family(fd_), storage); | ||
| HITCBC | 138 | 136 | if (::bind(fd_, reinterpret_cast<sockaddr*>(&storage), addrlen) != 0) | 138 | 136 | if (::bind(fd_, reinterpret_cast<sockaddr*>(&storage), addrlen) != 0) | ||
| HITCBC | 139 | 10 | return make_err(errno); | 139 | 10 | return make_err(errno); | ||
| 140 | 140 | |||||||
| HITCBC | 141 | 126 | sockaddr_storage local_storage{}; | 141 | 126 | sockaddr_storage local_storage{}; | ||
| HITCBC | 142 | 126 | socklen_t local_len = sizeof(local_storage); | 142 | 126 | socklen_t local_len = sizeof(local_storage); | ||
| HITCBC | 143 | 126 | if (::getsockname( | 143 | 126 | if (::getsockname( | ||
| 144 | fd_, reinterpret_cast<sockaddr*>(&local_storage), &local_len) | 144 | fd_, reinterpret_cast<sockaddr*>(&local_storage), &local_len) | |||||
| HITCBC | 145 | 126 | == 0) | 145 | 126 | == 0) | ||
| HITCBC | 146 | 96 | local_endpoint_ = | 146 | 96 | local_endpoint_ = | ||
| HITCBC | 147 | 126 | from_sockaddr_as(local_storage, local_len, Endpoint{}); | 147 | 126 | from_sockaddr_as(local_storage, local_len, Endpoint{}); | ||
| 148 | 148 | |||||||
| HITCBC | 149 | 126 | return {}; | 149 | 126 | return {}; | ||
| 150 | } | 150 | } | |||||
| 151 | }; | 151 | }; | |||||
| 152 | 152 | |||||||
| 153 | } // namespace boost::corosio::detail | 153 | } // namespace boost::corosio::detail | |||||
| 154 | 154 | |||||||
| 155 | #endif // BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP | 155 | #endif // BOOST_COROSIO_NATIVE_DETAIL_NATIVE_SOCKET_BASE_HPP | |||||