Branch data Line data Source code
1 : : /* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited
2 : : * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial
3 : : */
4 : :
5 : : #include "lwip/opt.h"
6 : :
7 : : #if LWIP_SOCKET && LWIP_TCP && LWIP_SOCKET_SELECT
8 : :
9 : : #include "lwip/errno.h"
10 : : #include "lwip/sockets.h"
11 : :
12 : : #include <stdbool.h>
13 : : #include <stddef.h>
14 : : #include <stdint.h>
15 : :
16 : : #include "SolidSyslogError.h"
17 : : #include "SolidSyslogErrorCategory.h"
18 : : #include "SolidSyslogLwipSocketAddressPrivate.h"
19 : : #include "SolidSyslogLwipSocketTcpStreamErrors.h"
20 : : #include "SolidSyslogLwipSocketTcpStreamPrivate.h"
21 : : #include "SolidSyslogNullStream.h"
22 : : #include "SolidSyslogStreamCategories.h"
23 : : #include "SolidSyslogTunables.h"
24 : :
25 : : const struct SolidSyslogErrorSource SolidSyslogLwipSocketTcpStreamErrorSource = {
26 : : "LwipSocketTcpStream",
27 : : SolidSyslogTcpStreamErrors_Name
28 : : };
29 : :
30 : : struct SolidSyslogAddress;
31 : :
32 : : enum
33 : : {
34 : : INVALID_SOCKET = -1,
35 : : MICROSECONDS_PER_MILLISECOND = 1000
36 : : };
37 : :
38 : : static uint32_t LwipSocketTcpStream_NullConnectTimeoutGetter(void* context);
39 : : static inline bool LwipSocketTcpStream_ConfigProvidesGetter(const struct SolidSyslogLwipSocketTcpStreamConfig* config);
40 : :
41 : : static bool LwipSocketTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr);
42 : : static bool LwipSocketTcpStream_TakeSocketAndConnect(
43 : : struct SolidSyslogLwipSocketTcpStream* self,
44 : : const struct sockaddr_in* sin
45 : : );
46 : : static bool LwipSocketTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size);
47 : : static SolidSyslogSsize LwipSocketTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size);
48 : : static void LwipSocketTcpStream_Close(struct SolidSyslogStream* base);
49 : : static uint32_t LwipSocketTcpStream_Version(struct SolidSyslogStream* base);
50 : : static void LwipSocketTcpStream_CloseSocket(struct SolidSyslogLwipSocketTcpStream* self);
51 : : static bool LwipSocketTcpStream_PeerIsStillThere(struct SolidSyslogLwipSocketTcpStream* self);
52 : : static bool LwipSocketTcpStream_WroteAllBytes(ssize_t sent, size_t expected);
53 : : static inline bool LwipSocketTcpStream_WouldBlock(int err);
54 : :
55 : : static inline struct SolidSyslogLwipSocketTcpStream* LwipSocketTcpStream_SelfFromBase(struct SolidSyslogStream* base);
56 : : static void LwipSocketTcpStream_TakeSocket(struct SolidSyslogLwipSocketTcpStream* self);
57 : : static void LwipSocketTcpStream_ApplySocketOptions(int fd);
58 : : static void LwipSocketTcpStream_ReportOptionRefused(void);
59 : : static inline bool LwipSocketTcpStream_IsSocketValid(int fd);
60 : : static bool LwipSocketTcpStream_ConnectOrCloseOnFailure(
61 : : struct SolidSyslogLwipSocketTcpStream* self,
62 : : const struct sockaddr_in* sin
63 : : );
64 : : static bool LwipSocketTcpStream_Connect(struct SolidSyslogLwipSocketTcpStream* self, const struct sockaddr_in* sin);
65 : : static bool LwipSocketTcpStream_WaitForConnectCompletion(
66 : : struct SolidSyslogLwipSocketTcpStream* self,
67 : : long timeoutMicros
68 : : );
69 : : static long LwipSocketTcpStream_ResolveConnectTimeoutMicros(struct SolidSyslogLwipSocketTcpStream* self);
70 : : static bool LwipSocketTcpStream_ReadDeferredConnectError(struct SolidSyslogLwipSocketTcpStream* self);
71 : : static bool LwipSocketTcpStream_ReadSocketError(int fd, int* code);
72 : : static void LwipSocketTcpStream_ReportConnectFailure(
73 : : struct SolidSyslogLwipSocketTcpStream* self,
74 : : enum SolidSyslogSeverity severity,
75 : : enum SolidSyslogTcpStreamErrors detail
76 : : );
77 : : static void LwipSocketTcpStream_ReportConnectFailureWithErrno(
78 : : struct SolidSyslogLwipSocketTcpStream* self,
79 : : enum SolidSyslogSeverity severity,
80 : : enum SolidSyslogTcpStreamErrors detail,
81 : : int nativeErrno
82 : : );
83 : : static void LwipSocketTcpStream_ReportUnfinishedWait(
84 : : struct SolidSyslogLwipSocketTcpStream* self,
85 : : int selectResult,
86 : : int selectErrno
87 : : );
88 : : static inline bool LwipSocketTcpStream_WaitTimedOut(int selectResult);
89 : : static inline bool LwipSocketTcpStream_IsRemoteConnectError(int connectErrno);
90 : :
91 : 67 : void SolidSyslogLwipSocketTcpStream_Initialise(
92 : : struct SolidSyslogStream* base,
93 : : const struct SolidSyslogLwipSocketTcpStreamConfig* config
94 : : )
95 : : {
96 : 67 : struct SolidSyslogLwipSocketTcpStream* self = LwipSocketTcpStream_SelfFromBase(base);
97 : 67 : self->Base.Open = LwipSocketTcpStream_Open;
98 : 67 : self->Base.Send = LwipSocketTcpStream_Send;
99 : 67 : self->Base.Read = LwipSocketTcpStream_Read;
100 : 67 : self->Base.Close = LwipSocketTcpStream_Close;
101 : 67 : self->Base.Version = LwipSocketTcpStream_Version;
102 : 67 : self->Fd = INVALID_SOCKET;
103 : 67 : self->OpenHealth = (struct SolidSyslogStreamOpenHealth) {0};
104 : 67 : self->Config.GetConnectTimeoutMs = LwipSocketTcpStream_NullConnectTimeoutGetter;
105 : 67 : self->Config.ConnectTimeoutContext = NULL;
106 [ + + ]: 67 : if (LwipSocketTcpStream_ConfigProvidesGetter(config) == true)
107 : : {
108 : 2 : self->Config = *config;
109 : : }
110 : 67 : }
111 : :
112 : 67 : static inline bool LwipSocketTcpStream_ConfigProvidesGetter(const struct SolidSyslogLwipSocketTcpStreamConfig* config)
113 : : {
114 [ + + + + ]: 67 : return (config != NULL) && (config->GetConnectTimeoutMs != NULL);
115 : : }
116 : :
117 : : /* Null Object substituted when the integrator installs no getter - the bounded
118 : : * wait then has one code path whether or not runtime tuning was wired. */
119 : 13 : static uint32_t LwipSocketTcpStream_NullConnectTimeoutGetter(void* context)
120 : : {
121 : : (void) context;
122 : 13 : return (uint32_t) SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS;
123 : : }
124 : :
125 : 67 : void SolidSyslogLwipSocketTcpStream_Cleanup(struct SolidSyslogStream* base)
126 : : {
127 : 67 : LwipSocketTcpStream_CloseSocket(LwipSocketTcpStream_SelfFromBase(base));
128 : : /* Overwrite the abstract base with the shared NullStream vtable so
129 : : * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */
130 : 67 : *base = *SolidSyslogNullStream_Get();
131 : 67 : }
132 : :
133 : 58 : static bool LwipSocketTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr)
134 : : {
135 : 58 : struct SolidSyslogLwipSocketTcpStream* self = LwipSocketTcpStream_SelfFromBase(base);
136 : 58 : bool connected = false;
137 [ + + ]: 58 : if (LwipSocketTcpStream_IsSocketValid(self->Fd))
138 : : {
139 : : /* Refused by contract, not reconnected: what is held stays as it is. */
140 : 2 : LwipSocketTcpStream_Report(
141 : : SOLIDSYSLOG_BAD_ARGUMENT_SEVERITY,
142 : : SOLIDSYSLOG_CAT_BAD_ARGUMENT,
143 : : SOLIDSYSLOG_TCP_STREAM_ERROR_ALREADY_OPEN
144 : : );
145 : : }
146 : : else
147 : : {
148 : : connected =
149 : 56 : LwipSocketTcpStream_TakeSocketAndConnect(self, SolidSyslogLwipSocketAddress_AsConstSockaddrIn(addr));
150 : : }
151 : 58 : return connected;
152 : : }
153 : :
154 : 56 : static bool LwipSocketTcpStream_TakeSocketAndConnect(
155 : : struct SolidSyslogLwipSocketTcpStream* self,
156 : : const struct sockaddr_in* sin
157 : : )
158 : : {
159 : 56 : bool connected = false;
160 : 56 : LwipSocketTcpStream_TakeSocket(self);
161 [ + + ]: 56 : if (LwipSocketTcpStream_IsSocketValid(self->Fd))
162 : : {
163 : 54 : connected = LwipSocketTcpStream_ConnectOrCloseOnFailure(self, sin);
164 : : }
165 [ + + ]: 56 : if (connected)
166 : : {
167 : 34 : SolidSyslogStreamOpenHealth_Opened(&self->OpenHealth);
168 : : }
169 : 56 : return connected;
170 : : }
171 : :
172 : 207 : static inline struct SolidSyslogLwipSocketTcpStream* LwipSocketTcpStream_SelfFromBase(struct SolidSyslogStream* base)
173 : : {
174 : 207 : return (struct SolidSyslogLwipSocketTcpStream*) base;
175 : : }
176 : :
177 : : /* Non-blocking from the start: the connect reports EINPROGRESS, the wait is
178 : : * bounded, and Send and Read never block the service thread on a wedged peer
179 : : * or a full send buffer. */
180 : 56 : static void LwipSocketTcpStream_TakeSocket(struct SolidSyslogLwipSocketTcpStream* self)
181 : : {
182 : 56 : self->Fd = lwip_socket(AF_INET, SOCK_STREAM, 0);
183 : : /* Captured immediately after lwip_socket so the report below satisfies
184 : : * MISRA 22.10 - no intervening library call between the errno-setting
185 : : * function and the read. */
186 [ + + ]: 56 : int socketErrno = LwipSocketTcpStream_IsSocketValid(self->Fd) ? 0 : errno;
187 [ + + ]: 56 : if (!LwipSocketTcpStream_IsSocketValid(self->Fd))
188 : : {
189 : 1 : LwipSocketTcpStream_ReportConnectFailureWithErrno(
190 : : self,
191 : : SOLIDSYSLOG_STREAM_CONNECT_LOCAL_SEVERITY,
192 : : SOLIDSYSLOG_TCP_STREAM_ERROR_ENDPOINT_UNAVAILABLE,
193 : : socketErrno
194 : : );
195 : : }
196 : : else
197 : : {
198 : : /* A socket the stack will not make non-blocking is no use to us: the
199 : : * bounded connect, and Send and Read never blocking the service thread,
200 : : * all rest on it. Give it back rather than proceed with a blocking one. */
201 : 55 : int rc = lwip_fcntl(self->Fd, F_SETFL, O_NONBLOCK);
202 [ + + ]: 55 : int fcntlErrno = (rc != 0) ? errno : 0;
203 [ + + ]: 55 : if (rc != 0)
204 : : {
205 : 1 : LwipSocketTcpStream_ReportConnectFailureWithErrno(
206 : : self,
207 : : SOLIDSYSLOG_STREAM_CONNECT_LOCAL_SEVERITY,
208 : : SOLIDSYSLOG_TCP_STREAM_ERROR_ENDPOINT_UNAVAILABLE,
209 : : fcntlErrno
210 : : );
211 : 1 : (void) lwip_close(self->Fd);
212 : 1 : self->Fd = INVALID_SOCKET;
213 : : }
214 : : }
215 : 56 : }
216 : :
217 : : /* Latency first: a syslog record is small and waiting to coalesce it with the
218 : : * next one only delays delivery. Keepalive then surfaces a peer that went away
219 : : * during an idle period, rather than on the next record. */
220 : 34 : static void LwipSocketTcpStream_ApplySocketOptions(int fd)
221 : : {
222 : 34 : int enable = 1;
223 : 34 : bool allAccepted = (lwip_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) == 0);
224 : :
225 [ + + + + ]: 34 : allAccepted = (lwip_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)) == 0) && allAccepted;
226 [ + + + + ]: 34 : allAccepted = SolidSyslogLwipSocketTcpStream_ApplyKeepalive(fd) && allAccepted;
227 : :
228 [ + + ]: 34 : if (!allAccepted)
229 : : {
230 : 6 : LwipSocketTcpStream_ReportOptionRefused();
231 : : }
232 : 34 : }
233 : :
234 : : /* One event per attempt however many options the stack declined: the
235 : : * connection stands either way, and the engineer's next step is the same
236 : : * whichever one it was. */
237 : 6 : static void LwipSocketTcpStream_ReportOptionRefused(void)
238 : : {
239 : 6 : LwipSocketTcpStream_Report(
240 : : SOLIDSYSLOG_SEVERITY_WARNING,
241 : : SOLIDSYSLOG_CAT_STREAM_OPTION_REFUSED,
242 : : SOLIDSYSLOG_TCP_STREAM_ERROR_SOCKET_OPTION_REFUSED
243 : : );
244 : 6 : }
245 : :
246 : 323 : static inline bool LwipSocketTcpStream_IsSocketValid(int fd)
247 : : {
248 : 323 : return fd >= 0;
249 : : }
250 : :
251 : : /* A socket the connect did not finish with is of no use to anyone, and a pool
252 : : * class that keeps one leaks it until Destroy. */
253 : 54 : static bool LwipSocketTcpStream_ConnectOrCloseOnFailure(
254 : : struct SolidSyslogLwipSocketTcpStream* self,
255 : : const struct sockaddr_in* sin
256 : : )
257 : : {
258 : 54 : bool connected = LwipSocketTcpStream_Connect(self, sin);
259 [ + + ]: 54 : if (connected)
260 : : {
261 : : /* After the connection stands, not before: a refused option says the
262 : : * connection is less robust than intended, which is only true once
263 : : * there is a connection. A failed connect reports that instead. */
264 : 34 : LwipSocketTcpStream_ApplySocketOptions(self->Fd);
265 : : }
266 : : else
267 : : {
268 : 20 : LwipSocketTcpStream_CloseSocket(self);
269 : : }
270 : 54 : return connected;
271 : : }
272 : :
273 : 54 : static bool LwipSocketTcpStream_Connect(struct SolidSyslogLwipSocketTcpStream* self, const struct sockaddr_in* sin)
274 : : {
275 : 54 : int rc = lwip_connect(self->Fd, (const struct sockaddr*) sin, sizeof(*sin));
276 : : /* Captured immediately after lwip_connect so the test below satisfies
277 : : * MISRA 22.10 - no intervening library call between the errno-setting
278 : : * function and the read. */
279 [ + + ]: 54 : int connectErrno = (rc < 0) ? errno : 0;
280 : 54 : bool connected = (rc == 0);
281 : :
282 [ + + ]: 54 : if (connectErrno == EINPROGRESS)
283 : : {
284 : 16 : connected =
285 [ + + + + ]: 26 : LwipSocketTcpStream_WaitForConnectCompletion(self, LwipSocketTcpStream_ResolveConnectTimeoutMicros(self)) &&
286 : 10 : LwipSocketTcpStream_ReadDeferredConnectError(self);
287 : : }
288 [ + + ]: 38 : else if (LwipSocketTcpStream_IsRemoteConnectError(connectErrno))
289 : : {
290 : 9 : LwipSocketTcpStream_ReportConnectFailureWithErrno(
291 : : self,
292 : : SOLIDSYSLOG_STREAM_CONNECT_REMOTE_SEVERITY,
293 : : SOLIDSYSLOG_TCP_STREAM_ERROR_CONNECT_REFUSED,
294 : : connectErrno
295 : : );
296 : : }
297 [ + + ]: 29 : else if (rc < 0)
298 : : {
299 : 2 : LwipSocketTcpStream_ReportConnectFailureWithErrno(
300 : : self,
301 : : SOLIDSYSLOG_STREAM_CONNECT_LOCAL_SEVERITY,
302 : : SOLIDSYSLOG_TCP_STREAM_ERROR_CONNECT_NOT_STARTED,
303 : : connectErrno
304 : : );
305 : : }
306 : : else
307 : : {
308 : : /* connected outright - nothing to report */
309 : : }
310 : 54 : return connected;
311 : : }
312 : :
313 : 6 : static bool LwipSocketTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size)
314 : : {
315 : 6 : struct SolidSyslogLwipSocketTcpStream* self = LwipSocketTcpStream_SelfFromBase(base);
316 : 6 : bool ok = LwipSocketTcpStream_PeerIsStillThere(self);
317 : :
318 [ + + ]: 6 : if (ok)
319 : : {
320 : 4 : ssize_t sent = lwip_send(self->Fd, buffer, size, 0);
321 : 4 : ok = LwipSocketTcpStream_WroteAllBytes(sent, size);
322 : : }
323 [ + + ]: 6 : if (!ok)
324 : : {
325 : 4 : LwipSocketTcpStream_CloseSocket(self);
326 : : }
327 : 6 : return ok;
328 : : }
329 : :
330 : : /* A peer that has closed its end leaves the socket writable, so lwip_send
331 : : * would take the record and the connection would die with it still in flight -
332 : : * and the Stream contract has the record gone from the caller's hands the
333 : : * moment Send returns true. Ask first, so a half-closed connection fails the
334 : : * send and store-and-forward replays the record instead of losing it. Bytes the
335 : : * peer sent that nothing has read stand in front of its FIN, and the peek sees
336 : : * those instead. They are left for Read rather than discarded: a stream layered
337 : : * over this one may be mid-handshake, with the peer's reply queued here. A
338 : : * syslog collector sends nothing back, so the queue is empty when the check
339 : : * runs. */
340 : 6 : static bool LwipSocketTcpStream_PeerIsStillThere(struct SolidSyslogLwipSocketTcpStream* self)
341 : : {
342 : 6 : char discard = 0;
343 : 6 : ssize_t peeked = lwip_recv(self->Fd, &discard, sizeof(discard), MSG_PEEK);
344 : : /* Captured immediately after lwip_recv so the test below satisfies
345 : : * MISRA 22.10 - no intervening library call between the errno-setting
346 : : * function and the read. */
347 [ + + ]: 6 : int peekErrno = (peeked < 0) ? errno : 0;
348 [ + + + + : 6 : return (peeked > 0) || ((peeked < 0) && LwipSocketTcpStream_WouldBlock(peekErrno));
+ + ]
349 : : }
350 : :
351 : : /* Non-blocking single-call contract: a short write or any error means the
352 : : * connection is gone; the sender reconnects on its next pass. */
353 : 4 : static bool LwipSocketTcpStream_WroteAllBytes(ssize_t sent, size_t expected)
354 : : {
355 [ + + + + ]: 4 : return (sent >= 0) && ((size_t) sent == expected);
356 : : }
357 : :
358 : 5 : static SolidSyslogSsize LwipSocketTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size)
359 : : {
360 : 5 : struct SolidSyslogLwipSocketTcpStream* self = LwipSocketTcpStream_SelfFromBase(base);
361 : 5 : ssize_t received = lwip_recv(self->Fd, buffer, size, 0);
362 : : /* Captured immediately after lwip_recv so the test below satisfies
363 : : * MISRA 22.10 - no intervening library call between the errno-setting
364 : : * function and the read. */
365 [ + + ]: 5 : int recvErrno = (received < 0) ? errno : 0;
366 : 5 : SolidSyslogSsize result = -1;
367 : :
368 [ + + ]: 5 : if (received > 0)
369 : : {
370 : 2 : result = (SolidSyslogSsize) received;
371 : : }
372 [ + + + + ]: 3 : else if ((received < 0) && LwipSocketTcpStream_WouldBlock(recvErrno))
373 : : {
374 : 1 : result = 0;
375 : : }
376 : : else
377 : : {
378 : 2 : LwipSocketTcpStream_CloseSocket(self);
379 : : }
380 : 5 : return result;
381 : : }
382 : :
383 : : /* lwIP maps both "nothing arrived yet" and the receive timeout onto
384 : : * EWOULDBLOCK, and EAGAIN is the same value; neither says the connection is
385 : : * gone, so the stream keeps it and the caller tries again. */
386 : 6 : static inline bool LwipSocketTcpStream_WouldBlock(int err)
387 : : {
388 [ + + - + ]: 6 : return (err == EWOULDBLOCK) || (err == EAGAIN);
389 : : }
390 : :
391 : 4 : static void LwipSocketTcpStream_Close(struct SolidSyslogStream* base)
392 : : {
393 : 4 : LwipSocketTcpStream_CloseSocket(LwipSocketTcpStream_SelfFromBase(base));
394 : 4 : }
395 : :
396 : : /* Nothing about a plain TCP socket's own configuration moves at runtime. The
397 : : * destination axis travels on the sender's endpoint version, which it polls
398 : : * independently of this. */
399 : 1 : static uint32_t LwipSocketTcpStream_Version(struct SolidSyslogStream* base)
400 : : {
401 : : (void) base;
402 : 1 : return 0U;
403 : : }
404 : :
405 : 97 : static void LwipSocketTcpStream_CloseSocket(struct SolidSyslogLwipSocketTcpStream* self)
406 : : {
407 [ + + ]: 97 : if (LwipSocketTcpStream_IsSocketValid(self->Fd))
408 : : {
409 : 54 : (void) lwip_close(self->Fd);
410 : 54 : self->Fd = INVALID_SOCKET;
411 : : }
412 : 97 : }
413 : :
414 : : /* Read on every attempt, so a runtime-tunable value takes effect on the next
415 : : * reconnect. */
416 : 16 : static long LwipSocketTcpStream_ResolveConnectTimeoutMicros(struct SolidSyslogLwipSocketTcpStream* self)
417 : : {
418 : 16 : uint32_t ms = self->Config.GetConnectTimeoutMs(self->Config.ConnectTimeoutContext);
419 : 16 : uint32_t boundedMs = (ms > SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS_MAX) ? SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS_MAX : ms;
420 : 16 : return (long) boundedMs * (long) MICROSECONDS_PER_MILLISECOND;
421 : : }
422 : :
423 : 16 : static bool LwipSocketTcpStream_WaitForConnectCompletion(
424 : : struct SolidSyslogLwipSocketTcpStream* self,
425 : : long timeoutMicros
426 : : )
427 : : {
428 : 16 : int fd = self->Fd;
429 : : fd_set writeSet;
430 [ + + ]: 272 : FD_ZERO(&writeSet);
431 : 16 : FD_SET(fd, &writeSet);
432 : :
433 : : fd_set errorSet;
434 [ + + ]: 272 : FD_ZERO(&errorSet);
435 : 16 : FD_SET(fd, &errorSet);
436 : :
437 : 16 : struct timeval timeout = {.tv_sec = timeoutMicros / 1000000L, .tv_usec = timeoutMicros % 1000000L};
438 : :
439 : 16 : int rc = lwip_select(fd + 1, NULL, &writeSet, &errorSet, &timeout);
440 : : /* Captured immediately after lwip_select so the report below satisfies
441 : : * MISRA 22.10 - no intervening library call between the errno-setting
442 : : * function and the read. */
443 [ + + ]: 16 : int selectErrno = (rc < 0) ? errno : 0;
444 [ + + + + : 16 : bool ready = (rc > 0) && FD_ISSET(fd, &writeSet) && !FD_ISSET(fd, &errorSet);
+ + ]
445 : :
446 [ + + ]: 16 : if (!ready)
447 : : {
448 : 6 : LwipSocketTcpStream_ReportUnfinishedWait(self, rc, selectErrno);
449 : : }
450 : 16 : return ready;
451 : : }
452 : :
453 : : /* The budget expiring is nothing to report a code for. A select that failed
454 : : * left its errno. A socket lwip_select answered for, flagged in the exception
455 : : * set, holds the failure in SO_ERROR. */
456 : : // NOLINTBEGIN(bugprone-easily-swappable-parameters) -- a select result and the errno it left; distinct semantics
457 : 6 : static void LwipSocketTcpStream_ReportUnfinishedWait(
458 : : struct SolidSyslogLwipSocketTcpStream* self,
459 : : int selectResult,
460 : : int selectErrno
461 : : )
462 : : {
463 [ + + ]: 6 : if (LwipSocketTcpStream_WaitTimedOut(selectResult))
464 : : {
465 : 2 : LwipSocketTcpStream_ReportConnectFailure(
466 : : self,
467 : : SOLIDSYSLOG_STREAM_CONNECT_REMOTE_SEVERITY,
468 : : SOLIDSYSLOG_TCP_STREAM_ERROR_CONNECT_TIMED_OUT
469 : : );
470 : : }
471 : : else
472 : : {
473 : 4 : int nativeErrno = selectErrno;
474 [ + + ]: 4 : if (selectResult > 0)
475 : : {
476 : 3 : (void) LwipSocketTcpStream_ReadSocketError(self->Fd, &nativeErrno);
477 : : }
478 : 4 : LwipSocketTcpStream_ReportConnectFailureWithErrno(
479 : : self,
480 : : SOLIDSYSLOG_STREAM_CONNECT_REMOTE_SEVERITY,
481 : : SOLIDSYSLOG_TCP_STREAM_ERROR_CONNECT_REFUSED,
482 : : nativeErrno
483 : : );
484 : : }
485 : 6 : }
486 : :
487 : : // NOLINTEND(bugprone-easily-swappable-parameters)
488 : :
489 : : /* Remote means the destination or the network answered, or failed to. Every
490 : : * other immediate failure is local - the stack would not accept the socket, or
491 : : * had no route or memory to start the attempt - and nothing left this device,
492 : : * which is what CONNECT_NOT_STARTED says. */
493 : 38 : static inline bool LwipSocketTcpStream_IsRemoteConnectError(int connectErrno)
494 : : {
495 [ + + + + ]: 38 : return (connectErrno == ECONNREFUSED) || (connectErrno == ETIMEDOUT);
496 : : }
497 : :
498 : : /* lwip_select answering zero is the budget expiring with nothing to report.
499 : : * Any other unready outcome means the destination answered, just not with a
500 : : * connection. */
501 : 6 : static inline bool LwipSocketTcpStream_WaitTimedOut(int selectResult)
502 : : {
503 : 6 : return selectResult == 0;
504 : : }
505 : :
506 : : /* Writability alone does not mean connected: a non-blocking connect reports
507 : : * its outcome through SO_ERROR, and a failed one becomes writable too. */
508 : 10 : static bool LwipSocketTcpStream_ReadDeferredConnectError(struct SolidSyslogLwipSocketTcpStream* self)
509 : : {
510 : 10 : int code = 0;
511 : 10 : bool asked = LwipSocketTcpStream_ReadSocketError(self->Fd, &code);
512 [ + + + + ]: 10 : bool connected = asked && (code == 0);
513 : :
514 [ + + ]: 10 : if (!connected)
515 : : {
516 : 3 : LwipSocketTcpStream_ReportConnectFailureWithErrno(
517 : : self,
518 : : SOLIDSYSLOG_STREAM_CONNECT_REMOTE_SEVERITY,
519 : : SOLIDSYSLOG_TCP_STREAM_ERROR_CONNECT_REFUSED,
520 : : code
521 : : );
522 : : }
523 : 10 : return connected;
524 : : }
525 : :
526 : : /* The error the socket holds, or, when it cannot be asked, the lookup's own:
527 : : * true when the socket answered. */
528 : 13 : static bool LwipSocketTcpStream_ReadSocketError(int fd, int* code)
529 : : {
530 : 13 : int err = 0;
531 : 13 : socklen_t errlen = (socklen_t) sizeof(err);
532 : 13 : int rc = lwip_getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen);
533 : : /* Captured immediately after lwip_getsockopt so the report satisfies
534 : : * MISRA 22.10 - no intervening library call between the errno-setting
535 : : * function and the read. */
536 [ + + ]: 13 : int lookupErrno = (rc != 0) ? errno : 0;
537 [ + + ]: 13 : *code = (rc == 0) ? err : lookupErrno;
538 : 13 : return rc == 0;
539 : : }
540 : :
541 : : /* Every connect failure is one category with the detail naming which step
542 : : * failed, so a portable handler reacts to "no connection" without knowing
543 : : * lwIP. Each failing step reports its own, because each is the only place that
544 : : * knows which one it was, and the only place the errno it left can be read.
545 : : * Reported once per run of the same detail, as the Open contract requires:
546 : : * the sender retries every service pass. */
547 : 4 : static void LwipSocketTcpStream_ReportConnectFailure(
548 : : struct SolidSyslogLwipSocketTcpStream* self,
549 : : enum SolidSyslogSeverity severity,
550 : : enum SolidSyslogTcpStreamErrors detail
551 : : )
552 : : {
553 : 4 : SolidSyslogStreamOpenHealth_ReportFailure(
554 : : &self->OpenHealth,
555 : : &SolidSyslogLwipSocketTcpStreamErrorSource,
556 : : severity,
557 : : SOLIDSYSLOG_CAT_STREAM_CONNECT_FAILED,
558 : : (int32_t) detail
559 : : );
560 : 4 : }
561 : :
562 : : /* The fault, with the errno behind it when the call left one. */
563 : 20 : static void LwipSocketTcpStream_ReportConnectFailureWithErrno(
564 : : struct SolidSyslogLwipSocketTcpStream* self,
565 : : enum SolidSyslogSeverity severity,
566 : : enum SolidSyslogTcpStreamErrors detail,
567 : : int nativeErrno
568 : : )
569 : : {
570 [ + + ]: 20 : if (nativeErrno != 0)
571 : : {
572 : 18 : SolidSyslogStreamOpenHealth_ReportFailureWithNativeCode(
573 : : &self->OpenHealth,
574 : : &SolidSyslogLwipSocketTcpStreamErrorSource,
575 : : severity,
576 : : SOLIDSYSLOG_CAT_STREAM_CONNECT_FAILED,
577 : : (int32_t) detail,
578 : : (int32_t) nativeErrno
579 : : );
580 : : }
581 : : else
582 : : {
583 : 2 : LwipSocketTcpStream_ReportConnectFailure(self, severity, detail);
584 : : }
585 : 20 : }
586 : :
587 : : #else
588 : :
589 : : /* ISO C forbids an empty translation unit. */
590 : : typedef int LwipSocketTcpStream_EmptyTranslationUnit;
591 : :
592 : : #endif /* LWIP_SOCKET && LWIP_TCP && LWIP_SOCKET_SELECT */
|