FastLED 3.9.15
Loading...
Searching...
No Matches
tcp.cpp.hpp
Go to the documentation of this file.
1#pragma once
2
3// TCP socket and acceptor implementation.
4// Requires native socket APIs (Windows or POSIX).
5#ifdef FASTLED_HAS_NETWORKING
6
7// Platform-specific socket includes (provides normalized POSIX API)
8#ifdef FL_IS_WIN
9#include "platforms/win/socket_win.h" // ok platform headers // IWYU pragma: keep
10#else
11#include "platforms/posix/socket_posix.h" // ok platform headers // IWYU pragma: keep
12#endif
13
14#include "fl/stl/asio/ip/tcp.h"
15#include "fl/stl/noexcept.h"
16#include "fl/stl/stdio.h" // fl::snprintf — avoids _svfprintf_r (#2773 item 1.1)
17
18// Ensure MSG_NOSIGNAL is available (not defined on Windows/macOS)
19#ifndef MSG_NOSIGNAL
20#define MSG_NOSIGNAL 0
21#endif
22
23namespace fl {
24namespace asio {
25namespace ip {
26namespace tcp {
27
28namespace {
29
30// Platform socket wrappers — avoids name conflicts.
31// On Windows: delegates to fl:: wrappers from socket_win.h
32// On POSIX: delegates to :: system calls from socket_posix.h
33
34int plat_socket(int domain, int type, int protocol) {
35#ifdef FL_IS_WIN
36 return fl::socket(domain, type, protocol);
37#else
38 return ::socket(domain, type, protocol);
39#endif
40}
41
42int plat_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) {
43#ifdef FL_IS_WIN
44 return fl::connect(fd, addr, addrlen);
45#else
46 return ::connect(fd, addr, addrlen);
47#endif
48}
49
50ssize_t plat_send(int fd, const void *buf, size_t len, int flags) {
51#ifdef FL_IS_WIN
52 return fl::send(fd, buf, len, flags);
53#else
54 return ::send(fd, buf, len, flags);
55#endif
56}
57
58ssize_t plat_recv(int fd, void *buf, size_t len, int flags) {
59#ifdef FL_IS_WIN
60 return fl::recv(fd, buf, len, flags);
61#else
62 return ::recv(fd, buf, len, flags);
63#endif
64}
65
66int plat_close(int fd) {
67#ifdef FL_IS_WIN
68 return fl::close(fd);
69#else
70 return ::close(fd);
71#endif
72}
73
74int plat_shutdown(int fd, int how) {
75#ifdef FL_IS_WIN
76 return fl::shutdown(fd, how);
77#else
78 return ::shutdown(fd, how);
79#endif
80}
81
82int plat_setsockopt(int fd, int level, int optname, const void *optval,
83 socklen_t optlen) {
84#ifdef FL_IS_WIN
85 return fl::setsockopt(fd, level, optname, optval, optlen);
86#else
87 return ::setsockopt(fd, level, optname, optval, optlen);
88#endif
89}
90
91int plat_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
92#ifdef FL_IS_WIN
93 return fl::bind(fd, addr, addrlen);
94#else
95 return ::bind(fd, addr, addrlen);
96#endif
97}
98
99int plat_listen(int fd, int backlog) {
100#ifdef FL_IS_WIN
101 return fl::listen(fd, backlog);
102#else
103 return ::listen(fd, backlog);
104#endif
105}
106
107int plat_accept(int fd, struct sockaddr *addr, socklen_t *addrlen) {
108#ifdef FL_IS_WIN
109 return fl::accept(fd, addr, addrlen);
110#else
111 return ::accept(fd, addr, addrlen);
112#endif
113}
114
115bool set_nonblocking(int fd, bool enabled) {
116#ifdef FL_IS_WIN
117 u_long mode = enabled ? 1 : 0;
118 return ioctlsocket(fd, FIONBIO, &mode) == 0;
119#else
120 int flags = fcntl(fd, F_GETFL, 0);
121 if (flags == -1)
122 return false;
123 if (enabled) {
124 return fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1;
125 } else {
126 return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) != -1;
127 }
128#endif
129}
130
131bool is_would_block() {
132#ifdef FL_IS_WIN
133 int err = WSAGetLastError();
134 return err == WSAEWOULDBLOCK;
135#else
136 return errno == EWOULDBLOCK || errno == EAGAIN;
137#endif
138}
139
140
141int plat_getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) {
142#ifdef FL_IS_WIN
143 return fl::getsockname(fd, addr, addrlen);
144#else
145 return ::getsockname(fd, addr, addrlen);
146#endif
147}
148
149} // anonymous namespace
150
151// ============================================================================
152// socket implementation
153// ============================================================================
154
155socket::socket() : mFd(-1), mNonBlocking(false) {}
156
157socket::~socket() FL_NOEXCEPT { close_fd(); }
158
159socket::socket(socket &&other) : mFd(other.mFd), mNonBlocking(other.mNonBlocking) {
160 other.mFd = -1;
161 other.mNonBlocking = false;
162}
163
164socket &socket::operator=(socket &&other) FL_NOEXCEPT {
165 if (this != &other) {
166 close_fd();
167 mFd = other.mFd;
168 mNonBlocking = other.mNonBlocking;
169 other.mFd = -1;
170 other.mNonBlocking = false;
171 }
172 return *this;
173}
174
175bool socket::is_open() const { return mFd != -1; }
176
177error_code socket::connect(const endpoint &ep) {
178 // Clean up existing
179 close_fd();
180
181#ifdef FL_IS_WIN
182 if (!initialize_winsock()) {
183 return error_code(errc::unknown, "winsock init failed");
184 }
185#endif
186
187 // Resolve hostname
188 struct addrinfo hints {};
189 struct addrinfo *result = nullptr;
190
191 hints.ai_family = AF_INET;
192 hints.ai_socktype = SOCK_STREAM;
193 hints.ai_protocol = IPPROTO_TCP;
194
195 char portStr[16];
196 fl::snprintf(portStr, sizeof(portStr), "%u", ep.port);
197
198 int ret = getaddrinfo(ep.host.c_str(), portStr, &hints, &result);
199 if (ret != 0 || result == nullptr) {
200 return error_code(errc::host_not_found, "getaddrinfo failed");
201 }
202
203 error_code ec(errc::connection_refused, "no addresses succeeded");
204
205 for (struct addrinfo *addr = result; addr != nullptr; addr = addr->ai_next) {
206 int sock =
207 plat_socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
208 if (sock < 0)
209 continue;
210
211 mFd = sock;
212 // Keep socket BLOCKING for connect() — non-blocking connect
213 // returns EINPROGRESS/WSAEWOULDBLOCK which is racy: subsequent
214 // send() can fail with ENOTCONN if the TCP handshake hasn't
215 // completed yet. A blocking connect on loopback completes in
216 // microseconds; on real networks, the timeout is the OS default
217 // (typically 75-120 s), which is acceptable for a "synchronous
218 // connect" API.
219
220 ret = plat_connect(mFd, addr->ai_addr,
221 static_cast<socklen_t>(addr->ai_addrlen));
222
223 if (ret == 0) {
224 // Connected — now switch to non-blocking for all I/O
225 set_nonblocking(mFd, true);
226 mNonBlocking = true;
227 ec = error_code();
228 break;
229 }
230
231 // Failed — try next address
232 plat_close(mFd);
233 mFd = -1;
234 }
235
236 freeaddrinfo(result);
237 return ec;
238}
239
240void socket::close() { close_fd(); }
241
242void socket::shutdown() {
243 if (mFd != -1) {
244 plat_shutdown(mFd, SHUT_RDWR);
245 }
246}
247
248size_t socket::read_some(fl::span<u8> buffer, error_code &ec) {
249 ec = error_code();
250 if (mFd == -1) {
251 ec = error_code(errc::operation_aborted, "socket not open");
252 return 0;
253 }
254
255 ssize_t result =
256 plat_recv(mFd, (char *)buffer.data(), buffer.size(), 0);
257
258 if (result < 0) {
259 if (is_would_block()) {
260 ec = error_code(errc::would_block);
261 return 0;
262 }
263#ifdef FL_IS_WIN
264 ec = error_code(errc::unknown, "recv failed");
265#else
266 ec = error_code::from_errno(errno);
267#endif
268 return 0;
269 }
270
271 if (result == 0) {
272 ec = error_code(errc::eof, "connection closed by peer");
273 return 0;
274 }
275
276 return static_cast<size_t>(result);
277}
278
279size_t socket::write_some(fl::span<const u8> buffer, error_code &ec) {
280 ec = error_code();
281 if (mFd == -1) {
282 ec = error_code(errc::operation_aborted, "socket not open");
283 return 0;
284 }
285
286 ssize_t result = plat_send(mFd, (const char *)buffer.data(),
287 buffer.size(), MSG_NOSIGNAL);
288
289 if (result < 0) {
290 if (is_would_block()) {
291 ec = error_code(errc::would_block);
292 return 0;
293 }
294#ifdef FL_IS_WIN
295 ec = error_code(errc::unknown, "send failed");
296#else
297 ec = error_code::from_errno(errno);
298#endif
299 return 0;
300 }
301
302 return static_cast<size_t>(result);
303}
304
305void socket::async_read_some(fl::span<u8> buffer, io_handler handler) {
306 error_code ec;
307 size_t n = read_some(buffer, ec);
308 if (handler) {
309 handler(ec, n);
310 }
311}
312
313void socket::async_write_some(fl::span<const u8> buffer, io_handler handler) {
314 error_code ec;
315 size_t n = write_some(buffer, ec);
316 if (handler) {
317 handler(ec, n);
318 }
319}
320
321void socket::async_connect(const endpoint &ep, connect_handler handler) {
322 error_code ec = connect(ep);
323 if (handler) {
324 handler(ec);
325 }
326}
327
328void socket::set_non_blocking(bool mode) {
329 if (mFd != -1) {
330 set_nonblocking(mFd, mode);
331 }
332 mNonBlocking = mode;
333}
334
335bool socket::is_non_blocking() const { return mNonBlocking; }
336
337int socket::native_handle() const { return mFd; }
338
339void socket::assign(int fd) {
340 close_fd();
341 mFd = fd;
342}
343
344void socket::close_fd() {
345 if (mFd != -1) {
346 plat_close(mFd);
347 mFd = -1;
348 }
349 mNonBlocking = false;
350}
351
352// ============================================================================
353// acceptor implementation
354// ============================================================================
355
356acceptor::acceptor() : mFd(-1), mPort(0) {}
357
358acceptor::~acceptor() FL_NOEXCEPT { close(); }
359
360error_code acceptor::open(u16 port) {
361 close();
362
363#ifdef FL_IS_WIN
364 if (!initialize_winsock()) {
365 return error_code(errc::unknown, "winsock init failed");
366 }
367#endif
368
369 int sock = plat_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
370 if (sock < 0) {
371 return error_code(errc::unknown, "socket creation failed");
372 }
373
374 // SO_REUSEADDR for quick restarts
375 int reuse = 1;
376 plat_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse,
377 sizeof(reuse));
378
379 struct sockaddr_in addr {};
380 addr.sin_family = AF_INET;
381 addr.sin_port = htons(port);
382 // Bind to loopback for consistent behavior across platforms
383 // especially for port 0 (ephemeral port assignment)
384 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
385
386 int ret =
387 plat_bind(sock, (struct sockaddr *)&addr, sizeof(addr));
388 if (ret != 0) {
389 plat_close(sock);
390 return error_code(errc::address_in_use, "bind failed");
391 }
392
393 // Query actual port if 0 was requested
394 if (port == 0) {
395 struct sockaddr_in boundAddr {};
396 socklen_t addrLen = sizeof(boundAddr);
397 if (plat_getsockname(sock, (struct sockaddr *)&boundAddr,
398 &addrLen) != 0) {
399 plat_close(sock);
400 return error_code(errc::unknown, "getsockname failed - cannot resolve ephemeral port");
401 }
402 port = ntohs(boundAddr.sin_port);
403 }
404
405 mFd = sock;
406 mPort = port;
407
408 // Non-blocking — blocking is never appropriate on embedded
409 set_nonblocking(mFd, true);
410
411 return error_code();
412}
413
414error_code acceptor::listen(int backlog) {
415 if (mFd == -1) {
416 return error_code(errc::operation_aborted, "acceptor not open");
417 }
418
419 int ret = plat_listen(mFd, backlog);
420 if (ret != 0) {
421 return error_code(errc::unknown, "listen failed");
422 }
423
424 return error_code();
425}
426
427error_code acceptor::accept(socket &peer) {
428 if (mFd == -1) {
429 return error_code(errc::operation_aborted, "acceptor not open");
430 }
431
432 struct sockaddr_in clientAddr {};
433 socklen_t addrLen = sizeof(clientAddr);
434 int clientFd =
435 plat_accept(mFd, (struct sockaddr *)&clientAddr, &addrLen);
436
437 if (clientFd < 0) {
438 if (is_would_block()) {
439 return error_code(errc::would_block);
440 }
441#ifdef FL_IS_WIN
442 return error_code(errc::unknown, "accept failed");
443#else
444 return error_code::from_errno(errno);
445#endif
446 }
447
448 // Non-blocking on accepted socket
449 set_nonblocking(clientFd, true);
450 peer.assign(clientFd);
451
452 return error_code();
453}
454
455void acceptor::async_accept(socket &peer, connect_handler handler) {
456 error_code ec = accept(peer);
457 if (handler) {
458 handler(ec);
459 }
460}
461
462void acceptor::close() {
463 if (mFd != -1) {
464 plat_close(mFd);
465 mFd = -1;
466 }
467 mPort = 0;
468}
469
470bool acceptor::is_open() const { return mFd != -1; }
471
472int acceptor::native_handle() const { return mFd; }
473
474u16 acceptor::port() const { return mPort; }
475
476} // namespace tcp
477} // namespace ip
478} // namespace asio
479} // namespace fl
480
481#endif // FASTLED_HAS_NETWORKING
const T * data() const FL_NOEXCEPT
Definition span.h:461
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
int snprintf(char *buffer, fl::size size, const char *format, const Args &... args) FL_NOEXCEPT
Snprintf-like formatting function that writes to a buffer.
Definition stdio.h:666
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT