SolidSyslog coverage report
Current view: top level - Platform/LwipSocket/Source - SolidSyslogLwipSocketTcpStream.c (source / functions) Coverage Total Hit
Test: SolidSyslog Lines: 100.0 % 173 173
Test Date: 2026-11-12 23:14:35 Functions: 100.0 % 30 30
Branches: 99.1 % 108 107

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

Generated by: LCOV version 2.0-1