diff --git a/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_FreeRtosTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_FreeRtosTcp.c index ee011ff9..e5dc1301 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_FreeRtosTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_FreeRtosTcp.c @@ -362,7 +362,7 @@ struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* return SolidSyslogNullSender_Get(); } - underlyingStream = SolidSyslogFreeRtosTcpStream_Create(); + underlyingStream = SolidSyslogFreeRtosTcpStream_Create(NULL); static struct SolidSyslogMbedTlsStreamConfig tlsStreamConfig; tlsStreamConfig = (struct SolidSyslogMbedTlsStreamConfig) {0}; diff --git a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c index 027a73ac..c8098867 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c @@ -1,4 +1,5 @@ #include +#include #include "BddTargetMtlsConfig.h" #include "BddTargetTlsConfig.h" @@ -19,7 +20,7 @@ static struct SolidSyslogSender* sender; struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* resolver, bool mtls) { - underlyingStream = SolidSyslogPosixTcpStream_Create(); + underlyingStream = SolidSyslogPosixTcpStream_Create(NULL); static struct SolidSyslogTlsStreamConfig tlsStreamConfig; tlsStreamConfig = (struct SolidSyslogTlsStreamConfig) {0}; diff --git a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c index fa588b24..4df63d28 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c @@ -19,7 +19,7 @@ static struct SolidSyslogSender* sender; struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* resolver, bool mtls) { - underlyingStream = SolidSyslogWinsockTcpStream_Create(); + underlyingStream = SolidSyslogWinsockTcpStream_Create(NULL); static struct SolidSyslogTlsStreamConfig tlsStreamConfig; tlsStreamConfig = (struct SolidSyslogTlsStreamConfig) {0}; diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index a29bd928..02894258 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -970,7 +970,7 @@ static void InteractiveTask(void* argument) * UDP endpoint callbacks because the BDD oracle (syslog-ng) listens on the * same host:port for both transports — the syslog-ng config in * Bdd/syslog-ng/syslog-ng.conf has a TCP listener on 5514 alongside UDP. */ - tcpStream = SolidSyslogFreeRtosTcpStream_Create(); + tcpStream = SolidSyslogFreeRtosTcpStream_Create(NULL); tcpAddress = SolidSyslogFreeRtosAddress_Create(); struct SolidSyslogStreamSenderConfig tcpConfig = { .Resolver = resolver, diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 9c1e840f..8e129816 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -95,7 +95,7 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetOptions* opt udpConfig.EndpointVersion = BddTargetUdpConfig_GetEndpointVersion; udpSender = SolidSyslogUdpSender_Create(&udpConfig); - plainTcpStream = SolidSyslogPosixTcpStream_Create(); + plainTcpStream = SolidSyslogPosixTcpStream_Create(NULL); plainTcpAddress = SolidSyslogPosixAddress_Create(); static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; tcpConfig.Resolver = resolver; diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index ef646c54..ebabd021 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -193,7 +193,7 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetWindowsOptio udpConfig.EndpointVersion = GetEndpointVersion; udpSender = SolidSyslogUdpSender_Create(&udpConfig); - plainTcpStream = SolidSyslogWinsockTcpStream_Create(); + plainTcpStream = SolidSyslogWinsockTcpStream_Create(NULL); plainTcpAddress = SolidSyslogWinsockAddress_Create(); static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; tcpConfig.Resolver = resolver; diff --git a/Core/Interface/SolidSyslogTcpConnectTimeoutFunction.h b/Core/Interface/SolidSyslogTcpConnectTimeoutFunction.h new file mode 100644 index 00000000..f79a7308 --- /dev/null +++ b/Core/Interface/SolidSyslogTcpConnectTimeoutFunction.h @@ -0,0 +1,20 @@ +#ifndef SOLIDSYSLOGTCPCONNECTTIMEOUTFUNCTION_H +#define SOLIDSYSLOGTCPCONNECTTIMEOUTFUNCTION_H + +#include + +#include "ExternC.h" + +EXTERN_C_BEGIN + + /* Returns the bounded-connect deadline in milliseconds. Called at the + start of every connect attempt so a runtime-tunable value (e.g. + commissioning-time NVRAM read, live web-UI override) takes effect on + the next reconnect without rebuilding or destroying the stream. + The void* context is passed through unchanged from the config slot + the integrator installed it on. */ + typedef uint32_t (*SolidSyslogTcpConnectTimeoutFunction)(void* context); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGTCPCONNECTTIMEOUTFUNCTION_H */ diff --git a/Core/Interface/SolidSyslogTlsHandshakeTimeoutFunction.h b/Core/Interface/SolidSyslogTlsHandshakeTimeoutFunction.h new file mode 100644 index 00000000..82e3967f --- /dev/null +++ b/Core/Interface/SolidSyslogTlsHandshakeTimeoutFunction.h @@ -0,0 +1,20 @@ +#ifndef SOLIDSYSLOGTLSHANDSHAKETIMEOUTFUNCTION_H +#define SOLIDSYSLOGTLSHANDSHAKETIMEOUTFUNCTION_H + +#include + +#include "ExternC.h" + +EXTERN_C_BEGIN + + /* Returns the bounded-handshake deadline in milliseconds. Called at the + start of every TLS handshake attempt so a runtime-tunable value (e.g. + commissioning-time NVRAM read, live web-UI override) takes effect on + the next reconnect without rebuilding or destroying the stream. + The void* context is passed through unchanged from the config slot + the integrator installed it on. */ + typedef uint32_t (*SolidSyslogTlsHandshakeTimeoutFunction)(void* context); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGTLSHANDSHAKETIMEOUTFUNCTION_H */ diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index a12f033f..e554607d 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -696,4 +696,48 @@ #error "SOLIDSYSLOG_ADDRESS_POOL_SIZE must be >= 1" #endif +/* + * Default bounded-connect deadline applied by every TCP Stream backend + * (POSIX, Winsock, FreeRTOS) when the integrator does not install a + * SolidSyslogTcpConnectTimeoutFunction on the config struct. 200 ms is + * comfortable for loopback / LAN and short enough that ten failing attempts + * cost 2 s; raise it for WAN deployments behind a high-RTT link. + * + * Runtime override: install GetConnectTimeoutMs on the per-Stream config — + * the getter is invoked on every connect attempt so live tuning takes effect + * without rebuilding or recreating the stream. + * + * Floor: 1 ms. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS 200U +#endif + +#if SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS < 1 +#error "SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS must be >= 1" +#endif + +/* + * Default bounded TLS handshake deadline applied by every TLS Stream backend + * (OpenSSL, Mbed TLS) when the integrator does not install a + * SolidSyslogTlsHandshakeTimeoutFunction on the config struct. 5 s covers a + * full TLS 1.2 / 1.3 exchange on a healthy LAN with cert validation; raise + * it for WAN deployments or constrained MCUs that handshake slowly. + * + * Runtime override: install GetHandshakeTimeoutMs on the per-Stream config — + * the getter is invoked on every handshake attempt so live tuning takes + * effect without rebuilding or recreating the stream. + * + * Floor: 1 ms. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS 5000U +#endif + +#if SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS < 1 +#error "SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h index b031c0bd..09dcdfe3 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h @@ -2,12 +2,22 @@ #define SOLIDSYSLOGFREERTOSTCPSTREAM_H #include "ExternC.h" +#include "SolidSyslogTcpConnectTimeoutFunction.h" EXTERN_C_BEGIN struct SolidSyslogStream; - struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(void); + struct SolidSyslogFreeRtosTcpStreamConfig + { + SolidSyslogTcpConnectTimeoutFunction + GetConnectTimeoutMs; /* NULL → use SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS tunable */ + void* ConnectTimeoutContext; /* passed through to GetConnectTimeoutMs; NULL is fine */ + }; + + struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create( + const struct SolidSyslogFreeRtosTcpStreamConfig* config + ); void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c index 7edff9bc..286aa867 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c @@ -18,6 +18,7 @@ #include "SolidSyslogNullStream.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTunables.h" /* SolidSyslogStream_Read returns < 0 to signal EOF/error (socket closed * internally); -1 is the in-tree convention shared with Posix/Winsock. */ @@ -35,12 +36,15 @@ enum SEND_RECV_FLAGS_DEFAULT = 0 }; +static uint32_t FreeRtosTcpStream_NullConnectTimeoutGetter(void* context); + static bool FreeRtosTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static bool FreeRtosTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize FreeRtosTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void FreeRtosTcpStream_Close(struct SolidSyslogStream* base); static inline struct SolidSyslogFreeRtosTcpStream* FreeRtosTcpStream_SelfFromBase(struct SolidSyslogStream* base); +static inline bool FreeRtosTcpStream_ConfigProvidesGetter(const struct SolidSyslogFreeRtosTcpStreamConfig* config); static inline bool FreeRtosTcpStream_IsOpen(const struct SolidSyslogFreeRtosTcpStream* self); static inline bool FreeRtosTcpStream_IsClosed(const struct SolidSyslogFreeRtosTcpStream* self); static void FreeRtosTcpStream_OpenSocket(struct SolidSyslogFreeRtosTcpStream* self); @@ -56,6 +60,7 @@ static inline void FreeRtosTcpStream_PrimeArpIfMissing(uint32_t ip); static void FreeRtosTcpStream_ClearTimeouts(Socket_t socket); static void FreeRtosTcpStream_SetSendTimeout(Socket_t socket, TickType_t ticks); static void FreeRtosTcpStream_SetRecvTimeout(Socket_t socket, TickType_t ticks); +static uint32_t FreeRtosTcpStream_ResolveConnectTimeoutMs(struct SolidSyslogFreeRtosTcpStream* self); static bool FreeRtosTcpStream_SendOrCloseOnFailure( struct SolidSyslogFreeRtosTcpStream* self, const void* buffer, @@ -70,14 +75,41 @@ static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure( ); static void FreeRtosTcpStream_CloseSocket(struct SolidSyslogFreeRtosTcpStream* self); -void FreeRtosTcpStream_Initialise(struct SolidSyslogStream* base) +void FreeRtosTcpStream_Initialise( + struct SolidSyslogStream* base, + const struct SolidSyslogFreeRtosTcpStreamConfig* config +) { + static const struct SolidSyslogFreeRtosTcpStream DefaultFreeRtosTcpStream = { + .Base = + {.Open = FreeRtosTcpStream_Open, + .Send = FreeRtosTcpStream_Send, + .Read = FreeRtosTcpStream_Read, + .Close = FreeRtosTcpStream_Close}, + .Config = {.GetConnectTimeoutMs = FreeRtosTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL}, + .Socket = FREERTOS_INVALID_SOCKET, + }; + struct SolidSyslogFreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); - self->Base.Open = FreeRtosTcpStream_Open; - self->Base.Send = FreeRtosTcpStream_Send; - self->Base.Read = FreeRtosTcpStream_Read; - self->Base.Close = FreeRtosTcpStream_Close; - self->Socket = FREERTOS_INVALID_SOCKET; + *self = DefaultFreeRtosTcpStream; + if (FreeRtosTcpStream_ConfigProvidesGetter(config) == true) + { + self->Config = *config; + } +} + +static inline bool FreeRtosTcpStream_ConfigProvidesGetter(const struct SolidSyslogFreeRtosTcpStreamConfig* config) +{ + return (config != NULL) && (config->GetConnectTimeoutMs != NULL); +} + +/* Null Object substituted when the integrator does not install a getter — + * returns the compile-time tunable so the bounded-wait path has a single + * code path regardless of whether the integrator wired runtime tuning. */ +static uint32_t FreeRtosTcpStream_NullConnectTimeoutGetter(void* context) +{ + (void) context; + return (uint32_t) SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS; } static inline struct SolidSyslogFreeRtosTcpStream* FreeRtosTcpStream_SelfFromBase(struct SolidSyslogStream* base) @@ -152,23 +184,31 @@ static bool FreeRtosTcpStream_TryConnect( const struct SolidSyslogAddress* addr ) { - /* 200 ms is short enough that the Service task keeps draining - * predictably during an outage, long enough for a healthy peer to - * ACK over slirp/LAN. Both SO_SNDTIMEO and SO_RCVTIMEO are set - * before FreeRTOS_connect — upstream gates connect on SO_RCVTIMEO, - * but we set both as belt-and-braces against an upstream change. - * After connect both timeouts go back to 0 so subsequent Send/Read - * follow the non-blocking single-call contract from - * SolidSyslogStream. */ - static const TickType_t CONNECT_TIMEOUT_TICKS = pdMS_TO_TICKS(200); + /* Both SO_SNDTIMEO and SO_RCVTIMEO are set before FreeRTOS_connect — + * upstream gates connect on SO_RCVTIMEO, but we set both as belt-and- + * braces against an upstream change. After connect both timeouts go + * back to 0 so subsequent Send/Read follow the non-blocking single- + * call contract from SolidSyslogStream. The default + * SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS = 200 is short enough that the + * Service task keeps draining predictably during an outage, long + * enough for a healthy peer to ACK over slirp/LAN. */ + const TickType_t connectTimeoutTicks = pdMS_TO_TICKS(FreeRtosTcpStream_ResolveConnectTimeoutMs(self)); const struct freertos_sockaddr* dest = SolidSyslogFreeRtosAddress_AsConstFreertosSockaddr(addr); FreeRtosTcpStream_PrimeArpIfMissing(dest->sin_address.ulIP_IPv4); - FreeRtosTcpStream_SetSendTimeout(self->Socket, CONNECT_TIMEOUT_TICKS); - FreeRtosTcpStream_SetRecvTimeout(self->Socket, CONNECT_TIMEOUT_TICKS); + FreeRtosTcpStream_SetSendTimeout(self->Socket, connectTimeoutTicks); + FreeRtosTcpStream_SetRecvTimeout(self->Socket, connectTimeoutTicks); return FreeRTOS_connect(self->Socket, dest, sizeof(*dest)) == 0; } +/* Bridges the integrator-installed getter (or the Null Object substituted in + * Initialise) to the bounded SO_*TIMEO deadline. Invoked on every connect + * attempt so a runtime-tunable value takes effect on the next reconnect. */ +static uint32_t FreeRtosTcpStream_ResolveConnectTimeoutMs(struct SolidSyslogFreeRtosTcpStream* self) +{ + return self->Config.GetConnectTimeoutMs(self->Config.ConnectTimeoutContext); +} + /* On ARP cache miss issue a probe and yield once for the reply to land * before FreeRTOS_connect runs. Without this, the cold-start SYN is dropped * at the IP layer (FreeRTOS-Plus-TCP does not queue while ARP resolves) and diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h index 031e86c2..5b8dbf79 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h @@ -4,15 +4,20 @@ #include "FreeRTOS.h" #include "FreeRTOS_Sockets.h" +#include "SolidSyslogFreeRtosTcpStream.h" #include "SolidSyslogStreamDefinition.h" struct SolidSyslogFreeRtosTcpStream { struct SolidSyslogStream Base; + struct SolidSyslogFreeRtosTcpStreamConfig Config; Socket_t Socket; }; -void FreeRtosTcpStream_Initialise(struct SolidSyslogStream* base); +void FreeRtosTcpStream_Initialise( + struct SolidSyslogStream* base, + const struct SolidSyslogFreeRtosTcpStreamConfig* config +); void FreeRtosTcpStream_Cleanup(struct SolidSyslogStream* base); #endif /* SOLIDSYSLOGFREERTOSTCPSTREAMPRIVATE_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c index d74f599b..232f7554 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c @@ -24,13 +24,13 @@ static struct SolidSyslogPoolAllocator FreeRtosTcpStream_Allocator = { SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE }; -struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(void) +struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(const struct SolidSyslogFreeRtosTcpStreamConfig* config) { size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&FreeRtosTcpStream_Allocator); struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); if (SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosTcpStream_Allocator, index) == true) { - FreeRtosTcpStream_Initialise(&FreeRtosTcpStream_Pool[index].Base); + FreeRtosTcpStream_Initialise(&FreeRtosTcpStream_Pool[index].Base, config); handle = &FreeRtosTcpStream_Pool[index].Base; } else diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index b8ac1216..2ab2f8eb 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -3,6 +3,7 @@ #include "ExternC.h" #include "SolidSyslogSleep.h" +#include "SolidSyslogTlsHandshakeTimeoutFunction.h" struct SolidSyslogStream; @@ -20,6 +21,9 @@ EXTERN_C_BEGIN struct SolidSyslogStream* Transport; /* underlying byte stream — caller owns */ SolidSyslogSleepFunction Sleep; /* drives bounded handshake retry between WANT_READ/WANT_WRITE polls — required */ + SolidSyslogTlsHandshakeTimeoutFunction + GetHandshakeTimeoutMs; /* NULL → use SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS tunable */ + void* HandshakeTimeoutContext; /* passed through to GetHandshakeTimeoutMs; NULL is fine */ struct mbedtls_ctr_drbg_context* Rng; /* seeded CTR-DRBG — caller owns */ struct mbedtls_x509_crt* CaChain; /* trust anchors — caller owns */ const char* ServerName; /* SNI + cert hostname check; NULL to skip */ diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 34b80b64..fcc308cb 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -13,19 +13,18 @@ #include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTunables.h" enum { - /* Bounded retry budget for the TLS handshake under non-blocking transport. - Mirrors the OpenSSL TlsStream constants (5s comfortably covers WAN - deployments without burning the service thread indefinitely on a - wedged peer). */ - HANDSHAKE_TIMEOUT_MILLISECONDS = 5000, HANDSHAKE_POLL_INTERVAL_MILLISECONDS = 1 }; struct SolidSyslogAddress; +static uint32_t MbedTlsStream_NullHandshakeTimeoutGetter(void* context); +static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogMbedTlsStreamConfig* config); +static inline uint32_t MbedTlsStream_ResolveHandshakeTimeoutMs(struct SolidSyslogMbedTlsStream* self); static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct SolidSyslogStream* base); static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTlsStream* self); @@ -35,7 +34,7 @@ static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbe static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbedTlsStream* self); static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self); static inline bool MbedTlsStream_IsRetryableHandshakeRc(int rc); -static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(int totalSleptMs); +static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs); static inline bool MbedTlsStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static inline SolidSyslogSsize MbedTlsStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static inline void MbedTlsStream_Close(struct SolidSyslogStream* base); @@ -50,6 +49,14 @@ void MbedTlsStream_Initialise(struct SolidSyslogStream* base, const struct Solid self->Base.Read = MbedTlsStream_Read; self->Base.Close = MbedTlsStream_Close; self->Config = *config; + if (MbedTlsStream_ConfigProvidesHandshakeGetter(config) == false) + { + /* Substitute the Null Object so the bounded-handshake loop has a + * single code path regardless of whether the integrator wired + * runtime tuning. */ + self->Config.GetHandshakeTimeoutMs = MbedTlsStream_NullHandshakeTimeoutGetter; + self->Config.HandshakeTimeoutContext = NULL; + } /* Eager init so mbedtls_*_free in Close is always safe — whether Open * was ever reached, whether it succeeded, or whether Close is being * called twice in a row. mbedTLS guarantees a freed struct is left in @@ -59,6 +66,29 @@ void MbedTlsStream_Initialise(struct SolidSyslogStream* base, const struct Solid mbedtls_ssl_config_init(&self->SslConfig); } +/* Null Object substituted in Initialise when the integrator does not install a + * getter — returns the compile-time tunable so the bounded-handshake path is a + * single code path regardless of whether the integrator wired runtime tuning. */ +static uint32_t MbedTlsStream_NullHandshakeTimeoutGetter(void* context) +{ + (void) context; + return (uint32_t) SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS; +} + +static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogMbedTlsStreamConfig* config) +{ + return (config != NULL) && (config->GetHandshakeTimeoutMs != NULL); +} + +/* Bridges the integrator-installed getter (or the Null Object substituted at + * config-copy time) to the bounded handshake deadline. Invoked at the start + * of each handshake attempt so runtime-tunable values take effect on the next + * reconnect. */ +static inline uint32_t MbedTlsStream_ResolveHandshakeTimeoutMs(struct SolidSyslogMbedTlsStream* self) +{ + return self->Config.GetHandshakeTimeoutMs(self->Config.HandshakeTimeoutContext); +} + static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct SolidSyslogStream* base) { return (struct SolidSyslogMbedTlsStream*) base; @@ -186,7 +216,8 @@ static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbe * timeout. Same shape as OpenSSL's TlsStream_PerformHandshake. */ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self) { - int totalSleptMs = 0; + uint32_t budgetMs = MbedTlsStream_ResolveHandshakeTimeoutMs(self); + uint32_t totalSleptMs = 0; bool result = false; bool done = false; @@ -207,7 +238,7 @@ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStrea ); done = true; } - else if (MbedTlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) + else if (MbedTlsStream_IsHandshakeBudgetExhausted(totalSleptMs, budgetMs)) { SolidSyslog_Error( SOLIDSYSLOG_SEVERITY_ERROR, @@ -219,7 +250,7 @@ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStrea else { self->Config.Sleep(HANDSHAKE_POLL_INTERVAL_MILLISECONDS); - totalSleptMs += HANDSHAKE_POLL_INTERVAL_MILLISECONDS; + totalSleptMs += (uint32_t) HANDSHAKE_POLL_INTERVAL_MILLISECONDS; } } return result; @@ -230,9 +261,9 @@ static inline bool MbedTlsStream_IsRetryableHandshakeRc(int rc) return (rc == MBEDTLS_ERR_SSL_WANT_READ) || (rc == MBEDTLS_ERR_SSL_WANT_WRITE); } -static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(int totalSleptMs) +static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs) { - return totalSleptMs >= HANDSHAKE_TIMEOUT_MILLISECONDS; + return totalSleptMs >= budgetMs; } static int MbedTlsStream_BioSend(void* ctx, const unsigned char* buf, size_t len) diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h index 2b175e54..73dafd0c 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h @@ -3,6 +3,7 @@ #include "ExternC.h" #include "SolidSyslogSleep.h" +#include "SolidSyslogTlsHandshakeTimeoutFunction.h" struct SolidSyslogStream; @@ -13,6 +14,9 @@ EXTERN_C_BEGIN struct SolidSyslogStream* Transport; /* underlying byte stream — caller owns */ SolidSyslogSleepFunction Sleep; /* drives bounded handshake retry between WANT_READ/WANT_WRITE polls — required */ + SolidSyslogTlsHandshakeTimeoutFunction + GetHandshakeTimeoutMs; /* NULL → use SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS tunable */ + void* HandshakeTimeoutContext; /* passed through to GetHandshakeTimeoutMs; NULL is fine */ const char* CaBundlePath; /* PEM file of trust anchors */ const char* ServerName; /* SNI + cert hostname check; NULL to skip */ const char* CipherList; /* TLS 1.2 cipher list; NULL = OpenSSL default */ diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index 7b95b6f7..2e6d07db 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -15,17 +15,17 @@ #include "SolidSyslogStreamDefinition.h" #include "SolidSyslogTlsStreamErrors.h" #include "SolidSyslogTlsStreamPrivate.h" +#include "SolidSyslogTunables.h" enum { - /* Bounded retry budget for the TLS handshake under non-blocking transport. - Real-world handshakes take ~100–500 ms (1–3 RTTs to a cloud SIEM); 5 s - comfortably covers WAN deployments without burning the service thread - indefinitely on a wedged peer. */ - HANDSHAKE_TIMEOUT_MILLISECONDS = 5000, HANDSHAKE_POLL_INTERVAL_MILLISECONDS = 1 }; +static uint32_t TlsStream_NullHandshakeTimeoutGetter(void* context); +static inline bool TlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogTlsStreamConfig* config); +static inline uint32_t TlsStream_ResolveHandshakeTimeoutMs(struct SolidSyslogTlsStream* self); + struct SolidSyslogAddress; static inline struct SolidSyslogTlsStream* TlsStream_SelfFromBase(struct SolidSyslogStream* base); @@ -64,6 +64,14 @@ void TlsStream_Initialise(struct SolidSyslogStream* base, const struct SolidSysl self->Base.Read = TlsStream_Read; self->Base.Close = TlsStream_Close; self->Config = *config; + if (TlsStream_ConfigProvidesHandshakeGetter(config) == false) + { + /* Substitute the Null Object so the bounded-handshake loop has a + * single code path regardless of whether the integrator wired + * runtime tuning. */ + self->Config.GetHandshakeTimeoutMs = TlsStream_NullHandshakeTimeoutGetter; + self->Config.HandshakeTimeoutContext = NULL; + } self->Ctx = NULL; self->Ssl = NULL; self->BioMethod = NULL; @@ -394,9 +402,32 @@ static inline bool TlsStream_IsRetryableSslError(int err) return (err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE); } -static inline bool TlsStream_IsHandshakeBudgetExhausted(int totalSleptMs) +static inline bool TlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs) +{ + return totalSleptMs >= budgetMs; +} + +/* Null Object substituted at Initialise when the integrator does not install a + * getter — returns the compile-time tunable so the bounded-handshake path is a + * single code path regardless of whether the integrator wired runtime tuning. */ +static uint32_t TlsStream_NullHandshakeTimeoutGetter(void* context) +{ + (void) context; + return (uint32_t) SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS; +} + +static inline bool TlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogTlsStreamConfig* config) +{ + return (config != NULL) && (config->GetHandshakeTimeoutMs != NULL); +} + +/* Bridges the integrator-installed getter (or the Null Object substituted at + * config-copy time) to the bounded handshake deadline. Invoked at the start + * of each handshake attempt so runtime-tunable values take effect on the next + * reconnect. */ +static inline uint32_t TlsStream_ResolveHandshakeTimeoutMs(struct SolidSyslogTlsStream* self) { - return totalSleptMs >= HANDSHAKE_TIMEOUT_MILLISECONDS; + return self->Config.GetHandshakeTimeoutMs(self->Config.HandshakeTimeoutContext); } /* Drive SSL_connect to completion under non-blocking transport. Each call may @@ -406,7 +437,8 @@ static inline bool TlsStream_IsHandshakeBudgetExhausted(int totalSleptMs) * expires. */ static inline bool TlsStream_PerformHandshake(struct SolidSyslogTlsStream* self) { - int totalSleptMs = 0; + uint32_t budgetMs = TlsStream_ResolveHandshakeTimeoutMs(self); + uint32_t totalSleptMs = 0; bool result = false; bool done = false; @@ -430,7 +462,7 @@ static inline bool TlsStream_PerformHandshake(struct SolidSyslogTlsStream* self) ); done = true; } - else if (TlsStream_IsHandshakeBudgetExhausted(totalSleptMs)) + else if (TlsStream_IsHandshakeBudgetExhausted(totalSleptMs, budgetMs)) { SolidSyslog_Error( SOLIDSYSLOG_SEVERITY_ERROR, @@ -442,7 +474,7 @@ static inline bool TlsStream_PerformHandshake(struct SolidSyslogTlsStream* self) else { self->Config.Sleep(HANDSHAKE_POLL_INTERVAL_MILLISECONDS); - totalSleptMs += HANDSHAKE_POLL_INTERVAL_MILLISECONDS; + totalSleptMs += (uint32_t) HANDSHAKE_POLL_INTERVAL_MILLISECONDS; } } } diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h index f1983a1c..fbdae6f7 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h @@ -2,12 +2,20 @@ #define SOLIDSYSLOGPOSIXTCPSTREAM_H #include "ExternC.h" +#include "SolidSyslogTcpConnectTimeoutFunction.h" EXTERN_C_BEGIN struct SolidSyslogStream; - struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void); + struct SolidSyslogPosixTcpStreamConfig + { + SolidSyslogTcpConnectTimeoutFunction + GetConnectTimeoutMs; /* NULL → use SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS tunable */ + void* ConnectTimeoutContext; /* passed through to GetConnectTimeoutMs; NULL is fine */ + }; + + struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(const struct SolidSyslogPosixTcpStreamConfig* config); void SolidSyslogPosixTcpStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 8df2aabb..cc4af021 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -16,17 +17,13 @@ #include "SolidSyslogPosixAddressPrivate.h" #include "SolidSyslogPosixTcpStreamPrivate.h" #include "SolidSyslogStream.h" -#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTunables.h" struct SolidSyslogAddress; enum { INVALID_FD = -1, - /* Caps the time a single connect() attempt can stall the service thread. - Mirrors the Winsock value: 200 ms is comfortable for loopback/LAN and - short enough that 10 failing attempts cost 2 s instead of 20+ s. */ - CONNECT_TIMEOUT_MICROSECONDS = 200000, /* Keepalive parameters — bound the dead-peer detection window when the socket is idle. Worst case: 45 + 4 * 10 = 85 s before ETIMEDOUT. TCP_USER_TIMEOUT covers the pending-write case (where keepalive does @@ -37,12 +34,15 @@ enum USER_TIMEOUT_MILLISECONDS = 30000 }; +static uint32_t PosixTcpStream_NullConnectTimeoutGetter(void* context); + static bool PosixTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static bool PosixTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize PosixTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void PosixTcpStream_Close(struct SolidSyslogStream* base); static inline struct SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromBase(struct SolidSyslogStream* base); +static inline bool PosixTcpStream_ConfigProvidesGetter(const struct SolidSyslogPosixTcpStreamConfig* config); static int PosixTcpStream_OpenAndConfigureSocket(void); static bool PosixTcpStream_ConfigureSocket(int fd); @@ -54,20 +54,45 @@ static bool PosixTcpStream_ConnectOrCloseOnFailure( struct SolidSyslogPosixTcpStream* self, const struct sockaddr_in* sin ); -static bool PosixTcpStream_Connect(int fd, const struct sockaddr_in* sin); -static bool PosixTcpStream_WaitForConnectCompletion(int fd); +static bool PosixTcpStream_Connect(int fd, const struct sockaddr_in* sin, long timeoutMicros); +static bool PosixTcpStream_WaitForConnectCompletion(int fd, long timeoutMicros); static bool PosixTcpStream_ReadDeferredConnectError(int fd); +static long PosixTcpStream_ResolveConnectTimeoutMicros(struct SolidSyslogPosixTcpStream* self); static bool PosixTcpStream_WroteAllBytes(ssize_t sent, size_t expected); static inline bool PosixTcpStream_WouldBlock(int err); -void PosixTcpStream_Initialise(struct SolidSyslogStream* base) +void PosixTcpStream_Initialise(struct SolidSyslogStream* base, const struct SolidSyslogPosixTcpStreamConfig* config) { + static const struct SolidSyslogPosixTcpStream DefaultPosixTcpStream = { + .Base = + {.Open = PosixTcpStream_Open, + .Send = PosixTcpStream_Send, + .Read = PosixTcpStream_Read, + .Close = PosixTcpStream_Close}, + .Config = {.GetConnectTimeoutMs = PosixTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL}, + .Fd = INVALID_FD, + }; + struct SolidSyslogPosixTcpStream* self = PosixTcpStream_SelfFromBase(base); - self->Base.Open = PosixTcpStream_Open; - self->Base.Send = PosixTcpStream_Send; - self->Base.Read = PosixTcpStream_Read; - self->Base.Close = PosixTcpStream_Close; - self->Fd = INVALID_FD; + *self = DefaultPosixTcpStream; + if (PosixTcpStream_ConfigProvidesGetter(config) == true) + { + self->Config = *config; + } +} + +static inline bool PosixTcpStream_ConfigProvidesGetter(const struct SolidSyslogPosixTcpStreamConfig* config) +{ + return (config != NULL) && (config->GetConnectTimeoutMs != NULL); +} + +/* Null Object substituted when the integrator does not install a getter — + * returns the compile-time tunable so the bounded-wait path has a single + * code path regardless of whether the integrator wired runtime tuning. */ +static uint32_t PosixTcpStream_NullConnectTimeoutGetter(void* context) +{ + (void) context; + return (uint32_t) SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS; } static inline struct SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromBase(struct SolidSyslogStream* base) @@ -165,7 +190,8 @@ static bool PosixTcpStream_ConnectOrCloseOnFailure( const struct sockaddr_in* sin ) { - bool connected = PosixTcpStream_Connect(self->Fd, sin); + long timeoutMicros = PosixTcpStream_ResolveConnectTimeoutMicros(self); + bool connected = PosixTcpStream_Connect(self->Fd, sin, timeoutMicros); if (!connected) { close(self->Fd); @@ -174,13 +200,22 @@ static bool PosixTcpStream_ConnectOrCloseOnFailure( return connected; } +/* Bridges the integrator-installed getter (or the Null Object substituted in + * Initialise) to the bounded select() deadline. Invoked on every connect + * attempt so a runtime-tunable value takes effect on the next reconnect. */ +static long PosixTcpStream_ResolveConnectTimeoutMicros(struct SolidSyslogPosixTcpStream* self) +{ + uint32_t ms = self->Config.GetConnectTimeoutMs(self->Config.ConnectTimeoutContext); + return (long) ms * 1000L; +} + /* Non-blocking connect with bounded wait. connect() returns immediately: * 0 — connected (loopback success path). * -1 EINPROGRESS — connect started; wait via select() up to * CONNECT_TIMEOUT_MICROSECONDS, then read SO_ERROR to * distinguish completed-success from deferred-failure. * -1 other — immediate fail-fast (refused, unreachable, etc.). */ -static bool PosixTcpStream_Connect(int fd, const struct sockaddr_in* sin) +static bool PosixTcpStream_Connect(int fd, const struct sockaddr_in* sin, long timeoutMicros) { bool connected = false; int rc = connect(fd, (const struct sockaddr*) sin, sizeof(*sin)); @@ -195,7 +230,8 @@ static bool PosixTcpStream_Connect(int fd, const struct sockaddr_in* sin) } else if (connectErrno == EINPROGRESS) { - connected = PosixTcpStream_WaitForConnectCompletion(fd) && PosixTcpStream_ReadDeferredConnectError(fd); + connected = + PosixTcpStream_WaitForConnectCompletion(fd, timeoutMicros) && PosixTcpStream_ReadDeferredConnectError(fd); } else { @@ -204,7 +240,8 @@ static bool PosixTcpStream_Connect(int fd, const struct sockaddr_in* sin) return connected; } -static bool PosixTcpStream_WaitForConnectCompletion(int fd) +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- fd is a kernel file descriptor; timeoutMicros is a duration; distinct semantics +static bool PosixTcpStream_WaitForConnectCompletion(int fd, long timeoutMicros) { fd_set writeSet; FD_ZERO(&writeSet); @@ -214,7 +251,7 @@ static bool PosixTcpStream_WaitForConnectCompletion(int fd) FD_ZERO(&errorSet); FD_SET(fd, &errorSet); - struct timeval timeout = {.tv_sec = 0, .tv_usec = CONNECT_TIMEOUT_MICROSECONDS}; + struct timeval timeout = {.tv_sec = timeoutMicros / 1000000L, .tv_usec = timeoutMicros % 1000000L}; int rc = select(fd + 1, NULL, &writeSet, &errorSet, &timeout); return (rc > 0) && FD_ISSET(fd, &writeSet) && !FD_ISSET(fd, &errorSet); diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h b/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h index 3b37a165..8cc5f9d4 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h @@ -1,15 +1,17 @@ #ifndef SOLIDSYSLOGPOSIXTCPSTREAMPRIVATE_H #define SOLIDSYSLOGPOSIXTCPSTREAMPRIVATE_H +#include "SolidSyslogPosixTcpStream.h" #include "SolidSyslogStreamDefinition.h" struct SolidSyslogPosixTcpStream { struct SolidSyslogStream Base; + struct SolidSyslogPosixTcpStreamConfig Config; int Fd; }; -void PosixTcpStream_Initialise(struct SolidSyslogStream* base); +void PosixTcpStream_Initialise(struct SolidSyslogStream* base, const struct SolidSyslogPosixTcpStreamConfig* config); void PosixTcpStream_Cleanup(struct SolidSyslogStream* base); #endif /* SOLIDSYSLOGPOSIXTCPSTREAMPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c b/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c index 5f29afbd..9d9cf64e 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c @@ -24,13 +24,13 @@ static struct SolidSyslogPoolAllocator PosixTcpStream_Allocator = { SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE }; -struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void) +struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(const struct SolidSyslogPosixTcpStreamConfig* config) { size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PosixTcpStream_Allocator); struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); if (SolidSyslogPoolAllocator_IndexIsValid(&PosixTcpStream_Allocator, index) == true) { - PosixTcpStream_Initialise(&PosixTcpStream_Pool[index].Base); + PosixTcpStream_Initialise(&PosixTcpStream_Pool[index].Base, config); handle = &PosixTcpStream_Pool[index].Base; } else diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h index a7590e79..0ccdcf22 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h @@ -2,14 +2,23 @@ #define SOLIDSYSLOGWINSOCKTCPSTREAM_H #include "SolidSyslogStream.h" +#include "SolidSyslogTcpConnectTimeoutFunction.h" #include "SolidSyslogTransport.h" EXTERN_C_BEGIN + struct SolidSyslogWinsockTcpStreamConfig + { + SolidSyslogTcpConnectTimeoutFunction + GetConnectTimeoutMs; /* NULL → use SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS tunable */ + void* ConnectTimeoutContext; /* passed through to GetConnectTimeoutMs; NULL is fine */ + }; + /* Precondition: caller has invoked WSAStartup() before using the stream, and will call WSACleanup() on shutdown. The library does not manage Winsock lifecycle. */ - struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(void); + struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(const struct SolidSyslogWinsockTcpStreamConfig* config + ); void SolidSyslogWinsockTcpStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c index abfe35b3..f9f2a70d 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c @@ -9,9 +9,11 @@ #include #include +#include #include "SolidSyslogNullStream.h" #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogWinsockAddressPrivate.h" #include "SolidSyslogWinsockTcpStreamInternal.h" #include "SolidSyslogWinsockTcpStreamPrivate.h" @@ -98,15 +100,6 @@ static int WSAAPI WinsockTcpStream_CallWSAGetLastError(void) enum { - /* Caps the time a single connect() attempt can stall the service thread. - Windows' default connect() to a refused loopback port retries internally - for ~2 s before returning WSAECONNREFUSED, which throttles the service - thread's drain rate during an outage and prevents the BlockStore's - discard policy from firing. Non-blocking connect + select() with this - timeout keeps each iteration bounded; on outage the next service tick - retries. 200 ms is comfortable for loopback/LAN and short enough that - 10 failing attempts cost 2 s instead of 20 s. */ - CONNECT_TIMEOUT_MILLISECONDS = 200, MILLISECONDS_PER_SECOND = 1000, MICROSECONDS_PER_MILLISECOND = 1000, /* Winsock ignores nfds (its fd_set is a literal array, not a bitmask), @@ -122,12 +115,15 @@ enum KEEPALIVE_PROBE_COUNT = 4 }; +static uint32_t WinsockTcpStream_NullConnectTimeoutGetter(void* context); + static bool WinsockTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static bool WinsockTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize WinsockTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void WinsockTcpStream_Close(struct SolidSyslogStream* base); static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromBase(struct SolidSyslogStream* base); +static inline bool WinsockTcpStream_ConfigProvidesGetter(const struct SolidSyslogWinsockTcpStreamConfig* config); static SOCKET WinsockTcpStream_OpenAndConfigureSocket(void); static bool WinsockTcpStream_ConfigureSocket(SOCKET fd); @@ -138,21 +134,46 @@ static bool WinsockTcpStream_ConnectOrCloseOnFailure( struct SolidSyslogWinsockTcpStream* self, const struct sockaddr_in* sin ); -static bool WinsockTcpStream_Connect(SOCKET fd, const struct sockaddr_in* sin); +static bool WinsockTcpStream_Connect(SOCKET fd, const struct sockaddr_in* sin, uint32_t connectTimeoutMs); static bool WinsockTcpStream_SetNonBlocking(SOCKET fd); -static bool WinsockTcpStream_WaitForConnectCompletion(SOCKET fd); +static bool WinsockTcpStream_WaitForConnectCompletion(SOCKET fd, uint32_t connectTimeoutMs); static bool WinsockTcpStream_ReadDeferredConnectError(SOCKET fd); +static uint32_t WinsockTcpStream_ResolveConnectTimeoutMs(struct SolidSyslogWinsockTcpStream* self); static bool WinsockTcpStream_WroteAllBytes(int sent, size_t expected); static inline bool WinsockTcpStream_WouldBlock(int wsaError); -void WinsockTcpStream_Initialise(struct SolidSyslogStream* base) +void WinsockTcpStream_Initialise(struct SolidSyslogStream* base, const struct SolidSyslogWinsockTcpStreamConfig* config) { + static const struct SolidSyslogWinsockTcpStream DefaultWinsockTcpStream = { + .Base = + {.Open = WinsockTcpStream_Open, + .Send = WinsockTcpStream_Send, + .Read = WinsockTcpStream_Read, + .Close = WinsockTcpStream_Close}, + .Config = {.GetConnectTimeoutMs = WinsockTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL}, + .Fd = INVALID_SOCKET, + }; + struct SolidSyslogWinsockTcpStream* self = WinsockTcpStream_SelfFromBase(base); - self->Base.Open = WinsockTcpStream_Open; - self->Base.Send = WinsockTcpStream_Send; - self->Base.Read = WinsockTcpStream_Read; - self->Base.Close = WinsockTcpStream_Close; - self->Fd = INVALID_SOCKET; + *self = DefaultWinsockTcpStream; + if (WinsockTcpStream_ConfigProvidesGetter(config) == true) + { + self->Config = *config; + } +} + +static inline bool WinsockTcpStream_ConfigProvidesGetter(const struct SolidSyslogWinsockTcpStreamConfig* config) +{ + return (config != NULL) && (config->GetConnectTimeoutMs != NULL); +} + +/* Null Object substituted when the integrator does not install a getter — + * returns the compile-time tunable so the bounded-wait path has a single + * code path regardless of whether the integrator wired runtime tuning. */ +static uint32_t WinsockTcpStream_NullConnectTimeoutGetter(void* context) +{ + (void) context; + return (uint32_t) SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS; } static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromBase(struct SolidSyslogStream* base) @@ -245,7 +266,8 @@ static bool WinsockTcpStream_ConnectOrCloseOnFailure( const struct sockaddr_in* sin ) { - bool connected = WinsockTcpStream_Connect(self->Fd, sin); + uint32_t connectTimeoutMs = WinsockTcpStream_ResolveConnectTimeoutMs(self); + bool connected = WinsockTcpStream_Connect(self->Fd, sin, connectTimeoutMs); if (!connected) { WinsockTcpStream_closesocket(self->Fd); @@ -254,6 +276,14 @@ static bool WinsockTcpStream_ConnectOrCloseOnFailure( return connected; } +/* Bridges the integrator-installed getter (or the Null Object substituted in + * Initialise) to the bounded select() deadline. Invoked on every connect + * attempt so a runtime-tunable value takes effect on the next reconnect. */ +static uint32_t WinsockTcpStream_ResolveConnectTimeoutMs(struct SolidSyslogWinsockTcpStream* self) +{ + return self->Config.GetConnectTimeoutMs(self->Config.ConnectTimeoutContext); +} + /* Non-blocking connect with bounded wait. Windows' default blocking connect() * to a refused loopback port retries internally for ~2 s before returning * WSAECONNREFUSED — slow enough that the BlockStore service thread's drain @@ -261,7 +291,7 @@ static bool WinsockTcpStream_ConnectOrCloseOnFailure( * discard policy from firing in BDD outage scenarios. The non-blocking path * bounds each connect attempt to CONNECT_TIMEOUT_MILLISECONDS; the socket * stays non-blocking thereafter so WinsockTcpStream_Send/WinsockTcpStream_Read are also fail-fast. */ -static bool WinsockTcpStream_Connect(SOCKET fd, const struct sockaddr_in* sin) +static bool WinsockTcpStream_Connect(SOCKET fd, const struct sockaddr_in* sin, uint32_t connectTimeoutMs) { bool connected = false; int rc = WinsockTcpStream_connect(fd, (const struct sockaddr*) sin, (int) sizeof(*sin)); @@ -272,7 +302,8 @@ static bool WinsockTcpStream_Connect(SOCKET fd, const struct sockaddr_in* sin) } else if (WinsockTcpStream_WSAGetLastError() == WSAEWOULDBLOCK) { - connected = WinsockTcpStream_WaitForConnectCompletion(fd) && WinsockTcpStream_ReadDeferredConnectError(fd); + connected = WinsockTcpStream_WaitForConnectCompletion(fd, connectTimeoutMs) && + WinsockTcpStream_ReadDeferredConnectError(fd); } else { @@ -287,7 +318,7 @@ static bool WinsockTcpStream_SetNonBlocking(SOCKET fd) return WinsockTcpStream_ioctlsocket(fd, (long) FIONBIO, &mode) != SOCKET_ERROR; } -static bool WinsockTcpStream_WaitForConnectCompletion(SOCKET fd) +static bool WinsockTcpStream_WaitForConnectCompletion(SOCKET fd, uint32_t connectTimeoutMs) { fd_set writeSet; FD_ZERO(&writeSet); @@ -298,8 +329,8 @@ static bool WinsockTcpStream_WaitForConnectCompletion(SOCKET fd) FD_SET(fd, &errorSet); struct timeval timeout = { - .tv_sec = CONNECT_TIMEOUT_MILLISECONDS / MILLISECONDS_PER_SECOND, - .tv_usec = (CONNECT_TIMEOUT_MILLISECONDS % MILLISECONDS_PER_SECOND) * MICROSECONDS_PER_MILLISECOND + .tv_sec = (long) (connectTimeoutMs / MILLISECONDS_PER_SECOND), + .tv_usec = (long) ((connectTimeoutMs % MILLISECONDS_PER_SECOND) * MICROSECONDS_PER_MILLISECOND) }; int rc = WinsockTcpStream_select(WINSOCK_NFDS_IGNORED, NULL, &writeSet, &errorSet, &timeout); diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h index 1396a39a..492f6822 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h @@ -4,14 +4,19 @@ #include #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogWinsockTcpStream.h" struct SolidSyslogWinsockTcpStream { struct SolidSyslogStream Base; + struct SolidSyslogWinsockTcpStreamConfig Config; SOCKET Fd; }; -void WinsockTcpStream_Initialise(struct SolidSyslogStream* base); +void WinsockTcpStream_Initialise( + struct SolidSyslogStream* base, + const struct SolidSyslogWinsockTcpStreamConfig* config +); void WinsockTcpStream_Cleanup(struct SolidSyslogStream* base); #endif /* SOLIDSYSLOGWINSOCKTCPSTREAMPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c index 41ddd04f..12957a4d 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c @@ -24,13 +24,13 @@ static struct SolidSyslogPoolAllocator WinsockTcpStream_Allocator = { SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE }; -struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(void) +struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(const struct SolidSyslogWinsockTcpStreamConfig* config) { size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WinsockTcpStream_Allocator); struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); if (SolidSyslogPoolAllocator_IndexIsValid(&WinsockTcpStream_Allocator, index) == true) { - WinsockTcpStream_Initialise(&WinsockTcpStream_Pool[index].Base); + WinsockTcpStream_Initialise(&WinsockTcpStream_Pool[index].Base, config); handle = &WinsockTcpStream_Pool[index].Base; } else diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp index 04e3720b..52992c46 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp @@ -45,6 +45,28 @@ static const size_t TEST_MESSAGE_LEN = sizeof(TEST_MESSAGE) - 1U; static const BaseType_t TEST_SHORT_WRITE_BYTES = 3; static const BaseType_t TEST_READ_BYTES = 7; +namespace +{ +int FakeGetConnectTimeoutMs_CallCount = 0; +void* FakeGetConnectTimeoutMs_LastContext = nullptr; +uint32_t FakeGetConnectTimeoutMs_ReturnValue = SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS; + +void FakeGetConnectTimeoutMs_Reset() +{ + FakeGetConnectTimeoutMs_CallCount = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- sentinel pointer; we never deref, only compare to nullptr after Open + FakeGetConnectTimeoutMs_LastContext = reinterpret_cast(0x1U); /* sentinel — overwritten on first call */ + FakeGetConnectTimeoutMs_ReturnValue = SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS; +} + +extern "C" uint32_t FakeGetConnectTimeoutMs(void* context) +{ + FakeGetConnectTimeoutMs_CallCount++; + FakeGetConnectTimeoutMs_LastContext = context; + return FakeGetConnectTimeoutMs_ReturnValue; +} +} // namespace + // clang-format off TEST_GROUP(SolidSyslogFreeRtosTcpStream) { @@ -57,7 +79,8 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) FreeRtosSocketsFake_Reset(); FreeRtosArpFake_Reset(); FreeRtosTaskFake_Reset(); - stream = SolidSyslogFreeRtosTcpStream_Create(); + FakeGetConnectTimeoutMs_Reset(); + stream = SolidSyslogFreeRtosTcpStream_Create(nullptr); addr = SolidSyslogFreeRtosAddress_Create(); struct freertos_sockaddr* sin = SolidSyslogFreeRtosAddress_AsFreertosSockaddr(addr); sin->sin_family = FREERTOS_AF_INET; @@ -83,6 +106,19 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) { return SolidSyslogStream_Read(stream, readBuffer, sizeof(readBuffer)); } + + /* Replaces the default NULL-config stream with one that uses the fake + * getter, then drives Open through the bounded-wait path. Each test sets + * only the fake-getter return value (or context) it needs different from + * the defaults restored in setup(). */ + void openStreamWithFakeGetter() + { + SolidSyslogFreeRtosTcpStream_Destroy(stream); + struct SolidSyslogFreeRtosTcpStreamConfig config = {}; + config.GetConnectTimeoutMs = FakeGetConnectTimeoutMs; + stream = SolidSyslogFreeRtosTcpStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + } }; // clang-format on @@ -153,13 +189,36 @@ TEST(SolidSyslogFreeRtosTcpStream, OpenSkipsArpProbeAndYieldOnCacheHit) TEST(SolidSyslogFreeRtosTcpStream, OpenSetsConnectTimeoutBeforeConnect) { openStream(); - LONGS_EQUAL(pdMS_TO_TICKS(200), FreeRtosSocketsFake_SndTimeoAtConnect()); + LONGS_EQUAL(pdMS_TO_TICKS(SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS), FreeRtosSocketsFake_SndTimeoAtConnect()); } TEST(SolidSyslogFreeRtosTcpStream, OpenSetsRecvTimeoutBeforeConnect) { openStream(); - LONGS_EQUAL(pdMS_TO_TICKS(200), FreeRtosSocketsFake_RcvTimeoAtConnect()); + LONGS_EQUAL(pdMS_TO_TICKS(SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS), FreeRtosSocketsFake_RcvTimeoAtConnect()); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenInvokesConfiguredConnectTimeoutGetter) +{ + openStreamWithFakeGetter(); + + LONGS_EQUAL(1, FakeGetConnectTimeoutMs_CallCount); +} + +TEST(SolidSyslogFreeRtosTcpStream, OpenUsesGetterReturnValueAsConnectTimeout) +{ + FakeGetConnectTimeoutMs_ReturnValue = 1234U; + + openStreamWithFakeGetter(); + + LONGS_EQUAL(pdMS_TO_TICKS(1234), FreeRtosSocketsFake_RcvTimeoAtConnect()); +} + +TEST(SolidSyslogFreeRtosTcpStream, GetterReceivesNullContextWhenContextNotConfigured) +{ + openStreamWithFakeGetter(); + + POINTERS_EQUAL(nullptr, FakeGetConnectTimeoutMs_LastContext); } TEST(SolidSyslogFreeRtosTcpStream, OpenCallsConnectWithSocketAndAddress) @@ -397,7 +456,7 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStreamPool) { for (auto*& slot : pooled) { - slot = SolidSyslogFreeRtosTcpStream_Create(); + slot = SolidSyslogFreeRtosTcpStream_Create(nullptr); } } }; @@ -408,7 +467,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, FillingPoolThenOverflowReturnsDistinctFal { FillPool(); - overflow = SolidSyslogFreeRtosTcpStream_Create(); + overflow = SolidSyslogFreeRtosTcpStream_Create(nullptr); CHECK_IS_FALLBACK(overflow, pooled); } @@ -418,7 +477,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, ExhaustedCreateReportsError) ErrorHandlerFake_Install(nullptr); FillPool(); - overflow = SolidSyslogFreeRtosTcpStream_Create(); + overflow = SolidSyslogFreeRtosTcpStream_Create(nullptr); CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); @@ -429,7 +488,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, ExhaustedCreateReportsError) TEST(SolidSyslogFreeRtosTcpStreamPool, FallbackVtableMethodsAreNoOps) { FillPool(); - overflow = SolidSyslogFreeRtosTcpStream_Create(); + overflow = SolidSyslogFreeRtosTcpStream_Create(nullptr); struct SolidSyslogAddress* localAddr = SolidSyslogFreeRtosAddress_Create(); char buf[8] = {0}; FreeRtosSocketsFake_Reset(); @@ -453,7 +512,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, CreateAcquiresAndReleasesConfigLockOnFirs { ConfigLockFake_Install(); - pooled[0] = SolidSyslogFreeRtosTcpStream_Create(); + pooled[0] = SolidSyslogFreeRtosTcpStream_Create(nullptr); CALLED_FAKE(ConfigLockFake_Lock, ONCE); CALLED_FAKE(ConfigLockFake_Unlock, ONCE); @@ -464,7 +523,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFul FillPool(); ConfigLockFake_Install(); - overflow = SolidSyslogFreeRtosTcpStream_Create(); + overflow = SolidSyslogFreeRtosTcpStream_Create(nullptr); LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); @@ -472,7 +531,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFul TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfPooledHandleLocksOnce) { - pooled[0] = SolidSyslogFreeRtosTcpStream_Create(); + pooled[0] = SolidSyslogFreeRtosTcpStream_Create(nullptr); ConfigLockFake_Install(); SolidSyslogFreeRtosTcpStream_Destroy(pooled[0]); @@ -508,7 +567,7 @@ TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfUnknownHandleReportsWarning) TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfStaleHandleReportsWarning) { - pooled[0] = SolidSyslogFreeRtosTcpStream_Create(); + pooled[0] = SolidSyslogFreeRtosTcpStream_Create(nullptr); SolidSyslogFreeRtosTcpStream_Destroy(pooled[0]); ErrorHandlerFake_Install(nullptr); diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 68246d53..5ac5c8c2 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -13,6 +13,7 @@ extern "C" #include "AddressFake.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTunables.h" #include "StreamFake.h" } @@ -43,6 +44,28 @@ static void NoOpSleep(int milliseconds) g_lastSleepMs = milliseconds; } +namespace +{ +int FakeGetHandshakeTimeoutMs_CallCount = 0; +void* FakeGetHandshakeTimeoutMs_LastContext = nullptr; +uint32_t FakeGetHandshakeTimeoutMs_ReturnValue = SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS; + +void FakeGetHandshakeTimeoutMs_Reset() +{ + FakeGetHandshakeTimeoutMs_CallCount = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- sentinel pointer; we never deref, only compare to nullptr after Open + FakeGetHandshakeTimeoutMs_LastContext = reinterpret_cast(0x1U); /* sentinel — overwritten on first call */ + FakeGetHandshakeTimeoutMs_ReturnValue = SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS; +} + +extern "C" uint32_t FakeGetHandshakeTimeoutMs(void* context) +{ + FakeGetHandshakeTimeoutMs_CallCount++; + FakeGetHandshakeTimeoutMs_LastContext = context; + return FakeGetHandshakeTimeoutMs_ReturnValue; +} +} // namespace + // clang-format off TEST_GROUP(SolidSyslogMbedTlsStream) { @@ -55,6 +78,7 @@ TEST_GROUP(SolidSyslogMbedTlsStream) { MbedTlsFake_Reset(); ErrorHandlerFake_Install(nullptr); + FakeGetHandshakeTimeoutMs_Reset(); NoOpSleepCallCount = 0; g_lastSleepMs = 0; transport = StreamFake_Create(); @@ -65,6 +89,17 @@ TEST_GROUP(SolidSyslogMbedTlsStream) addr = AddressFake_Get(); } + /* Replaces the default Null-getter handle with one that uses the fake + * handshake-timeout getter. Each test sets only the fake-getter return + * value (or context) it needs different from the defaults restored in + * setup(). */ + void RecreateHandleWithFakeHandshakeGetter() + { + SolidSyslogMbedTlsStream_Destroy(handle); + config.GetHandshakeTimeoutMs = FakeGetHandshakeTimeoutMs; + handle = SolidSyslogMbedTlsStream_Create(&config); + } + void teardown() override { SolidSyslogMbedTlsStream_Destroy(handle); @@ -233,6 +268,35 @@ TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenHandshakeB CHECK_OPEN_UNWOUND_WITH_ERROR(transport, MBEDTLSSTREAM_ERROR_HANDSHAKE_TIMEOUT); } +TEST(SolidSyslogMbedTlsStream, OpenInvokesConfiguredHandshakeTimeoutGetter) +{ + RecreateHandleWithFakeHandshakeGetter(); + SolidSyslogStream_Open(handle, addr); + + LONGS_EQUAL(1, FakeGetHandshakeTimeoutMs_CallCount); +} + +TEST(SolidSyslogMbedTlsStream, OpenUsesGetterReturnValueAsHandshakeBudget) +{ + /* 5 ms budget against the 1 ms poll interval → loop should sleep 5 times + before declaring HANDSHAKE_TIMEOUT and unwinding. */ + FakeGetHandshakeTimeoutMs_ReturnValue = 5U; + RecreateHandleWithFakeHandshakeGetter(); + ArrangePersistentHandshakeError(MBEDTLS_ERR_SSL_WANT_READ); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); + + LONGS_EQUAL(5, NoOpSleepCallCount); +} + +TEST(SolidSyslogMbedTlsStream, GetterReceivesNullContextWhenContextNotConfigured) +{ + RecreateHandleWithFakeHandshakeGetter(); + SolidSyslogStream_Open(handle, addr); + + POINTERS_EQUAL(nullptr, FakeGetHandshakeTimeoutMs_LastContext); +} + TEST(SolidSyslogMbedTlsStream, SecondOpenAfterFailedFirstOpenSucceeds) { /* The recovery contract that the per-failure-point unwinds enable: once diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index 8c156f8c..cedb97de 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "TestUtils.h" @@ -23,6 +24,28 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f #include "SolidSyslogTunables.h" #include "SocketFake.h" +namespace +{ +int FakeGetConnectTimeoutMs_CallCount = 0; +void* FakeGetConnectTimeoutMs_LastContext = nullptr; +uint32_t FakeGetConnectTimeoutMs_ReturnValue = 200U; + +void FakeGetConnectTimeoutMs_Reset() +{ + FakeGetConnectTimeoutMs_CallCount = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- sentinel pointer; we never deref, only compare to nullptr after Open + FakeGetConnectTimeoutMs_LastContext = reinterpret_cast(0x1U); /* sentinel — overwritten on first call */ + FakeGetConnectTimeoutMs_ReturnValue = 200U; +} + +extern "C" uint32_t FakeGetConnectTimeoutMs(void* context) +{ + FakeGetConnectTimeoutMs_CallCount++; + FakeGetConnectTimeoutMs_LastContext = context; + return FakeGetConnectTimeoutMs_ReturnValue; +} +} // namespace + // clang-format off static const char* const TEST_MESSAGE = "hello"; static const size_t TEST_MESSAGE_LEN = 5; @@ -39,8 +62,9 @@ TEST_GROUP(SolidSyslogPosixTcpStream) void setup() override { SocketFake_Reset(); + FakeGetConnectTimeoutMs_Reset(); // cppcheck-suppress unreadVariable -- used in tests; cppcheck does not model CppUTest macros - stream = SolidSyslogPosixTcpStream_Create(); + stream = SolidSyslogPosixTcpStream_Create(nullptr); addr = SolidSyslogPosixAddress_Create(); struct sockaddr_in* sin = SolidSyslogPosixAddress_AsSockaddrIn(addr); sin->sin_family = AF_INET; @@ -59,6 +83,21 @@ TEST_GROUP(SolidSyslogPosixTcpStream) char buf[16]; return SolidSyslogStream_Read(stream, buf, sizeof(buf)); } + + /* Replaces the default NULL-config stream with one that uses the fake + * getter, then drives Open through the bounded-wait path. Each test sets + * only the fake-getter return value (or context) it needs different from + * the defaults restored in setup(). */ + void OpenStreamWithFakeGetter() + { + SolidSyslogPosixTcpStream_Destroy(stream); + struct SolidSyslogPosixTcpStreamConfig config = {}; + config.GetConnectTimeoutMs = FakeGetConnectTimeoutMs; + stream = SolidSyslogPosixTcpStream_Create(&config); + + SocketFake_SetConnectFailsWithErrno(EINPROGRESS); + SolidSyslogStream_Open(stream, addr); + } }; // clang-format on @@ -399,11 +438,36 @@ TEST(SolidSyslogPosixTcpStream, OpenPassesBoundedConnectTimeoutToSelect) { SocketFake_SetConnectFailsWithErrno(EINPROGRESS); SolidSyslogStream_Open(stream, addr); - /* CONNECT_TIMEOUT_MICROSECONDS = 200 000 → 0 s + 200 000 µs. */ + /* Default tunable SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS = 200 → 0 s + 200 000 µs. */ LONGS_EQUAL(0, SocketFake_LastSelectTimeoutSec()); LONGS_EQUAL(200000, SocketFake_LastSelectTimeoutUsec()); } +TEST(SolidSyslogPosixTcpStream, OpenInvokesConfiguredConnectTimeoutGetter) +{ + OpenStreamWithFakeGetter(); + + LONGS_EQUAL(1, FakeGetConnectTimeoutMs_CallCount); +} + +TEST(SolidSyslogPosixTcpStream, OpenUsesGetterReturnValueAsConnectTimeout) +{ + FakeGetConnectTimeoutMs_ReturnValue = 1234U; + + OpenStreamWithFakeGetter(); + + /* 1234 ms = 1 s + 234 000 µs. */ + LONGS_EQUAL(1, SocketFake_LastSelectTimeoutSec()); + LONGS_EQUAL(234000, SocketFake_LastSelectTimeoutUsec()); +} + +TEST(SolidSyslogPosixTcpStream, GetterReceivesNullContextWhenContextNotConfigured) +{ + OpenStreamWithFakeGetter(); + + POINTERS_EQUAL(nullptr, FakeGetConnectTimeoutMs_LastContext); +} + TEST(SolidSyslogPosixTcpStream, OpenSucceedsWhenSelectReportsWritableAndZeroSO_ERROR) { SocketFake_SetConnectFailsWithErrno(EINPROGRESS); @@ -549,7 +613,7 @@ TEST_GROUP(SolidSyslogPosixTcpStreamPool) { for (auto*& slot : pooled) { - slot = SolidSyslogPosixTcpStream_Create(); + slot = SolidSyslogPosixTcpStream_Create(nullptr); } } }; @@ -560,7 +624,7 @@ TEST(SolidSyslogPosixTcpStreamPool, FillingPoolThenOverflowReturnsDistinctFallba { FillPool(); - overflow = SolidSyslogPosixTcpStream_Create(); + overflow = SolidSyslogPosixTcpStream_Create(nullptr); CHECK_IS_FALLBACK(overflow, pooled); } @@ -570,7 +634,7 @@ TEST(SolidSyslogPosixTcpStreamPool, ExhaustedCreateReportsError) ErrorHandlerFake_Install(nullptr); FillPool(); - overflow = SolidSyslogPosixTcpStream_Create(); + overflow = SolidSyslogPosixTcpStream_Create(nullptr); CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); @@ -581,7 +645,7 @@ TEST(SolidSyslogPosixTcpStreamPool, ExhaustedCreateReportsError) TEST(SolidSyslogPosixTcpStreamPool, FallbackSendReturnsTrue) { FillPool(); - overflow = SolidSyslogPosixTcpStream_Create(); + overflow = SolidSyslogPosixTcpStream_Create(nullptr); CHECK_TRUE(SolidSyslogStream_Send(overflow, "x", 1)); } @@ -590,7 +654,7 @@ TEST(SolidSyslogPosixTcpStreamPool, CreateAcquiresAndReleasesConfigLockOnFirstFr { ConfigLockFake_Install(); - pooled[0] = SolidSyslogPosixTcpStream_Create(); + pooled[0] = SolidSyslogPosixTcpStream_Create(nullptr); CALLED_FAKE(ConfigLockFake_Lock, ONCE); CALLED_FAKE(ConfigLockFake_Unlock, ONCE); @@ -601,7 +665,7 @@ TEST(SolidSyslogPosixTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) FillPool(); ConfigLockFake_Install(); - overflow = SolidSyslogPosixTcpStream_Create(); + overflow = SolidSyslogPosixTcpStream_Create(nullptr); LONGS_EQUAL(SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); LONGS_EQUAL(SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); @@ -609,7 +673,7 @@ TEST(SolidSyslogPosixTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) TEST(SolidSyslogPosixTcpStreamPool, DestroyOfPooledHandleLocksOnce) { - pooled[0] = SolidSyslogPosixTcpStream_Create(); + pooled[0] = SolidSyslogPosixTcpStream_Create(nullptr); ConfigLockFake_Install(); SolidSyslogPosixTcpStream_Destroy(pooled[0]); @@ -645,7 +709,7 @@ TEST(SolidSyslogPosixTcpStreamPool, DestroyOfUnknownHandleReportsWarning) TEST(SolidSyslogPosixTcpStreamPool, DestroyOfStaleHandleReportsWarning) { - pooled[0] = SolidSyslogPosixTcpStream_Create(); + pooled[0] = SolidSyslogPosixTcpStream_Create(nullptr); SolidSyslogPosixTcpStream_Destroy(pooled[0]); ErrorHandlerFake_Install(nullptr); diff --git a/Tests/SolidSyslogStreamSenderTest.cpp b/Tests/SolidSyslogStreamSenderTest.cpp index d53494fd..2797551f 100644 --- a/Tests/SolidSyslogStreamSenderTest.cpp +++ b/Tests/SolidSyslogStreamSenderTest.cpp @@ -102,7 +102,7 @@ TEST_GROUP(SolidSyslogStreamSender) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(); + stream = SolidSyslogPosixTcpStream_Create(nullptr); address = SolidSyslogPosixAddress_Create(); config = {resolver, stream, address, TestEndpoint, TestEndpointVersion}; // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle @@ -164,7 +164,7 @@ TEST_GROUP(SolidSyslogStreamSenderDestroy) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(); + stream = SolidSyslogPosixTcpStream_Create(nullptr); address = SolidSyslogPosixAddress_Create(); // cppcheck-suppress unreadVariable -- used in test bodies; cppcheck does not model CppUTest macros config = {resolver, stream, address, TestEndpoint, TestEndpointVersion}; @@ -372,7 +372,7 @@ TEST_GROUP(SolidSyslogStreamSenderConfig) endpointGetHost = getHostFn; endpointGetPort = getPortFn; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(); + stream = SolidSyslogPosixTcpStream_Create(nullptr); address = SolidSyslogPosixAddress_Create(); struct SolidSyslogStreamSenderConfig config = {resolver, stream, address, TestEndpoint, TestEndpointVersion}; // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle @@ -465,7 +465,7 @@ TEST_GROUP(SolidSyslogStreamSenderFailure) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(); + stream = SolidSyslogPosixTcpStream_Create(nullptr); address = SolidSyslogPosixAddress_Create(); config = {resolver, stream, address, TestEndpoint, TestEndpointVersion}; // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle @@ -670,7 +670,7 @@ TEST_GROUP(SolidSyslogStreamSenderPool) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(); + stream = SolidSyslogPosixTcpStream_Create(nullptr); address = SolidSyslogPosixAddress_Create(); // cppcheck-suppress unreadVariable -- read by MakeSender; cppcheck does not model CppUTest macros config = {resolver, stream, address, TestEndpoint, TestEndpointVersion}; diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index 56322bb5..b5b78654 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "ErrorHandlerFake.h" #include "OpenSslFake.h" @@ -12,6 +13,7 @@ #include "SolidSyslogTlsStream.h" #include "SolidSyslogTlsStreamErrors.h" #include "SolidSyslogTransport.h" +#include "SolidSyslogTunables.h" #include "StreamFake.h" #include "TestUtils.h" #include "CppUTest/TestHarness.h" @@ -44,6 +46,28 @@ static void NoOpSleep(int milliseconds) g_lastSleepMs = milliseconds; } +namespace +{ +int FakeGetHandshakeTimeoutMs_CallCount = 0; +void* FakeGetHandshakeTimeoutMs_LastContext = nullptr; +uint32_t FakeGetHandshakeTimeoutMs_ReturnValue = SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS; + +void FakeGetHandshakeTimeoutMs_Reset() +{ + FakeGetHandshakeTimeoutMs_CallCount = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- sentinel pointer; we never deref, only compare to nullptr after Open + FakeGetHandshakeTimeoutMs_LastContext = reinterpret_cast(0x1U); /* sentinel — overwritten on first call */ + FakeGetHandshakeTimeoutMs_ReturnValue = SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS; +} + +extern "C" uint32_t FakeGetHandshakeTimeoutMs(void* context) +{ + FakeGetHandshakeTimeoutMs_CallCount++; + FakeGetHandshakeTimeoutMs_LastContext = context; + return FakeGetHandshakeTimeoutMs_ReturnValue; +} +} // namespace + // clang-format off TEST_GROUP(SolidSyslogTlsStream) { @@ -56,6 +80,7 @@ TEST_GROUP(SolidSyslogTlsStream) { OpenSslFake_Reset(); ErrorHandlerFake_Install(nullptr); + FakeGetHandshakeTimeoutMs_Reset(); NoOpSleepCallCount = 0; g_lastSleepMs = 0; transport = StreamFake_Create(); @@ -67,6 +92,17 @@ TEST_GROUP(SolidSyslogTlsStream) addr = AddressFake_Get(); } + /* Replaces the default Null-getter stream with one that uses the fake + * handshake-timeout getter. Each test sets only the fake-getter return + * value (or context) it needs different from the defaults restored in + * setup(). */ + void RecreateStreamWithFakeHandshakeGetter() + { + SolidSyslogTlsStream_Destroy(stream); + config.GetHandshakeTimeoutMs = FakeGetHandshakeTimeoutMs; + stream = SolidSyslogTlsStream_Create(&config); + } + void teardown() override { SolidSyslogTlsStream_Destroy(stream); @@ -1069,6 +1105,35 @@ TEST(SolidSyslogTlsStream, OpenFailsWhenHandshakeNeverCompletes) CHECK_OPEN_UNWOUND_WITH_ERROR(transport, TLSSTREAM_ERROR_HANDSHAKE_TIMEOUT); } +TEST(SolidSyslogTlsStream, OpenInvokesConfiguredHandshakeTimeoutGetter) +{ + RecreateStreamWithFakeHandshakeGetter(); + SolidSyslogStream_Open(stream, addr); + + LONGS_EQUAL(1, FakeGetHandshakeTimeoutMs_CallCount); +} + +TEST(SolidSyslogTlsStream, OpenUsesGetterReturnValueAsHandshakeBudget) +{ + /* 5 ms budget against the 1 ms poll interval → loop should sleep 5 times + before declaring HANDSHAKE_TIMEOUT and unwinding. */ + FakeGetHandshakeTimeoutMs_ReturnValue = 5U; + RecreateStreamWithFakeHandshakeGetter(); + ArrangePersistentHandshakeError(SSL_ERROR_WANT_READ); + + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); + + LONGS_EQUAL(5, NoOpSleepCallCount); +} + +TEST(SolidSyslogTlsStream, GetterReceivesNullContextWhenContextNotConfigured) +{ + RecreateStreamWithFakeHandshakeGetter(); + SolidSyslogStream_Open(stream, addr); + + POINTERS_EQUAL(nullptr, FakeGetHandshakeTimeoutMs_LastContext); +} + TEST(SolidSyslogTlsStream, OpenFailsImmediatelyOnHardSslError) { /* Non-WANT error (e.g. SSL_ERROR_SSL) is fail-fast — no retry budget burn. */ diff --git a/Tests/SolidSyslogWinsockTcpStreamTest.cpp b/Tests/SolidSyslogWinsockTcpStreamTest.cpp index 4efee202..7e8452a8 100644 --- a/Tests/SolidSyslogWinsockTcpStreamTest.cpp +++ b/Tests/SolidSyslogWinsockTcpStreamTest.cpp @@ -36,6 +36,28 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f // NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) +namespace +{ +int FakeGetConnectTimeoutMs_CallCount = 0; +void* FakeGetConnectTimeoutMs_LastContext = nullptr; +uint32_t FakeGetConnectTimeoutMs_ReturnValue = 200U; + +void FakeGetConnectTimeoutMs_Reset() +{ + FakeGetConnectTimeoutMs_CallCount = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- sentinel pointer; we never deref, only compare to nullptr after Open + FakeGetConnectTimeoutMs_LastContext = reinterpret_cast(0x1U); /* sentinel — overwritten on first call */ + FakeGetConnectTimeoutMs_ReturnValue = 200U; +} + +extern "C" uint32_t FakeGetConnectTimeoutMs(void* context) +{ + FakeGetConnectTimeoutMs_CallCount++; + FakeGetConnectTimeoutMs_LastContext = context; + return FakeGetConnectTimeoutMs_ReturnValue; +} +} // namespace + // clang-format off static const char* const TEST_MESSAGE = "hello"; static const size_t TEST_MESSAGE_LEN = 5; @@ -52,6 +74,7 @@ TEST_GROUP(SolidSyslogWinsockTcpStream) void setup() override { WinsockFake_Reset(); + FakeGetConnectTimeoutMs_Reset(); UT_PTR_SET(WinsockTcpStream_socket, WinsockFake_socket); UT_PTR_SET(WinsockTcpStream_connect, WinsockFake_connect); UT_PTR_SET(WinsockTcpStream_send, WinsockFake_send); @@ -63,7 +86,7 @@ TEST_GROUP(SolidSyslogWinsockTcpStream) UT_PTR_SET(WinsockTcpStream_select, WinsockFake_select); UT_PTR_SET(WinsockTcpStream_WSAGetLastError, WinsockFake_WSAGetLastError); // cppcheck-suppress unreadVariable -- used in tests; cppcheck does not model CppUTest macros - stream = SolidSyslogWinsockTcpStream_Create(); + stream = SolidSyslogWinsockTcpStream_Create(nullptr); addr = SolidSyslogWinsockAddress_Create(); struct sockaddr_in* sin = SolidSyslogWinsockAddress_AsSockaddrIn(addr); sin->sin_family = AF_INET; @@ -82,6 +105,21 @@ TEST_GROUP(SolidSyslogWinsockTcpStream) char buf[16]; return SolidSyslogStream_Read(stream, buf, sizeof(buf)); } + + /* Replaces the default NULL-config stream with one that uses the fake + * getter, then drives Open through the bounded-wait path. Each test sets + * only the fake-getter return value (or context) it needs different from + * the defaults restored in setup(). */ + void OpenStreamWithFakeGetter() + { + SolidSyslogWinsockTcpStream_Destroy(stream); + struct SolidSyslogWinsockTcpStreamConfig config = {}; + config.GetConnectTimeoutMs = FakeGetConnectTimeoutMs; + stream = SolidSyslogWinsockTcpStream_Create(&config); + + WinsockFake_SetConnectFailsWithLastError(WSAEWOULDBLOCK); + SolidSyslogStream_Open(stream, addr); + } }; // clang-format on @@ -233,12 +271,36 @@ TEST(SolidSyslogWinsockTcpStream, OpenPassesBoundedConnectTimeoutToSelect) { WinsockFake_SetConnectFailsWithLastError(WSAEWOULDBLOCK); SolidSyslogStream_Open(stream, addr); - /* CONNECT_TIMEOUT_MILLISECONDS = 200 → 0 s + 200 000 µs. Bounding the - wait is the whole point of the non-blocking-connect rewrite. */ + /* Default tunable SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS = 200 → 0 s + 200 000 µs. */ LONGS_EQUAL(0, WinsockFake_LastSelectTimeoutSec()); LONGS_EQUAL(200000, WinsockFake_LastSelectTimeoutUsec()); } +TEST(SolidSyslogWinsockTcpStream, OpenInvokesConfiguredConnectTimeoutGetter) +{ + OpenStreamWithFakeGetter(); + + LONGS_EQUAL(1, FakeGetConnectTimeoutMs_CallCount); +} + +TEST(SolidSyslogWinsockTcpStream, OpenUsesGetterReturnValueAsConnectTimeout) +{ + FakeGetConnectTimeoutMs_ReturnValue = 1234U; + + OpenStreamWithFakeGetter(); + + /* 1234 ms = 1 s + 234 000 µs. */ + LONGS_EQUAL(1, WinsockFake_LastSelectTimeoutSec()); + LONGS_EQUAL(234000, WinsockFake_LastSelectTimeoutUsec()); +} + +TEST(SolidSyslogWinsockTcpStream, GetterReceivesNullContextWhenContextNotConfigured) +{ + OpenStreamWithFakeGetter(); + + POINTERS_EQUAL(nullptr, FakeGetConnectTimeoutMs_LastContext); +} + TEST(SolidSyslogWinsockTcpStream, OpenSucceedsWhenSelectReportsWritableAndZeroSO_ERROR) { WinsockFake_SetConnectFailsWithLastError(WSAEWOULDBLOCK); @@ -507,7 +569,7 @@ TEST_GROUP(SolidSyslogWinsockTcpStreamPool) { for (auto*& slot : pooled) { - slot = SolidSyslogWinsockTcpStream_Create(); + slot = SolidSyslogWinsockTcpStream_Create(nullptr); } } }; @@ -518,7 +580,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, FillingPoolThenOverflowReturnsDistinctFall { FillPool(); - overflow = SolidSyslogWinsockTcpStream_Create(); + overflow = SolidSyslogWinsockTcpStream_Create(nullptr); CHECK_IS_FALLBACK(overflow, pooled); } @@ -528,7 +590,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, ExhaustedCreateReportsError) ErrorHandlerFake_Install(nullptr); FillPool(); - overflow = SolidSyslogWinsockTcpStream_Create(); + overflow = SolidSyslogWinsockTcpStream_Create(nullptr); CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); @@ -539,7 +601,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, ExhaustedCreateReportsError) TEST(SolidSyslogWinsockTcpStreamPool, FallbackSendIsNoOp) { FillPool(); - overflow = SolidSyslogWinsockTcpStream_Create(); + overflow = SolidSyslogWinsockTcpStream_Create(nullptr); CHECK_TRUE(SolidSyslogStream_Send(overflow, "x", 1)); } @@ -548,7 +610,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, CreateAcquiresAndReleasesConfigLockOnFirst { ConfigLockFake_Install(); - pooled[0] = SolidSyslogWinsockTcpStream_Create(); + pooled[0] = SolidSyslogWinsockTcpStream_Create(nullptr); CALLED_FAKE(ConfigLockFake_Lock, ONCE); CALLED_FAKE(ConfigLockFake_Unlock, ONCE); @@ -559,7 +621,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull FillPool(); ConfigLockFake_Install(); - overflow = SolidSyslogWinsockTcpStream_Create(); + overflow = SolidSyslogWinsockTcpStream_Create(nullptr); LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); @@ -567,7 +629,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfPooledHandleLocksOnce) { - pooled[0] = SolidSyslogWinsockTcpStream_Create(); + pooled[0] = SolidSyslogWinsockTcpStream_Create(nullptr); ConfigLockFake_Install(); SolidSyslogWinsockTcpStream_Destroy(pooled[0]); @@ -603,7 +665,7 @@ TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfUnknownHandleReportsWarning) TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfStaleHandleReportsWarning) { - pooled[0] = SolidSyslogWinsockTcpStream_Create(); + pooled[0] = SolidSyslogWinsockTcpStream_Create(nullptr); SolidSyslogWinsockTcpStream_Destroy(pooled[0]); ErrorHandlerFake_Install(nullptr); diff --git a/misra_suppressions.txt b/misra_suppressions.txt index d0bd263f..97ba1e11 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -43,9 +43,9 @@ misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosAddressStatic.c:57 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c:47 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c:40 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosResolver.c:44 -misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c:85 -misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:64 -misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:74 +misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c:117 +misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:94 +misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:82 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddress.c:10 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:20 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:26 @@ -55,7 +55,7 @@ misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixDatagram.c:55 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixFile.c:66 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:129 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMutex.c:47 -misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:75 +misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:100 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddress.c:9 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddressPrivate.h:20 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddressPrivate.h:28 @@ -65,13 +65,13 @@ misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c:29 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsFile.c:73 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsMutex.c:34 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:94 -misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:160 +misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:181 misra-c2012-11.5:Core/Source/SolidSyslogUdpSender.c:205 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:240 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:252 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:271 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:283 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:138 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:322 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:342 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:353 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:373 # D.003 — Rule 5.7: repeating struct tags (no-typedef-struct convention) # See docs/misra-deviations.md#d003 @@ -93,7 +93,7 @@ misra-c2012-11.8:Core/Source/SolidSyslog.c:99 misra-c2012-11.8:Core/Source/SolidSyslog.c:100 misra-c2012-11.8:Core/Source/SolidSyslog.c:101 misra-c2012-11.8:Core/Source/SolidSyslogBlockStoreStatic.c:40 -misra-c2012-11.8:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:91 +misra-c2012-11.8:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:93 # D.007 — Rule 21.10: transitive via # See docs/misra-deviations.md#d007 @@ -117,15 +117,15 @@ misra-c2012-5.7:Core/Interface/SolidSyslogUdpPayload.h:15 misra-c2012-5.7:Core/Source/SolidSyslogStreamSender.c:19 misra-c2012-5.7:Core/Source/SolidSyslogUdpPayload.c:5 misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosResolver.c:21 -misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c:27 -misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:18 -misra-c2012-5.7:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:20 +misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c:28 +misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:19 +misra-c2012-5.7:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:21 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c:20 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixDatagram.c:21 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixSleep.c:6 -misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:24 +misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:25 misra-c2012-5.7:Platform/Windows/Source/SolidSyslogWinsockResolver.c:37 -misra-c2012-5.7:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:100 +misra-c2012-5.7:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:102 misra-c2012-5.7:Core/Source/BlockSequence.c:13 misra-c2012-5.7:Core/Source/BlockSequence.c:107 misra-c2012-5.7:Core/Source/RecordStore.c:14 @@ -169,5 +169,5 @@ misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:20 # D.013 — Rule 11.5: void* ↔ unsigned char* at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:277 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:296 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:308 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:327