From 75bf2e1da00f3348bc9eaad7cf4e9de696ab86c5 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 12:55:16 +0100 Subject: [PATCH 01/18] feat: S11.04 add SolidSyslogNullSd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared GoF null Structured Data for S11.04's sweep. Three classes (MetaSd, TimeQualitySd, OriginSd) need the same no-op fallback when their pool is exhausted; one Null type avoids three class-private copies. Get-only API — the instance is immutable and shared across consumers, so no _Create/_Destroy lifecycle is appropriate. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 1 + Core/Interface/SolidSyslogNullSd.h | 12 ++++++++++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogNullSd.c | 18 ++++++++++++++++++ Tests/CMakeLists.txt | 1 + Tests/SolidSyslogNullSdTest.cpp | 28 ++++++++++++++++++++++++++++ 6 files changed, 61 insertions(+) create mode 100644 Core/Interface/SolidSyslogNullSd.h create mode 100644 Core/Source/SolidSyslogNullSd.c create mode 100644 Tests/SolidSyslogNullSdTest.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 441ead56..76e7f2d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -375,6 +375,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `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*`) | +| `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`, `_Destroy` | | `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) | diff --git a/Core/Interface/SolidSyslogNullSd.h b/Core/Interface/SolidSyslogNullSd.h new file mode 100644 index 00000000..e2f59117 --- /dev/null +++ b/Core/Interface/SolidSyslogNullSd.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLSD_H +#define SOLIDSYSLOGNULLSD_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogStructuredData* SolidSyslogNullSd_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLSD_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 377305ff..0b60c25a 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES SolidSyslogNullMutex.c SolidSyslogSender.c SolidSyslogStore.c + SolidSyslogNullSd.c SolidSyslogNullStore.c SolidSyslogNullSecurityPolicy.c SolidSyslogCrc16.c diff --git a/Core/Source/SolidSyslogNullSd.c b/Core/Source/SolidSyslogNullSd.c new file mode 100644 index 00000000..3ba2e3d8 --- /dev/null +++ b/Core/Source/SolidSyslogNullSd.c @@ -0,0 +1,18 @@ +#include "SolidSyslogNullSd.h" + +#include "SolidSyslogStructuredDataDefinition.h" + +static void NullSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); + +static struct SolidSyslogStructuredData instance = {.Format = NullSd_Format}; + +struct SolidSyslogStructuredData* SolidSyslogNullSd_Get(void) +{ + return &instance; +} + +static void NullSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter) +{ + (void) base; + (void) formatter; +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index a427d719..9527ae1c 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -27,6 +27,7 @@ set(TEST_SOURCES SolidSyslogCircularBufferTest.cpp SolidSyslogPoolAllocatorTest.cpp SolidSyslogNullMutexTest.cpp + SolidSyslogNullSdTest.cpp SolidSyslogNullStoreTest.cpp SolidSyslogNullSecurityPolicyTest.cpp SolidSyslogCrc16Test.cpp diff --git a/Tests/SolidSyslogNullSdTest.cpp b/Tests/SolidSyslogNullSdTest.cpp new file mode 100644 index 00000000..61ddc967 --- /dev/null +++ b/Tests/SolidSyslogNullSdTest.cpp @@ -0,0 +1,28 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogFormatter.h" +#include "SolidSyslogNullSd.h" +#include "SolidSyslogStructuredData.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullSd) +{ + struct SolidSyslogStructuredData* sd = nullptr; + SolidSyslogFormatterStorage formatterStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(32)]; + struct SolidSyslogFormatter* formatter = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + sd = SolidSyslogNullSd_Get(); + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + formatter = SolidSyslogFormatter_Create(formatterStorage, 32); + } +}; + +// clang-format on + +TEST(SolidSyslogNullSd, FormatWritesNothing) +{ + SolidSyslogStructuredData_Format(sd, formatter); + LONGS_EQUAL(0, SolidSyslogFormatter_Length(formatter)); +} From 047f57a3bb39136946d7dab6bab58a0cd727f52c Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 12:56:34 +0100 Subject: [PATCH 02/18] feat: S11.04 add SolidSyslogNullSender MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared GoF null Sender for S11.04's sweep. UdpSender and SwitchingSender both need the same fallback when their pool is exhausted, and S11.05's StreamSender migration will reuse it too — one Null type keeps the fallback definition in one place. Send returns false (delivery semantics: "didn't send" — Service then takes the store-and-forward path or drops). Disconnect is a no-op. Get-only API because the instance is immutable and shared. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 1 + Core/Interface/SolidSyslogNullSender.h | 12 +++++++++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogNullSender.c | 29 ++++++++++++++++++++++++++ Tests/CMakeLists.txt | 1 + Tests/SolidSyslogNullSenderTest.cpp | 27 ++++++++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 Core/Interface/SolidSyslogNullSender.h create mode 100644 Core/Source/SolidSyslogNullSender.c create mode 100644 Tests/SolidSyslogNullSenderTest.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 76e7f2d7..d173ea96 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -321,6 +321,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogEndpoint.h` | System setup code that supplies destination host/port (and version on changes) | `SolidSyslogEndpoint`, `SolidSyslogEndpointFunction`, `SolidSyslogEndpointVersionFunction`, `SOLIDSYSLOG_MAX_HOST_SIZE` | | `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 `false`, Disconnect is a no-op) | `SolidSyslogNullSender_Get` | | `SolidSyslogTransport.h` | Any code selecting a transport or needing default port constants | `SolidSyslogTransport` enum (`UDP`, `TCP`), `SOLIDSYSLOG_UDP_DEFAULT_PORT`, `SOLIDSYSLOG_TCP_DEFAULT_PORT` | | `SolidSyslogResolver.h` | Any code that needs to resolve a destination | `SolidSyslogResolver_Resolve(resolver, transport, host, port, *out)` | | `SolidSyslogResolverDefinition.h` | Resolver implementors (extension point) | `SolidSyslogResolver` vtable struct (`Resolve`) | diff --git a/Core/Interface/SolidSyslogNullSender.h b/Core/Interface/SolidSyslogNullSender.h new file mode 100644 index 00000000..eb3e874c --- /dev/null +++ b/Core/Interface/SolidSyslogNullSender.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLSENDER_H +#define SOLIDSYSLOGNULLSENDER_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogSender* SolidSyslogNullSender_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLSENDER_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 0b60c25a..241cba9b 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -13,6 +13,7 @@ set(SOURCES SolidSyslogMutex.c SolidSyslogNullMutex.c SolidSyslogSender.c + SolidSyslogNullSender.c SolidSyslogStore.c SolidSyslogNullSd.c SolidSyslogNullStore.c diff --git a/Core/Source/SolidSyslogNullSender.c b/Core/Source/SolidSyslogNullSender.c new file mode 100644 index 00000000..c0c5afa8 --- /dev/null +++ b/Core/Source/SolidSyslogNullSender.c @@ -0,0 +1,29 @@ +#include "SolidSyslogNullSender.h" + +#include +#include + +#include "SolidSyslogSenderDefinition.h" + +static bool NullSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size); +static void NullSender_Disconnect(struct SolidSyslogSender* base); + +static struct SolidSyslogSender instance = {.Send = NullSender_Send, .Disconnect = NullSender_Disconnect}; + +struct SolidSyslogSender* SolidSyslogNullSender_Get(void) +{ + return &instance; +} + +static bool NullSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size) +{ + (void) base; + (void) buffer; + (void) size; + return false; +} + +static void NullSender_Disconnect(struct SolidSyslogSender* base) +{ + (void) base; +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 9527ae1c..b2fb4ab2 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -28,6 +28,7 @@ set(TEST_SOURCES SolidSyslogPoolAllocatorTest.cpp SolidSyslogNullMutexTest.cpp SolidSyslogNullSdTest.cpp + SolidSyslogNullSenderTest.cpp SolidSyslogNullStoreTest.cpp SolidSyslogNullSecurityPolicyTest.cpp SolidSyslogCrc16Test.cpp diff --git a/Tests/SolidSyslogNullSenderTest.cpp b/Tests/SolidSyslogNullSenderTest.cpp new file mode 100644 index 00000000..5227b80f --- /dev/null +++ b/Tests/SolidSyslogNullSenderTest.cpp @@ -0,0 +1,27 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogNullSender.h" +#include "SolidSyslogSender.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullSender) +{ + struct SolidSyslogSender* sender = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + sender = SolidSyslogNullSender_Get(); + } +}; + +// clang-format on + +TEST(SolidSyslogNullSender, SendReturnsFalse) +{ + CHECK_FALSE(SolidSyslogSender_Send(sender, "hello", 5)); +} + +TEST(SolidSyslogNullSender, DisconnectDoesNotCrash) +{ + SolidSyslogSender_Disconnect(sender); +} From 4f30094b8a544a248310c0aa32022389c43a1e0b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 13:05:30 +0100 Subject: [PATCH 03/18] refactor: S11.04 flip SolidSyslogNullStore to Get-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Null Object is immutable and inherently shared — Create/Destroy implied a lifecycle that doesn't exist, and a Destroy call from one consumer would have torn down the vtable for any other consumer holding the same instance. Replace with a single Get() returning the shared static-initialised instance. Drops the SolidSyslogNullStore wrapper struct (no extra fields) in favour of returning &instance directly typed as SolidSyslogStore. All callers (test fixtures, BDD targets) updated. DestroyStore in the three BDD targets now no-ops when the store is the Null one. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/Targets/FreeRtos/main.c | 7 +--- Bdd/Targets/Linux/main.c | 7 +--- Bdd/Targets/Windows/BddTargetWindows.c | 7 +--- CLAUDE.md | 2 +- Core/Interface/SolidSyslogNullStore.h | 3 +- Core/Source/SolidSyslogNullStore.c | 38 ++++++------------- .../Targets/BddTargetServiceThreadTest.cpp | 3 +- Tests/SolidSyslogNullStoreTest.cpp | 7 +--- ...SolidSyslogPosixMessageQueueBufferTest.cpp | 3 +- Tests/SolidSyslogTest.cpp | 9 ++--- 10 files changed, 25 insertions(+), 61 deletions(-) diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 10aed951..0889d92e 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -631,10 +631,7 @@ static void DestroyCurrentStore(void) SolidSyslogCrc16Policy_Destroy(); SolidSyslogFatFsFile_Destroy(storeFile); } - else - { - SolidSyslogNullStore_Destroy(); - } + /* else: NullStore is shared and immutable — nothing to destroy. */ } /* Full teardown of every resource InteractiveTask allocated during Setup. @@ -832,7 +829,7 @@ static void InteractiveTask(void* argument) /* Default store is NullStore — flipped to FatFs/BlockStore by * `set store file` via RebuildWithFileStore(). */ - currentStore = SolidSyslogNullStore_Create(); + currentStore = SolidSyslogNullStore_Get(); currentStoreIsFile = false; atomicCounter = SolidSyslogStdAtomicCounter_Create(&atomicCounterStorage); diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 81e998ab..4ed27f08 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -178,7 +178,7 @@ static struct SolidSyslogStore* CreateStore(const struct BddTargetOptions* optio return SolidSyslogBlockStore_Create(&storeStorage, &storeConfig); } - return SolidSyslogNullStore_Create(); + return SolidSyslogNullStore_Get(); } static void DestroySender(void) @@ -203,10 +203,7 @@ static void DestroyStore(struct SolidSyslogStore* store, const struct BddTargetO SolidSyslogCrc16Policy_Destroy(); SolidSyslogPosixFile_Destroy(storeFile); } - else - { - SolidSyslogNullStore_Destroy(); - } + /* else: NullStore is shared and immutable — nothing to destroy. */ } int main(int argc, char* argv[]) diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 682d1404..6661668f 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -252,7 +252,7 @@ static struct SolidSyslogStore* CreateStore(const struct BddTargetWindowsOptions return SolidSyslogBlockStore_Create(&storeStorage, &storeConfig); } - return SolidSyslogNullStore_Create(); + return SolidSyslogNullStore_Get(); } static void DestroyStore(struct SolidSyslogStore* store, const struct BddTargetWindowsOptions* options) @@ -266,10 +266,7 @@ static void DestroyStore(struct SolidSyslogStore* store, const struct BddTargetW SolidSyslogCrc16Policy_Destroy(); SolidSyslogWindowsFile_Destroy(storeFile); } - else - { - SolidSyslogNullStore_Destroy(); - } + /* else: NullStore is shared and immutable — nothing to destroy. */ } int BddTargetWindows_Run(int argc, char* argv[]) diff --git a/CLAUDE.md b/CLAUDE.md index d173ea96..658ba891 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -348,7 +348,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogFreeRtosMutex.h` | System setup code on FreeRTOS targets needing thread-safe buffering | `SolidSyslogFreeRtosMutexStorage`, `SOLIDSYSLOG_FREERTOSMUTEX_SIZE`, `SolidSyslogFreeRtosMutex_Create(storage)`, `_Destroy(mutex)` (wraps `xSemaphoreCreateMutexStatic` — caller's `StaticSemaphore_t` lives inside the injected storage; requires `configSUPPORT_STATIC_ALLOCATION=1`) | | `SolidSyslogStore.h` | Library internals consuming a store (Service algorithm) and integrator code querying capacity | `SolidSyslogStore_Write`, `_ReadNextUnsent`, `_MarkSent`, `_HasUnsent`, `_IsHalted`, `_GetTotalBytes`, `_GetUsedBytes` | | `SolidSyslogStoreDefinition.h` | Store implementors (extension point) | `SolidSyslogStore` vtable struct (`Write`, `ReadNextUnsent`, `MarkSent`, `HasUnsent`, `IsHalted`, `GetTotalBytes`, `GetUsedBytes`) | -| `SolidSyslogNullStore.h` | System setup code (no store-and-forward) | `SolidSyslogNullStore_Create`, `_Destroy` | +| `SolidSyslogNullStore.h` | System setup code (no store-and-forward) | `SolidSyslogNullStore_Get` | | `SolidSyslogFileDefinition.h` | File implementors (extension point) | `SolidSyslogFile` vtable struct | | `SolidSyslogFile.h` | Any code using the file abstraction | `SolidSyslogFile_Open`, `_Close`, `_IsOpen`, `_Read`, `_Write`, `_SeekTo`, `_Size`, `_Truncate` | | `SolidSyslogBlockDevice.h` | Library internals consuming a block device (BlockSequence inside BlockStore) and integrator code addressing blocks directly | `SolidSyslogBlockDevice_Acquire`, `_Dispose`, `_Exists`, `_Read`, `_Append`, `_WriteAt`, `_Size` (block-indexed I/O; Acquire makes a block ready for fresh writes, Dispose releases it) | diff --git a/Core/Interface/SolidSyslogNullStore.h b/Core/Interface/SolidSyslogNullStore.h index 7979c477..662cf365 100644 --- a/Core/Interface/SolidSyslogNullStore.h +++ b/Core/Interface/SolidSyslogNullStore.h @@ -5,8 +5,7 @@ EXTERN_C_BEGIN - struct SolidSyslogStore* SolidSyslogNullStore_Create(void); - void SolidSyslogNullStore_Destroy(void); + struct SolidSyslogStore* SolidSyslogNullStore_Get(void); EXTERN_C_END diff --git a/Core/Source/SolidSyslogNullStore.c b/Core/Source/SolidSyslogNullStore.c index 2ecbb303..f2789a8f 100644 --- a/Core/Source/SolidSyslogNullStore.c +++ b/Core/Source/SolidSyslogNullStore.c @@ -14,36 +14,20 @@ static size_t NullStore_GetTotalBytes(struct SolidSyslogStore* base); static size_t NullStore_GetUsedBytes(struct SolidSyslogStore* base); static bool NullStore_IsTransient(struct SolidSyslogStore* base); -struct SolidSyslogNullStore -{ - struct SolidSyslogStore Base; +static struct SolidSyslogStore instance = { + .Write = NullStore_Write, + .ReadNextUnsent = NullStore_ReadNextUnsent, + .MarkSent = NullStore_MarkSent, + .HasUnsent = NullStore_HasUnsent, + .IsHalted = NullStore_IsHalted, + .GetTotalBytes = NullStore_GetTotalBytes, + .GetUsedBytes = NullStore_GetUsedBytes, + .IsTransient = NullStore_IsTransient }; -static struct SolidSyslogNullStore instance; - -struct SolidSyslogStore* SolidSyslogNullStore_Create(void) -{ - instance.Base.Write = NullStore_Write; - instance.Base.ReadNextUnsent = NullStore_ReadNextUnsent; - instance.Base.MarkSent = NullStore_MarkSent; - instance.Base.HasUnsent = NullStore_HasUnsent; - instance.Base.IsHalted = NullStore_IsHalted; - instance.Base.GetTotalBytes = NullStore_GetTotalBytes; - instance.Base.GetUsedBytes = NullStore_GetUsedBytes; - instance.Base.IsTransient = NullStore_IsTransient; - return &instance.Base; -} - -void SolidSyslogNullStore_Destroy(void) +struct SolidSyslogStore* SolidSyslogNullStore_Get(void) { - instance.Base.Write = NULL; - instance.Base.ReadNextUnsent = NULL; - instance.Base.MarkSent = NULL; - instance.Base.HasUnsent = NULL; - instance.Base.IsHalted = NULL; - instance.Base.GetTotalBytes = NULL; - instance.Base.GetUsedBytes = NULL; - instance.Base.IsTransient = NULL; + return &instance; } /* NullStore never retains. Returns false to signal "not held by this store" diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index a283d01e..46b8f34f 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -68,7 +68,7 @@ TEST_GROUP(BddTargetServiceThread) SolidSyslogUdpSenderConfig udpConfig = {SolidSyslogGetAddrInfoResolver_Create(), SolidSyslogPosixDatagram_Create(), BddTargetEndpoint, BddTargetEndpointVersion}; sender = SolidSyslogUdpSender_Create(&udpConfig); buffer = SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); - store = SolidSyslogNullStore_Create(); + store = SolidSyslogNullStore_Get(); SolidSyslogConfig config = {buffer, sender, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; SolidSyslog_Create(&config); @@ -77,7 +77,6 @@ TEST_GROUP(BddTargetServiceThread) void teardown() override { SolidSyslog_Destroy(); - SolidSyslogNullStore_Destroy(); SolidSyslogPosixMessageQueueBuffer_Destroy(); SolidSyslogUdpSender_Destroy(); SolidSyslogPosixDatagram_Destroy(); diff --git a/Tests/SolidSyslogNullStoreTest.cpp b/Tests/SolidSyslogNullStoreTest.cpp index bfa9b064..ac305b57 100644 --- a/Tests/SolidSyslogNullStoreTest.cpp +++ b/Tests/SolidSyslogNullStoreTest.cpp @@ -12,12 +12,7 @@ TEST_GROUP(SolidSyslogNullStore) void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - store = SolidSyslogNullStore_Create(); - } - - void teardown() override - { - SolidSyslogNullStore_Destroy(); + store = SolidSyslogNullStore_Get(); } }; diff --git a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp index 2afd53c6..e2484c12 100644 --- a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp +++ b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp @@ -101,7 +101,7 @@ TEST(SolidSyslogPosixMessageQueueBuffer, SecondReadAfterSingleWriteReturnsFalse) TEST(SolidSyslogPosixMessageQueueBuffer, ServiceSendsMessageWrittenViaLog) { struct SolidSyslogSender* fakeSender = SenderFake_Create(); - SolidSyslogStore* nullStore = SolidSyslogNullStore_Create(); + SolidSyslogStore* nullStore = SolidSyslogNullStore_Get(); SolidSyslogConfig config = {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; SolidSyslog_Create(&config); @@ -111,7 +111,6 @@ TEST(SolidSyslogPosixMessageQueueBuffer, ServiceSendsMessageWrittenViaLog) CALLED_FAKE_ON(SenderFake_Send, fakeSender, ONCE); SolidSyslog_Destroy(); - SolidSyslogNullStore_Destroy(); SenderFake_Destroy(fakeSender); } diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index 6cdc31ce..c4bae784 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -367,7 +367,7 @@ TEST_GROUP(SolidSyslog) fakeSender = SenderFake_Create(); StringFake_Reset(); buffer = SolidSyslogPassthroughBuffer_Create(fakeSender); - store = SolidSyslogNullStore_Create(); + store = SolidSyslogNullStore_Get(); config = {buffer, nullptr, nullptr, StringFake_GetHostname, StringFake_GetAppName, StringFake_GetProcessId, store, nullptr, 0}; SolidSyslog_Create(&config); // cppcheck-suppress unreadVariable -- read via Log() through &message; cppcheck does not model CppUTest macros @@ -377,7 +377,6 @@ TEST_GROUP(SolidSyslog) void teardown() override { SolidSyslog_Destroy(); - SolidSyslogNullStore_Destroy(); SolidSyslogPassthroughBuffer_Destroy(); SenderFake_Destroy(fakeSender); } @@ -1315,7 +1314,7 @@ TEST(SolidSyslog, ServiceSendsMessageReadFromBuffer) TEST(SolidSyslog, ServiceSendsBufferedMessageWithNullStore) { SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); - SolidSyslogStore* nullStore = SolidSyslogNullStore_Create(); + SolidSyslogStore* nullStore = SolidSyslogNullStore_Get(); SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; @@ -1331,7 +1330,6 @@ TEST(SolidSyslog, ServiceSendsBufferedMessageWithNullStore) SolidSyslog_Destroy(); SolidSyslog_Create(&config); - SolidSyslogNullStore_Destroy(); BufferFake_Destroy(); } @@ -1646,14 +1644,13 @@ TEST_GROUP(SolidSyslogLifecycle) // cppcheck-suppress unreadVariable -- read via validConfig() in tests; cppcheck does not model CppUTest macros buffer = BufferFake_Create(); // cppcheck-suppress unreadVariable -- read via validConfig() in tests; cppcheck does not model CppUTest macros - store = SolidSyslogNullStore_Create(); + store = SolidSyslogNullStore_Get(); } void teardown() override { SolidSyslog_Destroy(); ErrorHandlerFake_Uninstall(); - SolidSyslogNullStore_Destroy(); BufferFake_Destroy(); SenderFake_Destroy(sender); } From ab251c4c07482d2f9212b25bf73144b4d8333a44 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 13:08:00 +0100 Subject: [PATCH 04/18] refactor: S11.04 flip SolidSyslogNullSecurityPolicy to Get-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same reasoning as the NullStore flip: a Null Object is immutable and inherently shared, so Create/Destroy implied a lifecycle that didn't exist. The instance was already a function-scope static — lift to file scope, rename _Create to _Get, drop the no-op _Destroy. BlockStore's resolver and both test callers updated. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 2 +- Core/Interface/SolidSyslogNullSecurityPolicy.h | 3 +-- Core/Source/SolidSyslogBlockStore.c | 2 +- Core/Source/SolidSyslogNullSecurityPolicy.c | 17 +++++++---------- .../SolidSyslogBlockStoreDrainOrderingTest.cpp | 3 +-- Tests/SolidSyslogNullSecurityPolicyTest.cpp | 14 ++------------ 6 files changed, 13 insertions(+), 28 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 658ba891..ed1ae2cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -356,7 +356,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogFileBlockDevice.h` | System setup code wiring a file-backed block device | `SolidSyslogFileBlockDeviceStorage`, `SOLIDSYSLOG_FILEBLOCKDEVICE_STORAGE_SIZE`, `SolidSyslogFileBlockDevice_Create(storage, readFile, writeFile, pathPrefix)` (sequence-numbered filenames `.log`, two cached file handles), `_Destroy(device)` | | `SolidSyslogBlockStore.h` | System setup code using a BlockDevice-backed store | `SolidSyslogBlockStoreStorage`, `SOLIDSYSLOG_BLOCKSTORE_STORAGE_SIZE`, `SolidSyslogBlockStoreConfig` (with `blockDevice`, `storeFullContext`, `getCapacityThreshold`, `onThresholdCrossed`, `thresholdContext`), `SolidSyslogBlockStore_Create(storage, config)`, `_Destroy(store)`, `SolidSyslogDiscardPolicy` (`SolidSyslogDiscardPolicy_Oldest` / `_Newest` / `_Halt`), `SolidSyslogStoreFullCallback(void* context)`, `SolidSyslogStoreThresholdFunction(void* context)` (returns threshold in bytes; 0 disables; queried each Write), `SolidSyslogStoreThresholdCallback(void* context)` (edge-triggered; PassthroughBuffer recursion gotcha — see header) | | `SolidSyslogSecurityPolicyDefinition.h` | SecurityPolicy implementors (extension point) | `SolidSyslogSecurityPolicy` vtable struct, `SOLIDSYSLOG_MAX_INTEGRITY_SIZE` | -| `SolidSyslogNullSecurityPolicy.h` | System setup code (no integrity checking) | `SolidSyslogNullSecurityPolicy_Create`, `_Destroy` | +| `SolidSyslogNullSecurityPolicy.h` | System setup code (no integrity checking) | `SolidSyslogNullSecurityPolicy_Get` | | `SolidSyslogCrc16Policy.h` | System setup code using CRC-16 integrity | `SolidSyslogCrc16Policy_Create`, `_Destroy` | | `SolidSyslogCrc16.h` | Any code needing CRC-16 computation | `SolidSyslogCrc16_Compute` | | `SolidSyslogPosixFile.h` | System setup code using POSIX file I/O | `SolidSyslogPosixFile_Create`, `_Destroy` | diff --git a/Core/Interface/SolidSyslogNullSecurityPolicy.h b/Core/Interface/SolidSyslogNullSecurityPolicy.h index 714b216d..554f85b8 100644 --- a/Core/Interface/SolidSyslogNullSecurityPolicy.h +++ b/Core/Interface/SolidSyslogNullSecurityPolicy.h @@ -5,8 +5,7 @@ EXTERN_C_BEGIN - struct SolidSyslogSecurityPolicy* SolidSyslogNullSecurityPolicy_Create(void); - void SolidSyslogNullSecurityPolicy_Destroy(void); + struct SolidSyslogSecurityPolicy* SolidSyslogNullSecurityPolicy_Get(void); EXTERN_C_END diff --git a/Core/Source/SolidSyslogBlockStore.c b/Core/Source/SolidSyslogBlockStore.c index fced2459..2c38f459 100644 --- a/Core/Source/SolidSyslogBlockStore.c +++ b/Core/Source/SolidSyslogBlockStore.c @@ -90,7 +90,7 @@ static inline struct SolidSyslogSecurityPolicy* BlockStore_ResolveSecurityPolicy if ((resolved == NULL) || (resolved->IntegritySize > SOLIDSYSLOG_MAX_INTEGRITY_SIZE)) { - resolved = SolidSyslogNullSecurityPolicy_Create(); + resolved = SolidSyslogNullSecurityPolicy_Get(); } return resolved; diff --git a/Core/Source/SolidSyslogNullSecurityPolicy.c b/Core/Source/SolidSyslogNullSecurityPolicy.c index a569da5a..a7529277 100644 --- a/Core/Source/SolidSyslogNullSecurityPolicy.c +++ b/Core/Source/SolidSyslogNullSecurityPolicy.c @@ -21,16 +21,13 @@ static bool NullSecurityPolicy_NullVerifyIntegrity(const uint8_t* data, uint16_t return true; } -struct SolidSyslogSecurityPolicy* SolidSyslogNullSecurityPolicy_Create(void) -{ - static struct SolidSyslogSecurityPolicy instance = { - 0, - NullSecurityPolicy_NullComputeIntegrity, - NullSecurityPolicy_NullVerifyIntegrity, - }; - return &instance; -} +static struct SolidSyslogSecurityPolicy instance = { + 0, + NullSecurityPolicy_NullComputeIntegrity, + NullSecurityPolicy_NullVerifyIntegrity, +}; -void SolidSyslogNullSecurityPolicy_Destroy(void) +struct SolidSyslogSecurityPolicy* SolidSyslogNullSecurityPolicy_Get(void) { + return &instance; } diff --git a/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp b/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp index c4db0c67..7601d746 100644 --- a/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp +++ b/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp @@ -123,12 +123,11 @@ TEST_BASE(DrainTestFixtureBase) { file = FileFake_Create(&fileStorage); device = SolidSyslogFileBlockDevice_Create(&deviceStorage, file, TEST_PATH_PREFIX); - policy = SolidSyslogNullSecurityPolicy_Create(); + policy = SolidSyslogNullSecurityPolicy_Get(); } void teardownBlockDeviceAndPolicy() const { - SolidSyslogNullSecurityPolicy_Destroy(); SolidSyslogFileBlockDevice_Destroy(device); FileFake_Destroy(); } diff --git a/Tests/SolidSyslogNullSecurityPolicyTest.cpp b/Tests/SolidSyslogNullSecurityPolicyTest.cpp index 53b7cf90..fec6eb34 100644 --- a/Tests/SolidSyslogNullSecurityPolicyTest.cpp +++ b/Tests/SolidSyslogNullSecurityPolicyTest.cpp @@ -10,18 +10,13 @@ TEST_GROUP(SolidSyslogNullSecurityPolicy) void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - policy = SolidSyslogNullSecurityPolicy_Create(); - } - - void teardown() override - { - SolidSyslogNullSecurityPolicy_Destroy(); + policy = SolidSyslogNullSecurityPolicy_Get(); } }; // clang-format on -TEST(SolidSyslogNullSecurityPolicy, CreateReturnsNonNull) +TEST(SolidSyslogNullSecurityPolicy, GetReturnsNonNull) { CHECK_TRUE(policy != nullptr); } @@ -35,8 +30,3 @@ TEST(SolidSyslogNullSecurityPolicy, VerifyIntegrityReturnsTrue) { CHECK_TRUE(policy->VerifyIntegrity(nullptr, 0, nullptr)); } - -TEST(SolidSyslogNullSecurityPolicy, DestroyDoesNotCrash) -{ - SolidSyslogNullSecurityPolicy_Destroy(); -} From 0ba5d7e1a1eafacacd2d4ce7c5683da351460bdc Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 13:17:59 +0100 Subject: [PATCH 05/18] refactor: S11.04 migrate PassthroughBuffer onto PoolAllocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the E11 canonical 3-TU split. Internal struct moves to SolidSyslogPassthroughBufferPrivate.h; vtable + Initialise/Cleanup stay in SolidSyslogPassthroughBuffer.c; pool, public Create/Destroy, IndexFromHandle, CleanupAtIndex, and the class-private Fallback live in SolidSyslogPassthroughBufferStatic.c. The Buffer abstract base has no shared GoF null in this sweep (CircularBuffer is the only other consumer and uses its own private Fallback too), so PassthroughBuffer keeps a class-private fallback. Public API: _Destroy(void) → _Destroy(struct SolidSyslogBuffer*). Caller updates in PassthroughBufferTest.cpp and SolidSyslogTest.cpp. Pool size tunable: SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE, default 1. Error messages: POOL_EXHAUSTED (ERROR severity) and UNKNOWN_DESTROY (WARNING severity). Pool tests: two new tests in SolidSyslogPassthroughBufferTest.cpp prove that filling to capacity returns distinct handles and the next Create returns a distinct no-op fallback. Generic per-probe lock and stale- handle behaviour is covered by SolidSyslogPoolAllocatorTest.cpp. Validated at SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE=3 via the user- tunables override — full suite green. 100% line coverage on the two new TUs. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 2 +- Core/Interface/SolidSyslogPassthroughBuffer.h | 3 +- Core/Interface/SolidSyslogTunablesDefaults.h | 21 +++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogErrorMessages.h | 4 + Core/Source/SolidSyslogPassthroughBuffer.c | 28 +++--- .../SolidSyslogPassthroughBufferPrivate.h | 17 ++++ .../SolidSyslogPassthroughBufferStatic.c | 87 +++++++++++++++++++ Tests/SolidSyslogPassthroughBufferTest.cpp | 81 ++++++++++++++++- Tests/SolidSyslogTest.cpp | 2 +- 10 files changed, 225 insertions(+), 21 deletions(-) create mode 100644 Core/Source/SolidSyslogPassthroughBufferPrivate.h create mode 100644 Core/Source/SolidSyslogPassthroughBufferStatic.c diff --git a/CLAUDE.md b/CLAUDE.md index ed1ae2cb..d57f2a60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -337,7 +337,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogSwitchingSender.h` | System setup code composing multiple inner senders | `SolidSyslogSwitchingSenderConfig` (senders, senderCount, selector), `SolidSyslogSwitchingSenderSelector`, `SolidSyslogSwitchingSender_Create`, `_Destroy` | | `SolidSyslogBuffer.h` | Library internals consuming a buffer (Service algorithm) | `SolidSyslogBuffer_Write`, `_Read` | | `SolidSyslogBufferDefinition.h` | Buffer implementors (extension point) | `SolidSyslogBuffer` vtable struct | -| `SolidSyslogPassthroughBuffer.h` | System setup code (single-task, no buffering) | `SolidSyslogPassthroughBuffer_Create`, `_Destroy` | +| `SolidSyslogPassthroughBuffer.h` | System setup code (single-task, no buffering) | `SolidSyslogPassthroughBuffer_Create(sender)`, `_Destroy(buffer)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create` to release the slot. | | `SolidSyslogPosixMessageQueueBuffer.h` | System setup code using POSIX message queue buffer | `SolidSyslogPosixMessageQueueBuffer_Create`, `_Destroy` | | `SolidSyslogCircularBuffer.h` | System setup code using an in-memory ring buffer (bare-metal / RTOS / Windows) | `SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES`, `SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(maxMessages)` (friendly: N max-sized messages → ring bytes), `SolidSyslogCircularBuffer_Create(mutex, ring, ringBytes)`, `_Destroy(buffer)` (uint16-length-prefixed records, drop-newest on overflow, no-split wrap, mutex-injected synchronization). Instance struct lives in a library-internal static pool (E11); caller supplies the ring memory only. | | `SolidSyslogMutex.h` | Any code holding a mutex handle | `SolidSyslogMutex_Lock`, `_Unlock` | diff --git a/Core/Interface/SolidSyslogPassthroughBuffer.h b/Core/Interface/SolidSyslogPassthroughBuffer.h index c5efa7b8..0f73446a 100644 --- a/Core/Interface/SolidSyslogPassthroughBuffer.h +++ b/Core/Interface/SolidSyslogPassthroughBuffer.h @@ -6,9 +6,10 @@ EXTERN_C_BEGIN struct SolidSyslogSender; + struct SolidSyslogBuffer; struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslogSender * sender); - void SolidSyslogPassthroughBuffer_Destroy(void); + void SolidSyslogPassthroughBuffer_Destroy(struct SolidSyslogBuffer * buffer); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 930efa3e..0249eba8 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -50,4 +50,25 @@ #error "SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogPassthroughBuffer instances the library's + * internal static pool can simultaneously hold. Each instance is + * tiny (vtable + a Sender pointer). + * + * PassthroughBuffer is the single-task "direct-send, no buffering" + * configuration — every integrator typically creates one. Default 1. + * Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more than one process or + * task needs its own passthrough Buffer instance. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 241cba9b..6e1a2a94 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES SolidSyslogFormatter.c SolidSyslogMetaSd.c SolidSyslogPassthroughBuffer.c + SolidSyslogPassthroughBufferStatic.c SolidSyslogCircularBuffer.c SolidSyslogCircularBufferStatic.c SolidSyslogPoolAllocator.c diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index e2529b20..eb29f6f5 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -21,5 +21,9 @@ "SolidSyslogCircularBuffer_Create pool exhausted; returning fallback buffer" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY \ "SolidSyslogCircularBuffer_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_PASSTHROUGHBUFFER_POOL_EXHAUSTED \ + "SolidSyslogPassthroughBuffer_Create pool exhausted; returning fallback buffer" +#define SOLIDSYSLOG_ERROR_MSG_PASSTHROUGHBUFFER_UNKNOWN_DESTROY \ + "SolidSyslogPassthroughBuffer_Destroy called with a handle not issued by this pool" #endif /* SOLIDSYSLOGERRORMESSAGES_H */ diff --git a/Core/Source/SolidSyslogPassthroughBuffer.c b/Core/Source/SolidSyslogPassthroughBuffer.c index 28ca0471..2f48cde0 100644 --- a/Core/Source/SolidSyslogPassthroughBuffer.c +++ b/Core/Source/SolidSyslogPassthroughBuffer.c @@ -4,6 +4,7 @@ #include #include "SolidSyslogBufferDefinition.h" +#include "SolidSyslogPassthroughBufferPrivate.h" #include "SolidSyslogSender.h" static bool PassthroughBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead); @@ -11,27 +12,20 @@ static void PassthroughBuffer_Write(struct SolidSyslogBuffer* base, const void* static inline struct SolidSyslogPassthroughBuffer* PassthroughBuffer_SelfFromBase(struct SolidSyslogBuffer* base); -struct SolidSyslogPassthroughBuffer +void PassthroughBuffer_Initialise(struct SolidSyslogBuffer* base, struct SolidSyslogSender* sender) { - struct SolidSyslogBuffer Base; - struct SolidSyslogSender* Sender; -}; - -static struct SolidSyslogPassthroughBuffer PassthroughBuffer_Instance; - -struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslogSender* sender) -{ - PassthroughBuffer_Instance.Base.Write = PassthroughBuffer_Write; - PassthroughBuffer_Instance.Base.Read = PassthroughBuffer_Read; - PassthroughBuffer_Instance.Sender = sender; - return &PassthroughBuffer_Instance.Base; + struct SolidSyslogPassthroughBuffer* self = PassthroughBuffer_SelfFromBase(base); + self->Base.Write = PassthroughBuffer_Write; + self->Base.Read = PassthroughBuffer_Read; + self->Sender = sender; } -void SolidSyslogPassthroughBuffer_Destroy(void) +void PassthroughBuffer_Cleanup(struct SolidSyslogBuffer* base) { - PassthroughBuffer_Instance.Base.Write = NULL; - PassthroughBuffer_Instance.Base.Read = NULL; - PassthroughBuffer_Instance.Sender = NULL; + struct SolidSyslogPassthroughBuffer* self = PassthroughBuffer_SelfFromBase(base); + self->Base.Write = NULL; + self->Base.Read = NULL; + self->Sender = NULL; } static bool PassthroughBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) diff --git a/Core/Source/SolidSyslogPassthroughBufferPrivate.h b/Core/Source/SolidSyslogPassthroughBufferPrivate.h new file mode 100644 index 00000000..5cb8fdb7 --- /dev/null +++ b/Core/Source/SolidSyslogPassthroughBufferPrivate.h @@ -0,0 +1,17 @@ +#ifndef SOLIDSYSLOGPASSTHROUGHBUFFERPRIVATE_H +#define SOLIDSYSLOGPASSTHROUGHBUFFERPRIVATE_H + +#include "SolidSyslogBufferDefinition.h" + +struct SolidSyslogSender; + +struct SolidSyslogPassthroughBuffer +{ + struct SolidSyslogBuffer Base; + struct SolidSyslogSender* Sender; +}; + +void PassthroughBuffer_Initialise(struct SolidSyslogBuffer* base, struct SolidSyslogSender* sender); +void PassthroughBuffer_Cleanup(struct SolidSyslogBuffer* base); + +#endif /* SOLIDSYSLOGPASSTHROUGHBUFFERPRIVATE_H */ diff --git a/Core/Source/SolidSyslogPassthroughBufferStatic.c b/Core/Source/SolidSyslogPassthroughBufferStatic.c new file mode 100644 index 00000000..a78458ad --- /dev/null +++ b/Core/Source/SolidSyslogPassthroughBufferStatic.c @@ -0,0 +1,87 @@ +#include "SolidSyslogPassthroughBuffer.h" + +#include +#include + +#include "SolidSyslogBufferDefinition.h" +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogPassthroughBufferPrivate.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogSender; + +static bool Fallback_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead); +static void Fallback_Write(struct SolidSyslogBuffer* base, const void* data, size_t size); +static size_t PassthroughBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base); +static void PassthroughBuffer_CleanupAtIndex(size_t index, void* context); + +static bool InUse[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE]; +static struct SolidSyslogPassthroughBuffer Pool[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE]; +static struct SolidSyslogBuffer Fallback = {Fallback_Write, Fallback_Read}; +static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE}; + +struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslogSender* sender) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + struct SolidSyslogBuffer* handle = &Fallback; + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + PassthroughBuffer_Initialise(&Pool[index].Base, sender); + handle = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_PASSTHROUGHBUFFER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogPassthroughBuffer_Destroy(struct SolidSyslogBuffer* base) +{ + size_t index = PassthroughBuffer_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, PassthroughBuffer_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_PASSTHROUGHBUFFER_UNKNOWN_DESTROY); + } +} + +static size_t PassthroughBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base) +{ + size_t result = SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE; poolIndex++) + { + if (base == &Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void PassthroughBuffer_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PassthroughBuffer_Cleanup(&Pool[index].Base); +} + +static bool Fallback_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) +{ + (void) base; + (void) data; + (void) maxSize; + *bytesRead = 0; + return false; +} + +static void Fallback_Write(struct SolidSyslogBuffer* base, const void* data, size_t size) +{ + (void) base; + (void) data; + (void) size; +} diff --git a/Tests/SolidSyslogPassthroughBufferTest.cpp b/Tests/SolidSyslogPassthroughBufferTest.cpp index f3e00c6a..47d0bdbc 100644 --- a/Tests/SolidSyslogPassthroughBufferTest.cpp +++ b/Tests/SolidSyslogPassthroughBufferTest.cpp @@ -7,6 +7,7 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f // macros #include "SolidSyslogBuffer.h" #include "SolidSyslogPassthroughBuffer.h" +#include "SolidSyslogTunables.h" #include "SenderFake.h" static const char* const TEST_MESSAGE = "hello"; @@ -27,7 +28,7 @@ TEST_GROUP(SolidSyslogPassthroughBuffer) void teardown() override { - SolidSyslogPassthroughBuffer_Destroy(); + SolidSyslogPassthroughBuffer_Destroy(buffer); SenderFake_Destroy(fakeSender); } @@ -89,3 +90,81 @@ IGNORE_TEST(SolidSyslogPassthroughBuffer, HappyPathOnly) // Write with NULL buffer does not crash // Destroy with NULL buffer does not crash } + +// Pool tests — prove SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE caps live +// instances and overflow falls back to a distinct no-op buffer. Generic +// pool mechanics (lock counts, per-probe locking, stale-handle warning) +// are covered by SolidSyslogPoolAllocatorTest.cpp. + +// clang-format off +TEST_GROUP(SolidSyslogPassthroughBufferPool) +{ + struct SolidSyslogSender* fakeSender = nullptr; + struct SolidSyslogBuffer* pooled[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE] = {}; + struct SolidSyslogBuffer* overflow = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + fakeSender = SenderFake_Create(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPassthroughBuffer_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogPassthroughBuffer_Destroy(overflow); + } + SenderFake_Destroy(fakeSender); + } + + struct SolidSyslogBuffer* MakeBuffer() + { + return SolidSyslogPassthroughBuffer_Create(fakeSender); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeBuffer(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPassthroughBufferPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeBuffer(); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} + +TEST(SolidSyslogPassthroughBufferPool, FallbackWriteAndReadAreNoOps) +{ + FillPool(); + overflow = MakeBuffer(); + + SolidSyslogBuffer_Write(overflow, "hello", 5); + + char readBuffer[16] = {}; + size_t bytesRead = 99; + CHECK_FALSE(SolidSyslogBuffer_Read(overflow, readBuffer, sizeof(readBuffer), &bytesRead)); + LONGS_EQUAL(0, bytesRead); + CALLED_FAKE_ON(SenderFake_Send, fakeSender, NEVER); +} diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index c4bae784..98edef6a 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -377,7 +377,7 @@ TEST_GROUP(SolidSyslog) void teardown() override { SolidSyslog_Destroy(); - SolidSyslogPassthroughBuffer_Destroy(); + SolidSyslogPassthroughBuffer_Destroy(buffer); SenderFake_Destroy(fakeSender); } From 3e6df3eb68ba6fede73a90816d6b068e245d1268 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 13:35:04 +0100 Subject: [PATCH 06/18] refactor: S11.04 migrate UdpSender onto PoolAllocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the E11 canonical 3-TU split. Internal struct moves to SolidSyslogUdpSenderPrivate.h; vtable, connect/disconnect logic, and the Send NULL-buffer guard stay in SolidSyslogUdpSender.c; pool, public Create/Destroy, IndexFromHandle, CleanupAtIndex, and the bad-config validation move to SolidSyslogUdpSenderStatic.c. The class-private NilUdpSender is gone — bad config and pool exhaustion both return the shared SolidSyslogNullSender_Get(). Public API: _Destroy(void) → _Destroy(struct SolidSyslogSender*). Caller updates in SolidSyslogUdpSenderTest.cpp, the BDD ServiceThread test, and all three BDD targets (Linux, Windows, FreeRTOS). The BDD targets now keep udpSender as a file-scope static so DestroySender() can pass it through. NullSender.Send flipped from false to true: a misconfigured Sender must drop messages at the null-object boundary, not retain them in the Store. The old NilUdpSender did this; the shared NullSender now inherits that behaviour. NullSenderTest assertion follows. The Send NULL-buffer SolidSyslog_Error call stays in UdpSender.c (not Static.c) — it's a runtime contract guard, not a pool/config error. The "ClassStatic.c is the only TU that calls Error" rule applies to pool/config errors; vtable-method runtime guards stay where the check is. Pool size tunable: SOLIDSYSLOG_UDP_SENDER_POOL_SIZE, default 1. Error messages: POOL_EXHAUSTED (ERROR severity) and UNKNOWN_DESTROY (WARNING severity). One new pool test in SolidSyslogUdpSenderTest.cpp proves capacity + overflow returns a distinct fallback. Validated at SOLIDSYSLOG_UDP_SENDER_POOL_SIZE=3. 100% line coverage on UdpSender.c, UdpSenderStatic.c, NullSender.c. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/Targets/FreeRtos/main.c | 5 +- Bdd/Targets/Linux/main.c | 5 +- Bdd/Targets/Windows/BddTargetWindows.c | 5 +- CLAUDE.md | 4 +- Core/Interface/SolidSyslogTunablesDefaults.h | 22 ++++ Core/Interface/SolidSyslogUdpSender.h | 2 +- Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogErrorMessages.h | 4 + Core/Source/SolidSyslogNullSender.c | 6 +- Core/Source/SolidSyslogUdpSender.c | 115 ++++-------------- Core/Source/SolidSyslogUdpSenderPrivate.h | 23 ++++ Core/Source/SolidSyslogUdpSenderStatic.c | 97 +++++++++++++++ .../Targets/BddTargetServiceThreadTest.cpp | 2 +- Tests/SolidSyslogNullSenderTest.cpp | 4 +- Tests/SolidSyslogUdpSenderTest.cpp | 82 +++++++++++-- 15 files changed, 266 insertions(+), 111 deletions(-) create mode 100644 Core/Source/SolidSyslogUdpSenderPrivate.h create mode 100644 Core/Source/SolidSyslogUdpSenderStatic.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 0889d92e..07f0cf9b 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -250,6 +250,7 @@ static struct SolidSyslogResolver* resolver = NULL; static struct SolidSyslogDatagram* datagram = NULL; static struct SolidSyslogStream* tcpStream = NULL; static struct SolidSyslogSender* tcpSender = NULL; +static struct SolidSyslogSender* udpSender = NULL; static struct SolidSyslogBuffer* buffer = NULL; static struct SolidSyslogMutex* bufferMutex = NULL; @@ -674,7 +675,7 @@ static void TeardownAll(void) SolidSyslogSwitchingSender_Destroy(); SolidSyslogStreamSender_Destroy(tcpSender); SolidSyslogFreeRtosTcpStream_Destroy(tcpStream); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(udpSender); SolidSyslogFreeRtosDatagram_Destroy(datagram); SolidSyslogFreeRtosStaticResolver_Destroy(resolver); } @@ -785,7 +786,7 @@ static void InteractiveTask(void* argument) .Endpoint = GetEndpoint, .EndpointVersion = GetEndpointVersion, }; - struct SolidSyslogSender* udpSender = SolidSyslogUdpSender_Create(&udpConfig); + udpSender = SolidSyslogUdpSender_Create(&udpConfig); /* Plain TCP path via the new FreeRTOS Plus-TCP stream adapter. Shares the * UDP endpoint callbacks because the BDD oracle (syslog-ng) listens on the diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 4ed27f08..4c0327ae 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -55,6 +55,7 @@ static SolidSyslogPosixTcpStreamStorage plainTcpStreamStorage; static struct SolidSyslogStream* plainTcpStream; static SolidSyslogStreamSenderStorage plainTcpSenderStorage; static struct SolidSyslogSender* plainTcpSender; +static struct SolidSyslogSender* udpSender; static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) { @@ -83,7 +84,7 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetOptions* opt udpConfig.Datagram = SolidSyslogPosixDatagram_Create(); udpConfig.Endpoint = BddTargetUdpConfig_GetEndpoint; udpConfig.EndpointVersion = BddTargetUdpConfig_GetEndpointVersion; - struct SolidSyslogSender* udpSender = SolidSyslogUdpSender_Create(&udpConfig); + udpSender = SolidSyslogUdpSender_Create(&udpConfig); plainTcpStream = SolidSyslogPosixTcpStream_Create(&plainTcpStreamStorage); static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; @@ -187,7 +188,7 @@ static void DestroySender(void) BddTargetTlsSender_Destroy(); SolidSyslogStreamSender_Destroy(plainTcpSender); SolidSyslogPosixTcpStream_Destroy(plainTcpStream); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(udpSender); SolidSyslogPosixDatagram_Destroy(); SolidSyslogGetAddrInfoResolver_Destroy(); } diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 6661668f..1311e18e 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -76,6 +76,7 @@ static volatile bool shutdownFlag; static struct SolidSyslogStream* plainTcpStream; static struct SolidSyslogSender* plainTcpSender; static struct SolidSyslogDatagram* udpDatagram; +static struct SolidSyslogSender* udpSender; /* Block-store backing — created in CreateStore, released in DestroyStore. */ static struct SolidSyslogFile* storeFile; @@ -186,7 +187,7 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetWindowsOptio udpConfig.Datagram = udpDatagram; udpConfig.Endpoint = GetEndpoint; udpConfig.EndpointVersion = GetEndpointVersion; - struct SolidSyslogSender* udpSender = SolidSyslogUdpSender_Create(&udpConfig); + udpSender = SolidSyslogUdpSender_Create(&udpConfig); plainTcpStream = SolidSyslogWinsockTcpStream_Create(&tcpStreamStorage); static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; @@ -218,7 +219,7 @@ static void DestroySender(void) BddTargetTlsSender_Destroy(); SolidSyslogStreamSender_Destroy(plainTcpSender); SolidSyslogWinsockTcpStream_Destroy(plainTcpStream); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(udpSender); SolidSyslogWinsockDatagram_Destroy(); SolidSyslogWinsockResolver_Destroy(); } diff --git a/CLAUDE.md b/CLAUDE.md index d57f2a60..8f6eb247 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -321,7 +321,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogEndpoint.h` | System setup code that supplies destination host/port (and version on changes) | `SolidSyslogEndpoint`, `SolidSyslogEndpointFunction`, `SolidSyslogEndpointVersionFunction`, `SOLIDSYSLOG_MAX_HOST_SIZE` | | `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 `false`, Disconnect is a no-op) | `SolidSyslogNullSender_Get` | +| `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` | | `SolidSyslogTransport.h` | Any code selecting a transport or needing default port constants | `SolidSyslogTransport` enum (`UDP`, `TCP`), `SOLIDSYSLOG_UDP_DEFAULT_PORT`, `SOLIDSYSLOG_TCP_DEFAULT_PORT` | | `SolidSyslogResolver.h` | Any code that needs to resolve a destination | `SolidSyslogResolver_Resolve(resolver, transport, host, port, *out)` | | `SolidSyslogResolverDefinition.h` | Resolver implementors (extension point) | `SolidSyslogResolver` vtable struct (`Resolve`) | @@ -332,7 +332,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogStream.h` | Sender implementors using stream transport | `SolidSyslogStream_Open`, `_Send`, `_Read`, `_Close`, `SolidSyslogSsize` | | `SolidSyslogStreamDefinition.h` | Stream implementors (extension point) | `SolidSyslogStream` vtable struct (`Open`, `Send`, `Read`, `Close`) | | `SolidSyslogFreeRtosTcpStream.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-TCP for TCP | `SolidSyslogFreeRtosTcpStreamStorage`, `SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE`, `SolidSyslogFreeRtosTcpStream_Create(storage)`, `_Destroy(stream)` (wraps `FreeRTOS_socket` / `FreeRTOS_connect` / `FreeRTOS_send` / `FreeRTOS_recv` / `FreeRTOS_closesocket`; bounded blocking connect via `SO_SNDTIMEO=200ms`, cleared post-connect so Send/Read are non-blocking) | -| `SolidSyslogUdpSender.h` | System setup code using UDP transport | `SolidSyslogUdpSenderConfig` (resolver, datagram, endpoint, endpointVersion), `SolidSyslogUdpSender_Create`, `_Destroy` | +| `SolidSyslogUdpSender.h` | System setup code using UDP transport | `SolidSyslogUdpSenderConfig` (resolver, datagram, endpoint, endpointVersion), `SolidSyslogUdpSender_Create(config)`, `_Destroy(sender)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create`. Pool-exhaustion and bad-config fallback is the shared `SolidSyslogNullSender`. | | `SolidSyslogStreamSender.h` | System setup code using octet-framed transport (RFC 6587) over any Stream — `SolidSyslogPosixTcpStream` (POSIX TCP), `SolidSyslogWinsockTcpStream` (Windows TCP), `SolidSyslogFreeRtosTcpStream` (FreeRTOS-Plus-TCP), `SolidSyslogTlsStream` (TLS; OpenSSL reference integration), or a caller-supplied Stream backend | `SolidSyslogStreamSenderConfig` (resolver, stream, endpoint, endpointVersion), `SolidSyslogStreamSender_Create`, `_Destroy` | | `SolidSyslogSwitchingSender.h` | System setup code composing multiple inner senders | `SolidSyslogSwitchingSenderConfig` (senders, senderCount, selector), `SolidSyslogSwitchingSenderSelector`, `SolidSyslogSwitchingSender_Create`, `_Destroy` | | `SolidSyslogBuffer.h` | Library internals consuming a buffer (Service algorithm) | `SolidSyslogBuffer_Write`, `_Read` | diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 0249eba8..ddece949 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -71,4 +71,26 @@ #error "SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogUdpSender instances the library's internal + * static pool can simultaneously hold. Each instance carries its + * config (resolver/datagram/endpoint pointers), the resolved address + * storage, and connection state. + * + * Default 1 — almost all integrators wire a single UDP sender into + * either SolidSyslogConfig directly or as one branch of a + * SwitchingSender. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more + * than one is genuinely needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_UDP_SENDER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_UDP_SENDER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_UDP_SENDER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_UDP_SENDER_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Interface/SolidSyslogUdpSender.h b/Core/Interface/SolidSyslogUdpSender.h index a6c800a9..62f950f8 100644 --- a/Core/Interface/SolidSyslogUdpSender.h +++ b/Core/Interface/SolidSyslogUdpSender.h @@ -15,7 +15,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogSender* SolidSyslogUdpSender_Create(const struct SolidSyslogUdpSenderConfig* config); - void SolidSyslogUdpSender_Destroy(void); + void SolidSyslogUdpSender_Destroy(struct SolidSyslogSender * sender); EXTERN_C_END diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 6e1a2a94..cef52cad 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -48,6 +48,7 @@ if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_WINSOCK OR DEFINED ENV{FREERTOS_KERNEL_PATH} SolidSyslogResolver.c SolidSyslogDatagram.c SolidSyslogUdpSender.c + SolidSyslogUdpSenderStatic.c SolidSyslogStreamSender.c ) endif() diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index eb29f6f5..9177f0e2 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -17,6 +17,10 @@ #define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_DATAGRAM "SolidSyslogUdpSender_Create config.Datagram is NULL" #define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_ENDPOINT "SolidSyslogUdpSender_Create config.Endpoint is NULL" #define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_SEND_NULL_BUFFER "SolidSyslogUdpSender_Send called with NULL buffer" +#define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_POOL_EXHAUSTED \ + "SolidSyslogUdpSender_Create pool exhausted; returning fallback sender" +#define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_UNKNOWN_DESTROY \ + "SolidSyslogUdpSender_Destroy called with a handle not issued by this pool" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_POOL_EXHAUSTED \ "SolidSyslogCircularBuffer_Create pool exhausted; returning fallback buffer" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogNullSender.c b/Core/Source/SolidSyslogNullSender.c index c0c5afa8..6efd9547 100644 --- a/Core/Source/SolidSyslogNullSender.c +++ b/Core/Source/SolidSyslogNullSender.c @@ -15,12 +15,16 @@ struct SolidSyslogSender* SolidSyslogNullSender_Get(void) return &instance; } +/* Send returns true ("delivered") so the Service algorithm drops the + * message rather than retaining it in the Store. A misconfigured Sender + * paired with a real Store would otherwise accumulate undeliverables + * forever; returning true at the null-object boundary contains that. */ static bool NullSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size) { (void) base; (void) buffer; (void) size; - return false; + return true; } static void NullSender_Disconnect(struct SolidSyslogSender* base) diff --git a/Core/Source/SolidSyslogUdpSender.c b/Core/Source/SolidSyslogUdpSender.c index b687b082..15a68b41 100644 --- a/Core/Source/SolidSyslogUdpSender.c +++ b/Core/Source/SolidSyslogUdpSender.c @@ -1,33 +1,23 @@ +#include #include #include -#include +#include "SolidSyslogAddress.h" +#include "SolidSyslogDatagram.h" #include "SolidSyslogEndpoint.h" #include "SolidSyslogError.h" #include "SolidSyslogErrorMessages.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogPrival.h" -#include "SolidSyslogUdpPayload.h" -#include "SolidSyslogUdpSender.h" -#include "SolidSyslogSenderDefinition.h" -#include "SolidSyslogAddress.h" -#include "SolidSyslogDatagram.h" #include "SolidSyslogResolver.h" +#include "SolidSyslogSenderDefinition.h" #include "SolidSyslogTransport.h" +#include "SolidSyslogUdpPayload.h" +#include "SolidSyslogUdpSender.h" +#include "SolidSyslogUdpSenderPrivate.h" struct SolidSyslogFormatter; -struct SolidSyslogUdpSender -{ - struct SolidSyslogSender Base; - struct SolidSyslogUdpSenderConfig Config; - SolidSyslogAddressStorage AddrStorage; - bool Connected; - uint32_t LastEndpointVersion; -}; - -static bool UdpSender_IsValidConfig(const struct SolidSyslogUdpSenderConfig* config); -static void UdpSender_InstallConfig(const struct SolidSyslogUdpSenderConfig* config); static bool UdpSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size); static void UdpSender_Disconnect(struct SolidSyslogSender* base); @@ -52,71 +42,33 @@ static inline enum SolidSyslogDatagramSendResult UdpSender_RetryAfterOversize( size_t size ); static uint32_t UdpSender_NilEndpointVersion(void); -static bool UdpSender_NilUdpSenderSend(struct SolidSyslogSender* base, const void* buffer, size_t size); -static void UdpSender_NilUdpSenderDisconnect(struct SolidSyslogSender* base); - -static const struct SolidSyslogUdpSender DEFAULT_INSTANCE = { - .Config = {.EndpointVersion = UdpSender_NilEndpointVersion} -}; -static struct SolidSyslogUdpSender instance; -static struct SolidSyslogSender NilUdpSender = { - .Send = UdpSender_NilUdpSenderSend, - .Disconnect = UdpSender_NilUdpSenderDisconnect -}; -struct SolidSyslogSender* SolidSyslogUdpSender_Create(const struct SolidSyslogUdpSenderConfig* config) +void UdpSender_Initialise(struct SolidSyslogSender* base, const struct SolidSyslogUdpSenderConfig* config) { - struct SolidSyslogSender* result = &NilUdpSender; - if (UdpSender_IsValidConfig(config)) - { - UdpSender_InstallConfig(config); - result = &instance.Base; - } - return result; -} - -static bool UdpSender_IsValidConfig(const struct SolidSyslogUdpSenderConfig* config) -{ - bool valid = false; - if (config == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_CONFIG); - } - else if (config->Resolver == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_RESOLVER); - } - else if (config->Datagram == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_DATAGRAM); - } - else if (config->Endpoint == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_ENDPOINT); - } - else - { - valid = true; - } - return valid; -} - -static void UdpSender_InstallConfig(const struct SolidSyslogUdpSenderConfig* config) -{ - instance = DEFAULT_INSTANCE; - instance.Config = *config; - if (instance.Config.EndpointVersion == NULL) + struct SolidSyslogUdpSender* self = UdpSender_SelfFromBase(base); + self->Base.Send = UdpSender_Send; + self->Base.Disconnect = UdpSender_Disconnect; + self->Config = *config; + if (self->Config.EndpointVersion == NULL) { - instance.Config.EndpointVersion = UdpSender_NilEndpointVersion; + self->Config.EndpointVersion = UdpSender_NilEndpointVersion; } - instance.Base.Send = UdpSender_Send; - instance.Base.Disconnect = UdpSender_Disconnect; + self->Connected = false; + self->LastEndpointVersion = 0; } -void SolidSyslogUdpSender_Destroy(void) +void UdpSender_Cleanup(struct SolidSyslogSender* base) { - UdpSender_Disconnect(&instance.Base); - instance = DEFAULT_INSTANCE; + struct SolidSyslogUdpSender* self = UdpSender_SelfFromBase(base); + UdpSender_Disconnect(base); + self->Base.Send = NULL; + self->Base.Disconnect = NULL; + self->Config.Resolver = NULL; + self->Config.Datagram = NULL; + self->Config.Endpoint = NULL; + self->Config.EndpointVersion = NULL; + self->Connected = false; + self->LastEndpointVersion = 0; } static bool UdpSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size) @@ -271,16 +223,3 @@ static uint32_t UdpSender_NilEndpointVersion(void) { return 0; } - -static bool UdpSender_NilUdpSenderSend(struct SolidSyslogSender* base, const void* buffer, size_t size) -{ - (void) base; - (void) buffer; - (void) size; - return true; -} - -static void UdpSender_NilUdpSenderDisconnect(struct SolidSyslogSender* base) -{ - (void) base; -} diff --git a/Core/Source/SolidSyslogUdpSenderPrivate.h b/Core/Source/SolidSyslogUdpSenderPrivate.h new file mode 100644 index 00000000..892db5c2 --- /dev/null +++ b/Core/Source/SolidSyslogUdpSenderPrivate.h @@ -0,0 +1,23 @@ +#ifndef SOLIDSYSLOGUDPSENDERPRIVATE_H +#define SOLIDSYSLOGUDPSENDERPRIVATE_H + +#include +#include + +#include "SolidSyslogAddress.h" +#include "SolidSyslogSenderDefinition.h" +#include "SolidSyslogUdpSender.h" + +struct SolidSyslogUdpSender +{ + struct SolidSyslogSender Base; + struct SolidSyslogUdpSenderConfig Config; + SolidSyslogAddressStorage AddrStorage; + bool Connected; + uint32_t LastEndpointVersion; +}; + +void UdpSender_Initialise(struct SolidSyslogSender* base, const struct SolidSyslogUdpSenderConfig* config); +void UdpSender_Cleanup(struct SolidSyslogSender* base); + +#endif /* SOLIDSYSLOGUDPSENDERPRIVATE_H */ diff --git a/Core/Source/SolidSyslogUdpSenderStatic.c b/Core/Source/SolidSyslogUdpSenderStatic.c new file mode 100644 index 00000000..277710b7 --- /dev/null +++ b/Core/Source/SolidSyslogUdpSenderStatic.c @@ -0,0 +1,97 @@ +#include "SolidSyslogUdpSender.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullSender.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogSenderDefinition.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogUdpSenderPrivate.h" + +static bool UdpSender_IsValidConfig(const struct SolidSyslogUdpSenderConfig* config); +static size_t UdpSender_IndexFromHandle(const struct SolidSyslogSender* base); +static void UdpSender_CleanupAtIndex(size_t index, void* context); + +static bool InUse[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE]; +static struct SolidSyslogUdpSender Pool[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_UDP_SENDER_POOL_SIZE}; + +struct SolidSyslogSender* SolidSyslogUdpSender_Create(const struct SolidSyslogUdpSenderConfig* config) +{ + struct SolidSyslogSender* result = SolidSyslogNullSender_Get(); + if (UdpSender_IsValidConfig(config)) + { + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + UdpSender_Initialise(&Pool[index].Base, config); + result = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_POOL_EXHAUSTED); + } + } + return result; +} + +void SolidSyslogUdpSender_Destroy(struct SolidSyslogSender* base) +{ + size_t index = UdpSender_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, UdpSender_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_UNKNOWN_DESTROY); + } +} + +static bool UdpSender_IsValidConfig(const struct SolidSyslogUdpSenderConfig* config) +{ + bool valid = false; + if (config == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_CONFIG); + } + else if (config->Resolver == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_RESOLVER); + } + else if (config->Datagram == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_DATAGRAM); + } + else if (config->Endpoint == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_ENDPOINT); + } + else + { + valid = true; + } + return valid; +} + +static size_t UdpSender_IndexFromHandle(const struct SolidSyslogSender* base) +{ + size_t result = SOLIDSYSLOG_UDP_SENDER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_UDP_SENDER_POOL_SIZE; poolIndex++) + { + if (base == &Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void UdpSender_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + UdpSender_Cleanup(&Pool[index].Base); +} diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index 46b8f34f..381cfefa 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -78,7 +78,7 @@ TEST_GROUP(BddTargetServiceThread) { SolidSyslog_Destroy(); SolidSyslogPosixMessageQueueBuffer_Destroy(); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); SolidSyslogPosixDatagram_Destroy(); SolidSyslogGetAddrInfoResolver_Destroy(); } diff --git a/Tests/SolidSyslogNullSenderTest.cpp b/Tests/SolidSyslogNullSenderTest.cpp index 5227b80f..2984d891 100644 --- a/Tests/SolidSyslogNullSenderTest.cpp +++ b/Tests/SolidSyslogNullSenderTest.cpp @@ -16,9 +16,9 @@ TEST_GROUP(SolidSyslogNullSender) // clang-format on -TEST(SolidSyslogNullSender, SendReturnsFalse) +TEST(SolidSyslogNullSender, SendReturnsTrueToDropOnTheFloor) { - CHECK_FALSE(SolidSyslogSender_Send(sender, "hello", 5)); + CHECK_TRUE(SolidSyslogSender_Send(sender, "hello", 5)); } TEST(SolidSyslogNullSender, DisconnectDoesNotCrash) diff --git a/Tests/SolidSyslogUdpSenderTest.cpp b/Tests/SolidSyslogUdpSenderTest.cpp index 9d7a936a..b5ad7d4b 100644 --- a/Tests/SolidSyslogUdpSenderTest.cpp +++ b/Tests/SolidSyslogUdpSenderTest.cpp @@ -163,7 +163,7 @@ TEST_GROUP_BASE(SolidSyslogUdpSender, UdpSenderTestBase) void teardown() override { - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); teardownFakesWithPosixDatagram(); } }; @@ -353,8 +353,8 @@ TEST_GROUP_BASE(SolidSyslogUdpSenderDestroy, UdpSenderTestBase) void CreateAndDestroy() const { - SolidSyslogUdpSender_Create(&config); - SolidSyslogUdpSender_Destroy(); + struct SolidSyslogSender* localSender = SolidSyslogUdpSender_Create(&config); + SolidSyslogUdpSender_Destroy(localSender); } }; @@ -370,7 +370,7 @@ TEST(SolidSyslogUdpSenderDestroy, DestroyAfterSendClosesSocket) { sender = SolidSyslogUdpSender_Create(&config); Send(); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); CALLED_FAKE(SocketFake_Close, ONCE); LONGS_EQUAL(SocketFake_SocketFd(), SocketFake_LastClosedFd()); } @@ -380,7 +380,7 @@ TEST(SolidSyslogUdpSenderDestroy, DestroyAfterDisconnectDoesNotDoubleClose) sender = SolidSyslogUdpSender_Create(&config); Send(); SolidSyslogSender_Disconnect(sender); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); CALLED_FAKE(SocketFake_Close, ONCE); } @@ -388,7 +388,7 @@ TEST(SolidSyslogUdpSenderDestroy, SimpleScenario) { sender = SolidSyslogUdpSender_Create(&config); Send(); - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); CALLED_FAKE(SocketFake_Socket, ONCE); LONGS_EQUAL(AF_INET, SocketFake_SocketDomain()); @@ -410,7 +410,7 @@ TEST_GROUP_BASE(SolidSyslogUdpSenderConfig, UdpSenderTestBase) void teardown() override { - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); teardownFakesWithPosixDatagram(); } }; @@ -483,7 +483,7 @@ TEST_GROUP_BASE(SolidSyslogUdpSenderFailure, UdpSenderTestBase) void teardown() override { - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); teardownFakesWithPosixDatagram(); } @@ -556,7 +556,7 @@ TEST_GROUP_BASE(SolidSyslogUdpSenderRetry, UdpSenderTestBase) void teardown() override { - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); teardownFakesWithDatagramFake(); } @@ -719,7 +719,7 @@ TEST_GROUP_BASE(SolidSyslogUdpSenderBadSetup, UdpSenderTestBase) void teardown() override { - SolidSyslogUdpSender_Destroy(); + SolidSyslogUdpSender_Destroy(sender); teardownFakesWithPosixDatagram(); ErrorHandlerFake_Uninstall(); } @@ -781,3 +781,65 @@ TEST(SolidSyslogUdpSenderBadSetup, SendWithNullBufferReportsErrorAndDoesNotSend) CHECK_REPORTED_ERROR("SolidSyslogUdpSender_Send called with NULL buffer"); CALLED_FAKE(SocketFake_Sendto, NEVER); } + +// Pool tests — prove SOLIDSYSLOG_UDP_SENDER_POOL_SIZE caps live instances +// and overflow falls back to the shared SolidSyslogNullSender. Generic +// pool mechanics (lock counts, per-probe locking, stale-handle warning) +// are covered by SolidSyslogPoolAllocatorTest.cpp. + +// clang-format off +TEST_GROUP_BASE(SolidSyslogUdpSenderPool, UdpSenderTestBase) +{ + struct SolidSyslogSender* pooled[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE] = {}; + struct SolidSyslogSender* overflow = nullptr; + + void setup() override + { + setupFakesWithPosixDatagram(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogUdpSender_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogUdpSender_Destroy(overflow); + } + teardownFakesWithPosixDatagram(); + } + + struct SolidSyslogSender* MakeSender() + { + return SolidSyslogUdpSender_Create(&config); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeSender(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogUdpSenderPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeSender(); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} From c4a18aa7f2fa31f3fa210323ad920b65c2600b9a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 13:49:13 +0100 Subject: [PATCH 07/18] refactor: S11.04 migrate SwitchingSender onto PoolAllocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the E11 canonical 3-TU split. SwitchingSenderPrivate.h holds the struct + Initialise/Cleanup decls; SwitchingSender.c keeps the vtable methods and selector-dispatch logic; SwitchingSenderStatic.c owns the pool, Create/Destroy, IndexFromHandle, CleanupAtIndex. The class-private NIL_SENDER is gone: pool exhaustion AND out-of-range selector both resolve to the shared SolidSyslogNullSender_Get(). Public API: _Destroy(void) → _Destroy(struct SolidSyslogSender*). Caller updates in SolidSyslogSwitchingSenderTest.cpp and all three BDD targets. BDD targets gain a file-scope `switchingSender` pointer. Two ZeroSenderCount / SelectorBeyondEnd tests flip from "Send returns false" to "Send returns true" — they now resolve to the shared NullSender, which drops on the floor. Same semantic as UdpSender's bad-setup path, by design. The test helper CreateSwitchingSender(count) gains a defensive Destroy of any previously-allocated sender. With singleton semantics the second call simply overwrote the slot; with pooling the second call would exhaust the pool and return the Fallback, leaving the first slot orphaned. The override-tests (Selector at last valid index, zero count) hit this path. Pool size tunable: SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE, default 1. Error messages: POOL_EXHAUSTED (ERROR) and UNKNOWN_DESTROY (WARNING). One new pool test in SolidSyslogSwitchingSenderTest.cpp proves capacity + overflow returns a distinct fallback. Validated at SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE=3. 100% line coverage on SwitchingSender.c and SwitchingSenderStatic.c. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/Targets/FreeRtos/main.c | 6 +- Bdd/Targets/Linux/main.c | 6 +- Bdd/Targets/Windows/BddTargetWindows.c | 6 +- CLAUDE.md | 2 +- Core/Interface/SolidSyslogSwitchingSender.h | 2 +- Core/Interface/SolidSyslogTunablesDefaults.h | 19 ++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogErrorMessages.h | 4 + Core/Source/SolidSyslogSwitchingSender.c | 61 +++++------- .../SolidSyslogSwitchingSenderPrivate.h | 17 ++++ .../Source/SolidSyslogSwitchingSenderStatic.c | 67 ++++++++++++++ Tests/SolidSyslogSwitchingSenderTest.cpp | 92 +++++++++++++++++-- 12 files changed, 229 insertions(+), 54 deletions(-) create mode 100644 Core/Source/SolidSyslogSwitchingSenderPrivate.h create mode 100644 Core/Source/SolidSyslogSwitchingSenderStatic.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 07f0cf9b..76893341 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -251,6 +251,7 @@ static struct SolidSyslogDatagram* datagram = NULL; static struct SolidSyslogStream* tcpStream = NULL; static struct SolidSyslogSender* tcpSender = NULL; static struct SolidSyslogSender* udpSender = NULL; +static struct SolidSyslogSender* switchingSender = NULL; static struct SolidSyslogBuffer* buffer = NULL; static struct SolidSyslogMutex* bufferMutex = NULL; @@ -672,7 +673,7 @@ static void TeardownAll(void) SolidSyslogFreeRtosMutex_Destroy(bufferMutex); SolidSyslogFreeRtosMutex_Destroy(lifecycleMutex); lifecycleMutex = NULL; - SolidSyslogSwitchingSender_Destroy(); + SolidSyslogSwitchingSender_Destroy(switchingSender); SolidSyslogStreamSender_Destroy(tcpSender); SolidSyslogFreeRtosTcpStream_Destroy(tcpStream); SolidSyslogUdpSender_Destroy(udpSender); @@ -814,7 +815,8 @@ static void InteractiveTask(void* argument) .Selector = BddTargetSwitchConfig_Selector, }; BddTargetSwitchConfig_SetByName("udp"); - struct SolidSyslogSender* sender = SolidSyslogSwitchingSender_Create(&switchConfig); + switchingSender = SolidSyslogSwitchingSender_Create(&switchConfig); + struct SolidSyslogSender* sender = switchingSender; /* CircularBuffer drained by ServiceTask below, with a FreeRtosMutex * gating concurrent producers (interactive task today; multi-task diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 4c0327ae..098f6250 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -56,6 +56,7 @@ static struct SolidSyslogStream* plainTcpStream; static SolidSyslogStreamSenderStorage plainTcpSenderStorage; static struct SolidSyslogSender* plainTcpSender; static struct SolidSyslogSender* udpSender; +static struct SolidSyslogSender* switchingSender; static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) { @@ -107,7 +108,8 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetOptions* opt switchConfig.Selector = BddTargetSwitchConfig_Selector; BddTargetSwitchConfig_SetByName(options->Transport); - return SolidSyslogSwitchingSender_Create(&switchConfig); + switchingSender = SolidSyslogSwitchingSender_Create(&switchConfig); + return switchingSender; } static enum SolidSyslogDiscardPolicy MapDiscardPolicy(const char* policy) @@ -184,7 +186,7 @@ static struct SolidSyslogStore* CreateStore(const struct BddTargetOptions* optio static void DestroySender(void) { - SolidSyslogSwitchingSender_Destroy(); + SolidSyslogSwitchingSender_Destroy(switchingSender); BddTargetTlsSender_Destroy(); SolidSyslogStreamSender_Destroy(plainTcpSender); SolidSyslogPosixTcpStream_Destroy(plainTcpStream); diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 1311e18e..08c895d0 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -77,6 +77,7 @@ static struct SolidSyslogStream* plainTcpStream; static struct SolidSyslogSender* plainTcpSender; static struct SolidSyslogDatagram* udpDatagram; static struct SolidSyslogSender* udpSender; +static struct SolidSyslogSender* switchingSender; /* Block-store backing — created in CreateStore, released in DestroyStore. */ static struct SolidSyslogFile* storeFile; @@ -210,12 +211,13 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetWindowsOptio switchConfig.Selector = BddTargetSwitchConfig_Selector; BddTargetSwitchConfig_SetByName(options->Transport); - return SolidSyslogSwitchingSender_Create(&switchConfig); + switchingSender = SolidSyslogSwitchingSender_Create(&switchConfig); + return switchingSender; } static void DestroySender(void) { - SolidSyslogSwitchingSender_Destroy(); + SolidSyslogSwitchingSender_Destroy(switchingSender); BddTargetTlsSender_Destroy(); SolidSyslogStreamSender_Destroy(plainTcpSender); SolidSyslogWinsockTcpStream_Destroy(plainTcpStream); diff --git a/CLAUDE.md b/CLAUDE.md index 8f6eb247..793bf4c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -334,7 +334,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogFreeRtosTcpStream.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-TCP for TCP | `SolidSyslogFreeRtosTcpStreamStorage`, `SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE`, `SolidSyslogFreeRtosTcpStream_Create(storage)`, `_Destroy(stream)` (wraps `FreeRTOS_socket` / `FreeRTOS_connect` / `FreeRTOS_send` / `FreeRTOS_recv` / `FreeRTOS_closesocket`; bounded blocking connect via `SO_SNDTIMEO=200ms`, cleared post-connect so Send/Read are non-blocking) | | `SolidSyslogUdpSender.h` | System setup code using UDP transport | `SolidSyslogUdpSenderConfig` (resolver, datagram, endpoint, endpointVersion), `SolidSyslogUdpSender_Create(config)`, `_Destroy(sender)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create`. Pool-exhaustion and bad-config fallback is the shared `SolidSyslogNullSender`. | | `SolidSyslogStreamSender.h` | System setup code using octet-framed transport (RFC 6587) over any Stream — `SolidSyslogPosixTcpStream` (POSIX TCP), `SolidSyslogWinsockTcpStream` (Windows TCP), `SolidSyslogFreeRtosTcpStream` (FreeRTOS-Plus-TCP), `SolidSyslogTlsStream` (TLS; OpenSSL reference integration), or a caller-supplied Stream backend | `SolidSyslogStreamSenderConfig` (resolver, stream, endpoint, endpointVersion), `SolidSyslogStreamSender_Create`, `_Destroy` | -| `SolidSyslogSwitchingSender.h` | System setup code composing multiple inner senders | `SolidSyslogSwitchingSenderConfig` (senders, senderCount, selector), `SolidSyslogSwitchingSenderSelector`, `SolidSyslogSwitchingSender_Create`, `_Destroy` | +| `SolidSyslogSwitchingSender.h` | System setup code composing multiple inner senders | `SolidSyslogSwitchingSenderConfig` (senders, senderCount, selector), `SolidSyslogSwitchingSenderSelector`, `SolidSyslogSwitchingSender_Create(config)`, `_Destroy(sender)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create`. Out-of-range selector and pool exhaustion both resolve to the shared `SolidSyslogNullSender`. | | `SolidSyslogBuffer.h` | Library internals consuming a buffer (Service algorithm) | `SolidSyslogBuffer_Write`, `_Read` | | `SolidSyslogBufferDefinition.h` | Buffer implementors (extension point) | `SolidSyslogBuffer` vtable struct | | `SolidSyslogPassthroughBuffer.h` | System setup code (single-task, no buffering) | `SolidSyslogPassthroughBuffer_Create(sender)`, `_Destroy(buffer)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create` to release the slot. | diff --git a/Core/Interface/SolidSyslogSwitchingSender.h b/Core/Interface/SolidSyslogSwitchingSender.h index d2860d74..f04e2c4a 100644 --- a/Core/Interface/SolidSyslogSwitchingSender.h +++ b/Core/Interface/SolidSyslogSwitchingSender.h @@ -18,7 +18,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogSender* SolidSyslogSwitchingSender_Create(const struct SolidSyslogSwitchingSenderConfig* config); - void SolidSyslogSwitchingSender_Destroy(void); + void SolidSyslogSwitchingSender_Destroy(struct SolidSyslogSender * sender); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index ddece949..01c4cec0 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -93,4 +93,23 @@ #error "SOLIDSYSLOG_UDP_SENDER_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogSwitchingSender instances the library's + * internal static pool can simultaneously hold. + * + * Default 1 — a SwitchingSender wraps several inner senders, so one + * per process is the typical pattern. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE + * if more than one is genuinely needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index cef52cad..30ec5b88 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES SolidSyslogError.c SolidSyslogConfigLock.c SolidSyslogSwitchingSender.c + SolidSyslogSwitchingSenderStatic.c SolidSyslogBuffer.c SolidSyslogFormatter.c SolidSyslogMetaSd.c diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index 9177f0e2..b0484649 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -21,6 +21,10 @@ "SolidSyslogUdpSender_Create pool exhausted; returning fallback sender" #define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_UNKNOWN_DESTROY \ "SolidSyslogUdpSender_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_POOL_EXHAUSTED \ + "SolidSyslogSwitchingSender_Create pool exhausted; returning fallback sender" +#define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_UNKNOWN_DESTROY \ + "SolidSyslogSwitchingSender_Destroy called with a handle not issued by this pool" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_POOL_EXHAUSTED \ "SolidSyslogCircularBuffer_Create pool exhausted; returning fallback buffer" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogSwitchingSender.c b/Core/Source/SolidSyslogSwitchingSender.c index 8ac12b58..5a871675 100644 --- a/Core/Source/SolidSyslogSwitchingSender.c +++ b/Core/Source/SolidSyslogSwitchingSender.c @@ -2,16 +2,11 @@ #include #include +#include "SolidSyslogNullSender.h" +#include "SolidSyslogSender.h" #include "SolidSyslogSenderDefinition.h" #include "SolidSyslogSwitchingSender.h" -#include "SolidSyslogSender.h" - -struct SolidSyslogSwitchingSender -{ - struct SolidSyslogSender Base; - struct SolidSyslogSwitchingSenderConfig Config; - struct SolidSyslogSender* CurrentSender; -}; +#include "SolidSyslogSwitchingSenderPrivate.h" static bool SwitchingSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size); static void SwitchingSender_Disconnect(struct SolidSyslogSender* base); @@ -27,25 +22,25 @@ static inline void SwitchingSender_SwitchTo( struct SolidSyslogSender* newCurrent ); static inline struct SolidSyslogSender* SwitchingSender_RequestedSender(const struct SolidSyslogSwitchingSender* self); -static bool SwitchingSender_NilSend(struct SolidSyslogSender* base, const void* buffer, size_t size); -static void SwitchingSender_NilDisconnect(struct SolidSyslogSender* base); -static struct SolidSyslogSender NIL_SENDER = {SwitchingSender_NilSend, SwitchingSender_NilDisconnect}; -static const struct SolidSyslogSwitchingSender DEFAULT_INSTANCE = {.CurrentSender = &NIL_SENDER}; -static struct SolidSyslogSwitchingSender instance; - -struct SolidSyslogSender* SolidSyslogSwitchingSender_Create(const struct SolidSyslogSwitchingSenderConfig* config) +void SwitchingSender_Initialise(struct SolidSyslogSender* base, const struct SolidSyslogSwitchingSenderConfig* config) { - instance = DEFAULT_INSTANCE; - instance.Config = *config; - instance.Base.Send = SwitchingSender_Send; - instance.Base.Disconnect = SwitchingSender_Disconnect; - return &instance.Base; + struct SolidSyslogSwitchingSender* self = SwitchingSender_SelfFromBase(base); + self->Base.Send = SwitchingSender_Send; + self->Base.Disconnect = SwitchingSender_Disconnect; + self->Config = *config; + self->CurrentSender = SolidSyslogNullSender_Get(); } -void SolidSyslogSwitchingSender_Destroy(void) +void SwitchingSender_Cleanup(struct SolidSyslogSender* base) { - instance = DEFAULT_INSTANCE; + struct SolidSyslogSwitchingSender* self = SwitchingSender_SelfFromBase(base); + self->Base.Send = NULL; + self->Base.Disconnect = NULL; + self->Config.Senders = NULL; + self->Config.SenderCount = 0; + self->Config.Selector = NULL; + self->CurrentSender = NULL; } static bool SwitchingSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size) @@ -93,13 +88,14 @@ static inline void SwitchingSender_SwitchTo( self->CurrentSender = newCurrent; } -/* Falls back to the nil sender when the selector returns an out-of-range - * index (including the empty-array case). Contains the application contract - * violation of an invalid selector without corrupting memory or crashing. */ +/* Out-of-range selector index (including empty-array case) resolves to the + * shared NullSender. NullSender.Send returns true so the Service algorithm + * drops the message — a misconfigured selector must not retain messages in + * the Store. */ static inline struct SolidSyslogSender* SwitchingSender_RequestedSender(const struct SolidSyslogSwitchingSender* self) { + struct SolidSyslogSender* result = SolidSyslogNullSender_Get(); uint8_t index = self->Config.Selector(); - struct SolidSyslogSender* result = &NIL_SENDER; if (index < self->Config.SenderCount) { @@ -108,16 +104,3 @@ static inline struct SolidSyslogSender* SwitchingSender_RequestedSender(const st return result; } - -static bool SwitchingSender_NilSend(struct SolidSyslogSender* base, const void* buffer, size_t size) -{ - (void) base; - (void) buffer; - (void) size; - return false; -} - -static void SwitchingSender_NilDisconnect(struct SolidSyslogSender* base) -{ - (void) base; -} diff --git a/Core/Source/SolidSyslogSwitchingSenderPrivate.h b/Core/Source/SolidSyslogSwitchingSenderPrivate.h new file mode 100644 index 00000000..2319284d --- /dev/null +++ b/Core/Source/SolidSyslogSwitchingSenderPrivate.h @@ -0,0 +1,17 @@ +#ifndef SOLIDSYSLOGSWITCHINGSENDERPRIVATE_H +#define SOLIDSYSLOGSWITCHINGSENDERPRIVATE_H + +#include "SolidSyslogSenderDefinition.h" +#include "SolidSyslogSwitchingSender.h" + +struct SolidSyslogSwitchingSender +{ + struct SolidSyslogSender Base; + struct SolidSyslogSwitchingSenderConfig Config; + struct SolidSyslogSender* CurrentSender; +}; + +void SwitchingSender_Initialise(struct SolidSyslogSender* base, const struct SolidSyslogSwitchingSenderConfig* config); +void SwitchingSender_Cleanup(struct SolidSyslogSender* base); + +#endif /* SOLIDSYSLOGSWITCHINGSENDERPRIVATE_H */ diff --git a/Core/Source/SolidSyslogSwitchingSenderStatic.c b/Core/Source/SolidSyslogSwitchingSenderStatic.c new file mode 100644 index 00000000..fed2b491 --- /dev/null +++ b/Core/Source/SolidSyslogSwitchingSenderStatic.c @@ -0,0 +1,67 @@ +#include "SolidSyslogSwitchingSender.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullSender.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogSenderDefinition.h" +#include "SolidSyslogSwitchingSenderPrivate.h" +#include "SolidSyslogTunables.h" + +static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* base); +static void SwitchingSender_CleanupAtIndex(size_t index, void* context); + +static bool InUse[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE]; +static struct SolidSyslogSwitchingSender Pool[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE}; + +struct SolidSyslogSender* SolidSyslogSwitchingSender_Create(const struct SolidSyslogSwitchingSenderConfig* config) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + struct SolidSyslogSender* handle = SolidSyslogNullSender_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + SwitchingSender_Initialise(&Pool[index].Base, config); + handle = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogSwitchingSender_Destroy(struct SolidSyslogSender* base) +{ + size_t index = SwitchingSender_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, SwitchingSender_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_UNKNOWN_DESTROY); + } +} + +static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* base) +{ + size_t result = SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE; poolIndex++) + { + if (base == &Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void SwitchingSender_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + SwitchingSender_Cleanup(&Pool[index].Base); +} diff --git a/Tests/SolidSyslogSwitchingSenderTest.cpp b/Tests/SolidSyslogSwitchingSenderTest.cpp index 5bdde009..8c5d3568 100644 --- a/Tests/SolidSyslogSwitchingSenderTest.cpp +++ b/Tests/SolidSyslogSwitchingSenderTest.cpp @@ -4,6 +4,7 @@ #include "SenderFake.h" #include "SolidSyslogSender.h" #include "SolidSyslogSwitchingSender.h" +#include "SolidSyslogTunables.h" #include "TestUtils.h" #include "CppUTest/TestHarness.h" @@ -60,7 +61,7 @@ TEST_GROUP(SolidSyslogSwitchingSender) void teardown() override { - SolidSyslogSwitchingSender_Destroy(); + SolidSyslogSwitchingSender_Destroy(sender); SenderFake_Destroy(innerC); SenderFake_Destroy(innerB); SenderFake_Destroy(innerA); @@ -68,6 +69,13 @@ TEST_GROUP(SolidSyslogSwitchingSender) void CreateSwitchingSender(size_t count) { + /* Pool semantics: if setup already created one (slot-allocated), destroy + * it before creating again so this call reuses the same slot rather than + * exhausting the pool and getting the Fallback. */ + if (sender != nullptr) + { + SolidSyslogSwitchingSender_Destroy(sender); + } struct SolidSyslogSwitchingSenderConfig config = {inners, count, TestSelector}; sender = SolidSyslogSwitchingSender_Create(&config); } @@ -86,14 +94,14 @@ TEST(SolidSyslogSwitchingSender, CreateDestroyWorksWithoutCrashing) TEST(SolidSyslogSwitchingSender, DestroyDoesNotSendToInnerSenders) { - SolidSyslogSwitchingSender_Destroy(); + SolidSyslogSwitchingSender_Destroy(sender); CALLED_FAKE_ON(SenderFake_Send, innerA, NEVER); CALLED_FAKE_ON(SenderFake_Send, innerB, NEVER); } TEST(SolidSyslogSwitchingSender, DestroyDoesNotDisconnectInnerSenders) { - SolidSyslogSwitchingSender_Destroy(); + SolidSyslogSwitchingSender_Destroy(sender); CALLED_FAKE_ON(SenderFake_Disconnect, innerA, NEVER); CALLED_FAKE_ON(SenderFake_Disconnect, innerB, NEVER); } @@ -203,10 +211,13 @@ TEST(SolidSyslogSwitchingSender, SelectorAtLastValidIndexDelegatesToThatSender) CALLED_FAKE_ON(SenderFake_Send, innerC, ONCE); } -TEST(SolidSyslogSwitchingSender, ZeroSenderCountSendReturnsFalse) +TEST(SolidSyslogSwitchingSender, ZeroSenderCountSendReturnsTrueToDropOnTheFloor) { + /* Out-of-range selector (or zero-count) resolves to the shared + * NullSender, whose Send returns true so messages drop rather than + * accumulate in the Store. */ CreateSwitchingSender(0); - CHECK_FALSE(SolidSyslogSender_Send(sender, "x", 1)); + CHECK_TRUE(SolidSyslogSender_Send(sender, "x", 1)); } TEST(SolidSyslogSwitchingSender, ZeroSenderCountDisconnectDoesNotCrash) @@ -215,10 +226,12 @@ TEST(SolidSyslogSwitchingSender, ZeroSenderCountDisconnectDoesNotCrash) SolidSyslogSender_Disconnect(sender); } -TEST(SolidSyslogSwitchingSender, SelectorBeyondEndSendReturnsFalseAndDoesNotTouchInnerSenders) +TEST(SolidSyslogSwitchingSender, SelectorBeyondEndSendReturnsTrueAndDoesNotTouchInnerSenders) { + /* Out-of-range selector resolves to the shared NullSender, whose + * Send returns true so messages drop rather than accumulate. */ selectorReturn = BEYOND_END; - CHECK_FALSE(SolidSyslogSender_Send(sender, "x", 1)); + CHECK_TRUE(SolidSyslogSender_Send(sender, "x", 1)); CALLED_FAKE_ON(SenderFake_Send, innerA, NEVER); CALLED_FAKE_ON(SenderFake_Send, innerB, NEVER); } @@ -244,3 +257,68 @@ TEST(SolidSyslogSwitchingSender, SelectorBeyondEndDisconnectBeforeSendDoesNotTou CALLED_FAKE_ON(SenderFake_Disconnect, innerA, NEVER); CALLED_FAKE_ON(SenderFake_Disconnect, innerB, NEVER); } + +// Pool tests — prove SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE caps live +// instances and overflow falls back to the shared SolidSyslogNullSender. + +// clang-format off +TEST_GROUP(SolidSyslogSwitchingSenderPool) +{ + struct SolidSyslogSender* innerA = nullptr; + struct SolidSyslogSender* inners[1] = {nullptr}; + struct SolidSyslogSender* pooled[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE] = {}; + struct SolidSyslogSender* overflow = nullptr; + SolidSyslogSwitchingSenderConfig config = {}; + + void setup() override + { + innerA = SenderFake_Create(); + inners[0] = innerA; + config = {inners, 1, TestSelector}; + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogSwitchingSender_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogSwitchingSender_Destroy(overflow); + } + SenderFake_Destroy(innerA); + } + + struct SolidSyslogSender* MakeSender() + { + return SolidSyslogSwitchingSender_Create(&config); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeSender(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogSwitchingSenderPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeSender(); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} From 138618aafc39cc6f6a65922779330fd896b4eb77 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 14:21:27 +0100 Subject: [PATCH 08/18] refactor: S11.04 migrate MetaSd onto PoolAllocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the E11 canonical 3-TU split. MetaSdPrivate.h holds the struct + Initialise/Cleanup decls; MetaSd.c keeps the vtable Format method and the per-field emit helpers; MetaSdStatic.c owns the pool, public Create/Destroy, IndexFromHandle, CleanupAtIndex, and the bad-config validation. The class-private nilMetaSd is gone — bad config and pool exhaustion both return SolidSyslogNullSd_Get(). Public API: _Destroy(void) → _Destroy(struct SolidSyslogStructuredData*). Caller updates in SolidSyslogMetaSdTest.cpp, SolidSyslogTest.cpp, and all three BDD targets. Pool size tunable: SOLIDSYSLOG_META_SD_POOL_SIZE, default 1. Error messages: POOL_EXHAUSTED (ERROR) and UNKNOWN_DESTROY (WARNING). One new pool test proves capacity + overflow returns a distinct fallback. Validated at SOLIDSYSLOG_META_SD_POOL_SIZE=3. 100% line coverage on MetaSd.c and MetaSdStatic.c. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/Targets/FreeRtos/main.c | 2 +- Bdd/Targets/Linux/main.c | 2 +- Bdd/Targets/Windows/BddTargetWindows.c | 2 +- CLAUDE.md | 2 +- Core/Interface/SolidSyslogMetaSd.h | 2 +- Core/Interface/SolidSyslogTunablesDefaults.h | 16 ++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogErrorMessages.h | 3 + Core/Source/SolidSyslogMetaSd.c | 58 +++---------- Core/Source/SolidSyslogMetaSdPrivate.h | 21 +++++ Core/Source/SolidSyslogMetaSdStatic.c | 89 ++++++++++++++++++++ Tests/SolidSyslogMetaSdTest.cpp | 69 ++++++++++++++- Tests/SolidSyslogTest.cpp | 8 +- 13 files changed, 219 insertions(+), 56 deletions(-) create mode 100644 Core/Source/SolidSyslogMetaSdPrivate.h create mode 100644 Core/Source/SolidSyslogMetaSdStatic.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 76893341..5b37152a 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -652,7 +652,7 @@ static void TeardownAll(void) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); SolidSyslogTimeQualitySd_Destroy(); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(atomicCounter); DestroyCurrentStore(); if (fatfsMounted) diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 098f6250..e56fb7b4 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -289,7 +289,7 @@ int main(int argc, char* argv[]) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); SolidSyslogTimeQualitySd_Destroy(); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(counter); DestroyStore(store, &options); SolidSyslogPosixMessageQueueBuffer_Destroy(); diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 08c895d0..e2c5844a 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -360,7 +360,7 @@ int BddTargetWindows_Run(int argc, char* argv[]) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); SolidSyslogTimeQualitySd_Destroy(); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogWindowsAtomicCounter_Destroy(counter); DestroyStore(store, &options); SolidSyslogCircularBuffer_Destroy(buffer); diff --git a/CLAUDE.md b/CLAUDE.md index 793bf4c0..a7303cca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -377,7 +377,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogStructuredData.h` | Library internals (SD dispatch) | `SolidSyslogStructuredData_Format` (writes into `SolidSyslogFormatter*`) | | `SolidSyslogStructuredDataDefinition.h` | SD implementors (extension point) | `SolidSyslogStructuredData` vtable struct (Format takes `SolidSyslogFormatter*`) | | `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`, `_Destroy` | +| `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`. | | `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) | | `SolidSyslogStdAtomicCounter.h` | System setup code on platforms with C11 `` | `SolidSyslogStdAtomicCounterStorage`, `SOLIDSYSLOG_STDATOMICCOUNTER_SIZE`, `SolidSyslogStdAtomicCounter_Create(storage)`, `_Destroy(base)`. Uses `_Atomic uint32_t` + `atomic_compare_exchange_strong_explicit` CAS loop. | diff --git a/Core/Interface/SolidSyslogMetaSd.h b/Core/Interface/SolidSyslogMetaSd.h index 2f690271..7c5fb8fb 100644 --- a/Core/Interface/SolidSyslogMetaSd.h +++ b/Core/Interface/SolidSyslogMetaSd.h @@ -21,7 +21,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogStructuredData* SolidSyslogMetaSd_Create(const struct SolidSyslogMetaSdConfig* config); - void SolidSyslogMetaSd_Destroy(void); + void SolidSyslogMetaSd_Destroy(struct SolidSyslogStructuredData * sd); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 01c4cec0..91c276e9 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -112,4 +112,20 @@ #error "SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogMetaSd instances the library's internal + * static pool can simultaneously hold. Default 1 — meta SD is typically + * wired into SolidSyslogConfig.Sd[] once per process. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_META_SD_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_META_SD_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_META_SD_POOL_SIZE < 1 +#error "SOLIDSYSLOG_META_SD_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 30ec5b88..48531b05 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES SolidSyslogBuffer.c SolidSyslogFormatter.c SolidSyslogMetaSd.c + SolidSyslogMetaSdStatic.c SolidSyslogPassthroughBuffer.c SolidSyslogPassthroughBufferStatic.c SolidSyslogCircularBuffer.c diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index b0484649..d11e398d 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -25,6 +25,9 @@ "SolidSyslogSwitchingSender_Create pool exhausted; returning fallback sender" #define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_UNKNOWN_DESTROY \ "SolidSyslogSwitchingSender_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_METASD_POOL_EXHAUSTED "SolidSyslogMetaSd_Create pool exhausted; returning fallback SD" +#define SOLIDSYSLOG_ERROR_MSG_METASD_UNKNOWN_DESTROY \ + "SolidSyslogMetaSd_Destroy called with a handle not issued by this pool" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_POOL_EXHAUSTED \ "SolidSyslogCircularBuffer_Create pool exhausted; returning fallback buffer" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogMetaSd.c b/Core/Source/SolidSyslogMetaSd.c index 19c63e82..0030ee98 100644 --- a/Core/Source/SolidSyslogMetaSd.c +++ b/Core/Source/SolidSyslogMetaSd.c @@ -3,67 +3,35 @@ #include #include "SolidSyslogAtomicCounter.h" -#include "SolidSyslogError.h" -#include "SolidSyslogErrorMessages.h" #include "SolidSyslogFormatter.h" -#include "SolidSyslogPrival.h" +#include "SolidSyslogMetaSdPrivate.h" #include "SolidSyslogStructuredDataDefinition.h" struct SolidSyslogFormatter; -struct SolidSyslogMetaSd -{ - struct SolidSyslogStructuredData Base; - struct SolidSyslogAtomicCounter* Counter; - SolidSyslogSysUpTimeFunction GetSysUpTime; - SolidSyslogStringFunction GetLanguage; -}; - static void MetaSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); -static void MetaSd_NilMetaSdFormat(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); static inline struct SolidSyslogMetaSd* MetaSd_SelfFromBase(struct SolidSyslogStructuredData* base); static inline void MetaSd_EmitSequenceId(struct SolidSyslogMetaSd* self, struct SolidSyslogFormatter* formatter); static inline void MetaSd_EmitSysUpTime(struct SolidSyslogMetaSd* self, struct SolidSyslogFormatter* formatter); static inline void MetaSd_EmitLanguage(struct SolidSyslogMetaSd* self, struct SolidSyslogFormatter* formatter); -static struct SolidSyslogMetaSd MetaSd_Instance; - -struct SolidSyslogStructuredData* SolidSyslogMetaSd_Create(const struct SolidSyslogMetaSdConfig* config) -{ - static struct SolidSyslogStructuredData nilMetaSd = {.Format = MetaSd_NilMetaSdFormat}; - struct SolidSyslogStructuredData* result = &nilMetaSd; - if (config == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_METASD_CREATE_NULL_CONFIG); - } - else if (config->Counter == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_METASD_CREATE_NULL_COUNTER); - } - else - { - MetaSd_Instance.Base.Format = MetaSd_Format; - MetaSd_Instance.Counter = config->Counter; - MetaSd_Instance.GetSysUpTime = config->GetSysUpTime; - MetaSd_Instance.GetLanguage = config->GetLanguage; - result = &MetaSd_Instance.Base; - } - return result; -} - -void SolidSyslogMetaSd_Destroy(void) +void MetaSd_Initialise(struct SolidSyslogStructuredData* base, const struct SolidSyslogMetaSdConfig* config) { - MetaSd_Instance.Base.Format = NULL; - MetaSd_Instance.Counter = NULL; - MetaSd_Instance.GetSysUpTime = NULL; - MetaSd_Instance.GetLanguage = NULL; + struct SolidSyslogMetaSd* self = MetaSd_SelfFromBase(base); + self->Base.Format = MetaSd_Format; + self->Counter = config->Counter; + self->GetSysUpTime = config->GetSysUpTime; + self->GetLanguage = config->GetLanguage; } -static void MetaSd_NilMetaSdFormat(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter) +void MetaSd_Cleanup(struct SolidSyslogStructuredData* base) { - (void) base; - (void) formatter; + struct SolidSyslogMetaSd* self = MetaSd_SelfFromBase(base); + self->Base.Format = NULL; + self->Counter = NULL; + self->GetSysUpTime = NULL; + self->GetLanguage = NULL; } static void MetaSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter) diff --git a/Core/Source/SolidSyslogMetaSdPrivate.h b/Core/Source/SolidSyslogMetaSdPrivate.h new file mode 100644 index 00000000..c7695997 --- /dev/null +++ b/Core/Source/SolidSyslogMetaSdPrivate.h @@ -0,0 +1,21 @@ +#ifndef SOLIDSYSLOGMETASDPRIVATE_H +#define SOLIDSYSLOGMETASDPRIVATE_H + +#include "SolidSyslogMetaSd.h" +#include "SolidSyslogStringFunction.h" +#include "SolidSyslogStructuredDataDefinition.h" + +struct SolidSyslogAtomicCounter; + +struct SolidSyslogMetaSd +{ + struct SolidSyslogStructuredData Base; + struct SolidSyslogAtomicCounter* Counter; + SolidSyslogSysUpTimeFunction GetSysUpTime; + SolidSyslogStringFunction GetLanguage; +}; + +void MetaSd_Initialise(struct SolidSyslogStructuredData* base, const struct SolidSyslogMetaSdConfig* config); +void MetaSd_Cleanup(struct SolidSyslogStructuredData* base); + +#endif /* SOLIDSYSLOGMETASDPRIVATE_H */ diff --git a/Core/Source/SolidSyslogMetaSdStatic.c b/Core/Source/SolidSyslogMetaSdStatic.c new file mode 100644 index 00000000..1d25ebc9 --- /dev/null +++ b/Core/Source/SolidSyslogMetaSdStatic.c @@ -0,0 +1,89 @@ +#include "SolidSyslogMetaSd.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogMetaSdPrivate.h" +#include "SolidSyslogNullSd.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTunables.h" + +static bool MetaSd_IsValidConfig(const struct SolidSyslogMetaSdConfig* config); +static size_t MetaSd_IndexFromHandle(const struct SolidSyslogStructuredData* base); +static void MetaSd_CleanupAtIndex(size_t index, void* context); + +static bool InUse[SOLIDSYSLOG_META_SD_POOL_SIZE]; +static struct SolidSyslogMetaSd Pool[SOLIDSYSLOG_META_SD_POOL_SIZE]; +static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_META_SD_POOL_SIZE}; + +struct SolidSyslogStructuredData* SolidSyslogMetaSd_Create(const struct SolidSyslogMetaSdConfig* config) +{ + struct SolidSyslogStructuredData* result = SolidSyslogNullSd_Get(); + if (MetaSd_IsValidConfig(config)) + { + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + MetaSd_Initialise(&Pool[index].Base, config); + result = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_METASD_POOL_EXHAUSTED); + } + } + return result; +} + +void SolidSyslogMetaSd_Destroy(struct SolidSyslogStructuredData* base) +{ + size_t index = MetaSd_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, MetaSd_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_METASD_UNKNOWN_DESTROY); + } +} + +static bool MetaSd_IsValidConfig(const struct SolidSyslogMetaSdConfig* config) +{ + bool valid = false; + if (config == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_METASD_CREATE_NULL_CONFIG); + } + else if (config->Counter == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_METASD_CREATE_NULL_COUNTER); + } + else + { + valid = true; + } + return valid; +} + +static size_t MetaSd_IndexFromHandle(const struct SolidSyslogStructuredData* base) +{ + size_t result = SOLIDSYSLOG_META_SD_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_META_SD_POOL_SIZE; poolIndex++) + { + if (base == &Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void MetaSd_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + MetaSd_Cleanup(&Pool[index].Base); +} diff --git a/Tests/SolidSyslogMetaSdTest.cpp b/Tests/SolidSyslogMetaSdTest.cpp index ce9257cf..35730ddc 100644 --- a/Tests/SolidSyslogMetaSdTest.cpp +++ b/Tests/SolidSyslogMetaSdTest.cpp @@ -7,6 +7,7 @@ #include "SolidSyslogMetaSd.h" #include "SolidSyslogPrival.h" #include "SolidSyslogStructuredData.h" +#include "SolidSyslogTunables.h" #include "TestUtils.h" #include "CppUTest/TestHarness.h" @@ -92,14 +93,14 @@ TEST_GROUP(SolidSyslogMetaSd) void teardown() override { - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(sd); TestAtomicCounter_Destroy(counter); ErrorHandlerFake_Uninstall(); } void recreateWith(const SolidSyslogMetaSdConfig* configPtr) { - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(sd); sd = SolidSyslogMetaSd_Create(configPtr); } @@ -285,3 +286,67 @@ TEST(SolidSyslogMetaSd, CreateWithNullCounterReportsWarning) LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); STRCMP_EQUAL("SolidSyslogMetaSd_Create config.Counter is NULL", ErrorHandlerFake_LastMessage()); } + +// Pool tests — prove SOLIDSYSLOG_META_SD_POOL_SIZE caps live instances +// and overflow falls back to the shared SolidSyslogNullSd. + +// clang-format off +TEST_GROUP(SolidSyslogMetaSdPool) +{ + TestAtomicCounterStorage counterStorage; + SolidSyslogAtomicCounter* counter = nullptr; + SolidSyslogMetaSdConfig config{}; + struct SolidSyslogStructuredData* pooled[SOLIDSYSLOG_META_SD_POOL_SIZE] = {}; + struct SolidSyslogStructuredData* overflow = nullptr; + + void setup() override + { + counter = TestAtomicCounter_Create(&counterStorage); + config.Counter = counter; + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogMetaSd_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogMetaSd_Destroy(overflow); + } + TestAtomicCounter_Destroy(counter); + } + + struct SolidSyslogStructuredData* MakeSd() + { + return SolidSyslogMetaSd_Create(&config); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeSd(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogMetaSdPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeSd(); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index 98edef6a..98e3f194 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -643,7 +643,7 @@ TEST(SolidSyslog, MetaSdProducesSequenceIdInStructuredData) SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("[meta sequenceId=\"1\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); TestAtomicCounter_Destroy(counter); } @@ -662,7 +662,7 @@ TEST(SolidSyslog, MetaSdSequenceIdIncrementsAcrossLogCalls) Log(); Log(); STRCMP_EQUAL("[meta sequenceId=\"2\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); TestAtomicCounter_Destroy(counter); } @@ -681,7 +681,7 @@ TEST(SolidSyslog, MsgFieldPreservedWithMetaSd) message.Msg = "hello world"; Log(); STRCMP_EQUAL("hello world", SyslogMsg(lastMessage()).c_str()); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); TestAtomicCounter_Destroy(counter); } @@ -748,7 +748,7 @@ TEST(SolidSyslog, MetaSdAndTimeQualitySdCoexistInSdArray) SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str() ); SolidSyslogTimeQualitySd_Destroy(); - SolidSyslogMetaSd_Destroy(); + SolidSyslogMetaSd_Destroy(metaSd); TestAtomicCounter_Destroy(counter); } From 5c88a0fdbc4eef356144cf1ddf09549f928e1d07 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 14:26:05 +0100 Subject: [PATCH 09/18] refactor: S11.04 migrate TimeQualitySd onto PoolAllocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the E11 canonical 3-TU split. TimeQualitySd has no bad-config validation today (no NULL check on the getTimeQuality callback) and this refactor preserves that — validation belongs in a separate feature commit, not here. Pool exhaustion resolves to SolidSyslogNullSd_Get(). Public API: _Destroy(void) → _Destroy(struct SolidSyslogStructuredData*). Caller updates in TimeQualitySdTest.cpp, SolidSyslogTest.cpp, and all three BDD targets. Pool size tunable: SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE, default 1. Error messages: POOL_EXHAUSTED (ERROR) and UNKNOWN_DESTROY (WARNING). One new pool test proves capacity + overflow returns a distinct fallback. Validated at SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE=3. 100% line coverage on TimeQualitySd.c and TimeQualitySdStatic.c. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/Targets/FreeRtos/main.c | 2 +- Bdd/Targets/Linux/main.c | 2 +- Bdd/Targets/Windows/BddTargetWindows.c | 2 +- CLAUDE.md | 2 +- Core/Interface/SolidSyslogTimeQualitySd.h | 2 +- Core/Interface/SolidSyslogTunablesDefaults.h | 15 +++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogErrorMessages.h | 4 ++ Core/Source/SolidSyslogTimeQualitySd.c | 24 +++---- Core/Source/SolidSyslogTimeQualitySdPrivate.h | 16 +++++ Core/Source/SolidSyslogTimeQualitySdStatic.c | 67 +++++++++++++++++++ Tests/SolidSyslogTest.cpp | 2 +- Tests/SolidSyslogTimeQualitySdTest.cpp | 57 +++++++++++++++- 13 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 Core/Source/SolidSyslogTimeQualitySdPrivate.h create mode 100644 Core/Source/SolidSyslogTimeQualitySdStatic.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 5b37152a..7a33b76b 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -651,7 +651,7 @@ static void TeardownAll(void) solidSyslogReady = false; SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); - SolidSyslogTimeQualitySd_Destroy(); + SolidSyslogTimeQualitySd_Destroy(timeQualitySd); SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(atomicCounter); DestroyCurrentStore(); diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index e56fb7b4..32061ff9 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -288,7 +288,7 @@ int main(int argc, char* argv[]) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); - SolidSyslogTimeQualitySd_Destroy(); + SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(counter); DestroyStore(store, &options); diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index e2c5844a..38dc0d36 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -359,7 +359,7 @@ int BddTargetWindows_Run(int argc, char* argv[]) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); - SolidSyslogTimeQualitySd_Destroy(); + SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogWindowsAtomicCounter_Destroy(counter); DestroyStore(store, &options); diff --git a/CLAUDE.md b/CLAUDE.md index a7303cca..7ca7b064 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -383,7 +383,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogStdAtomicCounter.h` | System setup code on platforms with C11 `` | `SolidSyslogStdAtomicCounterStorage`, `SOLIDSYSLOG_STDATOMICCOUNTER_SIZE`, `SolidSyslogStdAtomicCounter_Create(storage)`, `_Destroy(base)`. Uses `_Atomic uint32_t` + `atomic_compare_exchange_strong_explicit` CAS loop. | | `SolidSyslogWindowsAtomicCounter.h` | System setup code on Windows targets without `` (legacy MSVC) | `SolidSyslogWindowsAtomicCounterStorage`, `SOLIDSYSLOG_WINDOWSATOMICCOUNTER_SIZE`, `SolidSyslogWindowsAtomicCounter_Create(storage)`, `_Destroy(base)`. Uses `volatile LONG` + `InterlockedCompareExchange` CAS loop. | | `SolidSyslogTimeQuality.h` | Any code providing time quality data | `SolidSyslogTimeQuality`, `SolidSyslogTimeQualityFunction`, `SOLIDSYSLOG_SYNC_ACCURACY_OMIT` | -| `SolidSyslogTimeQualitySd.h` | System setup code using timeQuality SD | `SolidSyslogTimeQualitySd_Create`, `_Destroy` | +| `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`. | | `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`, `_Destroy` | Most application code only needs `SolidSyslog.h` — it never sees allocators, senders, buffers, or config structs. diff --git a/Core/Interface/SolidSyslogTimeQualitySd.h b/Core/Interface/SolidSyslogTimeQualitySd.h index 97b032e6..165a050e 100644 --- a/Core/Interface/SolidSyslogTimeQualitySd.h +++ b/Core/Interface/SolidSyslogTimeQualitySd.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN struct SolidSyslogStructuredData; struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTimeQualityFunction getTimeQuality); - void SolidSyslogTimeQualitySd_Destroy(void); + void SolidSyslogTimeQualitySd_Destroy(struct SolidSyslogStructuredData * sd); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 91c276e9..f9bd618c 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -128,4 +128,19 @@ #error "SOLIDSYSLOG_META_SD_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogTimeQualitySd instances the library's internal + * static pool can simultaneously hold. Default 1. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE < 1 +#error "SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 48531b05..14c15500 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -26,6 +26,7 @@ set(SOURCES SolidSyslogStream.c SolidSyslogStructuredData.c SolidSyslogTimeQualitySd.c + SolidSyslogTimeQualitySdStatic.c SolidSyslogOriginSd.c SolidSyslogFile.c SolidSyslogBlockStore.c diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index d11e398d..3e1b869b 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -28,6 +28,10 @@ #define SOLIDSYSLOG_ERROR_MSG_METASD_POOL_EXHAUSTED "SolidSyslogMetaSd_Create pool exhausted; returning fallback SD" #define SOLIDSYSLOG_ERROR_MSG_METASD_UNKNOWN_DESTROY \ "SolidSyslogMetaSd_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_POOL_EXHAUSTED \ + "SolidSyslogTimeQualitySd_Create pool exhausted; returning fallback SD" +#define SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_UNKNOWN_DESTROY \ + "SolidSyslogTimeQualitySd_Destroy called with a handle not issued by this pool" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_POOL_EXHAUSTED \ "SolidSyslogCircularBuffer_Create pool exhausted; returning fallback buffer" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogTimeQualitySd.c b/Core/Source/SolidSyslogTimeQualitySd.c index 65827dbc..820b5728 100644 --- a/Core/Source/SolidSyslogTimeQualitySd.c +++ b/Core/Source/SolidSyslogTimeQualitySd.c @@ -6,15 +6,10 @@ #include "SolidSyslogFormatter.h" #include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTimeQualitySdPrivate.h" struct SolidSyslogFormatter; -struct SolidSyslogTimeQualitySd -{ - struct SolidSyslogStructuredData Base; - SolidSyslogTimeQualityFunction GetTimeQuality; -}; - static void TimeQualitySd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); static inline struct SolidSyslogTimeQualitySd* TimeQualitySd_SelfFromBase(struct SolidSyslogStructuredData* base); @@ -26,19 +21,18 @@ static inline void TimeQualitySd_FormatBoolParam( ); static inline void TimeQualitySd_FormatSyncAccuracy(struct SolidSyslogFormatter* formatter, uint32_t value); -static struct SolidSyslogTimeQualitySd TimeQualitySd_Instance; - -struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTimeQualityFunction getTimeQuality) +void TimeQualitySd_Initialise(struct SolidSyslogStructuredData* base, SolidSyslogTimeQualityFunction getTimeQuality) { - TimeQualitySd_Instance.Base.Format = TimeQualitySd_Format; - TimeQualitySd_Instance.GetTimeQuality = getTimeQuality; - return &TimeQualitySd_Instance.Base; + struct SolidSyslogTimeQualitySd* self = TimeQualitySd_SelfFromBase(base); + self->Base.Format = TimeQualitySd_Format; + self->GetTimeQuality = getTimeQuality; } -void SolidSyslogTimeQualitySd_Destroy(void) +void TimeQualitySd_Cleanup(struct SolidSyslogStructuredData* base) { - TimeQualitySd_Instance.Base.Format = NULL; - TimeQualitySd_Instance.GetTimeQuality = NULL; + struct SolidSyslogTimeQualitySd* self = TimeQualitySd_SelfFromBase(base); + self->Base.Format = NULL; + self->GetTimeQuality = NULL; } static void TimeQualitySd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter) diff --git a/Core/Source/SolidSyslogTimeQualitySdPrivate.h b/Core/Source/SolidSyslogTimeQualitySdPrivate.h new file mode 100644 index 00000000..5f318057 --- /dev/null +++ b/Core/Source/SolidSyslogTimeQualitySdPrivate.h @@ -0,0 +1,16 @@ +#ifndef SOLIDSYSLOGTIMEQUALITYSDPRIVATE_H +#define SOLIDSYSLOGTIMEQUALITYSDPRIVATE_H + +#include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTimeQuality.h" + +struct SolidSyslogTimeQualitySd +{ + struct SolidSyslogStructuredData Base; + SolidSyslogTimeQualityFunction GetTimeQuality; +}; + +void TimeQualitySd_Initialise(struct SolidSyslogStructuredData* base, SolidSyslogTimeQualityFunction getTimeQuality); +void TimeQualitySd_Cleanup(struct SolidSyslogStructuredData* base); + +#endif /* SOLIDSYSLOGTIMEQUALITYSDPRIVATE_H */ diff --git a/Core/Source/SolidSyslogTimeQualitySdStatic.c b/Core/Source/SolidSyslogTimeQualitySdStatic.c new file mode 100644 index 00000000..c893a2cb --- /dev/null +++ b/Core/Source/SolidSyslogTimeQualitySdStatic.c @@ -0,0 +1,67 @@ +#include "SolidSyslogTimeQualitySd.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullSd.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTimeQualitySdPrivate.h" +#include "SolidSyslogTunables.h" + +static size_t TimeQualitySd_IndexFromHandle(const struct SolidSyslogStructuredData* base); +static void TimeQualitySd_CleanupAtIndex(size_t index, void* context); + +static bool InUse[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE]; +static struct SolidSyslogTimeQualitySd Pool[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE]; +static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE}; + +struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTimeQualityFunction getTimeQuality) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + struct SolidSyslogStructuredData* handle = SolidSyslogNullSd_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + TimeQualitySd_Initialise(&Pool[index].Base, getTimeQuality); + handle = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogTimeQualitySd_Destroy(struct SolidSyslogStructuredData* base) +{ + size_t index = TimeQualitySd_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, TimeQualitySd_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_UNKNOWN_DESTROY); + } +} + +static size_t TimeQualitySd_IndexFromHandle(const struct SolidSyslogStructuredData* base) +{ + size_t result = SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE; poolIndex++) + { + if (base == &Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void TimeQualitySd_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + TimeQualitySd_Cleanup(&Pool[index].Base); +} diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index 98e3f194..8955b92b 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -747,7 +747,7 @@ TEST(SolidSyslog, MetaSdAndTimeQualitySdCoexistInSdArray) "[meta sequenceId=\"1\"][timeQuality tzKnown=\"1\" isSynced=\"1\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str() ); - SolidSyslogTimeQualitySd_Destroy(); + SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); TestAtomicCounter_Destroy(counter); } diff --git a/Tests/SolidSyslogTimeQualitySdTest.cpp b/Tests/SolidSyslogTimeQualitySdTest.cpp index e9edd35f..a299fc9c 100644 --- a/Tests/SolidSyslogTimeQualitySdTest.cpp +++ b/Tests/SolidSyslogTimeQualitySdTest.cpp @@ -5,6 +5,7 @@ #include "SolidSyslogTimeQualitySd.h" #include "SolidSyslogStructuredData.h" #include "SolidSyslogTimeQuality.h" +#include "SolidSyslogTunables.h" #include "CppUTest/TestHarness.h" struct SolidSyslogFormatter; @@ -40,7 +41,7 @@ TEST_GROUP(SolidSyslogTimeQualitySd) void teardown() override { - SolidSyslogTimeQualitySd_Destroy(); + SolidSyslogTimeQualitySd_Destroy(sd); } void format() const @@ -142,3 +143,57 @@ TEST(SolidSyslogTimeQualitySd, DestroyDoesNotCrash) { // Covered by teardown — this test documents the intent } + +// Pool tests — prove SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE caps live +// instances and overflow falls back to the shared SolidSyslogNullSd. + +// clang-format off +TEST_GROUP(SolidSyslogTimeQualitySdPool) +{ + struct SolidSyslogStructuredData* pooled[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE] = {}; + struct SolidSyslogStructuredData* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogTimeQualitySd_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogTimeQualitySd_Destroy(overflow); + } + } + + struct SolidSyslogStructuredData* MakeSd() + { + return SolidSyslogTimeQualitySd_Create(StubGetTimeQuality); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeSd(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogTimeQualitySdPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeSd(); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} From dfaefc13a917086d0245e4dc33db62167240dd3e Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 14:41:53 +0100 Subject: [PATCH 10/18] refactor: S11.04 migrate OriginSd onto PoolAllocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the E11 canonical 3-TU split. OriginSdPrivate.h holds the struct, the size enums (ORIGIN_SOFTWARE_MAX, etc.), and Initialise/Cleanup decls; OriginSd.c keeps the vtable Format method, the pre-format static-prefix step, and the per-field emit helpers; OriginSdStatic.c owns the pool, Create/Destroy, IndexFromHandle, CleanupAtIndex. OriginSd_PreFormatStaticPrefix becomes a per-instance function taking the slot's `self` so each pool slot writes into its own FormattedStorage buffer (the largest per-slot footprint in the sweep — software + swVersion + enterpriseId pre-formatted at Create time). Public API: _Destroy(void) → _Destroy(struct SolidSyslogStructuredData*). Caller updates in OriginSdTest.cpp and all three BDD targets. Pool size tunable: SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE, default 1. Error messages: POOL_EXHAUSTED (ERROR) and UNKNOWN_DESTROY (WARNING). One new pool test proves capacity + overflow returns a distinct fallback. Validated at SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE=3. 100% line coverage on OriginSd.c and OriginSdStatic.c. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/Targets/FreeRtos/main.c | 2 +- Bdd/Targets/Linux/main.c | 2 +- Bdd/Targets/Windows/BddTargetWindows.c | 2 +- CLAUDE.md | 2 +- Core/Interface/SolidSyslogOriginSd.h | 2 +- Core/Interface/SolidSyslogTunablesDefaults.h | 20 ++++++ Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslogErrorMessages.h | 3 + Core/Source/SolidSyslogOriginSd.c | 62 +++++++--------- Core/Source/SolidSyslogOriginSdPrivate.h | 33 +++++++++ Core/Source/SolidSyslogOriginSdStatic.c | 67 ++++++++++++++++++ Tests/SolidSyslogOriginSdTest.cpp | 74 ++++++++++++++++++-- 12 files changed, 221 insertions(+), 49 deletions(-) create mode 100644 Core/Source/SolidSyslogOriginSdPrivate.h create mode 100644 Core/Source/SolidSyslogOriginSdStatic.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 7a33b76b..080f3907 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -650,7 +650,7 @@ static void TeardownAll(void) solidSyslogTeardown = true; solidSyslogReady = false; SolidSyslog_Destroy(); - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(originSd); SolidSyslogTimeQualitySd_Destroy(timeQualitySd); SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(atomicCounter); diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 32061ff9..235b5a1d 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -287,7 +287,7 @@ int main(int argc, char* argv[]) pthread_join(serviceThread, NULL); SolidSyslog_Destroy(); - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(originSd); SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(counter); diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 38dc0d36..463702e8 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -358,7 +358,7 @@ int BddTargetWindows_Run(int argc, char* argv[]) CloseHandle(serviceThread); SolidSyslog_Destroy(); - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(originSd); SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogWindowsAtomicCounter_Destroy(counter); diff --git a/CLAUDE.md b/CLAUDE.md index 7ca7b064..4ace6498 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -384,7 +384,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogWindowsAtomicCounter.h` | System setup code on Windows targets without `` (legacy MSVC) | `SolidSyslogWindowsAtomicCounterStorage`, `SOLIDSYSLOG_WINDOWSATOMICCOUNTER_SIZE`, `SolidSyslogWindowsAtomicCounter_Create(storage)`, `_Destroy(base)`. Uses `volatile LONG` + `InterlockedCompareExchange` CAS loop. | | `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`. | -| `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`, `_Destroy` | +| `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`. | Most application code only needs `SolidSyslog.h` — it never sees allocators, senders, buffers, or config structs. diff --git a/Core/Interface/SolidSyslogOriginSd.h b/Core/Interface/SolidSyslogOriginSd.h index daeb7971..fc82630e 100644 --- a/Core/Interface/SolidSyslogOriginSd.h +++ b/Core/Interface/SolidSyslogOriginSd.h @@ -23,7 +23,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidSyslogOriginSdConfig* config); - void SolidSyslogOriginSd_Destroy(void); + void SolidSyslogOriginSd_Destroy(struct SolidSyslogStructuredData * sd); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index f9bd618c..ec75d165 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -143,4 +143,24 @@ #error "SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogOriginSd instances the library's internal + * static pool can simultaneously hold. Each instance carries the + * pre-formatted static-prefix Formatter storage (software, swVersion, + * enterpriseId) so the per-message Format only splices in the IP + * params. Larger per-slot footprint than the other SDs. + * + * Default 1. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE < 1 +#error "SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 14c15500..51643cec 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES SolidSyslogTimeQualitySd.c SolidSyslogTimeQualitySdStatic.c SolidSyslogOriginSd.c + SolidSyslogOriginSdStatic.c SolidSyslogFile.c SolidSyslogBlockStore.c RecordStore.c diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index 3e1b869b..c1e5d07d 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -32,6 +32,9 @@ "SolidSyslogTimeQualitySd_Create pool exhausted; returning fallback SD" #define SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_UNKNOWN_DESTROY \ "SolidSyslogTimeQualitySd_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_ORIGINSD_POOL_EXHAUSTED "SolidSyslogOriginSd_Create pool exhausted; returning fallback SD" +#define SOLIDSYSLOG_ERROR_MSG_ORIGINSD_UNKNOWN_DESTROY \ + "SolidSyslogOriginSd_Destroy called with a handle not issued by this pool" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_POOL_EXHAUSTED \ "SolidSyslogCircularBuffer_Create pool exhausted; returning fallback buffer" #define SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogOriginSd.c b/Core/Source/SolidSyslogOriginSd.c index 1d4de1b8..4c89a661 100644 --- a/Core/Source/SolidSyslogOriginSd.c +++ b/Core/Source/SolidSyslogOriginSd.c @@ -1,33 +1,18 @@ #include "SolidSyslogOriginSd.h" + +#include + #include "SolidSyslogFormatter.h" +#include "SolidSyslogOriginSdPrivate.h" #include "SolidSyslogStructuredDataDefinition.h" -enum -{ - ORIGIN_SOFTWARE_MAX = 48, - ORIGIN_SWVERSION_MAX = 32, - ORIGIN_ENTERPRISE_ID_MAX = 64, - ORIGIN_IP_MAX = 64, - ORIGIN_LITERAL_BYTES = - 48, /* [origin software="" swVersion="" enterpriseId="" — closing ']' deferred to per-message OriginSd_Format */ - ORIGIN_CONTENT_MAX = ORIGIN_LITERAL_BYTES + SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_SOFTWARE_MAX) + - SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_SWVERSION_MAX) + - SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_ENTERPRISE_ID_MAX), - ORIGIN_FORMATTED_MAX = ORIGIN_CONTENT_MAX + 1 /* null terminator */ -}; - -struct SolidSyslogOriginSd -{ - struct SolidSyslogStructuredData Base; - SolidSyslogOriginIpCountFunction GetIpCount; - SolidSyslogOriginIpAtFunction GetIpAt; - SolidSyslogFormatterStorage FormattedStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(ORIGIN_FORMATTED_MAX)]; -}; - static void OriginSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); static inline struct SolidSyslogOriginSd* OriginSd_SelfFromBase(struct SolidSyslogStructuredData* base); -static inline void OriginSd_PreFormatStaticPrefix(const struct SolidSyslogOriginSdConfig* config); +static inline void OriginSd_PreFormatStaticPrefix( + struct SolidSyslogOriginSd* self, + const struct SolidSyslogOriginSdConfig* config +); static inline void OriginSd_EmitSoftware( struct SolidSyslogFormatter* f, const struct SolidSyslogOriginSdConfig* config @@ -47,22 +32,21 @@ static inline void OriginSd_EmitIp( size_t index ); -static struct SolidSyslogOriginSd OriginSd_Instance; - -struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidSyslogOriginSdConfig* config) +void OriginSd_Initialise(struct SolidSyslogStructuredData* base, const struct SolidSyslogOriginSdConfig* config) { - OriginSd_Instance.Base.Format = OriginSd_Format; - OriginSd_Instance.GetIpCount = config->GetIpCount; - OriginSd_Instance.GetIpAt = config->GetIpAt; - OriginSd_PreFormatStaticPrefix(config); - return &OriginSd_Instance.Base; + struct SolidSyslogOriginSd* self = OriginSd_SelfFromBase(base); + self->Base.Format = OriginSd_Format; + self->GetIpCount = config->GetIpCount; + self->GetIpAt = config->GetIpAt; + OriginSd_PreFormatStaticPrefix(self, config); } -void SolidSyslogOriginSd_Destroy(void) +void OriginSd_Cleanup(struct SolidSyslogStructuredData* base) { - OriginSd_Instance.Base.Format = NULL; - OriginSd_Instance.GetIpCount = NULL; - OriginSd_Instance.GetIpAt = NULL; + struct SolidSyslogOriginSd* self = OriginSd_SelfFromBase(base); + self->Base.Format = NULL; + self->GetIpCount = NULL; + self->GetIpAt = NULL; } static void OriginSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter) @@ -84,11 +68,13 @@ static inline struct SolidSyslogOriginSd* OriginSd_SelfFromBase(struct SolidSysl return (struct SolidSyslogOriginSd*) base; } -static inline void OriginSd_PreFormatStaticPrefix(const struct SolidSyslogOriginSdConfig* config) +static inline void OriginSd_PreFormatStaticPrefix( + struct SolidSyslogOriginSd* self, + const struct SolidSyslogOriginSdConfig* config +) { static const char sdPrefix[] = "[origin"; - struct SolidSyslogFormatter* f = - SolidSyslogFormatter_Create(OriginSd_Instance.FormattedStorage, ORIGIN_FORMATTED_MAX); + struct SolidSyslogFormatter* f = SolidSyslogFormatter_Create(self->FormattedStorage, ORIGIN_FORMATTED_MAX); SolidSyslogFormatter_BoundedString(f, sdPrefix, sizeof(sdPrefix) - 1U); OriginSd_EmitSoftware(f, config); diff --git a/Core/Source/SolidSyslogOriginSdPrivate.h b/Core/Source/SolidSyslogOriginSdPrivate.h new file mode 100644 index 00000000..be90f6dc --- /dev/null +++ b/Core/Source/SolidSyslogOriginSdPrivate.h @@ -0,0 +1,33 @@ +#ifndef SOLIDSYSLOGORIGINSDPRIVATE_H +#define SOLIDSYSLOGORIGINSDPRIVATE_H + +#include "SolidSyslogFormatter.h" +#include "SolidSyslogOriginSd.h" +#include "SolidSyslogStructuredDataDefinition.h" + +enum +{ + ORIGIN_SOFTWARE_MAX = 48, + ORIGIN_SWVERSION_MAX = 32, + ORIGIN_ENTERPRISE_ID_MAX = 64, + ORIGIN_IP_MAX = 64, + ORIGIN_LITERAL_BYTES = + 48, /* [origin software="" swVersion="" enterpriseId="" — closing ']' deferred to per-message OriginSd_Format */ + ORIGIN_CONTENT_MAX = ORIGIN_LITERAL_BYTES + SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_SOFTWARE_MAX) + + SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_SWVERSION_MAX) + + SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_ENTERPRISE_ID_MAX), + ORIGIN_FORMATTED_MAX = ORIGIN_CONTENT_MAX + 1 /* null terminator */ +}; + +struct SolidSyslogOriginSd +{ + struct SolidSyslogStructuredData Base; + SolidSyslogOriginIpCountFunction GetIpCount; + SolidSyslogOriginIpAtFunction GetIpAt; + SolidSyslogFormatterStorage FormattedStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(ORIGIN_FORMATTED_MAX)]; +}; + +void OriginSd_Initialise(struct SolidSyslogStructuredData* base, const struct SolidSyslogOriginSdConfig* config); +void OriginSd_Cleanup(struct SolidSyslogStructuredData* base); + +#endif /* SOLIDSYSLOGORIGINSDPRIVATE_H */ diff --git a/Core/Source/SolidSyslogOriginSdStatic.c b/Core/Source/SolidSyslogOriginSdStatic.c new file mode 100644 index 00000000..48e9e8df --- /dev/null +++ b/Core/Source/SolidSyslogOriginSdStatic.c @@ -0,0 +1,67 @@ +#include "SolidSyslogOriginSd.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullSd.h" +#include "SolidSyslogOriginSdPrivate.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTunables.h" + +static size_t OriginSd_IndexFromHandle(const struct SolidSyslogStructuredData* base); +static void OriginSd_CleanupAtIndex(size_t index, void* context); + +static bool InUse[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE]; +static struct SolidSyslogOriginSd Pool[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE]; +static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE}; + +struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidSyslogOriginSdConfig* config) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + struct SolidSyslogStructuredData* handle = SolidSyslogNullSd_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + OriginSd_Initialise(&Pool[index].Base, config); + handle = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_ORIGINSD_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogOriginSd_Destroy(struct SolidSyslogStructuredData* base) +{ + size_t index = OriginSd_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, OriginSd_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_ORIGINSD_UNKNOWN_DESTROY); + } +} + +static size_t OriginSd_IndexFromHandle(const struct SolidSyslogStructuredData* base) +{ + size_t result = SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE; poolIndex++) + { + if (base == &Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void OriginSd_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + OriginSd_Cleanup(&Pool[index].Base); +} diff --git a/Tests/SolidSyslogOriginSdTest.cpp b/Tests/SolidSyslogOriginSdTest.cpp index b89a0ed0..23f845f4 100644 --- a/Tests/SolidSyslogOriginSdTest.cpp +++ b/Tests/SolidSyslogOriginSdTest.cpp @@ -6,6 +6,7 @@ #include "SolidSyslogFormatter.h" #include "SolidSyslogOriginSd.h" #include "SolidSyslogStructuredData.h" +#include "SolidSyslogTunables.h" #include "CppUTest/TestHarness.h" class TEST_SolidSyslogOriginSd_EnterpriseIdContainingSpecialsIsEscaped_Test; @@ -95,13 +96,13 @@ TEST_GROUP(SolidSyslogOriginSd) void teardown() override { - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(sd); } // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- positional shorthand for the two existing config fields; tests assert exact output so a mix-up shows immediately void recreate(const char* software, const char* swVersion) { - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(sd); config.Software = software; config.SwVersion = swVersion; sd = SolidSyslogOriginSd_Create(&config); @@ -109,14 +110,14 @@ TEST_GROUP(SolidSyslogOriginSd) void useEnterpriseId(const char* enterpriseId) { - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(sd); config.EnterpriseId = enterpriseId; sd = SolidSyslogOriginSd_Create(&config); } void useIps(std::initializer_list ips) { - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(sd); fakeIpCount = ips.size(); size_t i = 0; for (const char* ip : ips) @@ -421,7 +422,7 @@ TEST(SolidSyslogOriginSd, IpContainingSpecialsIsEscaped) TEST(SolidSyslogOriginSd, GetIpCountSetButGetIpAtNullOmitsIpParams) { - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(sd); config.GetIpCount = FakeIpCount; config.GetIpAt = nullptr; sd = SolidSyslogOriginSd_Create(&config); @@ -435,7 +436,7 @@ TEST(SolidSyslogOriginSd, GetIpCountSetButGetIpAtNullOmitsIpParams) TEST(SolidSyslogOriginSd, GetIpAtSetButGetIpCountNullOmitsIpParams) { - SolidSyslogOriginSd_Destroy(); + SolidSyslogOriginSd_Destroy(sd); config.GetIpCount = nullptr; config.GetIpAt = FakeIpAt; sd = SolidSyslogOriginSd_Create(&config); @@ -492,3 +493,64 @@ TEST(SolidSyslogOriginSd, EnterpriseIdAndIpsNoSoftwareSwVersion) SolidSyslogFormatter_AsFormattedBuffer(formatter) ); } + +// Pool tests — prove SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE caps live instances +// and overflow falls back to the shared SolidSyslogNullSd. + +// clang-format off +TEST_GROUP(SolidSyslogOriginSdPool) +{ + SolidSyslogOriginSdConfig config{}; + struct SolidSyslogStructuredData* pooled[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE] = {}; + struct SolidSyslogStructuredData* overflow = nullptr; + + void setup() override + { + config.Software = "TestSoftware"; + config.SwVersion = "1.0.0"; + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogOriginSd_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogOriginSd_Destroy(overflow); + } + } + + struct SolidSyslogStructuredData* MakeSd() + { + return SolidSyslogOriginSd_Create(&config); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeSd(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogOriginSdPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeSd(); + + CHECK_TEXT(overflow != nullptr, "Fallback handle was nullptr"); + for (auto* slot : pooled) + { + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); + CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); + } +} From 248ec5a70ccf837f95198c7a23657f497fccc3a9 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 16:51:34 +0100 Subject: [PATCH 11/18] refactor: S11.04 align _Destroy parameter naming with NAMING.md The migrated _Destroy signatures had the parameter named after the concrete class (sender, sd, buffer). NAMING.md is explicit: when the declared parameter type is the abstract base struct, the parameter name is "base". When it's the concrete class, it's "self". All these _Destroy functions take the abstract base type, so the parameter name is "base" across the board. Two test fixture helpers (PassthroughBuffer MakeBuffer, TimeQualitySd MakeSd) also realigned to match the CircularBuffer reference shape so clang-tidy's readability-make-member-function-const / readability-convert-member-functions-to-static rules don't trip. Co-Authored-By: Claude Opus 4.7 (1M context) --- Core/Interface/SolidSyslogMetaSd.h | 2 +- Core/Interface/SolidSyslogOriginSd.h | 2 +- Core/Interface/SolidSyslogPassthroughBuffer.h | 2 +- Core/Interface/SolidSyslogSwitchingSender.h | 2 +- Core/Interface/SolidSyslogTimeQualitySd.h | 2 +- Core/Interface/SolidSyslogUdpSender.h | 2 +- Tests/SolidSyslogPassthroughBufferTest.cpp | 2 +- Tests/SolidSyslogTimeQualitySdTest.cpp | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/Interface/SolidSyslogMetaSd.h b/Core/Interface/SolidSyslogMetaSd.h index 7c5fb8fb..cd83dbea 100644 --- a/Core/Interface/SolidSyslogMetaSd.h +++ b/Core/Interface/SolidSyslogMetaSd.h @@ -21,7 +21,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogStructuredData* SolidSyslogMetaSd_Create(const struct SolidSyslogMetaSdConfig* config); - void SolidSyslogMetaSd_Destroy(struct SolidSyslogStructuredData * sd); + void SolidSyslogMetaSd_Destroy(struct SolidSyslogStructuredData * base); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogOriginSd.h b/Core/Interface/SolidSyslogOriginSd.h index fc82630e..8a170dfd 100644 --- a/Core/Interface/SolidSyslogOriginSd.h +++ b/Core/Interface/SolidSyslogOriginSd.h @@ -23,7 +23,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidSyslogOriginSdConfig* config); - void SolidSyslogOriginSd_Destroy(struct SolidSyslogStructuredData * sd); + void SolidSyslogOriginSd_Destroy(struct SolidSyslogStructuredData * base); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogPassthroughBuffer.h b/Core/Interface/SolidSyslogPassthroughBuffer.h index 0f73446a..57803af3 100644 --- a/Core/Interface/SolidSyslogPassthroughBuffer.h +++ b/Core/Interface/SolidSyslogPassthroughBuffer.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN struct SolidSyslogBuffer; struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslogSender * sender); - void SolidSyslogPassthroughBuffer_Destroy(struct SolidSyslogBuffer * buffer); + void SolidSyslogPassthroughBuffer_Destroy(struct SolidSyslogBuffer * base); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogSwitchingSender.h b/Core/Interface/SolidSyslogSwitchingSender.h index f04e2c4a..c3743723 100644 --- a/Core/Interface/SolidSyslogSwitchingSender.h +++ b/Core/Interface/SolidSyslogSwitchingSender.h @@ -18,7 +18,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogSender* SolidSyslogSwitchingSender_Create(const struct SolidSyslogSwitchingSenderConfig* config); - void SolidSyslogSwitchingSender_Destroy(struct SolidSyslogSender * sender); + void SolidSyslogSwitchingSender_Destroy(struct SolidSyslogSender * base); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTimeQualitySd.h b/Core/Interface/SolidSyslogTimeQualitySd.h index 165a050e..2cedfd8c 100644 --- a/Core/Interface/SolidSyslogTimeQualitySd.h +++ b/Core/Interface/SolidSyslogTimeQualitySd.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN struct SolidSyslogStructuredData; struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTimeQualityFunction getTimeQuality); - void SolidSyslogTimeQualitySd_Destroy(struct SolidSyslogStructuredData * sd); + void SolidSyslogTimeQualitySd_Destroy(struct SolidSyslogStructuredData * base); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogUdpSender.h b/Core/Interface/SolidSyslogUdpSender.h index 62f950f8..d96cfd0b 100644 --- a/Core/Interface/SolidSyslogUdpSender.h +++ b/Core/Interface/SolidSyslogUdpSender.h @@ -15,7 +15,7 @@ EXTERN_C_BEGIN }; struct SolidSyslogSender* SolidSyslogUdpSender_Create(const struct SolidSyslogUdpSenderConfig* config); - void SolidSyslogUdpSender_Destroy(struct SolidSyslogSender * sender); + void SolidSyslogUdpSender_Destroy(struct SolidSyslogSender * base); EXTERN_C_END diff --git a/Tests/SolidSyslogPassthroughBufferTest.cpp b/Tests/SolidSyslogPassthroughBufferTest.cpp index 47d0bdbc..2e4844f9 100644 --- a/Tests/SolidSyslogPassthroughBufferTest.cpp +++ b/Tests/SolidSyslogPassthroughBufferTest.cpp @@ -125,7 +125,7 @@ TEST_GROUP(SolidSyslogPassthroughBufferPool) SenderFake_Destroy(fakeSender); } - struct SolidSyslogBuffer* MakeBuffer() + [[nodiscard]] struct SolidSyslogBuffer* MakeBuffer() const { return SolidSyslogPassthroughBuffer_Create(fakeSender); } diff --git a/Tests/SolidSyslogTimeQualitySdTest.cpp b/Tests/SolidSyslogTimeQualitySdTest.cpp index a299fc9c..5bc4cde8 100644 --- a/Tests/SolidSyslogTimeQualitySdTest.cpp +++ b/Tests/SolidSyslogTimeQualitySdTest.cpp @@ -168,7 +168,7 @@ TEST_GROUP(SolidSyslogTimeQualitySdPool) } } - struct SolidSyslogStructuredData* MakeSd() + static struct SolidSyslogStructuredData* MakeSd() { return SolidSyslogTimeQualitySd_Create(StubGetTimeQuality); } From 47daf094005c2ba23fb0f0b603a20849f75c6152 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 16:52:39 +0100 Subject: [PATCH 12/18] docs: update DEVLOG for S11.04 Co-Authored-By: Claude Opus 4.7 (1M context) --- DEVLOG.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index ba5cc031..6bde8a9d 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -9001,3 +9001,89 @@ MISRA rule — different category, doesn't set precedent. ### Open questions - None. + +## 2026-05-18 — S11.04 Core singleton stateful classes onto PoolAllocator + +### Decisions + +- **Bundled scope, single PR.** The story body listed nine + commits (two new GoF nulls, six migrations, DEVLOG). The + session added two unplanned commits: flipping + `SolidSyslogNullStore` and `SolidSyslogNullSecurityPolicy` + from `_Create`/`_Destroy` to `_Get` so the four + null-object types now share one shape, and a naming + cleanup that put the public `_Destroy` parameter at `base` + per `docs/NAMING.md`. The signature change `_Destroy(void) + → _Destroy(base*)` is bundled into each per-class + migration commit; landed defaults from the three open + questions in the story body were confirmed by the + developer up front. +- **New GoF nulls keep state immutable.** `SolidSyslogNullSd` + and `SolidSyslogNullSender` ship as `_Get`-only — a single + static instance with the vtable wired at file-scope + initialisation. No wrapper struct, no `_Create`/`_Destroy` + pair. The retro-flip of the older two nulls follows the + same shape so any future Null* type slots in next to + these without inventing a new convention. +- **NullSender.Send returns true (drop on the floor).** The + story body said `false` ("didn't send"). That semantic + fills a real Store with undeliverables when the integrator + misconfigures the Sender. The old UdpSender NIL returned + true precisely to avoid that; the new shared NullSender + inherits it. `SwitchingSender`'s existing + out-of-range-selector behaviour (which returned false) + flips to match — same null-object boundary, same semantic. + Two SwitchingSender tests retitled accordingly. +- **UdpSender_Send NULL-buffer guard stays in UdpSender.c.** + The E11 three-TU split memo says `ClassStatic.c` is the + only TU that calls `SolidSyslog_Error` for the class — + intended for pool / bad-config errors. The Send NULL-buffer + guard is a runtime contract guard, not a pool error; it + belongs next to the check, in the vtable method. This is a + scoped exception, captured in the UdpSender commit message. +- **Pool-issue test pattern adopted everywhere.** Each per-class + migration adds one Pool `TEST_GROUP` with one test + (`FillingPoolThenOverflowReturnsDistinctFallback`) that + proves max-configured allocations succeed and one-more + returns a handle distinct from every pool slot. Generic + pool mechanics — lock counts, per-probe locking, stale- + handle warning — are covered by + `SolidSyslogPoolAllocatorTest.cpp` once, not duplicated + per class. PassthroughBuffer adds a second test for its + class-private fallback Write/Read no-ops because no shared + Null* class exercises those branches. +- **SwitchingSender test fixture defended against + double-Create.** Three SwitchingSender tests call + `CreateSwitchingSender(count)` to override the setup's + call. With singleton semantics the second call silently + overwrote the slot; with pooling the second call exhausts + the pool and returns the Fallback, leaving the first slot + orphaned. The helper now destroys any previously-allocated + sender before re-creating. Documented inline. +- **Validation per class.** Each migration was validated at + `SOLIDSYSLOG__POOL_SIZE=3` via the user-tunables + override mechanism. 100% line coverage on every new TU + pair (Class.c + ClassStatic.c) and on each new Null type. +- **Verified in `cpputest-freertos`.** Per + `feedback_verify_in_freertos_host_image.md`. With + `FREERTOS_KERNEL_PATH=/opt/freertos/kernel` set, the full + Core suite (1150 tests) plus seven FreeRTOS-specific + suites all green; no LayerGuard skip. + +### Deferred + +- **clang-tidy magic-numbers suppression revisit** — + `project_clang_tidy_magic_numbers.md` notes David's + preference to remove the per-rule disables under + `.clang-tidy`. The S11.04 migrations didn't introduce any + new magic-number suppressions; the cleanup is still its + own pass. +- **Pool-allocator unit test rename** — when the next sweep + story lands, consider renaming the macros in + `SolidSyslogCircularBufferTest.cpp` (the `CHECK_IS_FALLBACK` + reference) to be reusable across the per-class pool tests. + Currently inlined per-class to keep the macro file-local. + +### Open questions + +- None. From 0cebacf2684c8380d2f2dc672e7690468db7c79a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 17:21:39 +0100 Subject: [PATCH 13/18] fix: S11.04 silence cppcheck redundant-init and flow-analysis warnings cppcheck flagged the SwitchingSenderPool TEST_GROUP's `config = {}` followed by an immediate overwrite in setup() as `redundantInitialization`. Drop the explicit `= {}` and let setup() be the single initialisation site. cppcheck also flagged TimeQualitySdPool's `pooled` and `overflow` because that fixture has no setup() touching other members, so cppcheck cannot see TEST() bodies mutating them. Add per-line suppressions noting the same "cppcheck does not model CppUTest macros" reason used elsewhere in the project. Co-Authored-By: Claude Opus 4.7 (1M context) --- Tests/SolidSyslogSwitchingSenderTest.cpp | 2 +- Tests/SolidSyslogTimeQualitySdTest.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/SolidSyslogSwitchingSenderTest.cpp b/Tests/SolidSyslogSwitchingSenderTest.cpp index 8c5d3568..3a0d3a75 100644 --- a/Tests/SolidSyslogSwitchingSenderTest.cpp +++ b/Tests/SolidSyslogSwitchingSenderTest.cpp @@ -268,7 +268,7 @@ TEST_GROUP(SolidSyslogSwitchingSenderPool) struct SolidSyslogSender* inners[1] = {nullptr}; struct SolidSyslogSender* pooled[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE] = {}; struct SolidSyslogSender* overflow = nullptr; - SolidSyslogSwitchingSenderConfig config = {}; + SolidSyslogSwitchingSenderConfig config; void setup() override { diff --git a/Tests/SolidSyslogTimeQualitySdTest.cpp b/Tests/SolidSyslogTimeQualitySdTest.cpp index 5bc4cde8..ff81ec72 100644 --- a/Tests/SolidSyslogTimeQualitySdTest.cpp +++ b/Tests/SolidSyslogTimeQualitySdTest.cpp @@ -150,6 +150,7 @@ TEST(SolidSyslogTimeQualitySd, DestroyDoesNotCrash) // clang-format off TEST_GROUP(SolidSyslogTimeQualitySdPool) { + // cppcheck-suppress constVariable -- assigned in TEST() bodies; cppcheck does not model CppUTest macros struct SolidSyslogStructuredData* pooled[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE] = {}; struct SolidSyslogStructuredData* overflow = nullptr; @@ -162,6 +163,7 @@ TEST_GROUP(SolidSyslogTimeQualitySdPool) SolidSyslogTimeQualitySd_Destroy(handle); } } + // cppcheck-suppress knownConditionTrueFalse -- assigned in TEST() bodies; cppcheck does not model CppUTest macros if (overflow != nullptr) { SolidSyslogTimeQualitySd_Destroy(overflow); From 190173b061f6a106c4aac38211e910d29b542b41 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 17:22:49 +0100 Subject: [PATCH 14/18] fix: S11.04 apply IWYU include adjustments to S11.04 new files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Public headers (UdpSender.h, SwitchingSender.h) gain a forward declaration of `struct SolidSyslogSender` — the new _Destroy(struct SolidSyslogSender*) parameter type was relying on the caller transitively pulling SolidSyslogSenderDefinition.h. Static.c files drop their direct includes of the Definition headers where a forward declaration suffices (Sender/StructuredData) and add the explicit forward decls IWYU requests. TimeQualitySdStatic.c gains SolidSyslogTimeQuality.h for SolidSyslogTimeQualityFunction (it was arriving transitively from the now-dropped Definition header chain). NullSd.c gains `struct SolidSyslogFormatter;` forward decl. No behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) --- Core/Interface/SolidSyslogSwitchingSender.h | 2 ++ Core/Interface/SolidSyslogUdpSender.h | 2 ++ Core/Source/SolidSyslogMetaSdStatic.c | 3 ++- Core/Source/SolidSyslogNullSd.c | 2 ++ Core/Source/SolidSyslogOriginSdStatic.c | 3 ++- Core/Source/SolidSyslogSwitchingSenderStatic.c | 3 ++- Core/Source/SolidSyslogTimeQualitySdStatic.c | 4 +++- Core/Source/SolidSyslogUdpSenderStatic.c | 3 ++- 8 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Core/Interface/SolidSyslogSwitchingSender.h b/Core/Interface/SolidSyslogSwitchingSender.h index c3743723..01ed8057 100644 --- a/Core/Interface/SolidSyslogSwitchingSender.h +++ b/Core/Interface/SolidSyslogSwitchingSender.h @@ -8,6 +8,8 @@ EXTERN_C_BEGIN + struct SolidSyslogSender; + typedef uint8_t (*SolidSyslogSwitchingSenderSelector)(void); // NOLINT(modernize-redundant-void-arg) -- C idiom struct SolidSyslogSwitchingSenderConfig diff --git a/Core/Interface/SolidSyslogUdpSender.h b/Core/Interface/SolidSyslogUdpSender.h index d96cfd0b..c9520f62 100644 --- a/Core/Interface/SolidSyslogUdpSender.h +++ b/Core/Interface/SolidSyslogUdpSender.h @@ -6,6 +6,8 @@ EXTERN_C_BEGIN + struct SolidSyslogSender; + struct SolidSyslogUdpSenderConfig { struct SolidSyslogResolver* Resolver; diff --git a/Core/Source/SolidSyslogMetaSdStatic.c b/Core/Source/SolidSyslogMetaSdStatic.c index 1d25ebc9..170e673f 100644 --- a/Core/Source/SolidSyslogMetaSdStatic.c +++ b/Core/Source/SolidSyslogMetaSdStatic.c @@ -9,9 +9,10 @@ #include "SolidSyslogNullSd.h" #include "SolidSyslogPoolAllocator.h" #include "SolidSyslogPrival.h" -#include "SolidSyslogStructuredDataDefinition.h" #include "SolidSyslogTunables.h" +struct SolidSyslogStructuredData; + static bool MetaSd_IsValidConfig(const struct SolidSyslogMetaSdConfig* config); static size_t MetaSd_IndexFromHandle(const struct SolidSyslogStructuredData* base); static void MetaSd_CleanupAtIndex(size_t index, void* context); diff --git a/Core/Source/SolidSyslogNullSd.c b/Core/Source/SolidSyslogNullSd.c index 3ba2e3d8..e9d72ff1 100644 --- a/Core/Source/SolidSyslogNullSd.c +++ b/Core/Source/SolidSyslogNullSd.c @@ -2,6 +2,8 @@ #include "SolidSyslogStructuredDataDefinition.h" +struct SolidSyslogFormatter; + static void NullSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); static struct SolidSyslogStructuredData instance = {.Format = NullSd_Format}; diff --git a/Core/Source/SolidSyslogOriginSdStatic.c b/Core/Source/SolidSyslogOriginSdStatic.c index 48e9e8df..908a9064 100644 --- a/Core/Source/SolidSyslogOriginSdStatic.c +++ b/Core/Source/SolidSyslogOriginSdStatic.c @@ -9,9 +9,10 @@ #include "SolidSyslogOriginSdPrivate.h" #include "SolidSyslogPoolAllocator.h" #include "SolidSyslogPrival.h" -#include "SolidSyslogStructuredDataDefinition.h" #include "SolidSyslogTunables.h" +struct SolidSyslogStructuredData; + static size_t OriginSd_IndexFromHandle(const struct SolidSyslogStructuredData* base); static void OriginSd_CleanupAtIndex(size_t index, void* context); diff --git a/Core/Source/SolidSyslogSwitchingSenderStatic.c b/Core/Source/SolidSyslogSwitchingSenderStatic.c index fed2b491..ea0f0c72 100644 --- a/Core/Source/SolidSyslogSwitchingSenderStatic.c +++ b/Core/Source/SolidSyslogSwitchingSenderStatic.c @@ -8,10 +8,11 @@ #include "SolidSyslogNullSender.h" #include "SolidSyslogPoolAllocator.h" #include "SolidSyslogPrival.h" -#include "SolidSyslogSenderDefinition.h" #include "SolidSyslogSwitchingSenderPrivate.h" #include "SolidSyslogTunables.h" +struct SolidSyslogSender; + static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* base); static void SwitchingSender_CleanupAtIndex(size_t index, void* context); diff --git a/Core/Source/SolidSyslogTimeQualitySdStatic.c b/Core/Source/SolidSyslogTimeQualitySdStatic.c index c893a2cb..c14c5f73 100644 --- a/Core/Source/SolidSyslogTimeQualitySdStatic.c +++ b/Core/Source/SolidSyslogTimeQualitySdStatic.c @@ -8,10 +8,12 @@ #include "SolidSyslogNullSd.h" #include "SolidSyslogPoolAllocator.h" #include "SolidSyslogPrival.h" -#include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTimeQuality.h" #include "SolidSyslogTimeQualitySdPrivate.h" #include "SolidSyslogTunables.h" +struct SolidSyslogStructuredData; + static size_t TimeQualitySd_IndexFromHandle(const struct SolidSyslogStructuredData* base); static void TimeQualitySd_CleanupAtIndex(size_t index, void* context); diff --git a/Core/Source/SolidSyslogUdpSenderStatic.c b/Core/Source/SolidSyslogUdpSenderStatic.c index 277710b7..3372187f 100644 --- a/Core/Source/SolidSyslogUdpSenderStatic.c +++ b/Core/Source/SolidSyslogUdpSenderStatic.c @@ -8,10 +8,11 @@ #include "SolidSyslogNullSender.h" #include "SolidSyslogPoolAllocator.h" #include "SolidSyslogPrival.h" -#include "SolidSyslogSenderDefinition.h" #include "SolidSyslogTunables.h" #include "SolidSyslogUdpSenderPrivate.h" +struct SolidSyslogSender; + static bool UdpSender_IsValidConfig(const struct SolidSyslogUdpSenderConfig* config); static size_t UdpSender_IndexFromHandle(const struct SolidSyslogSender* base); static void UdpSender_CleanupAtIndex(size_t index, void* context); From 79b3593adeadfb75769fbe2736201a87c4868f45 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 17:26:02 +0100 Subject: [PATCH 15/18] fix: S11.04 validate SwitchingSender config in _Create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit (correctly) flagged that SolidSyslogSwitchingSender_Create dereferenced config without validating it — a NULL config or NULL Selector would crash inside _Initialise. Add the same bad-config contract UdpSender already had: NULL config, NULL config->Senders, or NULL config->Selector each emit a SOLIDSYSLOG_SEVERITY_ERROR with a descriptive message and route to SolidSyslogNullSender_Get(). Three new SolidSyslogSwitchingSenderBadSetup tests in SolidSyslogSwitchingSenderTest.cpp exercise each branch, plus one SendOnBadSetupSenderReturnsTrueAndDrops to pin the contract that bad-setup messages are dropped at the null-object boundary rather than retained in the Store. 100% line coverage preserved on SwitchingSenderStatic.c. Co-Authored-By: Claude Opus 4.7 (1M context) --- Core/Source/SolidSyslogErrorMessages.h | 6 ++ .../Source/SolidSyslogSwitchingSenderStatic.c | 38 +++++++++++-- Tests/SolidSyslogSwitchingSenderTest.cpp | 56 +++++++++++++++++++ 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index c1e5d07d..0a14eda4 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -21,6 +21,12 @@ "SolidSyslogUdpSender_Create pool exhausted; returning fallback sender" #define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_UNKNOWN_DESTROY \ "SolidSyslogUdpSender_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_CREATE_NULL_CONFIG \ + "SolidSyslogSwitchingSender_Create called with NULL config" +#define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_CREATE_NULL_SENDERS \ + "SolidSyslogSwitchingSender_Create config.Senders is NULL" +#define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_CREATE_NULL_SELECTOR \ + "SolidSyslogSwitchingSender_Create config.Selector is NULL" #define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_POOL_EXHAUSTED \ "SolidSyslogSwitchingSender_Create pool exhausted; returning fallback sender" #define SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogSwitchingSenderStatic.c b/Core/Source/SolidSyslogSwitchingSenderStatic.c index ea0f0c72..913ac615 100644 --- a/Core/Source/SolidSyslogSwitchingSenderStatic.c +++ b/Core/Source/SolidSyslogSwitchingSenderStatic.c @@ -13,6 +13,7 @@ struct SolidSyslogSender; +static bool SwitchingSender_IsValidConfig(const struct SolidSyslogSwitchingSenderConfig* config); static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* base); static void SwitchingSender_CleanupAtIndex(size_t index, void* context); @@ -22,18 +23,43 @@ static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_SWITCHING struct SolidSyslogSender* SolidSyslogSwitchingSender_Create(const struct SolidSyslogSwitchingSenderConfig* config) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); struct SolidSyslogSender* handle = SolidSyslogNullSender_Get(); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + if (SwitchingSender_IsValidConfig(config)) { - SwitchingSender_Initialise(&Pool[index].Base, config); - handle = &Pool[index].Base; + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + SwitchingSender_Initialise(&Pool[index].Base, config); + handle = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_POOL_EXHAUSTED); + } + } + return handle; +} + +static bool SwitchingSender_IsValidConfig(const struct SolidSyslogSwitchingSenderConfig* config) +{ + bool valid = false; + if (config == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_CREATE_NULL_CONFIG); + } + else if (config->Senders == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_CREATE_NULL_SENDERS); + } + else if (config->Selector == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_CREATE_NULL_SELECTOR); } else { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_POOL_EXHAUSTED); + valid = true; } - return handle; + return valid; } void SolidSyslogSwitchingSender_Destroy(struct SolidSyslogSender* base) diff --git a/Tests/SolidSyslogSwitchingSenderTest.cpp b/Tests/SolidSyslogSwitchingSenderTest.cpp index 3a0d3a75..6b31e9cf 100644 --- a/Tests/SolidSyslogSwitchingSenderTest.cpp +++ b/Tests/SolidSyslogSwitchingSenderTest.cpp @@ -1,6 +1,7 @@ #include #include +#include "ErrorHandlerFake.h" #include "SenderFake.h" #include "SolidSyslogSender.h" #include "SolidSyslogSwitchingSender.h" @@ -322,3 +323,58 @@ TEST(SolidSyslogSwitchingSenderPool, FillingPoolThenOverflowReturnsDistinctFallb CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); } } + +// Bad-setup tests — _Create rejects malformed config and routes to NullSender. + +// clang-format off +TEST_GROUP(SolidSyslogSwitchingSenderBadSetup) +{ + struct SolidSyslogSender* innerA = nullptr; + struct SolidSyslogSender* inners[1] = {nullptr}; + int sentinel = 0; + SolidSyslogSwitchingSenderConfig config{}; + + void setup() override + { + innerA = SenderFake_Create(); + inners[0] = innerA; + config = {inners, 1, TestSelector}; + ErrorHandlerFake_Install(&sentinel); + } + + void teardown() override + { + ErrorHandlerFake_Uninstall(); + SenderFake_Destroy(innerA); + } +}; + +// clang-format on + +TEST(SolidSyslogSwitchingSenderBadSetup, CreateWithNullConfigReportsError) +{ + SolidSyslogSwitchingSender_Create(nullptr); + CHECK_REPORTED_ERROR("SolidSyslogSwitchingSender_Create called with NULL config"); +} + +TEST(SolidSyslogSwitchingSenderBadSetup, CreateWithNullSendersReportsError) +{ + config.Senders = nullptr; + SolidSyslogSwitchingSender_Create(&config); + CHECK_REPORTED_ERROR("SolidSyslogSwitchingSender_Create config.Senders is NULL"); +} + +TEST(SolidSyslogSwitchingSenderBadSetup, CreateWithNullSelectorReportsError) +{ + config.Selector = nullptr; + SolidSyslogSwitchingSender_Create(&config); + CHECK_REPORTED_ERROR("SolidSyslogSwitchingSender_Create config.Selector is NULL"); +} + +TEST(SolidSyslogSwitchingSenderBadSetup, SendOnBadSetupSenderReturnsTrueAndDrops) +{ + /* Bad-config _Create returns NullSender (Send drops on the floor), + * so a misconfigured SwitchingSender doesn't fill the Store. */ + struct SolidSyslogSender* badSender = SolidSyslogSwitchingSender_Create(nullptr); + CHECK_TRUE(SolidSyslogSender_Send(badSender, "x", 1)); +} From 16efd12eb85cf328866bd30433702b9e131c6da0 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 17:35:45 +0100 Subject: [PATCH 16/18] fix: S11.04 validate TimeQualitySd callback in _Create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit flagged that SolidSyslogTimeQualitySd_Create accepted a NULL getTimeQuality callback, which then crashes inside TimeQualitySd_Format when the callback is dereferenced. Discussed severity choice: ERROR rather than WARNING because the integrator explicitly called _Create with no callback at all (programmer contract violation), matching the bad-setup pattern in feedback_bad_setup_contract.md. NULL callback → emits SOLIDSYSLOG_SEVERITY_ERROR with a descriptive message and routes to SolidSyslogNullSd_Get(). The pool migration is the natural place to add this safety because we now have a fallback Null object to return; the original singleton couldn't gracefully degrade. One new SolidSyslogTimeQualitySdBadSetup test exercises the branch. Co-Authored-By: Claude Opus 4.7 (1M context) --- Core/Source/SolidSyslogErrorMessages.h | 2 ++ Core/Source/SolidSyslogTimeQualitySdStatic.c | 17 +++++++--- Tests/SolidSyslogTimeQualitySdTest.cpp | 34 ++++++++++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index 0a14eda4..2236492e 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -34,6 +34,8 @@ #define SOLIDSYSLOG_ERROR_MSG_METASD_POOL_EXHAUSTED "SolidSyslogMetaSd_Create pool exhausted; returning fallback SD" #define SOLIDSYSLOG_ERROR_MSG_METASD_UNKNOWN_DESTROY \ "SolidSyslogMetaSd_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_CREATE_NULL_CALLBACK \ + "SolidSyslogTimeQualitySd_Create called with NULL getTimeQuality" #define SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_POOL_EXHAUSTED \ "SolidSyslogTimeQualitySd_Create pool exhausted; returning fallback SD" #define SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_UNKNOWN_DESTROY \ diff --git a/Core/Source/SolidSyslogTimeQualitySdStatic.c b/Core/Source/SolidSyslogTimeQualitySdStatic.c index c14c5f73..31dc12f3 100644 --- a/Core/Source/SolidSyslogTimeQualitySdStatic.c +++ b/Core/Source/SolidSyslogTimeQualitySdStatic.c @@ -23,16 +23,23 @@ static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_TIME_QUAL struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTimeQualityFunction getTimeQuality) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); struct SolidSyslogStructuredData* handle = SolidSyslogNullSd_Get(); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + if (getTimeQuality == NULL) { - TimeQualitySd_Initialise(&Pool[index].Base, getTimeQuality); - handle = &Pool[index].Base; + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_CREATE_NULL_CALLBACK); } else { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_POOL_EXHAUSTED); + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + { + TimeQualitySd_Initialise(&Pool[index].Base, getTimeQuality); + handle = &Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_POOL_EXHAUSTED); + } } return handle; } diff --git a/Tests/SolidSyslogTimeQualitySdTest.cpp b/Tests/SolidSyslogTimeQualitySdTest.cpp index ff81ec72..f5ffec4f 100644 --- a/Tests/SolidSyslogTimeQualitySdTest.cpp +++ b/Tests/SolidSyslogTimeQualitySdTest.cpp @@ -1,12 +1,16 @@ #include #include +#include "CppUTest/TestHarness.h" +#include "ErrorHandlerFake.h" #include "SolidSyslogFormatter.h" -#include "SolidSyslogTimeQualitySd.h" #include "SolidSyslogStructuredData.h" #include "SolidSyslogTimeQuality.h" +#include "SolidSyslogTimeQualitySd.h" #include "SolidSyslogTunables.h" -#include "CppUTest/TestHarness.h" +#include "TestUtils.h" + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER into scope for CALLED_FAKE struct SolidSyslogFormatter; struct SolidSyslogStructuredData; @@ -199,3 +203,29 @@ TEST(SolidSyslogTimeQualitySdPool, FillingPoolThenOverflowReturnsDistinctFallbac CHECK_TEXT(overflow != slot, "Fallback handle collided with a pool slot"); } } + +// Bad-setup test — _Create rejects NULL callback and routes to NullSd. + +// clang-format off +TEST_GROUP(SolidSyslogTimeQualitySdBadSetup) +{ + int sentinel = 0; + + void setup() override + { + ErrorHandlerFake_Install(&sentinel); + } + + void teardown() override + { + ErrorHandlerFake_Uninstall(); + } +}; + +// clang-format on + +TEST(SolidSyslogTimeQualitySdBadSetup, CreateWithNullCallbackReportsError) +{ + SolidSyslogTimeQualitySd_Create(nullptr); + CHECK_REPORTED_ERROR("SolidSyslogTimeQualitySd_Create called with NULL getTimeQuality"); +} From cf9ced3b51a5707b7fdd6b1862d26a09f18ff2f4 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 17:57:54 +0100 Subject: [PATCH 17/18] test: S11.04 restructure SwitchingSender destroy-callback tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DestroyDoesNotSendToInnerSenders and DestroyDoesNotDisconnectInnerSenders explicitly call _Destroy on the fixture's sender. Under pool semantics, teardown's _Destroy(sender) would then operate on a stale handle and emit the SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_UNKNOWN_DESTROY warning. Apply the destroy-before-recreate pattern: explicitly destroy, null the fixture pointer to suppress the CreateSwitchingSender guard's re-destroy, assert the inner-sender contracts, then re-create so teardown's destroy hits a live handle. Every Destroy in the test path now targets a live, in-pool handle. Test-only — production code unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- Tests/SolidSyslogSwitchingSenderTest.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/SolidSyslogSwitchingSenderTest.cpp b/Tests/SolidSyslogSwitchingSenderTest.cpp index 6b31e9cf..037bda28 100644 --- a/Tests/SolidSyslogSwitchingSenderTest.cpp +++ b/Tests/SolidSyslogSwitchingSenderTest.cpp @@ -96,15 +96,27 @@ TEST(SolidSyslogSwitchingSender, CreateDestroyWorksWithoutCrashing) TEST(SolidSyslogSwitchingSender, DestroyDoesNotSendToInnerSenders) { SolidSyslogSwitchingSender_Destroy(sender); + sender = nullptr; + CALLED_FAKE_ON(SenderFake_Send, innerA, NEVER); CALLED_FAKE_ON(SenderFake_Send, innerB, NEVER); + + // Re-create so teardown's Destroy(sender) targets a live handle — + // the destroy under test has already freed the original slot. + CreateSwitchingSender(2); } TEST(SolidSyslogSwitchingSender, DestroyDoesNotDisconnectInnerSenders) { SolidSyslogSwitchingSender_Destroy(sender); + sender = nullptr; + CALLED_FAKE_ON(SenderFake_Disconnect, innerA, NEVER); CALLED_FAKE_ON(SenderFake_Disconnect, innerB, NEVER); + + // Re-create so teardown's Destroy(sender) targets a live handle — + // the destroy under test has already freed the original slot. + CreateSwitchingSender(2); } TEST(SolidSyslogSwitchingSender, SendDelegatesToSenderAtSelectedIndex) From be3162bfab06189bcec01866bc40012ad4d5c69b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 18 May 2026 18:04:07 +0100 Subject: [PATCH 18/18] refactor: S11.04 prefix Static.c file-scope statics per NAMING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit (correctly) flagged that the InUse / Pool / Allocator file-scope statics in the new Static.c TUs lacked the class prefix required by NAMING.md (Tier 2: file-scope statics are Class_Function or CLASS_SCREAMING_SNAKE). Same gap exists in the pre-existing CircularBufferStatic.c — including it in the sweep keeps the project consistent under NAMING.md after S11.04 rather than leaving a single non-conformant file. All seven E11 Static.c files renamed: InUse → _InUse Pool → _Pool Allocator → _Allocator where is the file's class basename. Affected: CircularBuffer, PassthroughBuffer, UdpSender, SwitchingSender, MetaSd, TimeQualitySd, OriginSd. Out of scope: the `Fallback` struct + Fallback_Read / Fallback_Write functions in CircularBuffer and PassthroughBuffer. Same NAMING.md concern; CR didn't flag them; deferring to a focused cleanup. Also: drop the value-init from config in SwitchingSenderBadSetup fixture (cppcheck redundantInitialization), and suppress unreadVariable on the setup() assignment (cppcheck doesn't model the TEST() macro reads of fixture members). No behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) --- Core/Source/SolidSyslogCircularBufferStatic.c | 26 +++++++++------- Core/Source/SolidSyslogMetaSdStatic.c | 22 +++++++------- Core/Source/SolidSyslogOriginSdStatic.c | 22 +++++++------- .../SolidSyslogPassthroughBufferStatic.c | 30 ++++++++++++------- .../Source/SolidSyslogSwitchingSenderStatic.c | 26 +++++++++------- Core/Source/SolidSyslogTimeQualitySdStatic.c | 26 +++++++++------- Core/Source/SolidSyslogUdpSenderStatic.c | 22 +++++++------- Tests/SolidSyslogSwitchingSenderTest.cpp | 3 +- 8 files changed, 99 insertions(+), 78 deletions(-) diff --git a/Core/Source/SolidSyslogCircularBufferStatic.c b/Core/Source/SolidSyslogCircularBufferStatic.c index b76b3fbf..3973e2c7 100644 --- a/Core/Source/SolidSyslogCircularBufferStatic.c +++ b/Core/Source/SolidSyslogCircularBufferStatic.c @@ -19,10 +19,13 @@ static void Fallback_Write(struct SolidSyslogBuffer* base, const void* data, siz static size_t CircularBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base); static void CircularBuffer_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE]; -static struct SolidSyslogCircularBuffer Pool[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE]; +static bool CircularBuffer_InUse[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE]; +static struct SolidSyslogCircularBuffer CircularBuffer_Pool[SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE]; static struct SolidSyslogBuffer Fallback = {Fallback_Write, Fallback_Read}; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE}; +static struct SolidSyslogPoolAllocator CircularBuffer_Allocator = { + CircularBuffer_InUse, + SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE +}; struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create( struct SolidSyslogMutex* mutex, @@ -30,12 +33,12 @@ struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create( size_t ringBytes ) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&CircularBuffer_Allocator); struct SolidSyslogBuffer* handle = &Fallback; - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + if (SolidSyslogPoolAllocator_IndexIsValid(&CircularBuffer_Allocator, index)) { - CircularBuffer_Initialise(&Pool[index].Base, mutex, ring, ringBytes); - handle = &Pool[index].Base; + CircularBuffer_Initialise(&CircularBuffer_Pool[index].Base, mutex, ring, ringBytes); + handle = &CircularBuffer_Pool[index].Base; } else { @@ -47,8 +50,9 @@ struct SolidSyslogBuffer* SolidSyslogCircularBuffer_Create( void SolidSyslogCircularBuffer_Destroy(struct SolidSyslogBuffer* base) { size_t index = CircularBuffer_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, CircularBuffer_CleanupAtIndex, NULL); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&CircularBuffer_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&CircularBuffer_Allocator, index, CircularBuffer_CleanupAtIndex, NULL); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_CIRCULARBUFFER_UNKNOWN_DESTROY); @@ -60,7 +64,7 @@ static size_t CircularBuffer_IndexFromHandle(const struct SolidSyslogBuffer* bas size_t result = SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &CircularBuffer_Pool[poolIndex].Base) { result = poolIndex; break; @@ -72,7 +76,7 @@ static size_t CircularBuffer_IndexFromHandle(const struct SolidSyslogBuffer* bas static void CircularBuffer_CleanupAtIndex(size_t index, void* context) { (void) context; - CircularBuffer_Cleanup(&Pool[index].Base); + CircularBuffer_Cleanup(&CircularBuffer_Pool[index].Base); } static bool Fallback_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) diff --git a/Core/Source/SolidSyslogMetaSdStatic.c b/Core/Source/SolidSyslogMetaSdStatic.c index 170e673f..889c3697 100644 --- a/Core/Source/SolidSyslogMetaSdStatic.c +++ b/Core/Source/SolidSyslogMetaSdStatic.c @@ -17,20 +17,20 @@ static bool MetaSd_IsValidConfig(const struct SolidSyslogMetaSdConfig* config); static size_t MetaSd_IndexFromHandle(const struct SolidSyslogStructuredData* base); static void MetaSd_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_META_SD_POOL_SIZE]; -static struct SolidSyslogMetaSd Pool[SOLIDSYSLOG_META_SD_POOL_SIZE]; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_META_SD_POOL_SIZE}; +static bool MetaSd_InUse[SOLIDSYSLOG_META_SD_POOL_SIZE]; +static struct SolidSyslogMetaSd MetaSd_Pool[SOLIDSYSLOG_META_SD_POOL_SIZE]; +static struct SolidSyslogPoolAllocator MetaSd_Allocator = {MetaSd_InUse, SOLIDSYSLOG_META_SD_POOL_SIZE}; struct SolidSyslogStructuredData* SolidSyslogMetaSd_Create(const struct SolidSyslogMetaSdConfig* config) { struct SolidSyslogStructuredData* result = SolidSyslogNullSd_Get(); if (MetaSd_IsValidConfig(config)) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&MetaSd_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&MetaSd_Allocator, index)) { - MetaSd_Initialise(&Pool[index].Base, config); - result = &Pool[index].Base; + MetaSd_Initialise(&MetaSd_Pool[index].Base, config); + result = &MetaSd_Pool[index].Base; } else { @@ -43,8 +43,8 @@ struct SolidSyslogStructuredData* SolidSyslogMetaSd_Create(const struct SolidSys void SolidSyslogMetaSd_Destroy(struct SolidSyslogStructuredData* base) { size_t index = MetaSd_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, MetaSd_CleanupAtIndex, NULL); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&MetaSd_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&MetaSd_Allocator, index, MetaSd_CleanupAtIndex, NULL); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_METASD_UNKNOWN_DESTROY); @@ -74,7 +74,7 @@ static size_t MetaSd_IndexFromHandle(const struct SolidSyslogStructuredData* bas size_t result = SOLIDSYSLOG_META_SD_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_META_SD_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &MetaSd_Pool[poolIndex].Base) { result = poolIndex; break; @@ -86,5 +86,5 @@ static size_t MetaSd_IndexFromHandle(const struct SolidSyslogStructuredData* bas static void MetaSd_CleanupAtIndex(size_t index, void* context) { (void) context; - MetaSd_Cleanup(&Pool[index].Base); + MetaSd_Cleanup(&MetaSd_Pool[index].Base); } diff --git a/Core/Source/SolidSyslogOriginSdStatic.c b/Core/Source/SolidSyslogOriginSdStatic.c index 908a9064..4131b149 100644 --- a/Core/Source/SolidSyslogOriginSdStatic.c +++ b/Core/Source/SolidSyslogOriginSdStatic.c @@ -16,18 +16,18 @@ struct SolidSyslogStructuredData; static size_t OriginSd_IndexFromHandle(const struct SolidSyslogStructuredData* base); static void OriginSd_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE]; -static struct SolidSyslogOriginSd Pool[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE]; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE}; +static bool OriginSd_InUse[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE]; +static struct SolidSyslogOriginSd OriginSd_Pool[SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE]; +static struct SolidSyslogPoolAllocator OriginSd_Allocator = {OriginSd_InUse, SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE}; struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidSyslogOriginSdConfig* config) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&OriginSd_Allocator); struct SolidSyslogStructuredData* handle = SolidSyslogNullSd_Get(); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + if (SolidSyslogPoolAllocator_IndexIsValid(&OriginSd_Allocator, index)) { - OriginSd_Initialise(&Pool[index].Base, config); - handle = &Pool[index].Base; + OriginSd_Initialise(&OriginSd_Pool[index].Base, config); + handle = &OriginSd_Pool[index].Base; } else { @@ -39,8 +39,8 @@ struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidS void SolidSyslogOriginSd_Destroy(struct SolidSyslogStructuredData* base) { size_t index = OriginSd_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, OriginSd_CleanupAtIndex, NULL); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&OriginSd_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&OriginSd_Allocator, index, OriginSd_CleanupAtIndex, NULL); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_ORIGINSD_UNKNOWN_DESTROY); @@ -52,7 +52,7 @@ static size_t OriginSd_IndexFromHandle(const struct SolidSyslogStructuredData* b size_t result = SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &OriginSd_Pool[poolIndex].Base) { result = poolIndex; break; @@ -64,5 +64,5 @@ static size_t OriginSd_IndexFromHandle(const struct SolidSyslogStructuredData* b static void OriginSd_CleanupAtIndex(size_t index, void* context) { (void) context; - OriginSd_Cleanup(&Pool[index].Base); + OriginSd_Cleanup(&OriginSd_Pool[index].Base); } diff --git a/Core/Source/SolidSyslogPassthroughBufferStatic.c b/Core/Source/SolidSyslogPassthroughBufferStatic.c index a78458ad..eb1d3fec 100644 --- a/Core/Source/SolidSyslogPassthroughBufferStatic.c +++ b/Core/Source/SolidSyslogPassthroughBufferStatic.c @@ -18,19 +18,22 @@ static void Fallback_Write(struct SolidSyslogBuffer* base, const void* data, siz static size_t PassthroughBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base); static void PassthroughBuffer_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE]; -static struct SolidSyslogPassthroughBuffer Pool[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE]; +static bool PassthroughBuffer_InUse[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE]; +static struct SolidSyslogPassthroughBuffer PassthroughBuffer_Pool[SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE]; static struct SolidSyslogBuffer Fallback = {Fallback_Write, Fallback_Read}; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE}; +static struct SolidSyslogPoolAllocator PassthroughBuffer_Allocator = { + PassthroughBuffer_InUse, + SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE +}; struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslogSender* sender) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PassthroughBuffer_Allocator); struct SolidSyslogBuffer* handle = &Fallback; - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + if (SolidSyslogPoolAllocator_IndexIsValid(&PassthroughBuffer_Allocator, index)) { - PassthroughBuffer_Initialise(&Pool[index].Base, sender); - handle = &Pool[index].Base; + PassthroughBuffer_Initialise(&PassthroughBuffer_Pool[index].Base, sender); + handle = &PassthroughBuffer_Pool[index].Base; } else { @@ -42,8 +45,13 @@ struct SolidSyslogBuffer* SolidSyslogPassthroughBuffer_Create(struct SolidSyslog void SolidSyslogPassthroughBuffer_Destroy(struct SolidSyslogBuffer* base) { size_t index = PassthroughBuffer_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, PassthroughBuffer_CleanupAtIndex, NULL); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&PassthroughBuffer_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &PassthroughBuffer_Allocator, + index, + PassthroughBuffer_CleanupAtIndex, + NULL + ); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_PASSTHROUGHBUFFER_UNKNOWN_DESTROY); @@ -55,7 +63,7 @@ static size_t PassthroughBuffer_IndexFromHandle(const struct SolidSyslogBuffer* size_t result = SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_PASSTHROUGH_BUFFER_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &PassthroughBuffer_Pool[poolIndex].Base) { result = poolIndex; break; @@ -67,7 +75,7 @@ static size_t PassthroughBuffer_IndexFromHandle(const struct SolidSyslogBuffer* static void PassthroughBuffer_CleanupAtIndex(size_t index, void* context) { (void) context; - PassthroughBuffer_Cleanup(&Pool[index].Base); + PassthroughBuffer_Cleanup(&PassthroughBuffer_Pool[index].Base); } static bool Fallback_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) diff --git a/Core/Source/SolidSyslogSwitchingSenderStatic.c b/Core/Source/SolidSyslogSwitchingSenderStatic.c index 913ac615..9de9ecff 100644 --- a/Core/Source/SolidSyslogSwitchingSenderStatic.c +++ b/Core/Source/SolidSyslogSwitchingSenderStatic.c @@ -17,20 +17,23 @@ static bool SwitchingSender_IsValidConfig(const struct SolidSyslogSwitchingSende static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* base); static void SwitchingSender_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE]; -static struct SolidSyslogSwitchingSender Pool[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE]; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE}; +static bool SwitchingSender_InUse[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE]; +static struct SolidSyslogSwitchingSender SwitchingSender_Pool[SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator SwitchingSender_Allocator = { + SwitchingSender_InUse, + SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE +}; struct SolidSyslogSender* SolidSyslogSwitchingSender_Create(const struct SolidSyslogSwitchingSenderConfig* config) { struct SolidSyslogSender* handle = SolidSyslogNullSender_Get(); if (SwitchingSender_IsValidConfig(config)) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&SwitchingSender_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&SwitchingSender_Allocator, index)) { - SwitchingSender_Initialise(&Pool[index].Base, config); - handle = &Pool[index].Base; + SwitchingSender_Initialise(&SwitchingSender_Pool[index].Base, config); + handle = &SwitchingSender_Pool[index].Base; } else { @@ -65,8 +68,9 @@ static bool SwitchingSender_IsValidConfig(const struct SolidSyslogSwitchingSende void SolidSyslogSwitchingSender_Destroy(struct SolidSyslogSender* base) { size_t index = SwitchingSender_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, SwitchingSender_CleanupAtIndex, NULL); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&SwitchingSender_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&SwitchingSender_Allocator, index, SwitchingSender_CleanupAtIndex, NULL); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_SWITCHINGSENDER_UNKNOWN_DESTROY); @@ -78,7 +82,7 @@ static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* ba size_t result = SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_SWITCHING_SENDER_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &SwitchingSender_Pool[poolIndex].Base) { result = poolIndex; break; @@ -90,5 +94,5 @@ static size_t SwitchingSender_IndexFromHandle(const struct SolidSyslogSender* ba static void SwitchingSender_CleanupAtIndex(size_t index, void* context) { (void) context; - SwitchingSender_Cleanup(&Pool[index].Base); + SwitchingSender_Cleanup(&SwitchingSender_Pool[index].Base); } diff --git a/Core/Source/SolidSyslogTimeQualitySdStatic.c b/Core/Source/SolidSyslogTimeQualitySdStatic.c index 31dc12f3..e2ee8997 100644 --- a/Core/Source/SolidSyslogTimeQualitySdStatic.c +++ b/Core/Source/SolidSyslogTimeQualitySdStatic.c @@ -17,9 +17,12 @@ struct SolidSyslogStructuredData; static size_t TimeQualitySd_IndexFromHandle(const struct SolidSyslogStructuredData* base); static void TimeQualitySd_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE]; -static struct SolidSyslogTimeQualitySd Pool[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE]; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE}; +static bool TimeQualitySd_InUse[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE]; +static struct SolidSyslogTimeQualitySd TimeQualitySd_Pool[SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE]; +static struct SolidSyslogPoolAllocator TimeQualitySd_Allocator = { + TimeQualitySd_InUse, + SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE +}; struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTimeQualityFunction getTimeQuality) { @@ -30,11 +33,11 @@ struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTim } else { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&TimeQualitySd_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&TimeQualitySd_Allocator, index)) { - TimeQualitySd_Initialise(&Pool[index].Base, getTimeQuality); - handle = &Pool[index].Base; + TimeQualitySd_Initialise(&TimeQualitySd_Pool[index].Base, getTimeQuality); + handle = &TimeQualitySd_Pool[index].Base; } else { @@ -47,8 +50,9 @@ struct SolidSyslogStructuredData* SolidSyslogTimeQualitySd_Create(SolidSyslogTim void SolidSyslogTimeQualitySd_Destroy(struct SolidSyslogStructuredData* base) { size_t index = TimeQualitySd_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, TimeQualitySd_CleanupAtIndex, NULL); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&TimeQualitySd_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&TimeQualitySd_Allocator, index, TimeQualitySd_CleanupAtIndex, NULL); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_TIMEQUALITYSD_UNKNOWN_DESTROY); @@ -60,7 +64,7 @@ static size_t TimeQualitySd_IndexFromHandle(const struct SolidSyslogStructuredDa size_t result = SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_TIME_QUALITY_SD_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &TimeQualitySd_Pool[poolIndex].Base) { result = poolIndex; break; @@ -72,5 +76,5 @@ static size_t TimeQualitySd_IndexFromHandle(const struct SolidSyslogStructuredDa static void TimeQualitySd_CleanupAtIndex(size_t index, void* context) { (void) context; - TimeQualitySd_Cleanup(&Pool[index].Base); + TimeQualitySd_Cleanup(&TimeQualitySd_Pool[index].Base); } diff --git a/Core/Source/SolidSyslogUdpSenderStatic.c b/Core/Source/SolidSyslogUdpSenderStatic.c index 3372187f..82fcbedc 100644 --- a/Core/Source/SolidSyslogUdpSenderStatic.c +++ b/Core/Source/SolidSyslogUdpSenderStatic.c @@ -17,20 +17,20 @@ static bool UdpSender_IsValidConfig(const struct SolidSyslogUdpSenderConfig* con static size_t UdpSender_IndexFromHandle(const struct SolidSyslogSender* base); static void UdpSender_CleanupAtIndex(size_t index, void* context); -static bool InUse[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE]; -static struct SolidSyslogUdpSender Pool[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE]; -static struct SolidSyslogPoolAllocator Allocator = {InUse, SOLIDSYSLOG_UDP_SENDER_POOL_SIZE}; +static bool UdpSender_InUse[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE]; +static struct SolidSyslogUdpSender UdpSender_Pool[SOLIDSYSLOG_UDP_SENDER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator UdpSender_Allocator = {UdpSender_InUse, SOLIDSYSLOG_UDP_SENDER_POOL_SIZE}; struct SolidSyslogSender* SolidSyslogUdpSender_Create(const struct SolidSyslogUdpSenderConfig* config) { struct SolidSyslogSender* result = SolidSyslogNullSender_Get(); if (UdpSender_IsValidConfig(config)) { - size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&Allocator); - if (SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index)) + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&UdpSender_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&UdpSender_Allocator, index)) { - UdpSender_Initialise(&Pool[index].Base, config); - result = &Pool[index].Base; + UdpSender_Initialise(&UdpSender_Pool[index].Base, config); + result = &UdpSender_Pool[index].Base; } else { @@ -43,8 +43,8 @@ struct SolidSyslogSender* SolidSyslogUdpSender_Create(const struct SolidSyslogUd void SolidSyslogUdpSender_Destroy(struct SolidSyslogSender* base) { size_t index = UdpSender_IndexFromHandle(base); - bool released = SolidSyslogPoolAllocator_IndexIsValid(&Allocator, index) && - SolidSyslogPoolAllocator_FreeIfInUse(&Allocator, index, UdpSender_CleanupAtIndex, NULL); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&UdpSender_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&UdpSender_Allocator, index, UdpSender_CleanupAtIndex, NULL); if (!released) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_UDPSENDER_UNKNOWN_DESTROY); @@ -82,7 +82,7 @@ static size_t UdpSender_IndexFromHandle(const struct SolidSyslogSender* base) size_t result = SOLIDSYSLOG_UDP_SENDER_POOL_SIZE; for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_UDP_SENDER_POOL_SIZE; poolIndex++) { - if (base == &Pool[poolIndex].Base) + if (base == &UdpSender_Pool[poolIndex].Base) { result = poolIndex; break; @@ -94,5 +94,5 @@ static size_t UdpSender_IndexFromHandle(const struct SolidSyslogSender* base) static void UdpSender_CleanupAtIndex(size_t index, void* context) { (void) context; - UdpSender_Cleanup(&Pool[index].Base); + UdpSender_Cleanup(&UdpSender_Pool[index].Base); } diff --git a/Tests/SolidSyslogSwitchingSenderTest.cpp b/Tests/SolidSyslogSwitchingSenderTest.cpp index 037bda28..16beec5a 100644 --- a/Tests/SolidSyslogSwitchingSenderTest.cpp +++ b/Tests/SolidSyslogSwitchingSenderTest.cpp @@ -344,12 +344,13 @@ TEST_GROUP(SolidSyslogSwitchingSenderBadSetup) struct SolidSyslogSender* innerA = nullptr; struct SolidSyslogSender* inners[1] = {nullptr}; int sentinel = 0; - SolidSyslogSwitchingSenderConfig config{}; + SolidSyslogSwitchingSenderConfig config; void setup() override { innerA = SenderFake_Create(); inners[0] = innerA; + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros config = {inners, 1, TestSelector}; ErrorHandlerFake_Install(&sentinel); }