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..22ddda90 100644 --- a/Core/Interface/SolidSyslogStreamDefinition.h +++ b/Core/Interface/SolidSyslogStreamDefinition.h @@ -45,6 +45,14 @@ 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. 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); }; SOLIDSYSLOG_EXTERN_C_END diff --git a/Core/Source/SolidSyslogNullStream.c b/Core/Source/SolidSyslogNullStream.c index b2da379b..0c56d546 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 0U; +} 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..9038b670 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); @@ -229,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/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..70fbba6e 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 0U; +} + /* 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..ea2a9ded 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; @@ -52,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 @@ -82,6 +87,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..60ecff98 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 0U; +} + +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..19659a0a 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; @@ -38,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 @@ -69,6 +73,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..d4220495 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 0U; +} + +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..87fa2af2 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 0U; +} + // NOLINTEND(performance-no-int-to-ptr) #else diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 324bd768..14e6d466 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 0U; +} diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c index 76954335..819c8add 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 0U; +} + 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..abbe49dd 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 0U; +} diff --git a/Tests/OpenSslIntegration/BioPairStream.c b/Tests/OpenSslIntegration/BioPairStream.c index fac2068f..f6e2ca33 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 0U; +} 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..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) @@ -926,3 +940,39 @@ 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_BASE(SolidSyslogStreamSenderStreamVersion, StreamSenderOverStreamFakeTestBase) +{ + void setup() override + { + setupSenderOverStreamFake(); + } + + void teardown() override + { + teardownSenderOverStreamFake(); + } +}; + +// 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..ffd73ff8 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,10 +180,32 @@ 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 +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 @@ -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