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