From 1cdb72f5953d907120cf7de850c1a6065dc741bd Mon Sep 17 00:00:00 2001 From: David Cozens Date: Wed, 9 Sep 2026 14:44:49 +0100 Subject: [PATCH 1/2] feat: S39 detect a TLS configuration change from the stream Adds a mandatory Version member to the SolidSyslogStream vtable and a Version / VersionContext pair to both TLS stream configs. The stream sender polls it every Send alongside the endpoint version and reconnects when either has moved, so rotating credentials or repinning a peer is a counter bump from any task rather than a Disconnect call that races the servicing pass. Narrows the SolidSyslogSender_Disconnect contract to the servicing thread and moves both rotation passages in docs/tls.md onto the version. --- Core/Interface/SolidSyslogSender.h | 5 +- Core/Interface/SolidSyslogStream.h | 12 ++++ Core/Interface/SolidSyslogStreamDefinition.h | 6 ++ Core/Source/SolidSyslogNullStream.c | 9 +++ Core/Source/SolidSyslogStream.c | 6 ++ Core/Source/SolidSyslogStreamSender.c | 23 +++++++- Core/Source/SolidSyslogStreamSenderPrivate.h | 1 + .../Source/SolidSyslogLwipRawTcpStream.c | 10 +++- .../Interface/SolidSyslogMbedTlsStream.h | 8 +++ .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 29 ++++++++++ .../Interface/SolidSyslogOpenSslStream.h | 9 +++ .../OpenSsl/Source/SolidSyslogOpenSslStream.c | 29 ++++++++++ .../Source/SolidSyslogPlusTcpTcpStream.c | 10 +++- .../Posix/Source/SolidSyslogPosixTcpStream.c | 10 +++- .../Source/SolidSyslogWinsockTcpStream.c | 10 +++- .../SolidSyslogPlusTcpTcpStreamTest.cpp | 5 ++ .../Lwip/SolidSyslogLwipRawTcpStreamTest.cpp | 5 ++ .../MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 44 ++++++++++++++ Tests/MbedTlsIntegration/SocketStream.c | 9 +++ Tests/OpenSslIntegration/BioPairStream.c | 9 +++ Tests/SolidSyslogNullStreamTest.cpp | 5 ++ Tests/SolidSyslogOpenSslStreamTest.cpp | 44 ++++++++++++++ Tests/SolidSyslogPosixTcpStreamTest.cpp | 5 ++ Tests/SolidSyslogStreamSenderTest.cpp | 58 +++++++++++++++++++ Tests/SolidSyslogWinsockTcpStreamTest.cpp | 5 ++ Tests/StreamFake.c | 13 +++++ Tests/StreamFake.h | 2 + docs/platforms/mbedtls/index.md | 17 ++++-- docs/platforms/openssl/index.md | 4 +- docs/tls.md | 38 +++++++++--- misra_suppressions.txt | 32 +++++----- 31 files changed, 433 insertions(+), 39 deletions(-) diff --git a/Core/Interface/SolidSyslogSender.h b/Core/Interface/SolidSyslogSender.h index 3f19751a..1cdb84b2 100644 --- a/Core/Interface/SolidSyslogSender.h +++ b/Core/Interface/SolidSyslogSender.h @@ -22,7 +22,10 @@ SOLIDSYSLOG_EXTERN_C_BEGIN * first use, which can block on the transport. @p buffer is read only during the call. */ bool SolidSyslogSender_Send(struct SolidSyslogSender * sender, const void* buffer, size_t size); - /** Drop the connection; the next Send reconnects. Idempotent. */ + /** Drop the connection; the next Send reconnects. Idempotent. Touches the + * same connection state as Send and takes no lock, so call it from the + * servicing thread. From any other thread, move the version the sender polls + * instead - the endpoint's or the stream's - and the next Send reconnects. */ void SolidSyslogSender_Disconnect(struct SolidSyslogSender * sender); SOLIDSYSLOG_EXTERN_C_END diff --git a/Core/Interface/SolidSyslogStream.h b/Core/Interface/SolidSyslogStream.h index 635baff4..7705bac5 100644 --- a/Core/Interface/SolidSyslogStream.h +++ b/Core/Interface/SolidSyslogStream.h @@ -59,6 +59,18 @@ SOLIDSYSLOG_EXTERN_C_BEGIN * a later Open reconnects. */ void SolidSyslogStream_Close(struct SolidSyslogStream * stream); + /** Returns a monotonic version the integrator bumps when the stream's + * configuration changes - the material it presents, the peer it will accept. + * A stream implementation polls this to answer SolidSyslogStream_Version, so + * it must be cheap and pure. @p context is the paired context field on that + * stream's config, passed through unchanged. */ + typedef uint32_t (*SolidSyslogStreamVersionFunction)(void* context); + + /** The stream's current configuration version. The sender polls this every + * Send and reconnects when it has moved, so a stream whose configuration + * cannot change at runtime reports 0 throughout. */ + uint32_t SolidSyslogStream_Version(struct SolidSyslogStream * stream); + SOLIDSYSLOG_EXTERN_C_END #endif /* SOLIDSYSLOGSTREAM_H */ diff --git a/Core/Interface/SolidSyslogStreamDefinition.h b/Core/Interface/SolidSyslogStreamDefinition.h index 82edaebf..a1477728 100644 --- a/Core/Interface/SolidSyslogStreamDefinition.h +++ b/Core/Interface/SolidSyslogStreamDefinition.h @@ -45,6 +45,12 @@ SOLIDSYSLOG_EXTERN_C_BEGIN * reconnects it. Called on a stream that is already closed, on one that * never opened, and again from Destroy. */ void (*Close)(struct SolidSyslogStream* base); + /** Report a monotonic version of this stream's own configuration - what it + * will present or accept on the next Open. Bump it when that changes and + * the sender reconnects on its next pass. Polled every Send, so it must be + * cheap and pure. A stream with nothing to change reports 0 for its + * lifetime. */ + uint32_t (*Version)(struct SolidSyslogStream* base); }; SOLIDSYSLOG_EXTERN_C_END diff --git a/Core/Source/SolidSyslogNullStream.c b/Core/Source/SolidSyslogNullStream.c index b2da379b..c36374c7 100644 --- a/Core/Source/SolidSyslogNullStream.c +++ b/Core/Source/SolidSyslogNullStream.c @@ -6,6 +6,7 @@ #include #include +#include #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" @@ -16,6 +17,7 @@ static bool NullStream_Open(struct SolidSyslogStream* base, const struct SolidSy static bool NullStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize NullStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void NullStream_Close(struct SolidSyslogStream* base); +static uint32_t NullStream_Version(struct SolidSyslogStream* base); struct SolidSyslogStream* SolidSyslogNullStream_Get(void) { @@ -24,6 +26,7 @@ struct SolidSyslogStream* SolidSyslogNullStream_Get(void) .Send = NullStream_Send, .Read = NullStream_Read, .Close = NullStream_Close, + .Version = NullStream_Version, }; return &instance; } @@ -60,3 +63,9 @@ static void NullStream_Close(struct SolidSyslogStream* base) { (void) base; } + +static uint32_t NullStream_Version(struct SolidSyslogStream* base) +{ + (void) base; + return 0; +} diff --git a/Core/Source/SolidSyslogStream.c b/Core/Source/SolidSyslogStream.c index f4a732b7..8fec3801 100644 --- a/Core/Source/SolidSyslogStream.c +++ b/Core/Source/SolidSyslogStream.c @@ -4,6 +4,7 @@ #include #include +#include #include "SolidSyslogStreamDefinition.h" #include "SolidSyslogStream.h" @@ -29,3 +30,8 @@ void SolidSyslogStream_Close(struct SolidSyslogStream* stream) { stream->Close(stream); } + +uint32_t SolidSyslogStream_Version(struct SolidSyslogStream* stream) +{ + return stream->Version(stream); +} diff --git a/Core/Source/SolidSyslogStreamSender.c b/Core/Source/SolidSyslogStreamSender.c index 69227417..9e3f64c1 100644 --- a/Core/Source/SolidSyslogStreamSender.c +++ b/Core/Source/SolidSyslogStreamSender.c @@ -42,6 +42,11 @@ static inline struct SolidSyslogStreamSender* StreamSender_SelfFromBase(struct S static inline bool StreamSender_Reconcile(struct SolidSyslogStreamSender* self); static inline void StreamSender_DisconnectIfStale(struct SolidSyslogStreamSender* self); +static inline bool StreamSender_ConfigurationMoved( + const struct SolidSyslogStreamSender* self, + uint32_t endpointVersion, + uint32_t streamVersion +); static inline bool StreamSender_EnsureConnected(struct SolidSyslogStreamSender* self); static inline bool StreamSender_Connected(struct SolidSyslogStreamSender* self); static bool StreamSender_Connect(struct SolidSyslogStreamSender* self); @@ -74,6 +79,7 @@ void SolidSyslogStreamSender_Initialise( self->Connected = false; self->DeliveryHealthy = true; self->LastEndpointVersion = 0; + self->LastStreamVersion = 0; } void SolidSyslogStreamSender_Cleanup(struct SolidSyslogSender* base) @@ -112,15 +118,26 @@ static inline bool StreamSender_Reconcile(struct SolidSyslogStreamSender* self) static inline void StreamSender_DisconnectIfStale(struct SolidSyslogStreamSender* self) { - uint32_t version = self->Config.EndpointVersion(self->Config.EndpointContext); + uint32_t endpointVersion = self->Config.EndpointVersion(self->Config.EndpointContext); + uint32_t streamVersion = SolidSyslogStream_Version(self->Config.Stream); - if (version != self->LastEndpointVersion) + if (StreamSender_ConfigurationMoved(self, endpointVersion, streamVersion)) { StreamSender_Disconnect(&self->Base); - self->LastEndpointVersion = version; + self->LastEndpointVersion = endpointVersion; + self->LastStreamVersion = streamVersion; } } +static inline bool StreamSender_ConfigurationMoved( + const struct SolidSyslogStreamSender* self, + uint32_t endpointVersion, + uint32_t streamVersion +) +{ + return (endpointVersion != self->LastEndpointVersion) || (streamVersion != self->LastStreamVersion); +} + static inline bool StreamSender_EnsureConnected(struct SolidSyslogStreamSender* self) { return StreamSender_Connected(self) || StreamSender_Connect(self); diff --git a/Core/Source/SolidSyslogStreamSenderPrivate.h b/Core/Source/SolidSyslogStreamSenderPrivate.h index d8f26a7d..49f84f08 100644 --- a/Core/Source/SolidSyslogStreamSenderPrivate.h +++ b/Core/Source/SolidSyslogStreamSenderPrivate.h @@ -21,6 +21,7 @@ struct SolidSyslogStreamSender bool Connected; bool DeliveryHealthy; uint32_t LastEndpointVersion; + uint32_t LastStreamVersion; }; void SolidSyslogStreamSender_Initialise( diff --git a/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c b/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c index 5e7f7771..721093b9 100644 --- a/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c +++ b/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c @@ -57,6 +57,7 @@ static bool LwipRawTcpStream_Open(struct SolidSyslogStream* base, const struct S static bool LwipRawTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize LwipRawTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void LwipRawTcpStream_Close(struct SolidSyslogStream* base); +static uint32_t LwipRawTcpStream_Version(struct SolidSyslogStream* base); static inline struct SolidSyslogLwipRawTcpStream* LwipRawTcpStream_SelfFromBase(struct SolidSyslogStream* base); static inline struct SolidSyslogLwipRawTcpStream* LwipRawTcpStream_SelfFromArg(void* arg); @@ -102,7 +103,8 @@ void SolidSyslogLwipRawTcpStream_Initialise( {.Open = LwipRawTcpStream_Open, .Send = LwipRawTcpStream_Send, .Read = LwipRawTcpStream_Read, - .Close = LwipRawTcpStream_Close}, + .Close = LwipRawTcpStream_Close, + .Version = LwipRawTcpStream_Version}, .Config = {.GetConnectTimeoutMs = LwipRawTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL, @@ -460,6 +462,12 @@ static void LwipRawTcpStream_Close(struct SolidSyslogStream* base) } } +static uint32_t LwipRawTcpStream_Version(struct SolidSyslogStream* base) +{ + (void) base; + return 0; +} + /* Close touches lwIP only if there is a pcb to close or queued pbufs to * free. Close-before-open and close-after-tcp_err (Pcb already nulled, queue * empty) do no lwIP work and take no marshal hop. */ diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index 56defa30..445b08c6 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -40,6 +40,7 @@ #include "SolidSyslogExternC.h" #include "SolidSyslogSleep.h" +#include "SolidSyslogStream.h" #include "SolidSyslogTlsHandshakeTimeoutFunction.h" struct SolidSyslogStream; @@ -82,6 +83,13 @@ SOLIDSYSLOG_EXTERN_C_BEGIN * private CA): the peer must still satisfy whatever the credentials * installed, but the endpoint identity is not checked; no diagnostic. */ const char* ServerName; + /** Bumped by the integrator when anything above changes at runtime - the + * Credentials or ServerName. The sender polls it every Send and reconnects + * on the next pass when it moves, so a rotation applies without calling + * SolidSyslogSender_Disconnect. Polled from the servicing thread, so it + * must be cheap and pure. NULL means this configuration never changes. */ + SolidSyslogStreamVersionFunction Version; + void* VersionContext; /**< Passed to Version unchanged; NULL is fine. */ }; /** Draw a TLS stream from the pool over the config's Transport (see the file diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 4b3a51b0..52ffea02 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -37,7 +37,9 @@ enum struct SolidSyslogAddress; static uint32_t MbedTlsStream_NullHandshakeTimeoutGetter(void* context); +static uint32_t MbedTlsStream_NullVersion(void* context); static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogMbedTlsStreamConfig* config); +static inline bool MbedTlsStream_ConfigProvidesVersion(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); @@ -72,6 +74,7 @@ static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptM 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); +static uint32_t MbedTlsStream_Version(struct SolidSyslogStream* base); static int MbedTlsStream_BioSend(void* ctx, const unsigned char* buf, size_t len); static int MbedTlsStream_BioRecv(void* ctx, unsigned char* buf, size_t len); @@ -85,6 +88,7 @@ void SolidSyslogMbedTlsStream_Initialise( self->Base.Send = MbedTlsStream_Send; self->Base.Read = MbedTlsStream_Read; self->Base.Close = MbedTlsStream_Close; + self->Base.Version = MbedTlsStream_Version; self->Config = *config; self->CredentialsInstalled = false; if (MbedTlsStream_ConfigProvidesHandshakeGetter(config) == false) @@ -95,6 +99,11 @@ void SolidSyslogMbedTlsStream_Initialise( self->Config.GetHandshakeTimeoutMs = MbedTlsStream_NullHandshakeTimeoutGetter; self->Config.HandshakeTimeoutContext = NULL; } + if (MbedTlsStream_ConfigProvidesVersion(config) == false) + { + self->Config.Version = MbedTlsStream_NullVersion; + self->Config.VersionContext = 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 @@ -118,6 +127,20 @@ static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct Soli return (config != NULL) && (config->GetHandshakeTimeoutMs != NULL); } +/* Null Object substituted at Initialise when the integrator installs no version + * function - reports an unchanging configuration, so the sender never reconnects + * on this stream's account. */ +static uint32_t MbedTlsStream_NullVersion(void* context) +{ + (void) context; + return 0; +} + +static inline bool MbedTlsStream_ConfigProvidesVersion(const struct SolidSyslogMbedTlsStreamConfig* config) +{ + return (config != NULL) && (config->Version != 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 @@ -159,6 +182,12 @@ static inline void MbedTlsStream_Close(struct SolidSyslogStream* base) SolidSyslogStream_Close(self->Config.Transport); } +static uint32_t MbedTlsStream_Version(struct SolidSyslogStream* base) +{ + struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base); + return self->Config.Version(self->Config.VersionContext); +} + static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr) { struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base); diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h index f26edf5e..6e90bbe9 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h @@ -31,6 +31,7 @@ #include "SolidSyslogExternC.h" #include "SolidSyslogSleep.h" +#include "SolidSyslogStream.h" #include "SolidSyslogTlsHandshakeTimeoutFunction.h" struct SolidSyslogStream; @@ -69,6 +70,14 @@ SOLIDSYSLOG_EXTERN_C_BEGIN * endpoint identity unchecked; no diagnostic. */ const char* ServerName; const char* CipherList; /**< TLS 1.2 cipher list; NULL uses the OpenSSL default. */ + /** Bumped by the integrator when anything above changes at runtime - the + * Credentials, ServerName or CipherList. The sender polls it every Send and + * reconnects on the next pass when it moves, so a rotation applies without + * calling SolidSyslogSender_Disconnect. Polled from the servicing thread, + * so it must be cheap and pure. NULL means this configuration never + * changes. */ + SolidSyslogStreamVersionFunction Version; + void* VersionContext; /**< Passed to Version unchanged; NULL is fine. */ }; /** Draw a TLS stream from the pool over the injected transport (see the file diff --git a/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c b/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c index 37f2046d..1f18fe52 100644 --- a/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c @@ -37,7 +37,9 @@ enum }; static uint32_t OpenSslStream_NullHandshakeTimeoutGetter(void* context); +static uint32_t OpenSslStream_NullVersion(void* context); static inline bool OpenSslStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogOpenSslStreamConfig* config); +static inline bool OpenSslStream_ConfigProvidesVersion(const struct SolidSyslogOpenSslStreamConfig* config); static inline uint32_t OpenSslStream_ResolveHandshakeTimeoutMs(struct SolidSyslogOpenSslStream* self); struct SolidSyslogAddress; @@ -46,6 +48,7 @@ static inline struct SolidSyslogOpenSslStream* OpenSslStream_SelfFromBase(struct static inline bool OpenSslStream_AttachTransportBio(struct SolidSyslogOpenSslStream* self); static inline void OpenSslStream_Close(struct SolidSyslogStream* base); +static uint32_t OpenSslStream_Version(struct SolidSyslogStream* base); static inline bool OpenSslStream_ConfigureCipherList(SSL_CTX* ctx, const char* cipherList); static inline bool OpenSslStream_ConfigureExpectedHostname(struct SolidSyslogOpenSslStream* self); static inline bool OpenSslStream_ConfigureProtocolFloor(SSL_CTX* ctx); @@ -104,6 +107,7 @@ void SolidSyslogOpenSslStream_Initialise( self->Base.Send = OpenSslStream_Send; self->Base.Read = OpenSslStream_Read; self->Base.Close = OpenSslStream_Close; + self->Base.Version = OpenSslStream_Version; self->Config = *config; if (OpenSslStream_ConfigProvidesHandshakeGetter(config) == false) { @@ -113,6 +117,11 @@ void SolidSyslogOpenSslStream_Initialise( self->Config.GetHandshakeTimeoutMs = OpenSslStream_NullHandshakeTimeoutGetter; self->Config.HandshakeTimeoutContext = NULL; } + if (OpenSslStream_ConfigProvidesVersion(config) == false) + { + self->Config.Version = OpenSslStream_NullVersion; + self->Config.VersionContext = NULL; + } self->Ctx = NULL; self->Ssl = NULL; self->BioMethod = NULL; @@ -157,6 +166,12 @@ static inline void OpenSslStream_Close(struct SolidSyslogStream* base) SolidSyslogStream_Close(self->Config.Transport); } +static uint32_t OpenSslStream_Version(struct SolidSyslogStream* base) +{ + struct SolidSyslogOpenSslStream* self = OpenSslStream_SelfFromBase(base); + return self->Config.Version(self->Config.VersionContext); +} + static inline void OpenSslStream_ReleaseHandshakeState(struct SolidSyslogOpenSslStream* self) { OpenSslStream_ReleaseSsl(self); @@ -672,6 +687,20 @@ static inline bool OpenSslStream_ConfigProvidesHandshakeGetter(const struct Soli return (config != NULL) && (config->GetHandshakeTimeoutMs != NULL); } +/* Null Object substituted at Initialise when the integrator installs no version + * function - reports an unchanging configuration, so the sender never reconnects + * on this stream's account. */ +static uint32_t OpenSslStream_NullVersion(void* context) +{ + (void) context; + return 0; +} + +static inline bool OpenSslStream_ConfigProvidesVersion(const struct SolidSyslogOpenSslStreamConfig* config) +{ + return (config != NULL) && (config->Version != 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 diff --git a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c index 82d81bf0..10c81d85 100644 --- a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c +++ b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c @@ -54,6 +54,7 @@ static bool PlusTcpTcpStream_Open(struct SolidSyslogStream* base, const struct S static bool PlusTcpTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize PlusTcpTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void PlusTcpTcpStream_Close(struct SolidSyslogStream* base); +static uint32_t PlusTcpTcpStream_Version(struct SolidSyslogStream* base); static inline struct SolidSyslogPlusTcpTcpStream* PlusTcpTcpStream_SelfFromBase(struct SolidSyslogStream* base); static inline bool PlusTcpTcpStream_ConfigProvidesGetter(const struct SolidSyslogPlusTcpTcpStreamConfig* config); @@ -97,7 +98,8 @@ void SolidSyslogPlusTcpTcpStream_Initialise( {.Open = PlusTcpTcpStream_Open, .Send = PlusTcpTcpStream_Send, .Read = PlusTcpTcpStream_Read, - .Close = PlusTcpTcpStream_Close}, + .Close = PlusTcpTcpStream_Close, + .Version = PlusTcpTcpStream_Version}, .Config = {.GetConnectTimeoutMs = PlusTcpTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL}, .Socket = FREERTOS_INVALID_SOCKET, }; @@ -333,6 +335,12 @@ static void PlusTcpTcpStream_Close(struct SolidSyslogStream* base) PlusTcpTcpStream_CloseSocket(PlusTcpTcpStream_SelfFromBase(base)); } +static uint32_t PlusTcpTcpStream_Version(struct SolidSyslogStream* base) +{ + (void) base; + return 0; +} + // NOLINTEND(performance-no-int-to-ptr) #else diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 324bd768..7197084e 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -48,6 +48,7 @@ static bool PosixTcpStream_Open(struct SolidSyslogStream* base, const struct Sol 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 uint32_t PosixTcpStream_Version(struct SolidSyslogStream* base); static inline struct SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromBase(struct SolidSyslogStream* base); static inline bool PosixTcpStream_ConfigProvidesGetter(const struct SolidSyslogPosixTcpStreamConfig* config); @@ -79,7 +80,8 @@ void SolidSyslogPosixTcpStream_Initialise( {.Open = PosixTcpStream_Open, .Send = PosixTcpStream_Send, .Read = PosixTcpStream_Read, - .Close = PosixTcpStream_Close}, + .Close = PosixTcpStream_Close, + .Version = PosixTcpStream_Version}, .Config = {.GetConnectTimeoutMs = PosixTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL}, .Fd = INVALID_FD, }; @@ -335,3 +337,9 @@ static void PosixTcpStream_Close(struct SolidSyslogStream* base) self->Fd = INVALID_FD; } } + +static uint32_t PosixTcpStream_Version(struct SolidSyslogStream* base) +{ + (void) base; + return 0; +} diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c index 76954335..160315ae 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c @@ -126,6 +126,7 @@ static bool WinsockTcpStream_Open(struct SolidSyslogStream* base, const struct S 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 uint32_t WinsockTcpStream_Version(struct SolidSyslogStream* base); static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromBase(struct SolidSyslogStream* base); static inline bool WinsockTcpStream_ConfigProvidesGetter(const struct SolidSyslogWinsockTcpStreamConfig* config); @@ -157,7 +158,8 @@ void SolidSyslogWinsockTcpStream_Initialise( {.Open = WinsockTcpStream_Open, .Send = WinsockTcpStream_Send, .Read = WinsockTcpStream_Read, - .Close = WinsockTcpStream_Close}, + .Close = WinsockTcpStream_Close, + .Version = WinsockTcpStream_Version}, .Config = {.GetConnectTimeoutMs = WinsockTcpStream_NullConnectTimeoutGetter, .ConnectTimeoutContext = NULL}, .Fd = INVALID_SOCKET, }; @@ -207,6 +209,12 @@ static void WinsockTcpStream_Close(struct SolidSyslogStream* base) } } +static uint32_t WinsockTcpStream_Version(struct SolidSyslogStream* base) +{ + (void) base; + return 0; +} + static bool WinsockTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr) { struct SolidSyslogWinsockTcpStream* self = WinsockTcpStream_SelfFromBase(base); diff --git a/Tests/FreeRtos/SolidSyslogPlusTcpTcpStreamTest.cpp b/Tests/FreeRtos/SolidSyslogPlusTcpTcpStreamTest.cpp index becb1962..d9512446 100644 --- a/Tests/FreeRtos/SolidSyslogPlusTcpTcpStreamTest.cpp +++ b/Tests/FreeRtos/SolidSyslogPlusTcpTcpStreamTest.cpp @@ -623,3 +623,8 @@ TEST(SolidSyslogPlusTcpTcpStreamPool, DestroyOfStaleHandleReportsWarning) SOLIDSYSLOG_PLUSTCP_TCP_STREAM_ERROR_UNKNOWN_DESTROY ); } + +TEST(SolidSyslogPlusTcpTcpStream, VersionIsAlwaysZeroBecauseNothingChangesAtRuntime) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(stream)); +} diff --git a/Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp b/Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp index aeace77c..befa875b 100644 --- a/Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp +++ b/Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp @@ -990,3 +990,8 @@ TEST(SolidSyslogLwipRawTcpStreamPool, DestroyOfStaleHandleReportsWarning) SOLIDSYSLOG_LWIPRAW_TCP_STREAM_ERROR_UNKNOWN_DESTROY ); } + +TEST(SolidSyslogLwipRawTcpStream, VersionIsAlwaysZeroBecauseNothingChangesAtRuntime) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(stream)); +} diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 8d97eb21..9eeb118d 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -89,6 +89,23 @@ extern "C" uint32_t FakeGetHandshakeTimeoutMs(void* context) FakeGetHandshakeTimeoutMs_LastContext = context; return FakeGetHandshakeTimeoutMs_ReturnValue; } + +/* Stands in for whatever the integrator bumps when the credentials or the + * expected peer name change. */ +uint32_t FakeVersion_ReturnValue = 0; +void* FakeVersion_LastContext = nullptr; + +void FakeVersion_Reset() +{ + FakeVersion_ReturnValue = 0; + FakeVersion_LastContext = reinterpret_cast(0x1U); /* sentinel - overwritten on first call */ +} + +extern "C" uint32_t FakeVersion(void* context) +{ + FakeVersion_LastContext = context; + return FakeVersion_ReturnValue; +} } // namespace // clang-format off @@ -106,6 +123,7 @@ TEST_GROUP(SolidSyslogMbedTlsStream) MbedTlsCredentialsFake_Reset(); ErrorHandlerFake_Install(nullptr); FakeGetHandshakeTimeoutMs_Reset(); + FakeVersion_Reset(); NoOpSleepCallCount = 0; g_lastSleepMs = 0; transport = StreamFake_Create(); @@ -1262,3 +1280,29 @@ TEST(SolidSyslogMbedTlsStream, OpenConnectsWhenTheVerdictIsClean) CHECK_TRUE(SolidSyslogStream_Open(handle, addr)); } + +TEST(SolidSyslogMbedTlsStream, VersionReportsTheConfiguredFunctionsValue) +{ + FakeVersion_ReturnValue = 7U; + config.Version = FakeVersion; + ReCreateHandleWithUpdatedConfig(); + + LONGS_EQUAL(7, SolidSyslogStream_Version(handle)); +} + +TEST(SolidSyslogMbedTlsStream, VersionFunctionReceivesVersionContext) +{ + int context = 0; + config.Version = FakeVersion; + config.VersionContext = &context; + ReCreateHandleWithUpdatedConfig(); + + SolidSyslogStream_Version(handle); + + POINTERS_EQUAL(&context, FakeVersion_LastContext); +} + +TEST(SolidSyslogMbedTlsStream, VersionIsZeroWhenNoFunctionIsConfigured) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(handle)); +} diff --git a/Tests/MbedTlsIntegration/SocketStream.c b/Tests/MbedTlsIntegration/SocketStream.c index 66882920..03d3db15 100644 --- a/Tests/MbedTlsIntegration/SocketStream.c +++ b/Tests/MbedTlsIntegration/SocketStream.c @@ -1,6 +1,7 @@ #include "SocketStream.h" #include +#include #include #include #include @@ -21,6 +22,7 @@ static bool SocketStream_Open(struct SolidSyslogStream* self, const struct Solid static bool SocketStream_Send(struct SolidSyslogStream* self, const void* buffer, size_t size); static SolidSyslogSsize SocketStream_Read(struct SolidSyslogStream* self, void* buffer, size_t size); static void SocketStream_Close(struct SolidSyslogStream* self); +static uint32_t SocketStream_Version(struct SolidSyslogStream* self); struct SolidSyslogStream* SocketStream_Create(int fd) { @@ -29,6 +31,7 @@ struct SolidSyslogStream* SocketStream_Create(int fd) stream->Base.Send = SocketStream_Send; stream->Base.Read = SocketStream_Read; stream->Base.Close = SocketStream_Close; + stream->Base.Version = SocketStream_Version; stream->Fd = fd; return &stream->Base; } @@ -94,3 +97,9 @@ static void SocketStream_Close(struct SolidSyslogStream* self) stream->Fd = -1; } } + +static uint32_t SocketStream_Version(struct SolidSyslogStream* self) +{ + (void) self; + return 0; +} diff --git a/Tests/OpenSslIntegration/BioPairStream.c b/Tests/OpenSslIntegration/BioPairStream.c index fac2068f..5b7082e8 100644 --- a/Tests/OpenSslIntegration/BioPairStream.c +++ b/Tests/OpenSslIntegration/BioPairStream.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "SolidSyslogStreamDefinition.h" @@ -21,6 +22,7 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); static void Close(struct SolidSyslogStream* self); +static uint32_t Version(struct SolidSyslogStream* self); struct SolidSyslogStream* BioPairStream_Create(BIO* bio) { @@ -29,6 +31,7 @@ struct SolidSyslogStream* BioPairStream_Create(BIO* bio) stream->Base.Send = Send; stream->Base.Read = Read; stream->Base.Close = Close; + stream->Base.Version = Version; stream->Bio = bio; return &stream->Base; } @@ -92,3 +95,9 @@ static void Close(struct SolidSyslogStream* self) { (void) self; } + +static uint32_t Version(struct SolidSyslogStream* self) +{ + (void) self; + return 0; +} diff --git a/Tests/SolidSyslogNullStreamTest.cpp b/Tests/SolidSyslogNullStreamTest.cpp index d7939316..e8b6fc77 100644 --- a/Tests/SolidSyslogNullStreamTest.cpp +++ b/Tests/SolidSyslogNullStreamTest.cpp @@ -35,3 +35,8 @@ TEST(SolidSyslogNullStream, CloseDoesNotCrash) { SolidSyslogStream_Close(stream); } + +TEST(SolidSyslogNullStream, VersionIsAlwaysZeroBecauseNothingChangesAtRuntime) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(stream)); +} diff --git a/Tests/SolidSyslogOpenSslStreamTest.cpp b/Tests/SolidSyslogOpenSslStreamTest.cpp index aa3b31dc..59e6e13f 100644 --- a/Tests/SolidSyslogOpenSslStreamTest.cpp +++ b/Tests/SolidSyslogOpenSslStreamTest.cpp @@ -77,6 +77,23 @@ extern "C" uint32_t FakeGetHandshakeTimeoutMs(void* context) FakeGetHandshakeTimeoutMs_LastContext = context; return FakeGetHandshakeTimeoutMs_ReturnValue; } + +/* Stands in for whatever the integrator bumps when the credentials, the expected + * peer name or the cipher list change. */ +uint32_t FakeVersion_ReturnValue = 0; +void* FakeVersion_LastContext = nullptr; + +void FakeVersion_Reset() +{ + FakeVersion_ReturnValue = 0; + FakeVersion_LastContext = reinterpret_cast(0x1U); /* sentinel - overwritten on first call */ +} + +extern "C" uint32_t FakeVersion(void* context) +{ + FakeVersion_LastContext = context; + return FakeVersion_ReturnValue; +} } // namespace // clang-format off @@ -92,6 +109,7 @@ TEST_GROUP(SolidSyslogOpenSslStream) OpenSslFake_Reset(); ErrorHandlerFake_Install(nullptr); FakeGetHandshakeTimeoutMs_Reset(); + FakeVersion_Reset(); NoOpSleepCallCount = 0; g_lastSleepMs = 0; transport = StreamFake_Create(); @@ -1531,3 +1549,29 @@ TEST(SolidSyslogOpenSslStream, VerifyCallbackLeavesAnIssuerToOpenSslWhenNoPeerIs { LONGS_EQUAL(0, OpenThenVerifyIssuer(0, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)); } + +TEST(SolidSyslogOpenSslStream, VersionReportsTheConfiguredFunctionsValue) +{ + FakeVersion_ReturnValue = 7U; + config.Version = FakeVersion; + ReCreateStreamWithUpdatedConfig(); + + LONGS_EQUAL(7, SolidSyslogStream_Version(stream)); +} + +TEST(SolidSyslogOpenSslStream, VersionFunctionReceivesVersionContext) +{ + int context = 0; + config.Version = FakeVersion; + config.VersionContext = &context; + ReCreateStreamWithUpdatedConfig(); + + SolidSyslogStream_Version(stream); + + POINTERS_EQUAL(&context, FakeVersion_LastContext); +} + +TEST(SolidSyslogOpenSslStream, VersionIsZeroWhenNoFunctionIsConfigured) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(stream)); +} diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index 98be52cd..088b4381 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -713,3 +713,8 @@ TEST(SolidSyslogPosixTcpStreamPool, DestroyOfStaleHandleReportsWarning) SOLIDSYSLOG_POSIX_TCP_STREAM_ERROR_UNKNOWN_DESTROY ); } + +TEST(SolidSyslogPosixTcpStream, VersionIsAlwaysZeroBecauseNothingChangesAtRuntime) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(stream)); +} diff --git a/Tests/SolidSyslogStreamSenderTest.cpp b/Tests/SolidSyslogStreamSenderTest.cpp index 1a71e83e..de078731 100644 --- a/Tests/SolidSyslogStreamSenderTest.cpp +++ b/Tests/SolidSyslogStreamSenderTest.cpp @@ -926,3 +926,61 @@ TEST(SolidSyslogStreamSenderDeliveryHealth, StayingUpReportsNothing) Send(); CALLED_FAKE(ErrorHandlerFake_Handle, NEVER); } + +// The stream reports its own configuration version through the Stream vtable, +// so a rotated credential or a changed pinned peer reconnects on the next Send +// without the integrator calling Disconnect from off the servicing thread. +// StreamFake_SetVersion stands in for whatever the integrator bumps. + +// clang-format off +TEST_GROUP(SolidSyslogStreamSenderStreamVersion) +{ + struct SolidSyslogResolver* resolver = nullptr; + struct SolidSyslogStream* stream = nullptr; + struct SolidSyslogAddress* address = nullptr; + struct SolidSyslogStreamSenderConfig config{}; + struct SolidSyslogSender* sender = nullptr; + + void setup() override + { + SocketFake_Reset(); + endpointGetHost = GetHost; + endpointVersion = 0; + endpointGetPort = GetPort; + resolver = SolidSyslogPosixResolver_Create(); + stream = StreamFake_Create(); + address = SolidSyslogPosixAddress_Create(); + config = {resolver, stream, address, TestEndpoint, TestEndpointVersion, nullptr}; + sender = SolidSyslogStreamSender_Create(&config); + } + + void teardown() override + { + SolidSyslogStreamSender_Destroy(sender); + SolidSyslogPosixAddress_Destroy(address); + StreamFake_Destroy(stream); + SolidSyslogPosixResolver_Destroy(resolver); + } + + void Send() const + { + SolidSyslogSender_Send(sender, TEST_MESSAGE, TEST_MESSAGE_LEN); + } +}; + +// clang-format on + +TEST(SolidSyslogStreamSenderStreamVersion, VersionChangeBetweenSendsReopensTheStream) +{ + Send(); + StreamFake_SetVersion(stream, 1); + Send(); + LONGS_EQUAL(2, StreamFake_OpenCallCount(stream)); +} + +TEST(SolidSyslogStreamSenderStreamVersion, SendStillSucceedsAcrossTheReconnect) +{ + Send(); + StreamFake_SetVersion(stream, 1); + CHECK_TRUE(SolidSyslogSender_Send(sender, TEST_MESSAGE, TEST_MESSAGE_LEN)); +} diff --git a/Tests/SolidSyslogWinsockTcpStreamTest.cpp b/Tests/SolidSyslogWinsockTcpStreamTest.cpp index 6f266390..1919633e 100644 --- a/Tests/SolidSyslogWinsockTcpStreamTest.cpp +++ b/Tests/SolidSyslogWinsockTcpStreamTest.cpp @@ -680,3 +680,8 @@ TEST(SolidSyslogWinsockTcpStream, DefaultPortMatchesRfc6587) { LONGS_EQUAL(601, SOLIDSYSLOG_TCP_DEFAULT_PORT); } + +TEST(SolidSyslogWinsockTcpStream, VersionIsAlwaysZeroBecauseNothingChangesAtRuntime) +{ + LONGS_EQUAL(0, SolidSyslogStream_Version(stream)); +} diff --git a/Tests/StreamFake.c b/Tests/StreamFake.c index dd88475a..5fdea5a1 100644 --- a/Tests/StreamFake.c +++ b/Tests/StreamFake.c @@ -18,6 +18,7 @@ struct StreamFake size_t LastReadSize; SolidSyslogSsize ReadReturn; int CloseCallCount; + uint32_t Version; }; static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) @@ -46,6 +47,12 @@ static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_ return fake->ReadReturn; } +static uint32_t Version(struct SolidSyslogStream* self) +{ + struct StreamFake* fake = (struct StreamFake*) self; + return fake->Version; +} + static void Close(struct SolidSyslogStream* self) { struct StreamFake* fake = (struct StreamFake*) self; @@ -59,6 +66,7 @@ struct SolidSyslogStream* StreamFake_Create(void) fake->Base.Send = Send; fake->Base.Read = Read; fake->Base.Close = Close; + fake->Base.Version = Version; return &fake->Base; } @@ -126,3 +134,8 @@ int StreamFake_CloseCallCount(struct SolidSyslogStream* stream) { return ((struct StreamFake*) stream)->CloseCallCount; } + +void StreamFake_SetVersion(struct SolidSyslogStream* stream, uint32_t value) +{ + ((struct StreamFake*) stream)->Version = value; +} diff --git a/Tests/StreamFake.h b/Tests/StreamFake.h index 3fb1758d..7cded05e 100644 --- a/Tests/StreamFake.h +++ b/Tests/StreamFake.h @@ -3,6 +3,7 @@ #include #include +#include #include "SolidSyslogExternC.h" #include "SolidSyslogStream.h" @@ -27,6 +28,7 @@ SOLIDSYSLOG_EXTERN_C_BEGIN void StreamFake_SetOpenFails(struct SolidSyslogStream * stream, bool fails); void StreamFake_SetSendFails(struct SolidSyslogStream * stream, bool fails); int StreamFake_CloseCallCount(struct SolidSyslogStream * stream); + void StreamFake_SetVersion(struct SolidSyslogStream * stream, uint32_t value); SOLIDSYSLOG_EXTERN_C_END diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index d2b28868..81767b58 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -51,12 +51,17 @@ freed and with it every pointer into the material. That window is what a source reaching a secure element or an encrypted store needs, and the PEM-buffer source is the worked example of using it. -Rotation with the handle source is a disconnect and a re-parse: call -`SolidSyslogSender_Disconnect`, then free and re-parse into the same handle. The -next send reconnects with the new material. Freeing before the disconnect -completes is a use-after-free, because the open connection is still reading it. -With the PEM-buffer source, replacing the buffer is enough - the next connection -parses whatever it then points at. +With the PEM-buffer source, replacing the buffer and moving the stream's +configuration version is enough - nothing is freed, and the next connection +parses whatever the buffer then points at. + +The handle source is different, because rotating it means freeing material the +open connection is still reading. Moving the version applies the change but does +not say when the old handle stops being read, so the free and the re-parse belong +after the connection has closed: either call `SolidSyslogSender_Disconnect` from +the task that services the library and re-parse once it returns, or put the free +and the re-parse in a credentials source's `Release`, which the stream calls when +it has finished with the material. ## Coexistence is an auditable contract diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index ef8ecf67..04a9ed85 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -39,7 +39,9 @@ The `SSL_CTX` is rebuilt on every open and freed on close, and the credentials source is asked again each time. Nothing is held between connections. Rotation is therefore a replacement and a reconnection: put the new material in place, and it is in force on the next connection, either through ordinary reconnection -after an outage or immediately by calling `SolidSyslogSender_Disconnect`. +after an outage or immediately by moving the stream's configuration version. +Nothing has to be freed to rotate the shipped source, which names a path that +OpenSSL reads afresh on each connection, so the version is the whole of it. ## Where it differs from the contract diff --git a/docs/tls.md b/docs/tls.md index 5722c89d..3c9995bc 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -155,8 +155,8 @@ element, an encrypted store. Two things follow from that, and one does not. **A device issued new credentials while it is running uses them on its next -connection** without being restarted. Forcing that reconnection with -`SolidSyslogSender_Disconnect` makes it immediate. +connection** without being restarted. Moving the stream's configuration version +makes it immediate. **The window in which the integrator must keep material intact is the connection**, not the lifetime of the stream. That is the point of announcing the @@ -180,11 +180,33 @@ changed at runtime, redirecting a device to a different collector must carry the identity its certificate is checked against, or the redirection moves the device to an unverified peer. +### Apply a change by moving a version, not by reaching into the connection + +Every `Stream` reports a configuration version, and the sender reads it on every +record. Moving that version is how a change to the material, the expected peer or +the cipher policy is applied: the sender closes the connection on its next pass +and opens a new one, so the change is in force from the following record. + +This is the only lever an integrator needs from outside the task that services +the library. A version is a value the integrator owns, read back at a point of +the library's choosing; `SolidSyslogSender_Disconnect` touches the sender's own +connection state and takes no lock, so it belongs to the servicing task. + +A stream whose configuration cannot change at runtime reports one version for its +lifetime, and the sender never reconnects on its account. + +Applying a change is a separate question from destroying what it replaced. A +version moved from another task says nothing about when the connection actually +closed, so material the integrator must free rather than overwrite is still +governed by the release announcement above. Each platform page states what its +own credential sources require. + ### A connection is long-lived, and the integrator bounds it A `Stream` opens on the first record that needs it and stays open. It closes when -a send fails, when the destination changes, when the integrator calls -`SolidSyslogSender_Disconnect`, or when the stream is destroyed. There is no idle +a send fails, when the destination changes, when the stream's own configuration +version moves, when the integrator calls `SolidSyslogSender_Disconnect`, or when +the stream is destroyed. There is no idle timeout and no maximum lifetime, because a syslog client that reconnects on a timer costs a handshake each time and gains nothing for a device that logs steadily. @@ -195,10 +217,10 @@ material stays resident for all of it.** The private key is needed once, to sign during the handshake; it is retained for the rest because neither TLS library offers a client a way to hand it back. -Bounding that window is the integrator's, using `SolidSyslogSender_Disconnect`. A -deployment that wants the material resident for minutes rather than months -disconnects on its own schedule; the next record reconnects and the credential -source is asked again. The same lever serves RFC 5425 §4.4's requirement that a +Bounding that window is the integrator's. A deployment that wants the material +resident for minutes rather than months moves the configuration version on its +own schedule; the next record reconnects and the credential source is asked +again. The same lever serves RFC 5425 §4.4's requirement that a sender close a connection it does not expect to carry more messages. Where the key must not be in application memory at all, that is a property of the diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 065c239e..be5c89c4 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -31,7 +31,7 @@ misra-c2012-11.3:Core/Source/SolidSyslogFormatter.c:83 misra-c2012-11.3:Core/Source/SolidSyslogMetaSd.c:64 misra-c2012-11.3:Core/Source/SolidSyslogOriginSd.c:64 misra-c2012-11.3:Core/Source/SolidSyslogPassthroughBuffer.c:57 -misra-c2012-11.3:Core/Source/SolidSyslogStreamSender.c:177 +misra-c2012-11.3:Core/Source/SolidSyslogStreamSender.c:194 misra-c2012-11.3:Core/Source/SolidSyslogSwitchingSender.c:70 misra-c2012-11.3:Core/Source/SolidSyslogTimeQualitySd.c:70 misra-c2012-11.3:Core/Source/SolidSyslogUdpSender.c:125 @@ -44,7 +44,7 @@ misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressStatic.c:39 misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressStatic.c:59 misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c:74 misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:215 -misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:145 +misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:147 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpAddress.c:18 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpAddressPrivate.h:28 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpAddressPrivate.h:35 @@ -53,8 +53,8 @@ misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpAddressStatic.c:59 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c:57 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c:52 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpResolver.c:48 -misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:129 -misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:132 +misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:131 +misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:155 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c:59 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c:73 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsHmacSha256Policy.c:83 @@ -62,7 +62,7 @@ misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c:85 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslAesGcmPolicy.c:89 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslHmacSha256Policy.c:84 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslPemFileCredentials.c:58 -misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c:124 +misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c:133 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddress.c:18 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:27 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:32 @@ -72,7 +72,7 @@ misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixDatagram.c:63 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixFile.c:74 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:137 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixMutex.c:55 -misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:111 +misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixTcpStream.c:113 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddress.c:17 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddressPrivate.h:27 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockAddressPrivate.h:34 @@ -82,16 +82,16 @@ misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c:37 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsFile.c:81 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsMutex.c:42 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:102 -misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:189 +misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:191 misra-c2012-11.5:Core/Source/SolidSyslogUdpSender.c:231 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c:83 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:158 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:220 -misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:154 -misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:162 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:306 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:598 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:610 +misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:156 +misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:164 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:335 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:627 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:639 # D.003 — Rule 5.7: repeating struct tags (no-typedef-struct convention) # See docs/misra-deviations.md#d003 @@ -202,8 +202,8 @@ misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:24 # D.013 — Rule 11.5: void* <-> a byte pointer at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:635 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:653 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:664 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:682 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:146 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:361 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:381 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:369 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:389 From 1d8b9301f84fd35f1d052977fa63f8d1f17c57d2 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Wed, 9 Sep 2026 20:28:26 +0100 Subject: [PATCH 2/2] refactor: apply the review findings on the stream version Ordering is now part of the Version contract: make the change, then move the version, or the sender can reopen with what is being replaced. Both TLS config briefs say the struct is copied at Create, so a runtime change is made in what the fields point at rather than by reassigning one. S39.02 (#810) removes that trap for ServerName. docs/tls.md names the endpoint's version as the trigger rather than "the destination changes", matching how the stream's own is described. Unsigned zero literals in the version providers, and the two stream sender groups over StreamFake now share a TEST_BASE. --- Core/Interface/SolidSyslogStreamDefinition.h | 6 ++- Core/Source/SolidSyslogNullStream.c | 2 +- Core/Source/SolidSyslogStreamSender.c | 2 +- .../Source/SolidSyslogLwipRawTcpStream.c | 2 +- .../Interface/SolidSyslogMbedTlsStream.h | 4 ++ .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 2 +- .../Interface/SolidSyslogOpenSslStream.h | 5 +- .../OpenSsl/Source/SolidSyslogOpenSslStream.c | 2 +- .../Source/SolidSyslogPlusTcpTcpStream.c | 2 +- .../Posix/Source/SolidSyslogPosixTcpStream.c | 2 +- .../Source/SolidSyslogWinsockTcpStream.c | 2 +- Tests/MbedTlsIntegration/SocketStream.c | 2 +- Tests/OpenSslIntegration/BioPairStream.c | 2 +- Tests/SolidSyslogStreamSenderTest.cpp | 52 ++++++++----------- docs/tls.md | 6 +-- 15 files changed, 47 insertions(+), 46 deletions(-) diff --git a/Core/Interface/SolidSyslogStreamDefinition.h b/Core/Interface/SolidSyslogStreamDefinition.h index a1477728..22ddda90 100644 --- a/Core/Interface/SolidSyslogStreamDefinition.h +++ b/Core/Interface/SolidSyslogStreamDefinition.h @@ -48,8 +48,10 @@ SOLIDSYSLOG_EXTERN_C_BEGIN /** Report a monotonic version of this stream's own configuration - what it * will present or accept on the next Open. Bump it when that changes and * the sender reconnects on its next pass. Polled every Send, so it must be - * cheap and pure. A stream with nothing to change reports 0 for its - * lifetime. */ + * cheap and pure. Make the change before moving the version: the sender + * may reconnect on the very next record, and a version moved first lets + * it reopen with the configuration being replaced. A stream with nothing + * to change reports 0 for its lifetime. */ uint32_t (*Version)(struct SolidSyslogStream* base); }; diff --git a/Core/Source/SolidSyslogNullStream.c b/Core/Source/SolidSyslogNullStream.c index c36374c7..0c56d546 100644 --- a/Core/Source/SolidSyslogNullStream.c +++ b/Core/Source/SolidSyslogNullStream.c @@ -67,5 +67,5 @@ static void NullStream_Close(struct SolidSyslogStream* base) static uint32_t NullStream_Version(struct SolidSyslogStream* base) { (void) base; - return 0; + return 0U; } diff --git a/Core/Source/SolidSyslogStreamSender.c b/Core/Source/SolidSyslogStreamSender.c index 9e3f64c1..9038b670 100644 --- a/Core/Source/SolidSyslogStreamSender.c +++ b/Core/Source/SolidSyslogStreamSender.c @@ -246,5 +246,5 @@ static void StreamSender_NilEndpoint(struct SolidSyslogEndpoint* endpoint, void* static uint32_t StreamSender_NilEndpointVersion(void* context) { (void) context; - return 0; + return 0U; } diff --git a/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c b/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c index 721093b9..70fbba6e 100644 --- a/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c +++ b/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c @@ -465,7 +465,7 @@ static void LwipRawTcpStream_Close(struct SolidSyslogStream* base) static uint32_t LwipRawTcpStream_Version(struct SolidSyslogStream* base) { (void) base; - return 0; + return 0U; } /* Close touches lwIP only if there is a pcb to close or queued pbufs to diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index 445b08c6..ea2a9ded 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -53,6 +53,10 @@ struct mbedtls_ctr_drbg_context; SOLIDSYSLOG_EXTERN_C_BEGIN + /** Wires SolidSyslogMbedTlsStream to its transport, trust anchors, and identity. + * Copied at Create, so a runtime change is made in what these fields point at - + * rewrite the buffer, re-parse into the handle, hand back new material from the + * Credentials - never by reassigning a field here. */ struct SolidSyslogMbedTlsStreamConfig { /** Underlying byte stream the TLS records ride on; required - a NULL is diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 52ffea02..60ecff98 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -133,7 +133,7 @@ static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct Soli static uint32_t MbedTlsStream_NullVersion(void* context) { (void) context; - return 0; + return 0U; } static inline bool MbedTlsStream_ConfigProvidesVersion(const struct SolidSyslogMbedTlsStreamConfig* config) diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h index 6e90bbe9..19659a0a 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslStream.h @@ -39,7 +39,10 @@ struct SolidSyslogOpenSslCredentials; SOLIDSYSLOG_EXTERN_C_BEGIN - /** Wires SolidSyslogOpenSslStream to its transport, trust anchors, and identity. */ + /** Wires SolidSyslogOpenSslStream to its transport, trust anchors, and identity. + * Copied at Create, so a runtime change is made in what these fields point at - + * rewrite the buffer, re-parse into the handle, hand back new material from the + * Credentials - never by reassigning a field here. */ struct SolidSyslogOpenSslStreamConfig { /** Underlying byte stream carrying the ciphertext; required - a NULL is diff --git a/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c b/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c index 1f18fe52..d4220495 100644 --- a/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c @@ -693,7 +693,7 @@ static inline bool OpenSslStream_ConfigProvidesHandshakeGetter(const struct Soli static uint32_t OpenSslStream_NullVersion(void* context) { (void) context; - return 0; + return 0U; } static inline bool OpenSslStream_ConfigProvidesVersion(const struct SolidSyslogOpenSslStreamConfig* config) diff --git a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c index 10c81d85..87fa2af2 100644 --- a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c +++ b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c @@ -338,7 +338,7 @@ static void PlusTcpTcpStream_Close(struct SolidSyslogStream* base) static uint32_t PlusTcpTcpStream_Version(struct SolidSyslogStream* base) { (void) base; - return 0; + return 0U; } // NOLINTEND(performance-no-int-to-ptr) diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 7197084e..14e6d466 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -341,5 +341,5 @@ static void PosixTcpStream_Close(struct SolidSyslogStream* base) static uint32_t PosixTcpStream_Version(struct SolidSyslogStream* base) { (void) base; - return 0; + return 0U; } diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c index 160315ae..819c8add 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c @@ -212,7 +212,7 @@ static void WinsockTcpStream_Close(struct SolidSyslogStream* base) static uint32_t WinsockTcpStream_Version(struct SolidSyslogStream* base) { (void) base; - return 0; + return 0U; } static bool WinsockTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr) diff --git a/Tests/MbedTlsIntegration/SocketStream.c b/Tests/MbedTlsIntegration/SocketStream.c index 03d3db15..abbe49dd 100644 --- a/Tests/MbedTlsIntegration/SocketStream.c +++ b/Tests/MbedTlsIntegration/SocketStream.c @@ -101,5 +101,5 @@ static void SocketStream_Close(struct SolidSyslogStream* self) static uint32_t SocketStream_Version(struct SolidSyslogStream* self) { (void) self; - return 0; + return 0U; } diff --git a/Tests/OpenSslIntegration/BioPairStream.c b/Tests/OpenSslIntegration/BioPairStream.c index 5b7082e8..f6e2ca33 100644 --- a/Tests/OpenSslIntegration/BioPairStream.c +++ b/Tests/OpenSslIntegration/BioPairStream.c @@ -99,5 +99,5 @@ static void Close(struct SolidSyslogStream* self) static uint32_t Version(struct SolidSyslogStream* self) { (void) self; - return 0; + return 0U; } diff --git a/Tests/SolidSyslogStreamSenderTest.cpp b/Tests/SolidSyslogStreamSenderTest.cpp index de078731..daa0bc1d 100644 --- a/Tests/SolidSyslogStreamSenderTest.cpp +++ b/Tests/SolidSyslogStreamSenderTest.cpp @@ -844,16 +844,15 @@ TEST(SolidSyslogStreamSenderBadSetup, DisconnectOnBadSetupSenderDoesNotCrash) // boolean directly, isolating the edge logic from any platform stack. // clang-format off -TEST_GROUP(SolidSyslogStreamSenderDeliveryHealth) +TEST_BASE(StreamSenderOverStreamFakeTestBase) { struct SolidSyslogResolver* resolver = nullptr; struct SolidSyslogStream* stream = nullptr; struct SolidSyslogAddress* address = nullptr; struct SolidSyslogStreamSenderConfig config{}; struct SolidSyslogSender* sender = nullptr; - int sentinel = 0; - void setup() override + void setupSenderOverStreamFake() { SocketFake_Reset(); endpointGetHost = GetHost; @@ -864,10 +863,9 @@ TEST_GROUP(SolidSyslogStreamSenderDeliveryHealth) address = SolidSyslogPosixAddress_Create(); config = {resolver, stream, address, TestEndpoint, TestEndpointVersion, nullptr}; sender = SolidSyslogStreamSender_Create(&config); - ErrorHandlerFake_Install(&sentinel); } - void teardown() override + void teardownSenderOverStreamFake() const { SolidSyslogStreamSender_Destroy(sender); SolidSyslogPosixAddress_Destroy(address); @@ -881,6 +879,22 @@ TEST_GROUP(SolidSyslogStreamSenderDeliveryHealth) } }; +TEST_GROUP_BASE(SolidSyslogStreamSenderDeliveryHealth, StreamSenderOverStreamFakeTestBase) +{ + int sentinel = 0; + + void setup() override + { + setupSenderOverStreamFake(); + ErrorHandlerFake_Install(&sentinel); + } + + void teardown() override + { + teardownSenderOverStreamFake(); + } +}; + // clang-format on TEST(SolidSyslogStreamSenderDeliveryHealth, FirstFailingSendReportsDeliveryFailed) @@ -933,38 +947,16 @@ TEST(SolidSyslogStreamSenderDeliveryHealth, StayingUpReportsNothing) // StreamFake_SetVersion stands in for whatever the integrator bumps. // clang-format off -TEST_GROUP(SolidSyslogStreamSenderStreamVersion) +TEST_GROUP_BASE(SolidSyslogStreamSenderStreamVersion, StreamSenderOverStreamFakeTestBase) { - struct SolidSyslogResolver* resolver = nullptr; - struct SolidSyslogStream* stream = nullptr; - struct SolidSyslogAddress* address = nullptr; - struct SolidSyslogStreamSenderConfig config{}; - struct SolidSyslogSender* sender = nullptr; - void setup() override { - SocketFake_Reset(); - endpointGetHost = GetHost; - endpointVersion = 0; - endpointGetPort = GetPort; - resolver = SolidSyslogPosixResolver_Create(); - stream = StreamFake_Create(); - address = SolidSyslogPosixAddress_Create(); - config = {resolver, stream, address, TestEndpoint, TestEndpointVersion, nullptr}; - sender = SolidSyslogStreamSender_Create(&config); + setupSenderOverStreamFake(); } void teardown() override { - SolidSyslogStreamSender_Destroy(sender); - SolidSyslogPosixAddress_Destroy(address); - StreamFake_Destroy(stream); - SolidSyslogPosixResolver_Destroy(resolver); - } - - void Send() const - { - SolidSyslogSender_Send(sender, TEST_MESSAGE, TEST_MESSAGE_LEN); + teardownSenderOverStreamFake(); } }; diff --git a/docs/tls.md b/docs/tls.md index 3c9995bc..ffd73ff8 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -204,9 +204,9 @@ own credential sources require. ### A connection is long-lived, and the integrator bounds it A `Stream` opens on the first record that needs it and stays open. It closes when -a send fails, when the destination changes, when the stream's own configuration -version moves, when the integrator calls `SolidSyslogSender_Disconnect`, or when -the stream is destroyed. There is no idle +a send fails, when the destination's version moves, when the stream's own +configuration version moves, when the integrator calls +`SolidSyslogSender_Disconnect`, or when the stream is destroyed. There is no idle timeout and no maximum lifetime, because a syslog client that reconnects on a timer costs a handshake each time and gains nothing for a device that logs steadily.