From 2da7680ab90ca57d9db3f6170bd90d67e7331516 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 11:39:58 +0000 Subject: [PATCH 1/5] feat: S14.08 add SolidSyslogEndpointHost sink (slice 1) Additive opaque destination-host sink mirroring SolidSyslogHeaderField, wrapping a sender's stack formatter. Verbatim bounded copy (no PRINTUSASCII substitution) so a DNS name / IP literal reaches the resolver intact. No consumer yet; the endpoint cutover follows in slice 2. Co-Authored-By: Claude Opus 4.8 (1M context) --- Core/Interface/SolidSyslogEndpointHost.h | 30 +++++++++++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogEndpointHost.c | 16 +++++++ Core/Source/SolidSyslogEndpointHostPrivate.h | 30 +++++++++++++ Tests/CMakeLists.txt | 1 + Tests/SolidSyslogEndpointHostTest.cpp | 45 ++++++++++++++++++++ 6 files changed, 123 insertions(+) create mode 100644 Core/Interface/SolidSyslogEndpointHost.h create mode 100644 Core/Source/SolidSyslogEndpointHost.c create mode 100644 Core/Source/SolidSyslogEndpointHostPrivate.h create mode 100644 Tests/SolidSyslogEndpointHostTest.cpp diff --git a/Core/Interface/SolidSyslogEndpointHost.h b/Core/Interface/SolidSyslogEndpointHost.h new file mode 100644 index 00000000..ec477cce --- /dev/null +++ b/Core/Interface/SolidSyslogEndpointHost.h @@ -0,0 +1,30 @@ +#ifndef SOLIDSYSLOGENDPOINTHOST_H +#define SOLIDSYSLOGENDPOINTHOST_H + +#include "ExternC.h" + +#include + +EXTERN_C_BEGIN + + /* The value sink for a destination host (the SolidSyslogEndpoint a sender + * hands its endpoint callback). A callback is given only a + * SolidSyslogEndpointHost* — it can append the host string bounded to the + * host-field width, but cannot reach the raw formatter. Unlike a header + * field, the bytes are copied verbatim: a DNS name or IP literal headed to + * the resolver must not be silently substituted. Stack-transient, no pool + * (D.002). */ + struct SolidSyslogEndpointHost; + + /* Appends up to maxLength bytes of source (stopping at a NUL terminator) + * verbatim into the host sink, further bounded by the host-field width the + * sink was created with. */ + void SolidSyslogEndpointHost_String( + struct SolidSyslogEndpointHost* host, + const char* source, + size_t maxLength + ); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGENDPOINTHOST_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 2a84eb12..c92b1642 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -35,6 +35,7 @@ set(SOURCES SolidSyslogSdValue.c SolidSyslogSdElement.c SolidSyslogHeaderField.c + SolidSyslogEndpointHost.c SolidSyslogTimeQualitySd.c SolidSyslogTimeQualitySdStatic.c SolidSyslogOriginSd.c diff --git a/Core/Source/SolidSyslogEndpointHost.c b/Core/Source/SolidSyslogEndpointHost.c new file mode 100644 index 00000000..dd33589d --- /dev/null +++ b/Core/Source/SolidSyslogEndpointHost.c @@ -0,0 +1,16 @@ +#include "SolidSyslogEndpointHostPrivate.h" + +#include "SolidSyslogFormatter.h" + +void SolidSyslogEndpointHost_FromFormatter( + struct SolidSyslogEndpointHost* host, + struct SolidSyslogFormatter* formatter +) +{ + host->Formatter = formatter; +} + +void SolidSyslogEndpointHost_String(struct SolidSyslogEndpointHost* host, const char* source, size_t maxLength) +{ + SolidSyslogFormatter_BoundedString(host->Formatter, source, maxLength); +} diff --git a/Core/Source/SolidSyslogEndpointHostPrivate.h b/Core/Source/SolidSyslogEndpointHostPrivate.h new file mode 100644 index 00000000..d27d28d6 --- /dev/null +++ b/Core/Source/SolidSyslogEndpointHostPrivate.h @@ -0,0 +1,30 @@ +#ifndef SOLIDSYSLOGENDPOINTHOSTPRIVATE_H +#define SOLIDSYSLOGENDPOINTHOSTPRIVATE_H + +#include "ExternC.h" + +#include "SolidSyslogEndpointHost.h" + +EXTERN_C_BEGIN + + struct SolidSyslogFormatter; + + /* Definition lives here (not the public header) so an endpoint callback + * handed a SolidSyslogEndpointHost* cannot reach the wrapped formatter. */ + struct SolidSyslogEndpointHost + { + struct SolidSyslogFormatter* Formatter; + }; + + /* Internal constructor — wraps a sender's stack host formatter. The sender + * builds one per resolve, passes it to the configured endpoint callback, + * then reads the formatted host back out. Stack-transient: the caller owns + * the storage. */ + void SolidSyslogEndpointHost_FromFormatter( + struct SolidSyslogEndpointHost * host, + struct SolidSyslogFormatter * formatter + ); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGENDPOINTHOSTPRIVATE_H */ diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 5f38639a..b5b4c36a 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -52,6 +52,7 @@ set(TEST_SOURCES SolidSyslogSdValueTest.cpp SolidSyslogSdElementTest.cpp SolidSyslogHeaderFieldTest.cpp + SolidSyslogEndpointHostTest.cpp SolidSyslogNullSenderTest.cpp SolidSyslogNullStoreTest.cpp SolidSyslogNullSecurityPolicyTest.cpp diff --git a/Tests/SolidSyslogEndpointHostTest.cpp b/Tests/SolidSyslogEndpointHostTest.cpp new file mode 100644 index 00000000..7c7dd0d7 --- /dev/null +++ b/Tests/SolidSyslogEndpointHostTest.cpp @@ -0,0 +1,45 @@ +#include + +#include "CppUTest/TestHarness.h" +#include "SolidSyslogEndpointHost.h" +#include "SolidSyslogEndpointHostPrivate.h" +#include "SolidSyslogFormatter.h" + +enum +{ + TEST_BUFFER_SIZE = 64 +}; + +// clang-format off +TEST_GROUP(SolidSyslogEndpointHost) +{ + SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(TEST_BUFFER_SIZE)]; + struct SolidSyslogFormatter* formatter = nullptr; + struct SolidSyslogEndpointHost host{}; + + void setup() override + { + formatter = SolidSyslogFormatter_Create(storage, TEST_BUFFER_SIZE); + SolidSyslogEndpointHost_FromFormatter(&host, formatter); + } +}; + +// clang-format on + +TEST(SolidSyslogEndpointHost, StringWritesTheHostVerbatim) +{ + SolidSyslogEndpointHost_String(&host, "logs.example.com", 16); + STRCMP_EQUAL("logs.example.com", SolidSyslogFormatter_AsFormattedBuffer(formatter)); +} + +TEST(SolidSyslogEndpointHost, StringOfEmptyHostWritesNothing) +{ + SolidSyslogEndpointHost_String(&host, "", 0); + STRCMP_EQUAL("", SolidSyslogFormatter_AsFormattedBuffer(formatter)); +} + +TEST(SolidSyslogEndpointHost, StringIsBoundedByMaxLength) +{ + SolidSyslogEndpointHost_String(&host, "logs.example.com", 4); + STRCMP_EQUAL("logs", SolidSyslogFormatter_AsFormattedBuffer(formatter)); +} From d97c2347ca96bbbc5812ea44dfff50fb8641d516 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 11:47:57 +0000 Subject: [PATCH 2/5] feat: S14.08 route endpoint host through the sink (slice 2) Flip SolidSyslogEndpoint.Host from a raw SolidSyslogFormatter* to the opaque SolidSyslogEndpointHost*. Every endpoint-host writer migrates together: - both senders wrap their stack host formatter in the sink before invoking the endpoint callback and read the host back out unchanged; - StreamSender's nil-endpoint, all five BDD target configs (TCP/UDP/TLS/mTLS/ Windows), the FreeRTOS pipeline, and the BDD service-thread test write via the sink. Byte-identical: the existing sender + BDD-target tests are the safety net. SolidSyslogFormatter.h is now the only public header referencing the formatter, unblocking the move to Core/Source in slice 3. Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/Common/BddTargetFreeRtosPipeline.c | 4 ++-- Bdd/Targets/Common/BddTargetMtlsConfig.c | 4 ++-- Bdd/Targets/Common/BddTargetTlsConfig.c | 4 ++-- Bdd/Targets/Linux/BddTargetTcpConfig.c | 4 ++-- Bdd/Targets/Linux/BddTargetUdpConfig.c | 4 ++-- Bdd/Targets/Windows/BddTargetWindows.c | 4 ++-- Core/Interface/SolidSyslogEndpoint.h | 4 +++- Core/Source/SolidSyslogStreamSender.c | 7 +++++-- Core/Source/SolidSyslogUdpSender.c | 5 ++++- Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp | 4 ++-- Tests/SolidSyslogStreamSenderTest.cpp | 3 ++- Tests/SolidSyslogUdpSenderTest.cpp | 3 ++- 12 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c b/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c index 14003347..38f92b38 100644 --- a/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c +++ b/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c @@ -24,9 +24,9 @@ #include "SolidSyslogConfig.h" #include "SolidSyslogCrc16Policy.h" #include "SolidSyslogEndpoint.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogError.h" #include "SolidSyslogFileBlockDevice.h" -#include "SolidSyslogFormatter.h" #include "SolidSyslogHeaderField.h" #include "SolidSyslogFreeRtosMutex.h" #include "SolidSyslogFreeRtosSysUpTime.h" @@ -240,7 +240,7 @@ static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) void BddTargetFreeRtosPipeline_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, host, strlen(host)); + SolidSyslogEndpointHost_String(endpoint->Host, host, strlen(host)); endpoint->Port = port; } diff --git a/Bdd/Targets/Common/BddTargetMtlsConfig.c b/Bdd/Targets/Common/BddTargetMtlsConfig.c index 88a6c67a..a426a171 100644 --- a/Bdd/Targets/Common/BddTargetMtlsConfig.c +++ b/Bdd/Targets/Common/BddTargetMtlsConfig.c @@ -3,7 +3,7 @@ #include #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogEndpoint.h" enum @@ -69,7 +69,7 @@ const char* BddTargetMtlsConfig_GetClientKeyPath(void) void BddTargetMtlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, BddTargetMtlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, BddTargetMtlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = BddTargetMtlsConfig_GetPort(); } diff --git a/Bdd/Targets/Common/BddTargetTlsConfig.c b/Bdd/Targets/Common/BddTargetTlsConfig.c index eace3d8a..cd443a8b 100644 --- a/Bdd/Targets/Common/BddTargetTlsConfig.c +++ b/Bdd/Targets/Common/BddTargetTlsConfig.c @@ -3,7 +3,7 @@ #include #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogTransport.h" #include "SolidSyslogEndpoint.h" @@ -52,7 +52,7 @@ const char* BddTargetTlsConfig_GetServerName(void) void BddTargetTlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, BddTargetTlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, BddTargetTlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = BddTargetTlsConfig_GetPort(); } diff --git a/Bdd/Targets/Linux/BddTargetTcpConfig.c b/Bdd/Targets/Linux/BddTargetTcpConfig.c index b79833ec..e84c7f4d 100644 --- a/Bdd/Targets/Linux/BddTargetTcpConfig.c +++ b/Bdd/Targets/Linux/BddTargetTcpConfig.c @@ -2,7 +2,7 @@ #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogEndpoint.h" /* Unprivileged port used by the BDD syslog-ng container — library default is @@ -24,7 +24,7 @@ uint16_t BddTargetTcpConfig_GetPort(void) void BddTargetTcpConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, BddTargetTcpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, BddTargetTcpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = BddTargetTcpConfig_GetPort(); } diff --git a/Bdd/Targets/Linux/BddTargetUdpConfig.c b/Bdd/Targets/Linux/BddTargetUdpConfig.c index 7d00e6aa..bcc5d019 100644 --- a/Bdd/Targets/Linux/BddTargetUdpConfig.c +++ b/Bdd/Targets/Linux/BddTargetUdpConfig.c @@ -2,7 +2,7 @@ #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogEndpoint.h" /* Unprivileged mirror of SOLIDSYSLOG_UDP_DEFAULT_PORT (514) for BDD containers */ @@ -23,7 +23,7 @@ uint16_t BddTargetUdpConfig_GetPort(void) void BddTargetUdpConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, BddTargetUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, BddTargetUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = BddTargetUdpConfig_GetPort(); } diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 43ddd5f2..910c3390 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -21,7 +21,7 @@ #include "SolidSyslogFileBlockDevice.h" #include "SolidSyslogNullSecurityPolicy.h" #include "SolidSyslogOpenSslHmacSha256Policy.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogMetaSd.h" #include "SolidSyslogNullStore.h" #include "SolidSyslogOriginSd.h" @@ -109,7 +109,7 @@ static int GetPort(void) static void GetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = (uint16_t) GetPort(); } diff --git a/Core/Interface/SolidSyslogEndpoint.h b/Core/Interface/SolidSyslogEndpoint.h index 2835f14b..db24c565 100644 --- a/Core/Interface/SolidSyslogEndpoint.h +++ b/Core/Interface/SolidSyslogEndpoint.h @@ -12,9 +12,11 @@ EXTERN_C_BEGIN SOLIDSYSLOG_MAX_HOST_SIZE = 256 }; + struct SolidSyslogEndpointHost; + struct SolidSyslogEndpoint { - struct SolidSyslogFormatter* Host; /* library-provided; user writes destination host into it */ + struct SolidSyslogEndpointHost* Host; /* library-provided sink; user writes destination host into it */ uint16_t Port; /* user assigns destination port */ }; diff --git a/Core/Source/SolidSyslogStreamSender.c b/Core/Source/SolidSyslogStreamSender.c index 0897c0cb..d6015d26 100644 --- a/Core/Source/SolidSyslogStreamSender.c +++ b/Core/Source/SolidSyslogStreamSender.c @@ -3,6 +3,7 @@ #include #include "SolidSyslogEndpoint.h" +#include "SolidSyslogEndpointHostPrivate.h" #include "SolidSyslogError.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogNullSender.h" @@ -138,7 +139,9 @@ static bool StreamSender_ResolveDestination(struct SolidSyslogStreamSender* self { SolidSyslogFormatterStorage hostStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(SOLIDSYSLOG_MAX_HOST_SIZE)]; struct SolidSyslogFormatter* hostFormatter = SolidSyslogFormatter_Create(hostStorage, SOLIDSYSLOG_MAX_HOST_SIZE); - struct SolidSyslogEndpoint endpoint = {.Host = hostFormatter, .Port = 0}; + struct SolidSyslogEndpointHost hostSink; + SolidSyslogEndpointHost_FromFormatter(&hostSink, hostFormatter); + struct SolidSyslogEndpoint endpoint = {.Host = &hostSink, .Port = 0}; self->Config.Endpoint(&endpoint); @@ -210,7 +213,7 @@ static bool StreamSender_SendBytes(struct SolidSyslogStreamSender* self, const v static void StreamSender_NilEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, "", 0); + SolidSyslogEndpointHost_String(endpoint->Host, "", 0); endpoint->Port = 0; } diff --git a/Core/Source/SolidSyslogUdpSender.c b/Core/Source/SolidSyslogUdpSender.c index 88180520..f88d7d25 100644 --- a/Core/Source/SolidSyslogUdpSender.c +++ b/Core/Source/SolidSyslogUdpSender.c @@ -4,6 +4,7 @@ #include "SolidSyslogDatagram.h" #include "SolidSyslogEndpoint.h" +#include "SolidSyslogEndpointHostPrivate.h" #include "SolidSyslogError.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogNullSender.h" @@ -170,7 +171,9 @@ static inline uint16_t UdpSender_QueryEndpointPort( struct SolidSyslogFormatter* hostFormatter ) { - struct SolidSyslogEndpoint endpoint = {.Host = hostFormatter, .Port = 0}; + struct SolidSyslogEndpointHost hostSink; + SolidSyslogEndpointHost_FromFormatter(&hostSink, hostFormatter); + struct SolidSyslogEndpoint endpoint = {.Host = &hostSink, .Port = 0}; self->Config.Endpoint(&endpoint); return endpoint.Port; } diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index dc645efa..3fcf8a9f 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -5,7 +5,7 @@ #include "SolidSyslog.h" #include "SolidSyslogConfig.h" #include "SolidSyslogEndpoint.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogPosixMessageQueueBuffer.h" #include "SolidSyslogGetAddrInfoResolver.h" #include "SolidSyslogPosixAddress.h" @@ -37,7 +37,7 @@ static void SleepFake(int milliseconds) static void BddTargetEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, BddTargetUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, BddTargetUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = BddTargetUdpConfig_GetPort(); } diff --git a/Tests/SolidSyslogStreamSenderTest.cpp b/Tests/SolidSyslogStreamSenderTest.cpp index 9209a87b..fd87185a 100644 --- a/Tests/SolidSyslogStreamSenderTest.cpp +++ b/Tests/SolidSyslogStreamSenderTest.cpp @@ -8,6 +8,7 @@ #include "ErrorHandlerFake.h" #include "SocketFake.h" #include "SolidSyslogEndpoint.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogErrorCategory.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogGetAddrInfoResolver.h" @@ -80,7 +81,7 @@ static uint32_t endpointVersion = 0; static void TestEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, endpointGetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, endpointGetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = (uint16_t) endpointGetPort(); } diff --git a/Tests/SolidSyslogUdpSenderTest.cpp b/Tests/SolidSyslogUdpSenderTest.cpp index ec332476..9e394f4b 100644 --- a/Tests/SolidSyslogUdpSenderTest.cpp +++ b/Tests/SolidSyslogUdpSenderTest.cpp @@ -10,6 +10,7 @@ #include "SocketFake.h" #include "SolidSyslogDatagram.h" #include "SolidSyslogEndpoint.h" +#include "SolidSyslogEndpointHost.h" #include "SolidSyslogErrorCategory.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogGetAddrInfoResolver.h" @@ -82,7 +83,7 @@ static uint32_t endpointVersion = 0; static void TestEndpoint(struct SolidSyslogEndpoint* endpoint) { - SolidSyslogFormatter_BoundedString(endpoint->Host, endpointGetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + SolidSyslogEndpointHost_String(endpoint->Host, endpointGetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); endpoint->Port = (uint16_t) endpointGetPort(); } From e7c50eb21e166a9785b505b4e90fb71c6e7d4a4a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 12:08:30 +0000 Subject: [PATCH 3/5] feat: S14.08 make SolidSyslogFormatter library-private (#548) Move SolidSyslogFormatter.h from Core/Interface to Core/Source. With the endpoint host now behind SolidSyslogEndpointHost, no public header or Tier-3 target reaches the formatter; it is a Core implementation detail. Platform sources compile into the library target and keep their PRIVATE Core/Source view, and tests/BDD-target tests already add Core/Source to their include path, so the move needs no CMake include-path changes and the header drops out of the installed public set. The two worst-case sizing macros gain a NOLINTBEGIN/END(macro-usage) pair (matching SolidSyslogMacros.h) since the Core/Interface tier-wide disable no longer covers them; the stale Formatter.h example is dropped from that tier's .clang-tidy comment. Co-Authored-By: Claude Opus 4.8 (1M context) --- Core/Interface/.clang-tidy | 3 +-- Core/{Interface => Source}/SolidSyslogFormatter.h | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) rename Core/{Interface => Source}/SolidSyslogFormatter.h (85%) diff --git a/Core/Interface/.clang-tidy b/Core/Interface/.clang-tidy index 37760822..c87c0128 100644 --- a/Core/Interface/.clang-tidy +++ b/Core/Interface/.clang-tidy @@ -8,8 +8,7 @@ # defaults must be visible to the preprocessor (`#if`-checked floors) and # usable as array-size const-expressions in dependent headers. Replacing # with `static const` or `constexpr` loses both properties. The same idiom -# appears in SolidSyslogFormatter.h (worst-case output sizing macros), -# SolidSyslogCircularBuffer.h (ring-bytes helper) and ExternC.h +# appears in SolidSyslogCircularBuffer.h (ring-bytes helper) and ExternC.h # (extern "C" linkage block). The rule fires on every entry, the # rationale is uniform, so disable tier-wide here rather than carry the # same NOLINT comment on dozens of macro definitions. diff --git a/Core/Interface/SolidSyslogFormatter.h b/Core/Source/SolidSyslogFormatter.h similarity index 85% rename from Core/Interface/SolidSyslogFormatter.h rename to Core/Source/SolidSyslogFormatter.h index 75708cba..9aeea249 100644 --- a/Core/Interface/SolidSyslogFormatter.h +++ b/Core/Source/SolidSyslogFormatter.h @@ -15,11 +15,18 @@ EXTERN_C_BEGIN SOLIDSYSLOG_FORMATTER_OVERHEAD = 2U }; +/* NOLINTBEGIN(cppcoreguidelines-macro-usage) — worst-case output sizing + macros: must be preprocessor-visible array-size const-expressions, so a + static const / constexpr cannot replace them. Now that this header is + library-private (Core/Source) the root .clang-tidy governs it and enables + the rule; the Core/Interface tier-wide disable no longer covers it. Matches + the SolidSyslogMacros.h idiom. */ #define SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(bufferSize) \ (SOLIDSYSLOG_FORMATTER_OVERHEAD + \ (((bufferSize) + sizeof(SolidSyslogFormatterStorage) - 1U) / sizeof(SolidSyslogFormatterStorage))) #define SOLIDSYSLOG_ESCAPED_MAX_SIZE(maxDecodedLength) (2U * (maxDecodedLength)) +/* NOLINTEND(cppcoreguidelines-macro-usage) */ struct SolidSyslogFormatter; From 8471326322785827ba183b9ab075e55865e542b0 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 12:19:04 +0000 Subject: [PATCH 4/5] docs: S14.08 refresh public-header table and OriginSd compliance notes Drop the now-private SolidSyslogFormatter.h row and the retired SolidSyslogStringFunction.h row from the CLAUDE.md public-header table; add SolidSyslogEndpointHost, SolidSyslogHeaderField(+Function), and the SD writers SolidSyslogSdElement / SdValue / SdValueFunction; fix the Endpoint, StructuredData(Definition), MetaSd, and OriginSd rows to match the post-E14 writer model. Sweep the stale OriginSd lines in rfc-compliance.md (escaping is applied by SolidSyslogSdValue, not a raw formatter; no pre-formatted scratch storage). The SD-authoring design note (docs/structured-data.md) is deferred to S14.09, where it lands beside the integrator guide. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 18 +++++++++++------- docs/rfc-compliance.md | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0dd95766..aadf799f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -321,11 +321,12 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogError.h` | Any code installing a handler to react to library-internal errors (NULL guards, send failures); also the library-internal call site for emitting them | `struct SolidSyslogErrorEvent` (`Severity`, `Source`, `uint16_t Category`, `int32_t Detail`), `SolidSyslogErrorHandler` typedef (`void(void* context, const struct SolidSyslogErrorEvent* event)`), `SolidSyslog_SetErrorHandler(handler, context)`, `SolidSyslog_Error(severity, source, category, detail)`. The handler reads three orthogonal axes off the event: `Source` (extensible identity, pointer-identity match), `Category` (portable reaction axis — see `SolidSyslogErrorCategory.h`), `Detail` (per-class `enum SolidSyslogErrors` value today, native `errno`/`X509_V_ERR_*` later). Default handler is a silent no-op; setting `handler = NULL` restores the default. Single global slot — intended for setup-time configuration, not synchronised with concurrent `Error` calls. | | `SolidSyslogErrorCategory.h` | Any handler switching on the portable category axis; any emit site picking a category | Universal lifecycle category macros (`SOLIDSYSLOG_CAT_BAD_CONFIG` / `_BAD_ARGUMENT` / `_POOL_EXHAUSTED` / `_UNKNOWN_DESTROY`, all `uint16_t`) + per-role base ranges (`SOLIDSYSLOG_CAT__BASE`, errno-domain style; a role occupies `[BASE, BASE + 0xFF]`, `0xC000+` reserved for integrator-defined roles). Category constants are `uint16_t` macros carrying their own cast so emit sites stay clean; the wire type is `uint16_t` (not an enum) so integrator classes can supply their own categories without being boxed into a library enum. Role-specific categories live in `SolidSyslogCategories.h` beside each `*Definition.h` (`SolidSyslogSenderCategories.h` → `_SENDER_DELIVERY_FAILED` / `_SENDER_DELIVERY_RESTORED`, shared by `StreamSender` + `UdpSender`; `SolidSyslogResolverCategories.h` → `_RESOLVER_RESOLVE_FAILED`; `SolidSyslogTlsStreamCategories.h` → `_TLSSTREAM_INIT_FAILED` / `_HANDSHAKE_FAILED`; `SolidSyslogSecurityPolicyCategories.h` → `_SECURITYPOLICY_KEY_UNAVAILABLE` / `_SEAL_FAILED` / `_OPEN_FAILED`; `SolidSyslogBufferCategories.h` → `_BUFFER_BACKEND_FAILED`). A category is defined only once a live emit site raises it. | | `SolidSyslogConfigLock.h` | Setup code on systems where library-internal config-time critical sections (E11 pool `_Create`/`_Destroy` slot walks) may race across tasks or cores. Single-task setup needs nothing — defaults are no-ops. | `SolidSyslogConfigLockFunction` typedef (zero-arg `void(void)`), `SolidSyslog_SetConfigLock(lockFn, unlockFn)`, `SolidSyslog_LockConfig()`, `SolidSyslog_UnlockConfig()`. Pair API: both handlers installed together. `NULL` on either side restores that side's no-op default. Single global slot — intended for setup-time configuration, not synchronised with concurrent installs. Integrators wire `taskENTER_CRITICAL`/`taskEXIT_CRITICAL` (FreeRTOS), `pthread_mutex_lock`/`unlock` on a static `pthread_mutex_t` (POSIX), `EnterCriticalSection`/`LeaveCriticalSection` on a static `CRITICAL_SECTION` (Windows), or a spinlock pair. This is the only synchronisation primitive available to the Mutex and AtomicCounter pools for their own pool walks — chicken-and-egg eliminates their own injectables. | -| `SolidSyslogStringFunction.h` | Any code needing the string-callback typedef | `SolidSyslogStringFunction` (callback writes into a `SolidSyslogFormatter*`) — used by `SolidSyslogConfig` (hostname/appName/processId), `SolidSyslogMetaSdConfig` (language), `SolidSyslogOriginSdConfig` | -| `SolidSyslogFormatter.h` | Any code that formats into a bounded buffer | `SolidSyslogFormatter`, `SolidSyslogFormatterStorage`, `SOLIDSYSLOG_FORMATTER_STORAGE_SIZE`, `_Create`, `_FromStorage`, `_AsciiCharacter`, `_Bom`, `_BoundedString`, `_EscapedString`, `_PrintUsAsciiString`, `_Uint32`, `_TwoDigit`, `_FourDigit`, `_SixDigit`, `_AsFormattedBuffer`, `_Length` | +| `SolidSyslogHeaderField.h` | Any code writing an RFC 5424 header field (HOSTNAME / APP-NAME / PROCID) into the field sink it is handed | Opaque `struct SolidSyslogHeaderField` value sink + `_PrintUsAscii`, `_Uint32` — appends PRINTUSASCII content (any other byte, space included, substituted) bounded to the field width. The library owns the charset; a callback cannot reach the raw formatter or break the header framing. Stack-transient, no pool (D.002). | +| `SolidSyslogHeaderFieldFunction.h` | Any code needing the header-field callback typedef | `SolidSyslogHeaderFieldFunction` (callback writes into a `SolidSyslogHeaderField*` + `void* context`) — used by `SolidSyslogConfig` for hostname/appName/processId. Replaces the retired `SolidSyslogStringFunction`. | | `SolidSyslogPrival.h` | Any code that needs facility/severity enums | `SolidSyslogFacility`, `SolidSyslogSeverity` | | `SolidSyslogTimestamp.h` | Any code that needs the timestamp struct | `SolidSyslogTimestamp`, `SolidSyslogClockFunction` | -| `SolidSyslogEndpoint.h` | System setup code that supplies destination host/port (and version on changes) | `SolidSyslogEndpoint`, `SolidSyslogEndpointFunction`, `SolidSyslogEndpointVersionFunction`, `SOLIDSYSLOG_MAX_HOST_SIZE` | +| `SolidSyslogEndpoint.h` | System setup code that supplies destination host/port (and version on changes) | `SolidSyslogEndpoint` (its `Host` member is an opaque `SolidSyslogEndpointHost*` sink, not a formatter), `SolidSyslogEndpointFunction`, `SolidSyslogEndpointVersionFunction`, `SOLIDSYSLOG_MAX_HOST_SIZE` | +| `SolidSyslogEndpointHost.h` | Any code writing the destination host inside a `SolidSyslogEndpointFunction` | Opaque `struct SolidSyslogEndpointHost` value sink + `_String` — appends the host verbatim (no PRINTUSASCII substitution; a DNS name / IP literal must reach the resolver intact) bounded to the host-field width. The callback cannot reach the raw formatter. Stack-transient, no pool (D.002). | | `SolidSyslogSender.h` | Any code holding a sender handle | `SolidSyslogSender_Send`, `SolidSyslogSender_Disconnect` | | `SolidSyslogSenderDefinition.h` | Sender implementors (extension point) | `SolidSyslogSender` vtable struct (`Send`, `Disconnect`) | | `SolidSyslogNullSender.h` | Any code installing a no-op sender slot (Send returns `true` to drop on the floor — keeps Store from filling with undeliverables; Disconnect is a no-op) | `SolidSyslogNullSender_Get` | @@ -435,10 +436,13 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogFreeRtosSysUpTime.h` | SysUpTime callback implementor using `xTaskGetTickCount` on FreeRTOS targets | `SolidSyslogFreeRtosSysUpTime_Get` (returns `uint32_t` hundredths since boot, wraps per RFC 3418 `TimeTicks`; uint64 intermediate so the result is correct at any `configTICK_RATE_HZ`) | | `SolidSyslogWindowsSleep.h` | System setup code wiring a `SolidSyslogSleepFunction` on Windows targets | `SolidSyslogWindowsSleep` (wraps `Sleep`) | | `SolidSyslogSleep.h` | Any code passing or implementing a sleep callback | `SolidSyslogSleepFunction` typedef (used by `SolidSyslogTlsStreamConfig.sleep` for the bounded handshake retry) | -| `SolidSyslogStructuredData.h` | Library internals (SD dispatch) | `SolidSyslogStructuredData_Format` (writes into `SolidSyslogFormatter*`) | -| `SolidSyslogStructuredDataDefinition.h` | SD implementors (extension point) | `SolidSyslogStructuredData` vtable struct (Format takes `SolidSyslogFormatter*`) | +| `SolidSyslogStructuredData.h` | Library internals (SD dispatch) | `SolidSyslogStructuredData_Format` (writes into a `SolidSyslogSdElement*`) | +| `SolidSyslogStructuredDataDefinition.h` | SD implementors (extension point) | `SolidSyslogStructuredData` vtable struct (Format takes a `SolidSyslogSdElement*`, not a formatter) | +| `SolidSyslogSdElement.h` | SD implementors framing one `[SD-ID PARAM="value"…]` element | Opaque `struct SolidSyslogSdElement` + `_Begin(name, enterpriseNumber)`, `_Param(name) → SolidSyslogSdValue*`, `_End`. Owns the brackets, `@`-enterprise SD-ID suffix, and SD-NAME validation (NULL name → element/param skipped); a producer cannot break the framing. | +| `SolidSyslogSdValue.h` | SD implementors writing a PARAM value | Opaque `struct SolidSyslogSdValue` + `_String`, `_BoundedString`, `_Uint32` — appends a PARAM-VALUE with RFC 5424 `"`/`\`/`]` escaping applied by the library, streaming across calls. | +| `SolidSyslogSdValueFunction.h` | Any code needing the SD-value callback typedef | `SolidSyslogSdValueFunction` (callback writes into a `SolidSyslogSdValue*` + `void* context`) — used by `SolidSyslogMetaSdConfig` (language) and `SolidSyslogOriginSdConfig`. | | `SolidSyslogNullSd.h` | Any code installing a no-op Structured Data slot in `SolidSyslogConfig.Sd[]` | `SolidSyslogNullSd_Get` | -| `SolidSyslogMetaSd.h` | System setup code using meta SD (sequenceId, sysUpTime, language) | `SolidSyslogMetaSdConfig` (counter, getSysUpTime, getLanguage — each independently optional via NULL), `SolidSyslogSysUpTimeFunction`, `SolidSyslogMetaSd_Create(config)`, `_Destroy(sd)`. Instance struct lives in a library-internal static pool (E11). Bad-config and pool exhaustion both resolve to the shared `SolidSyslogNullSd`. | +| `SolidSyslogMetaSd.h` | System setup code using meta SD (sequenceId, sysUpTime, language) | `SolidSyslogMetaSdConfig` (counter, getSysUpTime, getLanguage — each independently optional via NULL; `getLanguage` is a `SolidSyslogSdValueFunction`), `SolidSyslogSysUpTimeFunction`, `SolidSyslogMetaSd_Create(config)`, `_Destroy(sd)`. Instance struct lives in a library-internal static pool (E11). Bad-config and pool exhaustion both resolve to the shared `SolidSyslogNullSd`. | | `SolidSyslogMetaSdErrors.h` | Any code installing an error handler that wants to react to MetaSd-specific events (pointer-identity match on `MetaSdErrorSource`, switch on `enum SolidSyslogMetaSdErrors`) | `enum SolidSyslogMetaSdErrors` (`METASD_ERROR_*` codes + `METASD_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource MetaSdErrorSource`. Integrators ignore if not handling errors per source. | | `SolidSyslogAtomicCounter.h` | Any code holding a counter handle | `SolidSyslogAtomicCounter_Increment(base)` — public vtable-dispatched call. Wrap-aware in [1, 2³¹ - 1] per RFC 5424 §7.3.1, never returns 0. | | `SolidSyslogAtomicCounterDefinition.h` | AtomicCounter implementors (extension point) | `SolidSyslogAtomicCounter` vtable struct (`Increment` function pointer) | @@ -450,7 +454,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogTimeQuality.h` | Any code providing time quality data | `SolidSyslogTimeQuality`, `SolidSyslogTimeQualityFunction`, `SOLIDSYSLOG_SYNC_ACCURACY_OMIT` | | `SolidSyslogTimeQualitySd.h` | System setup code using timeQuality SD | `SolidSyslogTimeQualitySd_Create(getTimeQuality)`, `_Destroy(sd)`. Instance struct lives in a library-internal static pool (E11). Pool exhaustion resolves to the shared `SolidSyslogNullSd`. | | `SolidSyslogTimeQualitySdErrors.h` | Any code installing an error handler that wants to react to TimeQualitySd-specific events (pointer-identity match on `TimeQualitySdErrorSource`, switch on `enum SolidSyslogTimeQualitySdErrors`) | `enum SolidSyslogTimeQualitySdErrors` (`TIMEQUALITYSD_ERROR_*` codes + `TIMEQUALITYSD_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource TimeQualitySdErrorSource`. Integrators ignore if not handling errors per source. | -| `SolidSyslogOriginSd.h` | System setup code using origin SD (software, swVersion, enterpriseId, ip) | `SolidSyslogOriginSdConfig` (software, swVersion, enterpriseId, getIpCount, getIpAt — each independently optional via NULL), `SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`, `SolidSyslogOriginSd_Create(config)`, `_Destroy(sd)`. Instance struct lives in a library-internal static pool (E11); each slot carries the pre-formatted static-prefix Formatter storage. Pool exhaustion resolves to the shared `SolidSyslogNullSd`. | +| `SolidSyslogOriginSd.h` | System setup code using origin SD (software, swVersion, enterpriseId, ip) | `SolidSyslogOriginSdConfig` (software, swVersion, enterpriseId, getIpCount, getIpAt — each independently optional via NULL), `SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`, `SolidSyslogOriginSd_Create(config)`, `_Destroy(sd)`. `getIpAt` is a `SolidSyslogOriginIpAtFunction` writing each address into an `SolidSyslogSdValue*`. Instance struct lives in a library-internal static pool (E11); each slot borrows its config strings and emits the whole element at Format time (no pre-formatted scratch storage). Pool exhaustion resolves to the shared `SolidSyslogNullSd`. | | `SolidSyslogOriginSdErrors.h` | Any code installing an error handler that wants to react to OriginSd-specific events (pointer-identity match on `OriginSdErrorSource`, switch on `enum SolidSyslogOriginSdErrors`) | `enum SolidSyslogOriginSdErrors` (`ORIGINSD_ERROR_*` codes + `ORIGINSD_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource OriginSdErrorSource`. Integrators ignore if not handling errors per source. | Most application code only needs `SolidSyslog.h` — it never sees allocators, senders, buffers, or config structs. diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index c9c0ea78..8a234270 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -23,9 +23,9 @@ Status key: | 6.2.5 | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | | 6.2.6 | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | | 6.3 | STRUCTURED-DATA — SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | -| 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogFormatter_EscapedString` — RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9); `OriginSd` escapes software, swVersion, enterpriseId, and each ip via this primitive; `MetaSd` escapes language via the integrator's `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies the same escaping. SD-NAME / SD-ID syntax validation only matters once callers can supply names — landed under [E14](https://github.com/DavidCozens/solid-syslog/issues/64) (Custom Structured Data); the standard SDs (meta / timeQuality / origin) use compile-time-constant names | +| 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. SD-NAME / SD-ID syntax validation only matters once callers can supply names — landed under [E14](https://github.com/DavidCozens/solid-syslog/issues/64) (Custom Structured Data); the standard SDs (meta / timeQuality / origin) use compile-time-constant names | | 6.3.3 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | -| 6.3.4, 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; each is escaped per §6.3.3 via `SolidSyslogFormatter_EscapedString` and pre-formatted at Create time into an internal storage buffer. `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1 framing each `ip="…"` token (with a leading space) while the integrator's at-callback writes one escaped IP value per call (this avoids a formatter rewind primitive). All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. Per-IP value capped at 64 chars via `_EscapedString`; no library-side cap on the IP count (bounded by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`). Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | +| 6.3.4, 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | | 6.3.5, 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | | 6.3.5, 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Supported | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one ([S12.13](https://github.com/DavidCozens/solid-syslog/issues/219)). Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint ([S12.10](https://github.com/DavidCozens/solid-syslog/issues/121)), and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU ([S12.12](https://github.com/DavidCozens/solid-syslog/issues/210)). TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | From 8dbe422dba30f5ba2f03cb76571a43736887ec01 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 12:46:33 +0000 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20S14.08=20pre-PR=20=E2=80=94=20MISR?= =?UTF-8?q?A=20suppressions,=20formatting,=20DEVLOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update misra_suppressions.txt for the SolidSyslogFormatter.h Core/Interface → Core/Source path move (11.2/11.3 cast 28→35, 5.7 stays 14) and the StreamSender/UdpSender line drift from the endpoint-host cutover (verified by re-running CI's exact --addon=misra command: branch returns exit 0, fired set matches the main baseline). clang-format reflow on the new sink files; DEVLOG entry for the session. Co-Authored-By: Claude Opus 4.8 (1M context) --- Core/Interface/SolidSyslogEndpointHost.h | 6 +-- Core/Source/SolidSyslogEndpointHost.c | 5 +-- Core/Source/SolidSyslogFormatter.h | 2 +- DEVLOG.md | 53 ++++++++++++++++++++++++ misra_suppressions.txt | 16 +++---- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/Core/Interface/SolidSyslogEndpointHost.h b/Core/Interface/SolidSyslogEndpointHost.h index ec477cce..cc2c4042 100644 --- a/Core/Interface/SolidSyslogEndpointHost.h +++ b/Core/Interface/SolidSyslogEndpointHost.h @@ -19,11 +19,7 @@ EXTERN_C_BEGIN /* Appends up to maxLength bytes of source (stopping at a NUL terminator) * verbatim into the host sink, further bounded by the host-field width the * sink was created with. */ - void SolidSyslogEndpointHost_String( - struct SolidSyslogEndpointHost* host, - const char* source, - size_t maxLength - ); + void SolidSyslogEndpointHost_String(struct SolidSyslogEndpointHost * host, const char* source, size_t maxLength); EXTERN_C_END diff --git a/Core/Source/SolidSyslogEndpointHost.c b/Core/Source/SolidSyslogEndpointHost.c index dd33589d..72095a2f 100644 --- a/Core/Source/SolidSyslogEndpointHost.c +++ b/Core/Source/SolidSyslogEndpointHost.c @@ -2,10 +2,7 @@ #include "SolidSyslogFormatter.h" -void SolidSyslogEndpointHost_FromFormatter( - struct SolidSyslogEndpointHost* host, - struct SolidSyslogFormatter* formatter -) +void SolidSyslogEndpointHost_FromFormatter(struct SolidSyslogEndpointHost* host, struct SolidSyslogFormatter* formatter) { host->Formatter = formatter; } diff --git a/Core/Source/SolidSyslogFormatter.h b/Core/Source/SolidSyslogFormatter.h index 9aeea249..32918632 100644 --- a/Core/Source/SolidSyslogFormatter.h +++ b/Core/Source/SolidSyslogFormatter.h @@ -26,7 +26,7 @@ EXTERN_C_BEGIN (((bufferSize) + sizeof(SolidSyslogFormatterStorage) - 1U) / sizeof(SolidSyslogFormatterStorage))) #define SOLIDSYSLOG_ESCAPED_MAX_SIZE(maxDecodedLength) (2U * (maxDecodedLength)) -/* NOLINTEND(cppcoreguidelines-macro-usage) */ + /* NOLINTEND(cppcoreguidelines-macro-usage) */ struct SolidSyslogFormatter; diff --git a/DEVLOG.md b/DEVLOG.md index b4680980..1c7d6889 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -14604,3 +14604,56 @@ MISRA rule — different category, doesn't set precedent. 5424 §6 ABNF is the source of the per-field caps (HOSTNAME 255 / APP-NAME 48 / PROCID 128 / MSGID 32), and streaming straight to the message buffer is exactly why the writer must carry the cap explicitly now (the old scratch formatter enforced it via its capacity). + +## 2026-06-06 — S14.08: make SolidSyslogFormatter library-private (#548) + +### Blocker resolved first +- #548's premise ("no remaining public consumer of `SolidSyslogFormatter`") was + wrong: `SolidSyslogEndpoint.Host` was still a raw `SolidSyslogFormatter*` the + endpoint callback wrote the destination host into. David's call (over reusing + `SolidSyslogHeaderField`, a plain `char[]`, or rescoping #548): **a new + dedicated endpoint-host writer**, mirroring the S14.07 HeaderField migration. + +### Decisions +- **New `SolidSyslogEndpointHost` sink** (TDD'd first, 3 tests): opaque, + stack-transient, wraps the sender's stack host formatter. `_String(source, + maxLength)` does a **verbatim** bounded copy — *no* PRINTUSASCII substitution, + unlike HeaderField — because a DNS name / IP literal headed to the resolver + must not be silently mangled. That charset difference is exactly why it's its + own type, not `SolidSyslogHeaderField`. +- **Cutover** flipped `SolidSyslogEndpoint.Host` to `SolidSyslogEndpointHost*`. + Every endpoint-host writer migrated together: both senders (wrap → call back → + read host out, byte-identical), StreamSender nil-endpoint, **all five BDD + configs** (Tcp/Udp/Tls/Mtls/Windows), the FreeRTOS pipeline, and the BDD + service-thread test. +- **Surprise:** the cutover was *not* just the two senders — five `Bdd/Targets` + config files + a BDD test also wrote `endpoint->Host` via the old formatter + API. They compile only in `BddTargetTests` / the BDD lanes, **not** the + `debug`/`junit` target, so the type mismatch was invisible to a plain debug + build. Caught by a repo-wide grep + building `BddTargetTests` explicitly. +- **`SolidSyslogFormatter.h` moved** `Core/Interface/` → `Core/Source/`. No CMake + include-path change needed: Platform sources compile into the `${PROJECT_NAME}` + target (so inherit its PRIVATE `Core/Source` view), and Tests/BddTargetTests + already add `Core/Source` to their include dirs. The two sizing macros gained a + `NOLINTBEGIN/END(cppcoreguidelines-macro-usage)` pair (the `Core/Interface` + tier-wide disable no longer covers them; matches `SolidSyslogMacros.h`). +- **CLAUDE.md table swept** (deferred since S14.01): dropped Formatter + + StringFunction rows; added EndpointHost / HeaderField(+Function) / SdElement / + SdValue / SdValueFunction; fixed Endpoint / StructuredData(Definition) / MetaSd + / OriginSd. Swept the stale OriginSd lines in `rfc-compliance.md`. +- cppcheck-MISRA path-move handling: the renumber script can't migrate a file + *path* change, and local cppcheck 2.10 prints baseline noise main also prints + (exit 0). Reproduced CI's exact `--addon=misra` command against a clean-main + worktree to isolate *my* drift, then updated 8 suppressions (Formatter.h path + + cast 28→35; StreamSender/UdpSender +1 line shifts). Re-ran → exit 0, fired + set == main baseline. +- Checks: debug green (1474 tests), BddTargetTests 66/66; clang-format applied; + cppcheck-MISRA exit 0. + +### Deferred +- `docs/structured-data.md` (the SD-authoring design note) → **S14.09**, David's + call, so it lands beside the integrator guide rather than alone here. #548 to + get a note. E14 then closes with S14.09. + +### Open questions +- None outstanding. diff --git a/misra_suppressions.txt b/misra_suppressions.txt index c8b486a6..5d36ba1d 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -11,7 +11,7 @@ # D.002 — Rules 11.2 / 11.3 / 11.5: vtable downcasts + Formatter # See docs/misra-deviations.md#d002 -misra-c2012-11.2:Core/Interface/SolidSyslogFormatter.h:28 +misra-c2012-11.2:Core/Source/SolidSyslogFormatter.h:35 misra-c2012-11.2:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressPrivate.h:29 misra-c2012-11.2:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressPrivate.h:36 misra-c2012-11.2:Platform/PlusTcp/Source/SolidSyslogPlusTcpAddress.c:14 @@ -23,7 +23,7 @@ misra-c2012-11.2:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:28 misra-c2012-11.2:Platform/Windows/Source/SolidSyslogWinsockAddress.c:13 misra-c2012-11.2:Platform/Windows/Source/SolidSyslogWinsockAddressPrivate.h:23 misra-c2012-11.2:Platform/Windows/Source/SolidSyslogWinsockAddressPrivate.h:30 -misra-c2012-11.3:Core/Interface/SolidSyslogFormatter.h:28 +misra-c2012-11.3:Core/Source/SolidSyslogFormatter.h:35 misra-c2012-11.3:Core/Source/SolidSyslogBlockStore.c:73 misra-c2012-11.3:Core/Source/SolidSyslogCircularBuffer.c:91 misra-c2012-11.3:Core/Source/SolidSyslogFileBlockDevice.c:106 @@ -31,10 +31,10 @@ misra-c2012-11.3:Core/Source/SolidSyslogFormatter.c:79 misra-c2012-11.3:Core/Source/SolidSyslogMetaSd.c:60 misra-c2012-11.3:Core/Source/SolidSyslogOriginSd.c:57 misra-c2012-11.3:Core/Source/SolidSyslogPassthroughBuffer.c:53 -misra-c2012-11.3:Core/Source/SolidSyslogStreamSender.c:166 +misra-c2012-11.3:Core/Source/SolidSyslogStreamSender.c:169 misra-c2012-11.3:Core/Source/SolidSyslogSwitchingSender.c:63 misra-c2012-11.3:Core/Source/SolidSyslogTimeQualitySd.c:54 -misra-c2012-11.3:Core/Source/SolidSyslogUdpSender.c:120 +misra-c2012-11.3:Core/Source/SolidSyslogUdpSender.c:121 misra-c2012-11.3:Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c:29 misra-c2012-11.3:Platform/FatFs/Source/SolidSyslogFatFsFile.c:59 misra-c2012-11.3:Platform/PlusFat/Source/SolidSyslogPlusFatFile.c:47 @@ -80,7 +80,7 @@ misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsFile.c:77 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsMutex.c:38 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:98 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:185 -misra-c2012-11.5:Core/Source/SolidSyslogUdpSender.c:224 +misra-c2012-11.5:Core/Source/SolidSyslogUdpSender.c:227 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c:74 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:149 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:211 @@ -138,10 +138,10 @@ misra-c2012-2.4:Core/Interface/SolidSyslogTransport.h:5 misra-c2012-2.4:Core/Source/BlockSequence.c:13 misra-c2012-2.4:Core/Source/BlockSequence.c:107 misra-c2012-2.4:Core/Source/RecordStore.c:14 -misra-c2012-2.4:Core/Source/SolidSyslogStreamSender.c:24 +misra-c2012-2.4:Core/Source/SolidSyslogStreamSender.c:25 misra-c2012-2.4:Core/Interface/SolidSyslogUdpPayload.h:15 misra-c2012-5.7:Core/Interface/SolidSyslogUdpPayload.h:15 -misra-c2012-5.7:Core/Source/SolidSyslogStreamSender.c:24 +misra-c2012-5.7:Core/Source/SolidSyslogStreamSender.c:25 misra-c2012-5.7:Core/Source/SolidSyslogUdpPayload.c:5 misra-c2012-5.7:Platform/PlusTcp/Source/SolidSyslogPlusTcpResolver.c:25 misra-c2012-5.7:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:32 @@ -167,7 +167,7 @@ misra-c2012-5.7:Platform/Windows/Source/SolidSyslogWindowsFile.c:28 misra-c2012-5.7:Core/Interface/SolidSyslogAtomicCounter.h:11 misra-c2012-5.7:Core/Interface/SolidSyslogCircularBuffer.h:17 misra-c2012-5.7:Core/Interface/SolidSyslogEndpoint.h:11 -misra-c2012-5.7:Core/Interface/SolidSyslogFormatter.h:14 +misra-c2012-5.7:Core/Source/SolidSyslogFormatter.h:14 misra-c2012-5.7:Core/Interface/SolidSyslogSecurityPolicyDefinition.h:13 misra-c2012-5.7:Core/Interface/SolidSyslogTimeQuality.h:12 misra-c2012-5.7:Core/Interface/SolidSyslogTransport.h:5