diff --git a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c index ab836e2e..c51785a0 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c @@ -11,7 +11,7 @@ struct SolidSyslogResolver; static struct SolidSyslogStream* underlyingStream; -static SolidSyslogTlsStreamStorage tlsStreamStorage; + static struct SolidSyslogStream* tlsStream; static struct SolidSyslogSender* sender; @@ -35,7 +35,7 @@ struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* tlsStreamConfig.CaBundlePath = BddTargetTlsConfig_GetCaBundlePath(); tlsStreamConfig.ServerName = BddTargetTlsConfig_GetServerName(); } - tlsStream = SolidSyslogTlsStream_Create(&tlsStreamStorage, &tlsStreamConfig); + tlsStream = SolidSyslogTlsStream_Create(&tlsStreamConfig); static struct SolidSyslogStreamSenderConfig senderConfig; senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; diff --git a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c index ca88d8dd..68aad843 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c @@ -11,7 +11,7 @@ struct SolidSyslogResolver; static struct SolidSyslogStream* underlyingStream; -static SolidSyslogTlsStreamStorage tlsStreamStorage; + static struct SolidSyslogStream* tlsStream; static struct SolidSyslogSender* sender; @@ -35,7 +35,7 @@ struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* tlsStreamConfig.CaBundlePath = BddTargetTlsConfig_GetCaBundlePath(); tlsStreamConfig.ServerName = BddTargetTlsConfig_GetServerName(); } - tlsStream = SolidSyslogTlsStream_Create(&tlsStreamStorage, &tlsStreamConfig); + tlsStream = SolidSyslogTlsStream_Create(&tlsStreamConfig); static struct SolidSyslogStreamSenderConfig senderConfig; senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; diff --git a/Bdd/Targets/FreeRtos/CMakeLists.txt b/Bdd/Targets/FreeRtos/CMakeLists.txt index 63212898..45faf05c 100644 --- a/Bdd/Targets/FreeRtos/CMakeLists.txt +++ b/Bdd/Targets/FreeRtos/CMakeLists.txt @@ -174,6 +174,7 @@ add_executable(SolidSyslogBddTarget ${CMAKE_CURRENT_SOURCE_DIR}/Common/CmsdkUart.c ${CMAKE_CURRENT_SOURCE_DIR}/Common/Syscalls.c ${CMAKE_SOURCE_DIR}/Platform/FatFs/Source/SolidSyslogFatFsFile.c + ${CMAKE_SOURCE_DIR}/Platform/FatFs/Source/SolidSyslogFatFsFileStatic.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogAddress.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramStatic.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index e955bdff..3c185df4 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -188,8 +188,6 @@ static volatile bool solidSyslogTeardown = false; * in our ffconf.h). */ static const char STORE_PATH_PREFIX[] = "STORE"; -static SolidSyslogFatFsFileStorage storeFileStorage; - /* FATFS object lives in .bss because f_mount stores its address inside the * FatFs volume registry — the object must outlive every f_open / f_stat / * f_unlink. One per volume (FF_VOLUMES = 1). */ @@ -225,7 +223,6 @@ static volatile bool pendingNoSd = false; * .Store and pass the same struct back into SolidSyslog_Create. */ static struct SolidSyslogConfig solidSyslogConfig; static struct SolidSyslogStructuredData* sdList[3]; -static SolidSyslogStdAtomicCounterStorage atomicCounterStorage; static struct SolidSyslogAtomicCounter* atomicCounter = NULL; static struct SolidSyslogStructuredData* metaSd = NULL; static struct SolidSyslogStructuredData* timeQualitySd = NULL; @@ -581,7 +578,7 @@ static bool RebuildWithFileStore(void) /* Build a fresh FatFs-backed BlockStore. With the volume mounted above, * BlockSequence_Open's f_stat / f_open calls now hit a live filesystem * via disk_read / disk_write semihosting traps. */ - storeFile = SolidSyslogFatFsFile_Create(&storeFileStorage); + storeFile = SolidSyslogFatFsFile_Create(); storeBlockDevice = SolidSyslogFileBlockDevice_Create(storeFile, STORE_PATH_PREFIX); struct SolidSyslogSecurityPolicy* policy = SolidSyslogCrc16Policy_Create(); @@ -826,7 +823,7 @@ static void InteractiveTask(void* argument) currentStore = SolidSyslogNullStore_Get(); currentStoreIsFile = false; - atomicCounter = SolidSyslogStdAtomicCounter_Create(&atomicCounterStorage); + atomicCounter = SolidSyslogStdAtomicCounter_Create(); struct SolidSyslogMetaSdConfig metaConfig = { .Counter = atomicCounter, .GetSysUpTime = SolidSyslogFreeRtosSysUpTime_Get, diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 360ed3a1..657be28a 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -232,8 +232,7 @@ int main(int argc, char* argv[]) struct SolidSyslogStore* store = CreateStore(&options); struct SolidSyslogBuffer* buffer = SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); - SolidSyslogStdAtomicCounterStorage counterStorage; - struct SolidSyslogAtomicCounter* counter = SolidSyslogStdAtomicCounter_Create(&counterStorage); + struct SolidSyslogAtomicCounter* counter = SolidSyslogStdAtomicCounter_Create(); struct SolidSyslogMetaSdConfig metaConfig = { .Counter = counter, .GetSysUpTime = SolidSyslogPosixSysUpTime_Get, diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index 24b3aa81..fc9ef5fb 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -65,7 +65,6 @@ static const char* const STORE_PATH_PREFIX = "Bdd/output/STORE"; static const char* const THRESHOLD_MARKER_PATH = "Bdd/output/solidsyslog_threshold_marker.log"; static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)]; -static SolidSyslogWindowsAtomicCounterStorage counterStorage; static volatile bool shutdownFlag; /* Created in CreateSender, destroyed in DestroySender — held in file scope so @@ -299,7 +298,7 @@ int BddTargetWindows_Run(int argc, char* argv[]) struct SolidSyslogMutex* mutex = SolidSyslogWindowsMutex_Create(); struct SolidSyslogBuffer* buffer = SolidSyslogCircularBuffer_Create(mutex, bufferRing, sizeof(bufferRing)); - struct SolidSyslogAtomicCounter* counter = SolidSyslogWindowsAtomicCounter_Create(&counterStorage); + struct SolidSyslogAtomicCounter* counter = SolidSyslogWindowsAtomicCounter_Create(); struct SolidSyslogMetaSdConfig metaConfig = { .Counter = counter, .GetSysUpTime = SolidSyslogWindowsSysUpTime_Get, diff --git a/CLAUDE.md b/CLAUDE.md index fab64f07..a2fadea3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -338,6 +338,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `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`. | | `SolidSyslogPosixTcpStream.h` | System setup code on POSIX targets using non-blocking TCP with bounded connect/keepalive | `SolidSyslogPosixTcpStream_Create(void)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11); default pool size 2 to support the BDD-target plain-TCP + TLS-underlying-TCP pair. Pool-exhaustion fallback is the shared `SolidSyslogNullStream`. | | `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(config)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create`. Pool exhaustion resolves to the shared `SolidSyslogNullSender`. | +| `SolidSyslogTlsStream.h` | System setup code using TLS over an injected `SolidSyslogStream` transport (OpenSSL reference integration) | `SolidSyslogTlsStreamConfig` (transport, sleep, caBundlePath, serverName, cipherList, clientCertChainPath, clientKeyPath), `SolidSyslogTlsStream_Create(config)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullStream`. | | `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` | | `SolidSyslogNullBuffer.h` | Any code installing a no-op buffer slot (Read returns `false`/`bytesRead=0`; Write swallows the record) | `SolidSyslogNullBuffer_Get` | @@ -368,7 +369,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogCrc16.h` | Any code needing CRC-16 computation | `SolidSyslogCrc16_Compute` | | `SolidSyslogPosixFile.h` | System setup code using POSIX file I/O | `SolidSyslogPosixFile_Create(void)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. | | `SolidSyslogWindowsFile.h` | System setup code using Windows file I/O (MSVC ``) | `SolidSyslogWindowsFile_Create(void)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. | -| `SolidSyslogFatFsFile.h` | System setup code on targets using ChaN FatFs as the file layer (bare-metal flash / SD / eMMC, FreeRTOS, Zephyr, NuttX, …) | `SolidSyslogFatFsFileStorage`, `SOLIDSYSLOG_FATFS_FILE_SIZE`, `SolidSyslogFatFsFile_Create(storage)`, `_Destroy(file)` (wraps `f_open` / `f_close` / `f_read` / `f_write` with `f_sync` after every successful write so a power loss never loses a record the BlockStore claimed it stored; integrator supplies `diskio.c` and — if `FF_FS_REENTRANT=1` — `ffsystem.c`) | +| `SolidSyslogFatFsFile.h` | System setup code on targets using ChaN FatFs as the file layer (bare-metal flash / SD / eMMC, FreeRTOS, Zephyr, NuttX, …) | `SolidSyslogFatFsFile_Create(void)`, `_Destroy(file)` (wraps `f_open` / `f_close` / `f_read` / `f_write` with `f_sync` after every successful write so a power loss never loses a record the BlockStore claimed it stored; integrator supplies `diskio.c` and — if `FF_FS_REENTRANT=1` — `ffsystem.c`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. | | `SolidSyslogPosixClock.h` | System setup code using POSIX clock | `SolidSyslogPosixClock_GetTimestamp` | | `SolidSyslogPosixHostname.h` | String callback implementor using POSIX hostname | `SolidSyslogPosixHostname_Get` (writes into `SolidSyslogFormatter*`) | | `SolidSyslogPosixProcessId.h` | String callback implementor using POSIX process ID | `SolidSyslogPosixProcessId_Get` (writes into `SolidSyslogFormatter*`) | @@ -387,8 +388,9 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `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. | -| `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. | +| `SolidSyslogNullAtomicCounter.h` | Any code installing a no-op counter slot (`Increment` returns `1U` unconditionally — RFC 5424 §7.3.1 forbids `0`, and `1` is indistinguishable from the post-power-on / post-wrap state) | `SolidSyslogNullAtomicCounter_Get` | +| `SolidSyslogStdAtomicCounter.h` | System setup code on platforms with C11 `` | `SolidSyslogStdAtomicCounter_Create(void)`, `_Destroy(base)`. Uses `_Atomic uint32_t` + `atomic_compare_exchange_strong_explicit` CAS loop. Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullAtomicCounter`. | +| `SolidSyslogWindowsAtomicCounter.h` | System setup code on Windows targets without `` (legacy MSVC) | `SolidSyslogWindowsAtomicCounter_Create(void)`, `_Destroy(base)`. Uses `volatile LONG` + `InterlockedCompareExchange` CAS loop. Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullAtomicCounter`. | | `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(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`. | diff --git a/Core/Interface/SolidSyslogNullAtomicCounter.h b/Core/Interface/SolidSyslogNullAtomicCounter.h new file mode 100644 index 00000000..0f206eed --- /dev/null +++ b/Core/Interface/SolidSyslogNullAtomicCounter.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLATOMICCOUNTER_H +#define SOLIDSYSLOGNULLATOMICCOUNTER_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogAtomicCounter* SolidSyslogNullAtomicCounter_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLATOMICCOUNTER_H */ diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 20439250..4b6c7f6a 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -541,4 +541,90 @@ #error "SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogStdAtomicCounter instances the library's + * internal static pool can simultaneously hold. Each instance carries + * a single _Atomic uint32_t (the sequenceId counter). + * + * Default 1 — RFC 5424 sequenceIds are scoped per SolidSyslog instance, + * and almost all integrators run a single SolidSyslog instance per + * process. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more than one is + * genuinely needed (e.g. several independent SolidSyslog instances). + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogWindowsAtomicCounter instances the library's + * internal static pool can simultaneously hold. Each instance carries + * a single `volatile LONG` (the sequenceId counter, manipulated via + * `InterlockedCompareExchange`). + * + * Default 1 — RFC 5424 sequenceIds are scoped per SolidSyslog instance, + * and almost all integrators run a single SolidSyslog instance per + * process. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more than one is + * genuinely needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogFatFsFile instances the library's internal static + * pool can simultaneously hold. Each instance carries a FatFs FIL object + * (~56 B header + 512 B sector buffer when FF_MAX_SS=512, FF_FS_TINY=0) + * plus an IsOpen flag. + * + * Default 1 — store-and-forward integrations wire a single file under + * BlockStore + FileBlockDevice; that's the dominant 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_FATFS_FILE_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_FATFS_FILE_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_FATFS_FILE_POOL_SIZE < 1 +#error "SOLIDSYSLOG_FATFS_FILE_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogTlsStream instances the library's internal static + * pool can simultaneously hold. Each instance carries an SSL_CTX*, SSL*, + * BIO_METHOD*, and the integrator's TlsStreamConfig (transport pointer, + * sleep callback, cert/key/CA paths). + * + * Default 1 — TLS senders are scoped per destination and almost all + * integrators wire a single TLS sender per process. Bump via + * SOLIDSYSLOG_USER_TUNABLES_FILE if more than one is genuinely needed + * (e.g. multi-destination egress with separate TLS sessions per peer). + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_TLS_STREAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_TLS_STREAM_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_TLS_STREAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_TLS_STREAM_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index dbdee9c5..485bd7fa 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -47,13 +47,17 @@ set(SOURCES ) if(HAVE_ATOMIC_COUNTER) - list(APPEND SOURCES SolidSyslogAtomicCounter.c) + list(APPEND SOURCES + SolidSyslogAtomicCounter.c + SolidSyslogNullAtomicCounter.c + ) endif() # SolidSyslogAtomicCounter.c is the public vtable dispatcher — included # under the same HAVE_ATOMIC_COUNTER gate as the platform impls # (HAVE_STDATOMIC_H -> Platform/Atomics, HAVE_WINDOWS_INTERLOCKED -> # Platform/Windows), so it's only compiled when at least one atomic -# backend is available. +# backend is available. SolidSyslogNullAtomicCounter.c is the shared +# pool-exhaustion fallback for both backends. if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_WINSOCK OR DEFINED ENV{FREERTOS_KERNEL_PATH}) list(APPEND SOURCES diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index 87563b8f..913c6d35 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -123,5 +123,21 @@ "SolidSyslogFreeRtosTcpStream_Create pool exhausted; returning fallback stream" #define SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_UNKNOWN_DESTROY \ "SolidSyslogFreeRtosTcpStream_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_POOL_EXHAUSTED \ + "SolidSyslogStdAtomicCounter_Create pool exhausted; returning fallback counter" +#define SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_UNKNOWN_DESTROY \ + "SolidSyslogStdAtomicCounter_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_POOL_EXHAUSTED \ + "SolidSyslogWindowsAtomicCounter_Create pool exhausted; returning fallback counter" +#define SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_UNKNOWN_DESTROY \ + "SolidSyslogWindowsAtomicCounter_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_FATFSFILE_POOL_EXHAUSTED \ + "SolidSyslogFatFsFile_Create pool exhausted; returning fallback file" +#define SOLIDSYSLOG_ERROR_MSG_FATFSFILE_UNKNOWN_DESTROY \ + "SolidSyslogFatFsFile_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_POOL_EXHAUSTED \ + "SolidSyslogTlsStream_Create pool exhausted; returning fallback stream" +#define SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_UNKNOWN_DESTROY \ + "SolidSyslogTlsStream_Destroy called with a handle not issued by this pool" #endif /* SOLIDSYSLOGERRORMESSAGES_H */ diff --git a/Core/Source/SolidSyslogNullAtomicCounter.c b/Core/Source/SolidSyslogNullAtomicCounter.c new file mode 100644 index 00000000..c47ec5c7 --- /dev/null +++ b/Core/Source/SolidSyslogNullAtomicCounter.c @@ -0,0 +1,24 @@ +#include "SolidSyslogNullAtomicCounter.h" + +#include + +#include "SolidSyslogAtomicCounterDefinition.h" + +static uint32_t NullAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base); + +static struct SolidSyslogAtomicCounter instance = {.Increment = NullAtomicCounter_Increment}; + +struct SolidSyslogAtomicCounter* SolidSyslogNullAtomicCounter_Get(void) +{ + return &instance; +} + +/* Returns 1U unconditionally. RFC 5424 §7.3.1 requires sequenceIds in + * [1, 2^31 - 1] and never 0; 1U is indistinguishable from the post-power-on + * or post-wrap state, which is the safest fallback when an integrator's + * AtomicCounter pool is exhausted. */ +static uint32_t NullAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base) +{ + (void) base; + return 1U; +} diff --git a/DEVLOG.md b/DEVLOG.md index 9d328bb7..6f6ac68b 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,222 @@ # Dev Log +## 2026-05-20 — S11.09: AtomicCounter family + TLS + FatFs onto PoolAllocator + +Closes S11.09 (#412). Closes out E11's class-by-class migration with the +four storage-cast classes that don't fit one platform umbrella — +`StdAtomicCounter` (Platform/Atomics), `WindowsAtomicCounter` +(Platform/Windows), `FatFsFile` (Platform/FatFs), `TlsStream` +(Platform/OpenSsl). Each gets the canonical 3-TU split +(`Class.c` + `ClassPrivate.h` + `ClassStatic.c`) + `SolidSyslogPoolAllocator` ++ the public `Storage` typedef and `SOLIDSYSLOG_*_SIZE` enum +deleted from the public header. + +Four commits on the branch, then DEVLOG. + +- `7c25e84` — **`feat: S11.09 add SolidSyslogNullAtomicCounter`**. New + public GoF null with `Increment` returning `1U` unconditionally. RFC + 5424 §7.3.1 forbids sequenceId `0`; `1U` is indistinguishable from + the post-power-on / post-wrap state, which is the safest fallback for + pool-exhaustion. Symmetric with the S11.06 wave (NullDatagram / + NullStream / NullFile / NullResolver) — both AtomicCounter backends + need a fallback and `_Get` doesn't fire `SolidSyslog_Error` because + `_Get` is also called by deliberate integrator wiring (e.g. + `currentStore = SolidSyslogNullStore_Get();` in the FreeRTOS BDD + target's default-store path). + +- `f7d0eb9` — **`refactor: S11.09 migrate AtomicCounter family onto + PoolAllocator`** (Std + Windows in one commit, not two as the story + body sequenced them). The two backends share + `Tests/SolidSyslogAtomicCounterTestHelper.h`'s shim signature + (`TestAtomicCounter_Create(void) → handle`); splitting per backend + would leave the inactive platform's compile broken between commits. + Combined commit preserves the per-commit must-compile invariant. + +- `804b83a` — **`refactor: S11.09 migrate FatFsFile onto PoolAllocator`**. + Deletes the `SOLIDSYSLOG_FATFS_FILE_SIZE = sizeof(intptr_t) * 180U` + enum from the public header — the 180-intptr footprint (driven by + `FF_MAX_SS=512` and `FIL`'s sector buffer) is now an internal pool + concern. The planned S21.03 follow-up that was going to make + `FF_MAX_SS > 512` overridable becomes simpler: it's a pool-internal + tunable, not a public-API one. + +- `2aa9403` — **`refactor: S11.09 migrate TlsStream onto PoolAllocator`**. + Largest of the four (~460 lines pre-migration). Refactor-only; + bounded handshake retry via the injected Sleep callback, mTLS + all-or-nothing client-identity contract, BIO_METHOD lifecycle, + SSL_read fail-fast on non-WANT_READ, idempotent Close all preserved + verbatim. Drops one obsolete test + (`CreateReturnsHandleInsideCallerSuppliedStorage` — asserted the + storage-cast invariant which no longer holds). + +### Decisions + +- **One new public GoF null only.** `SolidSyslogNullAtomicCounter` + (commit 1) is the only new public API surface. `NullStream` (S11.06) + covers `TlsStream`; `NullFile` (S11.06) covers `FatFsFile`. + +- **AtomicCounter siblings ship in one commit.** Per the per-commit + must-compile rule: a Std-only commit would leave Windows broken + between commits because the shared `TestAtomicCounter_*` shim + signature changes for both. Story body sequenced them as two; the + executed plan diverges to preserve the invariant. The squash-merged + PR collapses to one commit anyway, so the on-`main` history is + unchanged from the story body's plan. + +- **Pool size defaults — all four at 1U.** None of the four has a + documented multi-instance use case. TlsStream could go to 2U on the + same logic that gave PosixTcpStream / WinsockTcpStream / FreeRtosTcpStream + their 2U defaults (mbedTLS-via-TCP composes two streams), but the + TLS path itself is the composition point — bumping the underlying + TCP stream is the right place, not bumping TlsStream itself. + Integrators wanting multiple concurrent TLS sessions override via + `SOLIDSYSLOG_USER_TUNABLES_FILE`. + +- **`TestAtomicCounter_PoolSize()` shim function.** Added to the + shared `Tests/SolidSyslogAtomicCounterTestHelper.h` so the + backend-agnostic contract test can runtime-gate the + `TwoCountersFromPoolAreIndependent` scenario. Each backend's + TestHelper.c returns its own + `SOLIDSYSLOG_{STD,WINDOWS}_ATOMIC_COUNTER_POOL_SIZE`. Avoids the + preprocessor-conditional gating the contract test would otherwise + need. + +- **Tunable-override fixture updated.** `SmallMessageSizeTunables.h` + bumps both `_STD_ATOMIC_COUNTER_POOL_SIZE` and + `_WINDOWS_ATOMIC_COUNTER_POOL_SIZE` to 2U so the + `tunable-override-debug` preset exercises the two-counter + independence contract test. Same precedent as the + `PosixMessageQueueBuffer` pool-size bump in S11.06. + +- **AtomicCounter whitebox-test access preserved.** Each backend's + `_Init(uint32_t)` helper (used by the contract test for the + wraparound-at-INT32_MAX scenario) stays in `*.c` alongside + `_Increment`. The TestHelper continues to `#include "*.c"` (whitebox + include) and now also sees the struct definition via the new + `*Private.h`. Wraparound contract test mechanism unchanged. + +- **`TlsStream`'s `DEFAULT_INSTANCE` / `DESTROYED_INSTANCE` static + consts go away.** `Initialise` now sets vtable fields + zeroes + Ctx/Ssl/BioMethod explicitly; `Cleanup` overwrites the abstract base + with `*SolidSyslogNullStream_Get()`. Matches every other + 3-TU-migrated class. + +- **INTERFACE-library shape preserved for FatFs and Atomics.** + `Platform/FatFs/CMakeLists.txt` stays INTERFACE; `SolidSyslogFatFsFileStatic.c` + joins `SolidSyslogFatFsFile.c` in every consumer's source list + (`Bdd/Targets/FreeRtos/CMakeLists.txt` + `Tests/FatFs/CMakeLists.txt`). + `Platform/Atomics/CMakeLists.txt` and `Platform/Windows/CMakeLists.txt`'s + `HAVE_WINDOWS_INTERLOCKED` block both stay PRIVATE-source. + `Platform/OpenSsl/CMakeLists.txt` likewise PRIVATE. + +- **Pool-exhaustion + use-after-destroy fallback severity follows the + existing contract** — ERROR on exhausted Create, WARNING on + unknown/stale Destroy. Error message constants follow the + `SOLIDSYSLOG_ERROR_MSG__POOL_EXHAUSTED` / + `_UNKNOWN_DESTROY` pattern verbatim from S11.06/07/08. + +### Per-class delta numbers + +- `NullAtomicCounter`: 2 new tests (returns 1, idempotent). +- `StdAtomicCounter`: 6 existing contract tests (one runtime-gated on + pool size ≥ 2; in default builds the contract test runs 5, plus the + one that prints TEST_EXIT) + 9 Pool tests = 15 total. Under + `tunable-override-debug` all 6 + 9 = 15 run with the gated test live. +- `WindowsAtomicCounter`: contract tests shared with Std (same + backend-agnostic shim) + 9 Pool tests (MSVC build only). +- `FatFsFile`: 28 existing tests + 9 Pool = 37 total. +- `TlsStream`: 114 existing tests (one obsolete deleted) + 9 Pool = + 123 total. + +### Local validation + +All host gates green from the gcc devcontainer (StdAtomicCounter, +TlsStream, NullAtomicCounter) and the freertos-host devcontainer +(FatFsFile, which only compiles when `$FATFS_PATH` is set): + +- debug build + 1278 unit tests +- sanitize build + suite (a pre-existing UBSan finding on `Tests/FileFake.c` + is unrelated to this story; `git blame` points to S10.04) +- coverage: 99.5% line aggregated, 100% line on every new/migrated + file when measured per-test-binary (`SolidSyslogTlsStream.c` 183/183, + `SolidSyslogTlsStreamStatic.c` 26/26, `SolidSyslogStdAtomicCounter.c` + 20/20, `SolidSyslogStdAtomicCounterStatic.c` 26/26, + `SolidSyslogNullAtomicCounter.c` 4/4, `SolidSyslogFatFsFile.c` and + `SolidSyslogFatFsFileStatic.c` 83/83 combined via the separate + `SolidSyslogFatFsFileTest` binary). The lcov capture in the `coverage` + custom target only runs the main `SolidSyslogTests` exe — same + pre-existing aggregation gap as for the WindowsAtomicCounter and the + separate `Tests/FatFs/` and `Tests/FreeRtos/` binaries. Worth a + separate gate-improvement story; not in scope here. +- tidy clean on every new/migrated file (the gcc-container CI image + doesn't include `Tests/FreeRtos` so the pre-existing CmsdkUart tidy + errors flagged in S11.08's DEVLOG are still out-of-CI-scope). +- cppcheck clean. +- format clean. +- IWYU clean via the cpputest-clang container (caught one stray + unused include in `SolidSyslogNullAtomicCounter.c` after the first + consumer landed — folded into the AtomicCounter migration commit). +- OpenSSL integration suite green (9 tests). + +### Handoff for CI + +Four CI-gated checks remain. Per pre-flight agreement, **MSVC +validation moves to CI for this story** (departing from +`feedback_local_msvc` for S11.09 specifically — the AtomicCounter +family + the OpenSSL surface mean the local-MSVC overhead doesn't +buy a meaningfully different signal than the existing +`build-windows-msvc` + `integration-windows-openssl` CI jobs). + +1. **`build-windows-msvc`** — `WindowsAtomicCounter` and the + TlsStream-OpenSsl-WinsockTcp BDD target sender both compile only + under MSVC. CI is the gate; expected pass. +2. **`bdd-windows-otel`** — the OpenSSL+Winsock TLS BDD scenario + exercises `TlsStream_Create(&config)` against the cert-validated + syslog-ng oracle. Expected pass. +3. **`analyze-iwyu`** — already validated locally via the clang + container; CI re-runs to be sure. +4. **`bdd-freertos-qemu`** — exercises the FatFsFile pool migration + end-to-end (the `set store file` BDD scenario flips the BDD + target's store to a FatFs-backed BlockStore, which now goes through + the FatFsFile pool). The host smoke (37 tests on the FatFsFakes + harness) covers the unit logic; QEMU end-to-end via CI. + +### Known stale findings (this devcontainer, pre-existing) + +- `Tests/FileFake.c:424` triggers a UBSan `null pointer passed as + argument 1` warning under the `sanitize` preset on this branch. + Pre-existing: `git log -1 Tests/FileFake.c` points to S10.04 (#364), + same shape on `main`. Out of scope for S11.09. +- `Tests/FreeRtos/CmsdkUartFake.c` tidy errors documented in S11.08's + DEVLOG — unchanged. The gcc CI image excludes `Tests/FreeRtos` so + these are not gating. +- Per-test-binary coverage aggregation gap (FatFsFile, FreeRtos, + WindowsAtomicCounter only measured by their own exes, not folded + into the main `coverage` target's `coverage.info`). Pre-existing, + same shape as S11.08's report. Probably worth lifting into a + separate coverage-gate story. + +### Deferred + +- **S11.10 — `SolidSyslog` core retrofit.** Already singleton-shaped; + adopt the canonical file layout under fixed pool size 1, no + tunable. Story stays as planned in the epic body. Per + pre-flight agreement S11.10 and S11.11 stay separate stories from + S11.09. +- **S11.11 — wholesale MISRA `D.002` storage-cast deviation cleanup.** + Per-class deviations for StdAtomicCounter / WindowsAtomicCounter / + FatFsFile / TlsStream evaporate naturally with this story (the + underlying casts are gone). The bulk `docs/misra-deviations.md` + edit and `misra_suppressions.txt` pruning is S11.11. +- **`SOLIDSYSLOG_FATFS_FILE_SIZE` override mechanism for + `FF_MAX_SS > 512` (S21.03 follow-up)** — simpler now that the size + is no longer in the public surface; it becomes a pool-internal + CMake tunable. + +### Open questions + +- None. + ## 2026-05-20 — S11.08: FreeRTOS adapters onto PoolAllocator Closes S11.08 (#410). The third platform-adapter sweep in E11 — applies diff --git a/Platform/Atomics/CMakeLists.txt b/Platform/Atomics/CMakeLists.txt index 7041eec1..2f68b5f8 100644 --- a/Platform/Atomics/CMakeLists.txt +++ b/Platform/Atomics/CMakeLists.txt @@ -1,6 +1,7 @@ if(HAVE_STDATOMIC_H) target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogStdAtomicCounter.c + Source/SolidSyslogStdAtomicCounterStatic.c ) endif() diff --git a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h index c1dff74c..4788ecbc 100644 --- a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h +++ b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h @@ -3,23 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogAtomicCounter; - enum - { - SOLIDSYSLOG_STD_ATOMIC_COUNTER_SIZE = sizeof(intptr_t) * 2U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_STD_ATOMIC_COUNTER_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogStdAtomicCounterStorage; - - struct SolidSyslogAtomicCounter* SolidSyslogStdAtomicCounter_Create(SolidSyslogStdAtomicCounterStorage * storage); + struct SolidSyslogAtomicCounter* SolidSyslogStdAtomicCounter_Create(void); void SolidSyslogStdAtomicCounter_Destroy(struct SolidSyslogAtomicCounter * base); EXTERN_C_END diff --git a/Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c b/Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c index 61e916ee..14763cba 100644 --- a/Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c +++ b/Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c @@ -1,42 +1,39 @@ #include "SolidSyslogStdAtomicCounter.h" -#include "SolidSyslogAtomicCounter.h" -#include "SolidSyslogAtomicCounterDefinition.h" -#include "SolidSyslogMacros.h" - #include -#include - -struct SolidSyslogStdAtomicCounter -{ - struct SolidSyslogAtomicCounter Base; - _Atomic uint32_t Value; -}; +#include -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogStdAtomicCounter) <= sizeof(SolidSyslogStdAtomicCounterStorage), - SolidSyslogStdAtomicCounterStorage_too_small -); +#include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogAtomicCounterDefinition.h" +#include "SolidSyslogNullAtomicCounter.h" +#include "SolidSyslogStdAtomicCounterPrivate.h" static uint32_t StdAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base); -static void StdAtomicCounter_Init(struct SolidSyslogStdAtomicCounter* self, uint32_t value); + static inline struct SolidSyslogStdAtomicCounter* StdAtomicCounter_SelfFromBase(struct SolidSyslogAtomicCounter* base); -static inline struct SolidSyslogStdAtomicCounter* StdAtomicCounter_SelfFromStorage( - SolidSyslogStdAtomicCounterStorage* storage -); -struct SolidSyslogAtomicCounter* SolidSyslogStdAtomicCounter_Create(SolidSyslogStdAtomicCounterStorage* storage) +void StdAtomicCounter_Initialise(struct SolidSyslogAtomicCounter* base) { - struct SolidSyslogStdAtomicCounter* self = StdAtomicCounter_SelfFromStorage(storage); + struct SolidSyslogStdAtomicCounter* self = StdAtomicCounter_SelfFromBase(base); self->Base.Increment = StdAtomicCounter_Increment; StdAtomicCounter_Init(self, 0U); - return &self->Base; } -void SolidSyslogStdAtomicCounter_Destroy(struct SolidSyslogAtomicCounter* base) +void StdAtomicCounter_Cleanup(struct SolidSyslogAtomicCounter* base) { - struct SolidSyslogStdAtomicCounter* self = StdAtomicCounter_SelfFromBase(base); - self->Base.Increment = NULL; + /* Overwrite the abstract base with the shared NullAtomicCounter vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullAtomicCounter_Get(); +} + +void StdAtomicCounter_Init(struct SolidSyslogStdAtomicCounter* self, uint32_t value) +{ + atomic_init(&self->Value, value); +} + +static inline struct SolidSyslogStdAtomicCounter* StdAtomicCounter_SelfFromBase(struct SolidSyslogAtomicCounter* base) +{ + return (struct SolidSyslogStdAtomicCounter*) base; } static uint32_t StdAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base) @@ -56,20 +53,3 @@ static uint32_t StdAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base )); return next; } - -static void StdAtomicCounter_Init(struct SolidSyslogStdAtomicCounter* self, uint32_t value) -{ - atomic_init(&self->Value, value); -} - -static inline struct SolidSyslogStdAtomicCounter* StdAtomicCounter_SelfFromBase(struct SolidSyslogAtomicCounter* base) -{ - return (struct SolidSyslogStdAtomicCounter*) base; -} - -static inline struct SolidSyslogStdAtomicCounter* StdAtomicCounter_SelfFromStorage( - SolidSyslogStdAtomicCounterStorage* storage -) -{ - return (struct SolidSyslogStdAtomicCounter*) storage; -} diff --git a/Platform/Atomics/Source/SolidSyslogStdAtomicCounterPrivate.h b/Platform/Atomics/Source/SolidSyslogStdAtomicCounterPrivate.h new file mode 100644 index 00000000..04dfe63a --- /dev/null +++ b/Platform/Atomics/Source/SolidSyslogStdAtomicCounterPrivate.h @@ -0,0 +1,23 @@ +#ifndef SOLIDSYSLOGSTDATOMICCOUNTERPRIVATE_H +#define SOLIDSYSLOGSTDATOMICCOUNTERPRIVATE_H + +#include +#include + +#include "SolidSyslogAtomicCounterDefinition.h" + +struct SolidSyslogStdAtomicCounter +{ + struct SolidSyslogAtomicCounter Base; + _Atomic uint32_t Value; +}; + +void StdAtomicCounter_Initialise(struct SolidSyslogAtomicCounter* base); +void StdAtomicCounter_Cleanup(struct SolidSyslogAtomicCounter* base); + +/* Test-only helper — Tests/SolidSyslogStdAtomicCounterTestHelper.c + * whitebox-includes this TU to seed the counter near INT32_MAX for the + * wraparound contract test. Not part of the public API. */ +void StdAtomicCounter_Init(struct SolidSyslogStdAtomicCounter* self, uint32_t value); + +#endif /* SOLIDSYSLOGSTDATOMICCOUNTERPRIVATE_H */ diff --git a/Platform/Atomics/Source/SolidSyslogStdAtomicCounterStatic.c b/Platform/Atomics/Source/SolidSyslogStdAtomicCounterStatic.c new file mode 100644 index 00000000..728b21e7 --- /dev/null +++ b/Platform/Atomics/Source/SolidSyslogStdAtomicCounterStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogStdAtomicCounter.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullAtomicCounter.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStdAtomicCounterPrivate.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogAtomicCounter; + +static inline size_t StdAtomicCounter_IndexFromHandle(const struct SolidSyslogAtomicCounter* base); +static inline void StdAtomicCounter_CleanupAtIndex(size_t index, void* context); + +static bool StdAtomicCounter_InUse[SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE]; +static struct SolidSyslogStdAtomicCounter StdAtomicCounter_Pool[SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator StdAtomicCounter_Allocator = { + StdAtomicCounter_InUse, + SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE +}; + +struct SolidSyslogAtomicCounter* SolidSyslogStdAtomicCounter_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&StdAtomicCounter_Allocator); + struct SolidSyslogAtomicCounter* handle = SolidSyslogNullAtomicCounter_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&StdAtomicCounter_Allocator, index)) + { + StdAtomicCounter_Initialise(&StdAtomicCounter_Pool[index].Base); + handle = &StdAtomicCounter_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogStdAtomicCounter_Destroy(struct SolidSyslogAtomicCounter* base) +{ + size_t index = StdAtomicCounter_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&StdAtomicCounter_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&StdAtomicCounter_Allocator, index, StdAtomicCounter_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_UNKNOWN_DESTROY); + } +} + +static inline size_t StdAtomicCounter_IndexFromHandle(const struct SolidSyslogAtomicCounter* base) +{ + size_t result = SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE; poolIndex++) + { + if (base == &StdAtomicCounter_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void StdAtomicCounter_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + StdAtomicCounter_Cleanup(&StdAtomicCounter_Pool[index].Base); +} diff --git a/Platform/FatFs/Interface/SolidSyslogFatFsFile.h b/Platform/FatFs/Interface/SolidSyslogFatFsFile.h index 1af950d8..78543f7e 100644 --- a/Platform/FatFs/Interface/SolidSyslogFatFsFile.h +++ b/Platform/FatFs/Interface/SolidSyslogFatFsFile.h @@ -1,32 +1,13 @@ #ifndef SOLIDSYSLOGFATFSFILE_H #define SOLIDSYSLOGFATFSFILE_H -#include - #include "ExternC.h" struct SolidSyslogFile; EXTERN_C_BEGIN - /* Sized for real FatFs with FF_MAX_SS=512 (99% case — SD/MMC, USB, HDD, - * semihosting flat files) and FF_FS_TINY=0: 10-ptr base (~40 B) + FIL - * (header ~56 B + 512 B sector buffer) + isOpen flag + alignment = - * ~620 B. 180 intptrs gives ~100 B headroom on 32-bit. Integrators - * who pick FF_MAX_SS > 512 hit the SOLIDSYSLOG_STATIC_ASSERT in - * SolidSyslogFatFsFile.c at compile time; the planned S21.03 follow-up - * makes this overridable via the tunables mechanism. */ - enum - { - SOLIDSYSLOG_FATFS_FILE_SIZE = sizeof(intptr_t) * 180U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_FATFS_FILE_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogFatFsFileStorage; - - struct SolidSyslogFile* SolidSyslogFatFsFile_Create(SolidSyslogFatFsFileStorage * storage); + struct SolidSyslogFile* SolidSyslogFatFsFile_Create(void); void SolidSyslogFatFsFile_Destroy(struct SolidSyslogFile * base); EXTERN_C_END diff --git a/Platform/FatFs/Source/SolidSyslogFatFsFile.c b/Platform/FatFs/Source/SolidSyslogFatFsFile.c index eb7974a5..8865f1c6 100644 --- a/Platform/FatFs/Source/SolidSyslogFatFsFile.c +++ b/Platform/FatFs/Source/SolidSyslogFatFsFile.c @@ -3,8 +3,9 @@ #include #include +#include "SolidSyslogFatFsFilePrivate.h" #include "SolidSyslogFileDefinition.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogNullFile.h" #include "ff.h" #define READ_WRITE_OR_CREATE (FA_READ | FA_WRITE | FA_OPEN_ALWAYS) @@ -20,52 +21,36 @@ static void FatFsFile_Truncate(struct SolidSyslogFile* base); static bool FatFsFile_Exists(struct SolidSyslogFile* base, const char* path); static bool FatFsFile_Delete(struct SolidSyslogFile* base, const char* path); -static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromStorage(SolidSyslogFatFsFileStorage* storage); static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromBase(struct SolidSyslogFile* base); static inline FIL* FatFsFile_Handle(struct SolidSyslogFile* base); -struct SolidSyslogFatFsFile +void FatFsFile_Initialise(struct SolidSyslogFile* base) { - struct SolidSyslogFile Base; - FIL Fp; - bool IsOpen; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogFatFsFile) <= sizeof(SolidSyslogFatFsFileStorage), - "SOLIDSYSLOG_FATFS_FILE_SIZE is too small for struct SolidSyslogFatFsFile" -); - -static const struct SolidSyslogFatFsFile DEFAULT_INSTANCE = { - .Base = - {FatFsFile_Open, - FatFsFile_Close, - FatFsFile_IsOpen, - FatFsFile_Read, - FatFsFile_Write, - FatFsFile_SeekTo, - FatFsFile_Size, - FatFsFile_Truncate, - FatFsFile_Exists, - FatFsFile_Delete}, - .IsOpen = false, -}; - -struct SolidSyslogFile* SolidSyslogFatFsFile_Create(SolidSyslogFatFsFileStorage* storage) -{ - struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; + struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromBase(base); + self->Base.Open = FatFsFile_Open; + self->Base.Close = FatFsFile_Close; + self->Base.IsOpen = FatFsFile_IsOpen; + self->Base.Read = FatFsFile_Read; + self->Base.Write = FatFsFile_Write; + self->Base.SeekTo = FatFsFile_SeekTo; + self->Base.Size = FatFsFile_Size; + self->Base.Truncate = FatFsFile_Truncate; + self->Base.Exists = FatFsFile_Exists; + self->Base.Delete = FatFsFile_Delete; + self->IsOpen = false; } -static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromStorage(SolidSyslogFatFsFileStorage* storage) +void FatFsFile_Cleanup(struct SolidSyslogFile* base) { - return (struct SolidSyslogFatFsFile*) storage; + FatFsFile_Close(base); + /* Overwrite the abstract base with the shared NullFile vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullFile_Get(); } -void SolidSyslogFatFsFile_Destroy(struct SolidSyslogFile* base) +static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromBase(struct SolidSyslogFile* base) { - FatFsFile_Close(base); + return (struct SolidSyslogFatFsFile*) base; } static bool FatFsFile_Open(struct SolidSyslogFile* base, const char* path) @@ -76,11 +61,6 @@ static bool FatFsFile_Open(struct SolidSyslogFile* base, const char* path) return self->IsOpen; } -static inline struct SolidSyslogFatFsFile* FatFsFile_SelfFromBase(struct SolidSyslogFile* base) -{ - return (struct SolidSyslogFatFsFile*) base; -} - static void FatFsFile_Close(struct SolidSyslogFile* base) { struct SolidSyslogFatFsFile* self = FatFsFile_SelfFromBase(base); diff --git a/Platform/FatFs/Source/SolidSyslogFatFsFilePrivate.h b/Platform/FatFs/Source/SolidSyslogFatFsFilePrivate.h new file mode 100644 index 00000000..954a95b3 --- /dev/null +++ b/Platform/FatFs/Source/SolidSyslogFatFsFilePrivate.h @@ -0,0 +1,19 @@ +#ifndef SOLIDSYSLOGFATFSFILEPRIVATE_H +#define SOLIDSYSLOGFATFSFILEPRIVATE_H + +#include + +#include "SolidSyslogFileDefinition.h" +#include "ff.h" + +struct SolidSyslogFatFsFile +{ + struct SolidSyslogFile Base; + FIL Fp; + bool IsOpen; +}; + +void FatFsFile_Initialise(struct SolidSyslogFile* base); +void FatFsFile_Cleanup(struct SolidSyslogFile* base); + +#endif /* SOLIDSYSLOGFATFSFILEPRIVATE_H */ diff --git a/Platform/FatFs/Source/SolidSyslogFatFsFileStatic.c b/Platform/FatFs/Source/SolidSyslogFatFsFileStatic.c new file mode 100644 index 00000000..ccc3c334 --- /dev/null +++ b/Platform/FatFs/Source/SolidSyslogFatFsFileStatic.c @@ -0,0 +1,69 @@ +#include "SolidSyslogFatFsFile.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogFatFsFilePrivate.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogFile; + +static inline size_t FatFsFile_IndexFromHandle(const struct SolidSyslogFile* base); +static inline void FatFsFile_CleanupAtIndex(size_t index, void* context); + +static bool FatFsFile_InUse[SOLIDSYSLOG_FATFS_FILE_POOL_SIZE]; +static struct SolidSyslogFatFsFile FatFsFile_Pool[SOLIDSYSLOG_FATFS_FILE_POOL_SIZE]; +static struct SolidSyslogPoolAllocator FatFsFile_Allocator = {FatFsFile_InUse, SOLIDSYSLOG_FATFS_FILE_POOL_SIZE}; + +struct SolidSyslogFile* SolidSyslogFatFsFile_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&FatFsFile_Allocator); + struct SolidSyslogFile* handle = SolidSyslogNullFile_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&FatFsFile_Allocator, index)) + { + FatFsFile_Initialise(&FatFsFile_Pool[index].Base); + handle = &FatFsFile_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_FATFSFILE_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogFatFsFile_Destroy(struct SolidSyslogFile* base) +{ + size_t index = FatFsFile_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&FatFsFile_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&FatFsFile_Allocator, index, FatFsFile_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_FATFSFILE_UNKNOWN_DESTROY); + } +} + +static inline size_t FatFsFile_IndexFromHandle(const struct SolidSyslogFile* base) +{ + size_t result = SOLIDSYSLOG_FATFS_FILE_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_FATFS_FILE_POOL_SIZE; poolIndex++) + { + if (base == &FatFsFile_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void FatFsFile_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + FatFsFile_Cleanup(&FatFsFile_Pool[index].Base); +} diff --git a/Platform/OpenSsl/CMakeLists.txt b/Platform/OpenSsl/CMakeLists.txt index da9ab417..a9c95826 100644 --- a/Platform/OpenSsl/CMakeLists.txt +++ b/Platform/OpenSsl/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogTlsStream.c + Source/SolidSyslogTlsStreamStatic.c ) # Headers at compile time; libssl is linked by consumers (real linkage) or diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h index fd27ef9b..2b175e54 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h @@ -1,8 +1,6 @@ #ifndef SOLIDSYSLOGTLSSTREAM_H #define SOLIDSYSLOGTLSSTREAM_H -#include - #include "ExternC.h" #include "SolidSyslogSleep.h" @@ -10,16 +8,6 @@ struct SolidSyslogStream; EXTERN_C_BEGIN - enum - { - SOLIDSYSLOG_TLS_STREAM_SIZE = sizeof(intptr_t) * 14U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_TLS_STREAM_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogTlsStreamStorage; - struct SolidSyslogTlsStreamConfig { struct SolidSyslogStream* Transport; /* underlying byte stream — caller owns */ @@ -32,10 +20,7 @@ EXTERN_C_BEGIN const char* ClientKeyPath; /* PEM: matching private key; NULL = no mTLS */ }; - struct SolidSyslogStream* SolidSyslogTlsStream_Create( - SolidSyslogTlsStreamStorage * storage, - const struct SolidSyslogTlsStreamConfig* config - ); + struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config); void SolidSyslogTlsStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index 08407c51..d9535116 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -1,3 +1,5 @@ +#include "SolidSyslogTlsStream.h" + #include #include #include @@ -5,10 +7,10 @@ #include #include -#include "SolidSyslogMacros.h" +#include "SolidSyslogNullStream.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" -#include "SolidSyslogTlsStream.h" +#include "SolidSyslogTlsStreamPrivate.h" enum { @@ -22,21 +24,6 @@ enum struct SolidSyslogAddress; -struct SolidSyslogTlsStream -{ - struct SolidSyslogStream Base; - struct SolidSyslogTlsStreamConfig Config; - SSL_CTX* Ctx; - SSL* Ssl; - BIO_METHOD* BioMethod; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogTlsStream) <= sizeof(SolidSyslogTlsStreamStorage), - "SOLIDSYSLOG_TLS_STREAM_SIZE is too small for struct SolidSyslogTlsStream" -); - -static inline struct SolidSyslogTlsStream* TlsStream_SelfFromStorage(SolidSyslogTlsStreamStorage* storage); static inline struct SolidSyslogTlsStream* TlsStream_SelfFromBase(struct SolidSyslogStream* base); static inline bool TlsStream_AttachTransportBio(struct SolidSyslogTlsStream* self); @@ -65,44 +52,32 @@ static inline long TlsStream_TransportBioCtrl(BIO* bio, int cmd, long larg, void static inline int TlsStream_TransportBioRead(BIO* bio, char* buffer, int size); static inline int TlsStream_TransportBioWrite(BIO* bio, const char* buffer, int size); -static const struct SolidSyslogTlsStream DEFAULT_INSTANCE = { - {TlsStream_Open, TlsStream_Send, TlsStream_Read, TlsStream_Close}, - {NULL, NULL, NULL, NULL, NULL, NULL, NULL}, - NULL, - NULL, - NULL, -}; - -static const struct SolidSyslogTlsStream DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL}, - {NULL, NULL, NULL, NULL, NULL, NULL, NULL}, - NULL, - NULL, - NULL, -}; - -struct SolidSyslogStream* SolidSyslogTlsStream_Create( - SolidSyslogTlsStreamStorage* storage, - const struct SolidSyslogTlsStreamConfig* config -) +void TlsStream_Initialise(struct SolidSyslogStream* base, const struct SolidSyslogTlsStreamConfig* config) { - struct SolidSyslogTlsStream* self = TlsStream_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; + struct SolidSyslogTlsStream* self = TlsStream_SelfFromBase(base); + self->Base.Open = TlsStream_Open; + self->Base.Send = TlsStream_Send; + self->Base.Read = TlsStream_Read; + self->Base.Close = TlsStream_Close; self->Config = *config; - return &self->Base; -} - -static inline struct SolidSyslogTlsStream* TlsStream_SelfFromStorage(SolidSyslogTlsStreamStorage* storage) -{ - return (struct SolidSyslogTlsStream*) storage; + self->Ctx = NULL; + self->Ssl = NULL; + self->BioMethod = NULL; } -void SolidSyslogTlsStream_Destroy(struct SolidSyslogStream* base) +void TlsStream_Cleanup(struct SolidSyslogStream* base) { struct SolidSyslogTlsStream* self = TlsStream_SelfFromBase(base); - TlsStream_ReleaseHandshakeState(self); + /* Close first so an integrator who destroys a still-Open stream doesn't + * leak the underlying transport. Close is idempotent (guards on Ssl != + * NULL for the TLS-side teardown; transport Close is itself idempotent + * on every Stream impl), so the normal Open → Close → Destroy lifecycle + * is unaffected. */ + TlsStream_Close(base); TlsStream_ReleaseSslContext(self); - *self = DESTROYED_INSTANCE; + /* Overwrite the abstract base with the shared NullStream vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullStream_Get(); } static inline struct SolidSyslogTlsStream* TlsStream_SelfFromBase(struct SolidSyslogStream* base) diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStreamPrivate.h b/Platform/OpenSsl/Source/SolidSyslogTlsStreamPrivate.h new file mode 100644 index 00000000..58f596d8 --- /dev/null +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStreamPrivate.h @@ -0,0 +1,22 @@ +#ifndef SOLIDSYSLOGTLSSTREAMPRIVATE_H +#define SOLIDSYSLOGTLSSTREAMPRIVATE_H + +#include +#include + +#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTlsStream.h" + +struct SolidSyslogTlsStream +{ + struct SolidSyslogStream Base; + struct SolidSyslogTlsStreamConfig Config; + SSL_CTX* Ctx; + SSL* Ssl; + BIO_METHOD* BioMethod; +}; + +void TlsStream_Initialise(struct SolidSyslogStream* base, const struct SolidSyslogTlsStreamConfig* config); +void TlsStream_Cleanup(struct SolidSyslogStream* base); + +#endif /* SOLIDSYSLOGTLSSTREAMPRIVATE_H */ diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStreamStatic.c b/Platform/OpenSsl/Source/SolidSyslogTlsStreamStatic.c new file mode 100644 index 00000000..96d83cdc --- /dev/null +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStreamStatic.c @@ -0,0 +1,69 @@ +#include "SolidSyslogTlsStream.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTlsStreamPrivate.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogStream; + +static inline size_t TlsStream_IndexFromHandle(const struct SolidSyslogStream* base); +static inline void TlsStream_CleanupAtIndex(size_t index, void* context); + +static bool TlsStream_InUse[SOLIDSYSLOG_TLS_STREAM_POOL_SIZE]; +static struct SolidSyslogTlsStream TlsStream_Pool[SOLIDSYSLOG_TLS_STREAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator TlsStream_Allocator = {TlsStream_InUse, SOLIDSYSLOG_TLS_STREAM_POOL_SIZE}; + +struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&TlsStream_Allocator); + struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&TlsStream_Allocator, index)) + { + TlsStream_Initialise(&TlsStream_Pool[index].Base, config); + handle = &TlsStream_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogTlsStream_Destroy(struct SolidSyslogStream* base) +{ + size_t index = TlsStream_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&TlsStream_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&TlsStream_Allocator, index, TlsStream_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_UNKNOWN_DESTROY); + } +} + +static inline size_t TlsStream_IndexFromHandle(const struct SolidSyslogStream* base) +{ + size_t result = SOLIDSYSLOG_TLS_STREAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_TLS_STREAM_POOL_SIZE; poolIndex++) + { + if (base == &TlsStream_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void TlsStream_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + TlsStream_Cleanup(&TlsStream_Pool[index].Base); +} diff --git a/Platform/Windows/CMakeLists.txt b/Platform/Windows/CMakeLists.txt index 2d0ad9c2..cd66e5ad 100644 --- a/Platform/Windows/CMakeLists.txt +++ b/Platform/Windows/CMakeLists.txt @@ -1,6 +1,7 @@ if(HAVE_WINDOWS_INTERLOCKED) target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogWindowsAtomicCounter.c + Source/SolidSyslogWindowsAtomicCounterStatic.c ) endif() diff --git a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h index 9cd64ca9..193458dd 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h @@ -3,25 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogAtomicCounter; - enum - { - SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_SIZE = sizeof(intptr_t) * 2U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogWindowsAtomicCounterStorage; - - struct SolidSyslogAtomicCounter* SolidSyslogWindowsAtomicCounter_Create( - SolidSyslogWindowsAtomicCounterStorage * storage - ); + struct SolidSyslogAtomicCounter* SolidSyslogWindowsAtomicCounter_Create(void); void SolidSyslogWindowsAtomicCounter_Destroy(struct SolidSyslogAtomicCounter * base); EXTERN_C_END diff --git a/Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c b/Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c index 7a9bf18f..2637573a 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c +++ b/Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c @@ -1,44 +1,43 @@ #include "SolidSyslogWindowsAtomicCounter.h" -#include "SolidSyslogAtomicCounter.h" -#include "SolidSyslogAtomicCounterDefinition.h" -#include "SolidSyslogMacros.h" - -#include +#include #include -struct SolidSyslogWindowsAtomicCounter -{ - struct SolidSyslogAtomicCounter Base; - volatile LONG Value; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogWindowsAtomicCounter) <= sizeof(SolidSyslogWindowsAtomicCounterStorage), - SolidSyslogWindowsAtomicCounterStorage_too_small -); +#include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogAtomicCounterDefinition.h" +#include "SolidSyslogNullAtomicCounter.h" +#include "SolidSyslogWindowsAtomicCounterPrivate.h" static uint32_t WindowsAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base); -static void WindowsAtomicCounter_Init(struct SolidSyslogWindowsAtomicCounter* self, uint32_t value); + static inline struct SolidSyslogWindowsAtomicCounter* WindowsAtomicCounter_SelfFromBase( struct SolidSyslogAtomicCounter* base ); -static inline struct SolidSyslogWindowsAtomicCounter* WindowsAtomicCounter_SelfFromStorage( - SolidSyslogWindowsAtomicCounterStorage* storage -); -struct SolidSyslogAtomicCounter* SolidSyslogWindowsAtomicCounter_Create(SolidSyslogWindowsAtomicCounterStorage* storage) +void WindowsAtomicCounter_Initialise(struct SolidSyslogAtomicCounter* base) { - struct SolidSyslogWindowsAtomicCounter* self = WindowsAtomicCounter_SelfFromStorage(storage); + struct SolidSyslogWindowsAtomicCounter* self = WindowsAtomicCounter_SelfFromBase(base); self->Base.Increment = WindowsAtomicCounter_Increment; WindowsAtomicCounter_Init(self, 0U); - return &self->Base; } -void SolidSyslogWindowsAtomicCounter_Destroy(struct SolidSyslogAtomicCounter* base) +void WindowsAtomicCounter_Cleanup(struct SolidSyslogAtomicCounter* base) { - struct SolidSyslogWindowsAtomicCounter* self = WindowsAtomicCounter_SelfFromBase(base); - self->Base.Increment = NULL; + /* Overwrite the abstract base with the shared NullAtomicCounter vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullAtomicCounter_Get(); +} + +void WindowsAtomicCounter_Init(struct SolidSyslogWindowsAtomicCounter* self, uint32_t value) +{ + self->Value = (LONG) value; +} + +static inline struct SolidSyslogWindowsAtomicCounter* WindowsAtomicCounter_SelfFromBase( + struct SolidSyslogAtomicCounter* base +) +{ + return (struct SolidSyslogWindowsAtomicCounter*) base; } static uint32_t WindowsAtomicCounter_Increment(struct SolidSyslogAtomicCounter* base) @@ -59,22 +58,3 @@ static uint32_t WindowsAtomicCounter_Increment(struct SolidSyslogAtomicCounter* } while (1); return (uint32_t) next; } - -static void WindowsAtomicCounter_Init(struct SolidSyslogWindowsAtomicCounter* self, uint32_t value) -{ - self->Value = (LONG) value; -} - -static inline struct SolidSyslogWindowsAtomicCounter* WindowsAtomicCounter_SelfFromBase( - struct SolidSyslogAtomicCounter* base -) -{ - return (struct SolidSyslogWindowsAtomicCounter*) base; -} - -static inline struct SolidSyslogWindowsAtomicCounter* WindowsAtomicCounter_SelfFromStorage( - SolidSyslogWindowsAtomicCounterStorage* storage -) -{ - return (struct SolidSyslogWindowsAtomicCounter*) storage; -} diff --git a/Platform/Windows/Source/SolidSyslogWindowsAtomicCounterPrivate.h b/Platform/Windows/Source/SolidSyslogWindowsAtomicCounterPrivate.h new file mode 100644 index 00000000..354176e6 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWindowsAtomicCounterPrivate.h @@ -0,0 +1,23 @@ +#ifndef SOLIDSYSLOGWINDOWSATOMICCOUNTERPRIVATE_H +#define SOLIDSYSLOGWINDOWSATOMICCOUNTERPRIVATE_H + +#include +#include + +#include "SolidSyslogAtomicCounterDefinition.h" + +struct SolidSyslogWindowsAtomicCounter +{ + struct SolidSyslogAtomicCounter Base; + volatile LONG Value; +}; + +void WindowsAtomicCounter_Initialise(struct SolidSyslogAtomicCounter* base); +void WindowsAtomicCounter_Cleanup(struct SolidSyslogAtomicCounter* base); + +/* Test-only helper — Tests/SolidSyslogWindowsAtomicCounterTestHelper.c + * whitebox-includes this TU to seed the counter near INT32_MAX for the + * wraparound contract test. Not part of the public API. */ +void WindowsAtomicCounter_Init(struct SolidSyslogWindowsAtomicCounter* self, uint32_t value); + +#endif /* SOLIDSYSLOGWINDOWSATOMICCOUNTERPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWindowsAtomicCounterStatic.c b/Platform/Windows/Source/SolidSyslogWindowsAtomicCounterStatic.c new file mode 100644 index 00000000..648b6dab --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWindowsAtomicCounterStatic.c @@ -0,0 +1,76 @@ +#include "SolidSyslogWindowsAtomicCounter.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullAtomicCounter.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWindowsAtomicCounterPrivate.h" + +struct SolidSyslogAtomicCounter; + +static inline size_t WindowsAtomicCounter_IndexFromHandle(const struct SolidSyslogAtomicCounter* base); +static inline void WindowsAtomicCounter_CleanupAtIndex(size_t index, void* context); + +static bool WindowsAtomicCounter_InUse[SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE]; +static struct SolidSyslogWindowsAtomicCounter WindowsAtomicCounter_Pool[SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator WindowsAtomicCounter_Allocator = { + WindowsAtomicCounter_InUse, + SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE +}; + +struct SolidSyslogAtomicCounter* SolidSyslogWindowsAtomicCounter_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WindowsAtomicCounter_Allocator); + struct SolidSyslogAtomicCounter* handle = SolidSyslogNullAtomicCounter_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&WindowsAtomicCounter_Allocator, index)) + { + WindowsAtomicCounter_Initialise(&WindowsAtomicCounter_Pool[index].Base); + handle = &WindowsAtomicCounter_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogWindowsAtomicCounter_Destroy(struct SolidSyslogAtomicCounter* base) +{ + size_t index = WindowsAtomicCounter_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&WindowsAtomicCounter_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &WindowsAtomicCounter_Allocator, + index, + WindowsAtomicCounter_CleanupAtIndex, + NULL + ); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_UNKNOWN_DESTROY); + } +} + +static inline size_t WindowsAtomicCounter_IndexFromHandle(const struct SolidSyslogAtomicCounter* base) +{ + size_t result = SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE; poolIndex++) + { + if (base == &WindowsAtomicCounter_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void WindowsAtomicCounter_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + WindowsAtomicCounter_Cleanup(&WindowsAtomicCounter_Pool[index].Base); +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index e4a84e33..821d903f 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -79,6 +79,7 @@ endif() if(HAVE_ATOMIC_COUNTER) list(APPEND TEST_SOURCES SolidSyslogAtomicCounterContractTest.cpp + SolidSyslogNullAtomicCounterTest.cpp ) endif() @@ -89,10 +90,12 @@ endif() if(HAVE_STDATOMIC_H) list(APPEND TEST_SOURCES SolidSyslogStdAtomicCounterTestHelper.c + SolidSyslogStdAtomicCounterPoolTest.cpp ) elseif(HAVE_WINDOWS_INTERLOCKED) list(APPEND TEST_SOURCES SolidSyslogWindowsAtomicCounterTestHelper.c + SolidSyslogWindowsAtomicCounterPoolTest.cpp ) endif() @@ -118,6 +121,7 @@ endif() if(SOLIDSYSLOG_OPENSSL) list(APPEND TEST_SOURCES SolidSyslogTlsStreamTest.cpp + SolidSyslogTlsStreamPoolTest.cpp OpenSslFakeTest.cpp ) endif() diff --git a/Tests/FatFs/CMakeLists.txt b/Tests/FatFs/CMakeLists.txt index d9f3a576..a379863f 100644 --- a/Tests/FatFs/CMakeLists.txt +++ b/Tests/FatFs/CMakeLists.txt @@ -10,13 +10,17 @@ add_executable(SolidSyslogFatFsFileTest SolidSyslogFatFsFileTest.cpp + SolidSyslogFatFsFilePoolTest.cpp main.cpp ${CMAKE_SOURCE_DIR}/Platform/FatFs/Source/SolidSyslogFatFsFile.c + ${CMAKE_SOURCE_DIR}/Platform/FatFs/Source/SolidSyslogFatFsFileStatic.c ${CMAKE_SOURCE_DIR}/Tests/Support/FatFsFakes/Source/FatFsFake.c ) target_link_libraries(SolidSyslogFatFsFileTest PRIVATE ${PROJECT_NAME} + ErrorHandlerFake + ConfigLockFake CppUTest CppUTestExt ) diff --git a/Tests/FatFs/SolidSyslogFatFsFilePoolTest.cpp b/Tests/FatFs/SolidSyslogFatFsFilePoolTest.cpp new file mode 100644 index 00000000..333477a4 --- /dev/null +++ b/Tests/FatFs/SolidSyslogFatFsFilePoolTest.cpp @@ -0,0 +1,173 @@ +#include "CppUTest/TestHarness.h" +#include "TestUtils.h" + +extern "C" +{ +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "FatFsFake.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogFatFsFile.h" +#include "SolidSyslogFile.h" +#include "SolidSyslogFileDefinition.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +} + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER into scope for CALLED_FAKE + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site + +// Asserts handle is non-null and not one of the slots in pool. +#define CHECK_IS_FALLBACK(handle, pool) \ + do \ + { \ + CHECK_TEXT((handle) != nullptr, "Fallback handle was nullptr"); \ + for (auto* slot : (pool)) \ + { \ + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); \ + CHECK_TEXT((handle) != slot, "Fallback handle collided with a pool slot"); \ + } \ + } while (0) + +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + +// clang-format off +TEST_GROUP(SolidSyslogFatFsFilePool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogFile* pooled[SOLIDSYSLOG_FATFS_FILE_POOL_SIZE] = {}; + struct SolidSyslogFile* overflow = nullptr; + + void setup() override + { + FatFsFake_Reset(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogFatFsFile_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogFatFsFile_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogFatFsFile_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogFatFsFilePool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogFatFsFile_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogFatFsFilePool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogFatFsFile_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FATFSFILE_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFatFsFilePool, FallbackOpenReturnsFalse) +{ + FillPool(); + overflow = SolidSyslogFatFsFile_Create(); + + CHECK_FALSE(SolidSyslogFile_Open(overflow, "anything.log")); +} + +TEST(SolidSyslogFatFsFilePool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogFatFsFile_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFatFsFilePool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogFatFsFile_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_FATFS_FILE_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_FATFS_FILE_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogFatFsFilePool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogFatFsFile_Create(); + ConfigLockFake_Install(); + + SolidSyslogFatFsFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFatFsFilePool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogFile stranger = {}; + + SolidSyslogFatFsFile_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogFatFsFilePool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogFile stranger = {}; + + SolidSyslogFatFsFile_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FATFSFILE_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFatFsFilePool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogFatFsFile_Create(); + SolidSyslogFatFsFile_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogFatFsFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FATFSFILE_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/FatFs/SolidSyslogFatFsFileTest.cpp b/Tests/FatFs/SolidSyslogFatFsFileTest.cpp index 823ade19..8ac80178 100644 --- a/Tests/FatFs/SolidSyslogFatFsFileTest.cpp +++ b/Tests/FatFs/SolidSyslogFatFsFileTest.cpp @@ -29,16 +29,15 @@ static const char* const TEST_PATH = "test.log"; // clang-format off TEST_GROUP(SolidSyslogFatFsFile) { - SolidSyslogFatFsFileStorage storage = {}; - struct SolidSyslogFile* file = nullptr; + struct SolidSyslogFile* file = nullptr; // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - char buffer[5] = {'h', 'e', 'l', 'l', 'o'}; + char buffer[5] = {'h', 'e', 'l', 'l', 'o'}; void setup() override { FatFsFake_Reset(); // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - file = SolidSyslogFatFsFile_Create(&storage); + file = SolidSyslogFatFsFile_Create(); } void teardown() override @@ -106,10 +105,9 @@ TEST(SolidSyslogFatFsFile, CloseIsNoOpWhenAlreadyClosed) TEST(SolidSyslogFatFsFile, DestroyClosesOpenFile) { - SolidSyslogFatFsFileStorage localStorage = {}; - struct SolidSyslogFile* localFile = SolidSyslogFatFsFile_Create(&localStorage); - SolidSyslogFile_Open(localFile, TEST_PATH); - SolidSyslogFatFsFile_Destroy(localFile); + SolidSyslogFile_Open(file, TEST_PATH); + SolidSyslogFatFsFile_Destroy(file); + file = nullptr; CALLED_FAKE(FatFsFake_Close, ONCE); } diff --git a/Tests/Fixtures/SmallMessageSizeTunables.h b/Tests/Fixtures/SmallMessageSizeTunables.h index a515b06d..f7acbaab 100644 --- a/Tests/Fixtures/SmallMessageSizeTunables.h +++ b/Tests/Fixtures/SmallMessageSizeTunables.h @@ -8,6 +8,11 @@ * - SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE bumped to 2 enables * `EachPooledHandleHasIsolatedQueue` in SolidSyslogPosixMessageQueueBufferTest * to exercise the slot-indexed queue-name discriminator (CodeRabbit - * feedback on PR #407). */ + * feedback on PR #407). + * - SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE bumped to 2 enables + * `TwoCountersFromPoolAreIndependent` in SolidSyslogAtomicCounterContractTest + * to exercise two concurrent counters drawn from the same pool. */ #define SOLIDSYSLOG_MAX_MESSAGE_SIZE 512 #define SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE 2U +#define SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE 2U +#define SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE 2U diff --git a/Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp b/Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp index afffef25..31f32e39 100644 --- a/Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp +++ b/Tests/OpenSslIntegration/SolidSyslogTlsStreamIntegrationTest.cpp @@ -33,7 +33,7 @@ TEST_GROUP(TlsStreamIntegration) struct TlsTestServer* server = nullptr; struct SolidSyslogStream* transport = nullptr; struct SolidSyslogTlsStreamConfig tlsConfig = {}; - SolidSyslogTlsStreamStorage tlsStreamStorage{}; + struct SolidSyslogStream* tlsStream = nullptr; SolidSyslogAddressStorage addrStorage = {}; struct SolidSyslogAddress* addr = nullptr; @@ -92,7 +92,7 @@ TEST_GROUP(TlsStreamIntegration) tlsConfig.Sleep = NoOpSleep; tlsConfig.CaBundlePath = caPath; tlsConfig.ServerName = clientServerName; - tlsStream = SolidSyslogTlsStream_Create(&tlsStreamStorage, &tlsConfig); + tlsStream = SolidSyslogTlsStream_Create(&tlsConfig); } /* Creates the client-side mTLS material and writes it to disk. diff --git a/Tests/SolidSyslogAtomicCounterContractTest.cpp b/Tests/SolidSyslogAtomicCounterContractTest.cpp index cc1fc8d5..0035f1e3 100644 --- a/Tests/SolidSyslogAtomicCounterContractTest.cpp +++ b/Tests/SolidSyslogAtomicCounterContractTest.cpp @@ -11,8 +11,7 @@ TEST_GROUP(AtomicCounterContract){}; TEST(AtomicCounterContract, CreateReturnsNonNullHandle) { - TestAtomicCounterStorage storage; - struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&storage); + struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(); CHECK(counter != nullptr); @@ -21,8 +20,7 @@ TEST(AtomicCounterContract, CreateReturnsNonNullHandle) TEST(AtomicCounterContract, FirstIncrementReturnsOne) { - TestAtomicCounterStorage storage; - struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&storage); + struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(); LONGS_EQUAL(1, TestAtomicCounter_Increment(counter)); @@ -31,8 +29,7 @@ TEST(AtomicCounterContract, FirstIncrementReturnsOne) TEST(AtomicCounterContract, SequentialIncrementsCount1Then2Then3) { - TestAtomicCounterStorage storage; - struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&storage); + struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(); LONGS_EQUAL(1, TestAtomicCounter_Increment(counter)); LONGS_EQUAL(2, TestAtomicCounter_Increment(counter)); @@ -41,12 +38,22 @@ TEST(AtomicCounterContract, SequentialIncrementsCount1Then2Then3) TestAtomicCounter_Destroy(counter); } -TEST(AtomicCounterContract, TwoCountersInSeparateStorageAreIndependent) +/* Independence of two simultaneous counters is only observable when the + * runtime pool can host at least two slots. The default build runs at + * pool size 1; the `tunable-override-debug` preset bumps it to 2 (see + * Tests/Fixtures/SmallMessageSizeTunables.h). When pool size < 2, print + * a notice and exit cleanly via TEST_EXIT so the test is honestly + * accounted for. */ +TEST(AtomicCounterContract, TwoCountersFromPoolAreIndependent) { - TestAtomicCounterStorage storageA; - TestAtomicCounterStorage storageB; - struct SolidSyslogAtomicCounter* counterA = TestAtomicCounter_Create(&storageA); - struct SolidSyslogAtomicCounter* counterB = TestAtomicCounter_Create(&storageB); + if (TestAtomicCounter_PoolSize() < 2U) + { + UT_PRINT("Pool size < 2 — counter independence only observable under tunable-override-debug"); + TEST_EXIT; + } + + struct SolidSyslogAtomicCounter* counterA = TestAtomicCounter_Create(); + struct SolidSyslogAtomicCounter* counterB = TestAtomicCounter_Create(); LONGS_EQUAL(1, TestAtomicCounter_Increment(counterA)); LONGS_EQUAL(2, TestAtomicCounter_Increment(counterA)); @@ -60,8 +67,7 @@ TEST(AtomicCounterContract, TwoCountersInSeparateStorageAreIndependent) TEST(AtomicCounterContract, IncrementAfterInitReturnsValuePlusOne) { - TestAtomicCounterStorage storage; - struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&storage); + struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(); TestAtomicCounter_Init(counter, 5U); @@ -72,8 +78,7 @@ TEST(AtomicCounterContract, IncrementAfterInitReturnsValuePlusOne) TEST(AtomicCounterContract, IncrementAtMaxWrapsToOne) { - TestAtomicCounterStorage storage; - struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&storage); + struct SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(); /* RFC 5424 §7.3.1: sequenceId values are in [1, 2^31 - 1] and wrap to 1, not 0. */ TestAtomicCounter_Init(counter, (uint32_t) INT32_MAX); diff --git a/Tests/SolidSyslogAtomicCounterTestHelper.h b/Tests/SolidSyslogAtomicCounterTestHelper.h index f0a65810..6c41507e 100644 --- a/Tests/SolidSyslogAtomicCounterTestHelper.h +++ b/Tests/SolidSyslogAtomicCounterTestHelper.h @@ -3,27 +3,22 @@ #include "ExternC.h" +#include #include EXTERN_C_BEGIN struct SolidSyslogAtomicCounter; - enum - { - TEST_ATOMIC_COUNTER_STORAGE_SIZE = 64 - }; - - typedef struct - { - intptr_t Slots[(TEST_ATOMIC_COUNTER_STORAGE_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } TestAtomicCounterStorage; - - struct SolidSyslogAtomicCounter* TestAtomicCounter_Create(TestAtomicCounterStorage * storage); + struct SolidSyslogAtomicCounter* TestAtomicCounter_Create(void); void TestAtomicCounter_Init(struct SolidSyslogAtomicCounter * base, uint32_t value); uint32_t TestAtomicCounter_Increment(struct SolidSyslogAtomicCounter * base); void TestAtomicCounter_Destroy(struct SolidSyslogAtomicCounter * base); + /* Pool size of the active backend. Lets backend-agnostic contract tests + runtime-gate scenarios that need two simultaneous handles. */ + size_t TestAtomicCounter_PoolSize(void); + EXTERN_C_END #endif /* SOLIDSYSLOGATOMICCOUNTERTESTHELPER_H */ diff --git a/Tests/SolidSyslogMetaSdTest.cpp b/Tests/SolidSyslogMetaSdTest.cpp index 686af3a7..84f043d8 100644 --- a/Tests/SolidSyslogMetaSdTest.cpp +++ b/Tests/SolidSyslogMetaSdTest.cpp @@ -65,8 +65,6 @@ static void FakeLanguage_Get(struct SolidSyslogFormatter* formatter) // clang-format off TEST_GROUP(SolidSyslogMetaSd) { - // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro - TestAtomicCounterStorage counterStorage; // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro SolidSyslogAtomicCounter* counter; // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro @@ -82,7 +80,7 @@ TEST_GROUP(SolidSyslogMetaSd) { ErrorHandlerFake_Install(&sentinel); formatter = SolidSyslogFormatter_Create(storage, TEST_BUFFER_SIZE); - counter = TestAtomicCounter_Create(&counterStorage); + counter = TestAtomicCounter_Create(); fakeSysUpTimeValue = 0; fakeLanguageContent = nullptr; fakeLanguageMaxLength = 0; @@ -304,7 +302,6 @@ TEST(SolidSyslogMetaSd, CreateWithNullCounterReportsWarning) // clang-format off TEST_GROUP(SolidSyslogMetaSdPool) { - TestAtomicCounterStorage counterStorage; SolidSyslogAtomicCounter* counter = nullptr; SolidSyslogMetaSdConfig config{}; struct SolidSyslogStructuredData* pooled[SOLIDSYSLOG_META_SD_POOL_SIZE] = {}; @@ -312,7 +309,7 @@ TEST_GROUP(SolidSyslogMetaSdPool) void setup() override { - counter = TestAtomicCounter_Create(&counterStorage); + counter = TestAtomicCounter_Create(); config.Counter = counter; } diff --git a/Tests/SolidSyslogNullAtomicCounterTest.cpp b/Tests/SolidSyslogNullAtomicCounterTest.cpp new file mode 100644 index 00000000..d3a39a7e --- /dev/null +++ b/Tests/SolidSyslogNullAtomicCounterTest.cpp @@ -0,0 +1,28 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogNullAtomicCounter.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullAtomicCounter) +{ + struct SolidSyslogAtomicCounter* counter = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + counter = SolidSyslogNullAtomicCounter_Get(); + } +}; + +// clang-format on + +TEST(SolidSyslogNullAtomicCounter, IncrementReturnsOneToAvoidZero) +{ + LONGS_EQUAL(1U, SolidSyslogAtomicCounter_Increment(counter)); +} + +TEST(SolidSyslogNullAtomicCounter, IncrementIsIdempotent) +{ + SolidSyslogAtomicCounter_Increment(counter); + LONGS_EQUAL(1U, SolidSyslogAtomicCounter_Increment(counter)); +} diff --git a/Tests/SolidSyslogStdAtomicCounterPoolTest.cpp b/Tests/SolidSyslogStdAtomicCounterPoolTest.cpp new file mode 100644 index 00000000..559f9f73 --- /dev/null +++ b/Tests/SolidSyslogStdAtomicCounterPoolTest.cpp @@ -0,0 +1,164 @@ +#include "CppUTest/TestHarness.h" + +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogAtomicCounterDefinition.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStdAtomicCounter.h" +#include "SolidSyslogTunables.h" +#include "TestUtils.h" + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER into scope for CALLED_FAKE + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site + +// Asserts handle is non-null and not one of the slots in pool. +#define CHECK_IS_FALLBACK(handle, pool) \ + do \ + { \ + CHECK_TEXT((handle) != nullptr, "Fallback handle was nullptr"); \ + for (auto* slot : (pool)) \ + { \ + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); \ + CHECK_TEXT((handle) != slot, "Fallback handle collided with a pool slot"); \ + } \ + } while (0) + +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + +// clang-format off +TEST_GROUP(SolidSyslogStdAtomicCounterPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogAtomicCounter* pooled[SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE] = {}; + struct SolidSyslogAtomicCounter* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogStdAtomicCounter_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogStdAtomicCounter_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogStdAtomicCounter_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogStdAtomicCounterPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogStdAtomicCounter_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogStdAtomicCounterPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogStdAtomicCounter_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogStdAtomicCounterPool, FallbackIncrementReturnsOne) +{ + FillPool(); + overflow = SolidSyslogStdAtomicCounter_Create(); + + LONGS_EQUAL(1U, SolidSyslogAtomicCounter_Increment(overflow)); +} + +TEST(SolidSyslogStdAtomicCounterPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogStdAtomicCounter_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogStdAtomicCounterPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogStdAtomicCounter_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogStdAtomicCounterPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogStdAtomicCounter_Create(); + ConfigLockFake_Install(); + + SolidSyslogStdAtomicCounter_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogStdAtomicCounterPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogAtomicCounter stranger = {}; + + SolidSyslogStdAtomicCounter_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogStdAtomicCounterPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogAtomicCounter stranger = {}; + + SolidSyslogStdAtomicCounter_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogStdAtomicCounterPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogStdAtomicCounter_Create(); + SolidSyslogStdAtomicCounter_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogStdAtomicCounter_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_STDATOMICCOUNTER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogStdAtomicCounterTestHelper.c b/Tests/SolidSyslogStdAtomicCounterTestHelper.c index 2e4a676f..fb468c88 100644 --- a/Tests/SolidSyslogStdAtomicCounterTestHelper.c +++ b/Tests/SolidSyslogStdAtomicCounterTestHelper.c @@ -1,9 +1,10 @@ #include "SolidSyslogAtomicCounterTestHelper.h" #include "SolidSyslogAtomicCounter.h" -#include "SolidSyslogMacros.h" #include "SolidSyslogStdAtomicCounter.h" +#include "SolidSyslogTunables.h" +#include #include struct SolidSyslogAtomicCounter; @@ -18,14 +19,9 @@ struct SolidSyslogAtomicCounter; // NOLINTNEXTLINE(bugprone-suspicious-include) #include "SolidSyslogStdAtomicCounter.c" -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(SolidSyslogStdAtomicCounterStorage) <= TEST_ATOMIC_COUNTER_STORAGE_SIZE, - TestAtomicCounterStorage_too_small_for_Std -); - -struct SolidSyslogAtomicCounter* TestAtomicCounter_Create(TestAtomicCounterStorage* storage) +struct SolidSyslogAtomicCounter* TestAtomicCounter_Create(void) { - return SolidSyslogStdAtomicCounter_Create((SolidSyslogStdAtomicCounterStorage*) storage); + return SolidSyslogStdAtomicCounter_Create(); } void TestAtomicCounter_Init(struct SolidSyslogAtomicCounter* base, uint32_t value) @@ -42,3 +38,8 @@ void TestAtomicCounter_Destroy(struct SolidSyslogAtomicCounter* base) { SolidSyslogStdAtomicCounter_Destroy(base); } + +size_t TestAtomicCounter_PoolSize(void) +{ + return SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE; +} diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index c87cd440..c1b5ea1a 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -361,6 +361,16 @@ TEST_GROUP(SolidSyslog) SolidSyslogStore *store; // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro struct SolidSyslogSender *fakeSender; + /* Pool-backed handles owned by tests that exercise Meta/TimeQuality SD. + Held as fixture state so teardown releases their pool slots even if a + test body fails mid-assertion — otherwise the leaked slot returns the + fallback to subsequent tests and cascades the failure. */ + // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro + struct SolidSyslogAtomicCounter *metaSdCounter; + // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro + struct SolidSyslogStructuredData *metaSd; + // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro + struct SolidSyslogStructuredData *timeQualitySd; void setup() override { @@ -368,6 +378,9 @@ TEST_GROUP(SolidSyslog) StringFake_Reset(); buffer = SolidSyslogPassthroughBuffer_Create(fakeSender); store = SolidSyslogNullStore_Get(); + metaSdCounter = nullptr; + metaSd = nullptr; + timeQualitySd = nullptr; 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,6 +390,18 @@ TEST_GROUP(SolidSyslog) void teardown() override { SolidSyslog_Destroy(); + if (timeQualitySd != nullptr) + { + SolidSyslogTimeQualitySd_Destroy(timeQualitySd); + } + if (metaSd != nullptr) + { + SolidSyslogMetaSd_Destroy(metaSd); + } + if (metaSdCounter != nullptr) + { + TestAtomicCounter_Destroy(metaSdCounter); + } SolidSyslogPassthroughBuffer_Destroy(buffer); SenderFake_Destroy(fakeSender); } @@ -631,11 +656,10 @@ TEST(SolidSyslog, InjectedSdObjectFormatIsCalledDuringLog) TEST(SolidSyslog, MetaSdProducesSequenceIdInStructuredData) { - TestAtomicCounterStorage counterStorage; - SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&counterStorage); + metaSdCounter = TestAtomicCounter_Create(); SolidSyslogMetaSdConfig metaConfig{}; - metaConfig.Counter = counter; - SolidSyslogStructuredData* metaSd = SolidSyslogMetaSd_Create(&metaConfig); + metaConfig.Counter = metaSdCounter; + metaSd = SolidSyslogMetaSd_Create(&metaConfig); SolidSyslogStructuredData* sdList[] = {metaSd}; config.Sd = sdList; config.SdCount = 1; @@ -643,17 +667,14 @@ TEST(SolidSyslog, MetaSdProducesSequenceIdInStructuredData) SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("[meta sequenceId=\"1\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); - SolidSyslogMetaSd_Destroy(metaSd); - TestAtomicCounter_Destroy(counter); } TEST(SolidSyslog, MetaSdSequenceIdIncrementsAcrossLogCalls) { - TestAtomicCounterStorage counterStorage; - SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&counterStorage); + metaSdCounter = TestAtomicCounter_Create(); SolidSyslogMetaSdConfig metaConfig{}; - metaConfig.Counter = counter; - SolidSyslogStructuredData* metaSd = SolidSyslogMetaSd_Create(&metaConfig); + metaConfig.Counter = metaSdCounter; + metaSd = SolidSyslogMetaSd_Create(&metaConfig); SolidSyslogStructuredData* sdList[] = {metaSd}; config.Sd = sdList; config.SdCount = 1; @@ -662,17 +683,14 @@ TEST(SolidSyslog, MetaSdSequenceIdIncrementsAcrossLogCalls) Log(); Log(); STRCMP_EQUAL("[meta sequenceId=\"2\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); - SolidSyslogMetaSd_Destroy(metaSd); - TestAtomicCounter_Destroy(counter); } TEST(SolidSyslog, MsgFieldPreservedWithMetaSd) { - TestAtomicCounterStorage counterStorage; - SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&counterStorage); + metaSdCounter = TestAtomicCounter_Create(); SolidSyslogMetaSdConfig metaConfig{}; - metaConfig.Counter = counter; - SolidSyslogStructuredData* metaSd = SolidSyslogMetaSd_Create(&metaConfig); + metaConfig.Counter = metaSdCounter; + metaSd = SolidSyslogMetaSd_Create(&metaConfig); SolidSyslogStructuredData* sdList[] = {metaSd}; config.Sd = sdList; config.SdCount = 1; @@ -681,8 +699,6 @@ TEST(SolidSyslog, MsgFieldPreservedWithMetaSd) message.Msg = "hello world"; Log(); STRCMP_EQUAL("hello world", SyslogMsg(lastMessage()).c_str()); - SolidSyslogMetaSd_Destroy(metaSd); - TestAtomicCounter_Destroy(counter); } TEST(SolidSyslog, MultipleSdItemsAreConcatenated) @@ -731,13 +747,12 @@ TEST(SolidSyslog, AllSdFailingProducesNilvalue) TEST(SolidSyslog, MetaSdAndTimeQualitySdCoexistInSdArray) { - TestAtomicCounterStorage counterStorage; - SolidSyslogAtomicCounter* counter = TestAtomicCounter_Create(&counterStorage); + metaSdCounter = TestAtomicCounter_Create(); SolidSyslogMetaSdConfig metaConfig{}; - metaConfig.Counter = counter; - SolidSyslogStructuredData* metaSd = SolidSyslogMetaSd_Create(&metaConfig); - SolidSyslogStructuredData* timeQuality = SolidSyslogTimeQualitySd_Create(IntegrationGetTimeQuality); - SolidSyslogStructuredData* sdList[] = {metaSd, timeQuality}; + metaConfig.Counter = metaSdCounter; + metaSd = SolidSyslogMetaSd_Create(&metaConfig); + timeQualitySd = SolidSyslogTimeQualitySd_Create(IntegrationGetTimeQuality); + SolidSyslogStructuredData* sdList[] = {metaSd, timeQualitySd}; config.Sd = sdList; config.SdCount = 2; SolidSyslog_Destroy(); @@ -747,9 +762,6 @@ TEST(SolidSyslog, MetaSdAndTimeQualitySdCoexistInSdArray) "[meta sequenceId=\"1\"][timeQuality tzKnown=\"1\" isSynced=\"1\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str() ); - SolidSyslogTimeQualitySd_Destroy(timeQuality); - SolidSyslogMetaSd_Destroy(metaSd); - TestAtomicCounter_Destroy(counter); } TEST(SolidSyslog, NullMessageOmitsMsgField) diff --git a/Tests/SolidSyslogTlsStreamPoolTest.cpp b/Tests/SolidSyslogTlsStreamPoolTest.cpp new file mode 100644 index 00000000..10aa8d47 --- /dev/null +++ b/Tests/SolidSyslogTlsStreamPoolTest.cpp @@ -0,0 +1,189 @@ +#include "CppUTest/TestHarness.h" + +extern "C" +{ +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "OpenSslFake.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogStream.h" +#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTlsStream.h" +#include "SolidSyslogTunables.h" +#include "StreamFake.h" +} + +#include "TestUtils.h" + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER into scope for CALLED_FAKE + +namespace +{ +void NoOpSleep(int milliseconds) +{ + (void) milliseconds; +} +} // namespace + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site + +// Asserts handle is non-null and not one of the slots in pool. +#define CHECK_IS_FALLBACK(handle, pool) \ + do \ + { \ + CHECK_TEXT((handle) != nullptr, "Fallback handle was nullptr"); \ + for (auto* slot : (pool)) \ + { \ + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); \ + CHECK_TEXT((handle) != slot, "Fallback handle collided with a pool slot"); \ + } \ + } while (0) + +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + +// clang-format off +TEST_GROUP(SolidSyslogTlsStreamPool) +{ + struct SolidSyslogStream* transport = nullptr; + struct SolidSyslogTlsStreamConfig config = {}; + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogStream* pooled[SOLIDSYSLOG_TLS_STREAM_POOL_SIZE] = {}; + struct SolidSyslogStream* overflow = nullptr; + + void setup() override + { + OpenSslFake_Reset(); + transport = StreamFake_Create(); + config.Transport = transport; + config.Sleep = NoOpSleep; + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogTlsStream_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogTlsStream_Destroy(overflow); + } + StreamFake_Destroy(transport); + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogTlsStream_Create(&config); + } + } +}; + +// clang-format on + +TEST(SolidSyslogTlsStreamPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogTlsStream_Create(&config); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogTlsStreamPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogTlsStream_Create(&config); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogTlsStreamPool, FallbackSendReturnsTrueToDropOnTheFloor) +{ + FillPool(); + overflow = SolidSyslogTlsStream_Create(&config); + + CHECK_TRUE(SolidSyslogStream_Send(overflow, "x", 1)); +} + +TEST(SolidSyslogTlsStreamPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogTlsStream_Create(&config); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogTlsStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogTlsStream_Create(&config); + + LONGS_EQUAL(SOLIDSYSLOG_TLS_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_TLS_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogTlsStreamPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogTlsStream_Create(&config); + ConfigLockFake_Install(); + + SolidSyslogTlsStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogTlsStreamPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogStream stranger = {}; + + SolidSyslogTlsStream_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogTlsStreamPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogStream stranger = {}; + + SolidSyslogTlsStream_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogTlsStreamPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogTlsStream_Create(&config); + SolidSyslogTlsStream_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogTlsStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index 8cffd4a4..d1b90548 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -34,7 +34,6 @@ TEST_GROUP(SolidSyslogTlsStream) { struct SolidSyslogStream* transport = nullptr; struct SolidSyslogTlsStreamConfig config = {}; - SolidSyslogTlsStreamStorage streamStorage{}; struct SolidSyslogStream* stream = nullptr; SolidSyslogAddressStorage addrStorage = {0}; struct SolidSyslogAddress* addr = nullptr; @@ -48,7 +47,7 @@ TEST_GROUP(SolidSyslogTlsStream) config.Transport = transport; config.Sleep = NoOpSleep; // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros addr = SolidSyslogAddress_FromStorage(&addrStorage); } @@ -164,16 +163,6 @@ TEST(SolidSyslogTlsStream, CreateSucceeds) CHECK_TRUE(stream != nullptr); } -TEST(SolidSyslogTlsStream, CreateReturnsHandleInsideCallerSuppliedStorage) -{ - SolidSyslogTlsStreamStorage localStorage{}; - struct SolidSyslogTlsStreamConfig localConfig = {}; - localConfig.Transport = transport; - struct SolidSyslogStream* localStream = SolidSyslogTlsStream_Create(&localStorage, &localConfig); - POINTERS_EQUAL(&localStorage, localStream); - SolidSyslogTlsStream_Destroy(localStream); -} - TEST(SolidSyslogTlsStream, OpenOpensTransport) { SolidSyslogStream_Open(stream, addr); @@ -196,7 +185,7 @@ TEST(SolidSyslogTlsStream, OpenLoadsCaBundleFromConfig) { SolidSyslogTlsStream_Destroy(stream); config.CaBundlePath = "/some/path/ca.pem"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); STRCMP_EQUAL("/some/path/ca.pem", OpenSslFake_LastCaBundlePath()); } @@ -217,7 +206,7 @@ TEST(SolidSyslogTlsStream, OpenPassesCipherListToSslCtx) { SolidSyslogTlsStream_Destroy(stream); config.CipherList = "ECDHE+AESGCM"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); STRCMP_EQUAL("ECDHE+AESGCM", OpenSslFake_LastCipherList()); } @@ -232,7 +221,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenCipherListRejected) { SolidSyslogTlsStream_Destroy(stream); config.CipherList = "not-a-real-cipher"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetCipherListFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -241,7 +230,7 @@ TEST(SolidSyslogTlsStream, CipherListFailureFreesCtx) { SolidSyslogTlsStream_Destroy(stream); config.CipherList = "not-a-real-cipher"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetCipherListFails(true); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_CtxFree, ONCE); @@ -299,7 +288,7 @@ TEST(SolidSyslogTlsStream, OpenSetsSniHostnameFromConfig) { SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); STRCMP_EQUAL("logs.example", OpenSslFake_LastSniHostname()); } @@ -308,7 +297,7 @@ TEST(SolidSyslogTlsStream, OpenSetsExpectedCertHostname) { SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); STRCMP_EQUAL("logs.example", OpenSslFake_LastSet1Host()); } @@ -439,6 +428,22 @@ TEST(SolidSyslogTlsStream, CloseClosesTransport) CALLED_FAKE_ON(StreamFake_Close, transport, ONCE); } +/* Destroy on a still-Open stream must release the underlying transport, + otherwise an integrator that omits the explicit Close call leaks the + transport's socket / fd. Verified before the teardown's Destroy fires + (which would itself trigger another transport Close). */ +TEST(SolidSyslogTlsStream, DestroyClosesTransportWhenStillOpen) +{ + SolidSyslogStream_Open(stream, addr); + + SolidSyslogTlsStream_Destroy(stream); + + CALLED_FAKE_ON(StreamFake_Close, transport, ONCE); + /* Re-create so teardown's Destroy targets a live slot rather than a + stale handle (which would fire SOLIDSYSLOG_ERROR_MSG_TLSSTREAM_UNKNOWN_DESTROY). */ + stream = SolidSyslogTlsStream_Create(&config); +} + TEST(SolidSyslogTlsStream, CloseFreesBioMethod) { SolidSyslogStream_Open(stream, addr); @@ -548,7 +553,7 @@ TEST(SolidSyslogTlsStream, OpenPassesSslToSniCtrl) { SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSslCtrlSslArg()); } @@ -557,7 +562,7 @@ TEST(SolidSyslogTlsStream, OpenPassesSslFromNewToSet1Host) { SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSet1HostSslArg()); } @@ -611,7 +616,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenSet1HostFails) { SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetSet1HostFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -620,7 +625,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenSniHostnameSetupFails) { SolidSyslogTlsStream_Destroy(stream); config.ServerName = "logs.example"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetSniHostnameFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -773,7 +778,7 @@ TEST(SolidSyslogTlsStream, OpenLoadsClientCertChainFromConfig) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); STRCMP_EQUAL("/some/path/client.pem", OpenSslFake_LastClientCertChainPath()); } @@ -783,7 +788,7 @@ TEST(SolidSyslogTlsStream, OpenLoadsClientKeyFromConfig) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); STRCMP_EQUAL("/some/path/client.key", OpenSslFake_LastClientKeyPath()); LONGS_EQUAL(SSL_FILETYPE_PEM, OpenSslFake_LastClientKeyFileType()); @@ -794,7 +799,7 @@ TEST(SolidSyslogTlsStream, OpenChecksClientKeyMatchesCert) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_CheckPrivateKey, ONCE); } @@ -804,7 +809,7 @@ TEST(SolidSyslogTlsStream, OpenFailsWhenOnlyClientCertIsSet) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = nullptr; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -813,7 +818,7 @@ TEST(SolidSyslogTlsStream, OpenMakesNoClientIdentityCallsWhenOnlyClientCertIsSet SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = nullptr; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_UseCertChainFile, NEVER); CALLED_FAKE(OpenSslFake_UsePrivateKeyFile, NEVER); @@ -825,7 +830,7 @@ TEST(SolidSyslogTlsStream, OpenFailsWhenOnlyClientKeyIsSet) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = nullptr; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -834,7 +839,7 @@ TEST(SolidSyslogTlsStream, OpenMakesNoClientIdentityCallsWhenOnlyClientKeyIsSet) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = nullptr; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_UseCertChainFile, NEVER); CALLED_FAKE(OpenSslFake_UsePrivateKeyFile, NEVER); @@ -846,7 +851,7 @@ TEST(SolidSyslogTlsStream, PartialClientIdentityConfigFreesCtx) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = nullptr; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_CtxFree, ONCE); } @@ -856,7 +861,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenUseCertChainFileFails) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetUseCertChainFileFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -866,7 +871,7 @@ TEST(SolidSyslogTlsStream, UseCertChainFileFailureFreesCtx) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetUseCertChainFileFails(true); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_CtxFree, ONCE); @@ -877,7 +882,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenUsePrivateKeyFileFails) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetUsePrivateKeyFileFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -887,7 +892,7 @@ TEST(SolidSyslogTlsStream, UsePrivateKeyFileFailureFreesCtx) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetUsePrivateKeyFileFails(true); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_CtxFree, ONCE); @@ -898,7 +903,7 @@ TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenCheckPrivateKeyFails) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetCheckPrivateKeyFails(true); CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); } @@ -908,7 +913,7 @@ TEST(SolidSyslogTlsStream, CheckPrivateKeyFailureFreesCtx) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); OpenSslFake_SetCheckPrivateKeyFails(true); SolidSyslogStream_Open(stream, addr); CALLED_FAKE(OpenSslFake_CtxFree, ONCE); @@ -919,7 +924,7 @@ TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToUseCertChainFile) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastUseCertChainFileCtxArg()); } @@ -929,7 +934,7 @@ TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToUsePrivateKeyFile) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastUsePrivateKeyFileCtxArg()); } @@ -939,7 +944,7 @@ TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToCheckPrivateKey) SolidSyslogTlsStream_Destroy(stream); config.ClientCertChainPath = "/some/path/client.pem"; config.ClientKeyPath = "/some/path/client.key"; - stream = SolidSyslogTlsStream_Create(&streamStorage, &config); + stream = SolidSyslogTlsStream_Create(&config); SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastCheckPrivateKeyCtxArg()); } diff --git a/Tests/SolidSyslogWindowsAtomicCounterPoolTest.cpp b/Tests/SolidSyslogWindowsAtomicCounterPoolTest.cpp new file mode 100644 index 00000000..936056ec --- /dev/null +++ b/Tests/SolidSyslogWindowsAtomicCounterPoolTest.cpp @@ -0,0 +1,164 @@ +#include "CppUTest/TestHarness.h" + +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogAtomicCounterDefinition.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWindowsAtomicCounter.h" +#include "TestUtils.h" + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER into scope for CALLED_FAKE + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site + +// Asserts handle is non-null and not one of the slots in pool. +#define CHECK_IS_FALLBACK(handle, pool) \ + do \ + { \ + CHECK_TEXT((handle) != nullptr, "Fallback handle was nullptr"); \ + for (auto* slot : (pool)) \ + { \ + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); \ + CHECK_TEXT((handle) != slot, "Fallback handle collided with a pool slot"); \ + } \ + } while (0) + +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + +// clang-format off +TEST_GROUP(SolidSyslogWindowsAtomicCounterPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogAtomicCounter* pooled[SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE] = {}; + struct SolidSyslogAtomicCounter* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogWindowsAtomicCounter_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogWindowsAtomicCounter_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogWindowsAtomicCounter_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogWindowsAtomicCounterPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogWindowsAtomicCounter_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogWindowsAtomicCounter_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, FallbackIncrementReturnsOne) +{ + FillPool(); + overflow = SolidSyslogWindowsAtomicCounter_Create(); + + LONGS_EQUAL(1U, SolidSyslogAtomicCounter_Increment(overflow)); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogWindowsAtomicCounter_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogWindowsAtomicCounter_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogWindowsAtomicCounter_Create(); + ConfigLockFake_Install(); + + SolidSyslogWindowsAtomicCounter_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogAtomicCounter stranger = {}; + + SolidSyslogWindowsAtomicCounter_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogAtomicCounter stranger = {}; + + SolidSyslogWindowsAtomicCounter_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWindowsAtomicCounterPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogWindowsAtomicCounter_Create(); + SolidSyslogWindowsAtomicCounter_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogWindowsAtomicCounter_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSATOMICCOUNTER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogWindowsAtomicCounterTestHelper.c b/Tests/SolidSyslogWindowsAtomicCounterTestHelper.c index 1476e8a8..3bc08a5a 100644 --- a/Tests/SolidSyslogWindowsAtomicCounterTestHelper.c +++ b/Tests/SolidSyslogWindowsAtomicCounterTestHelper.c @@ -1,9 +1,10 @@ #include "SolidSyslogAtomicCounterTestHelper.h" #include "SolidSyslogAtomicCounter.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogWindowsAtomicCounter.h" +#include #include struct SolidSyslogAtomicCounter; @@ -17,14 +18,9 @@ struct SolidSyslogAtomicCounter; // NOLINTNEXTLINE(bugprone-suspicious-include) #include "SolidSyslogWindowsAtomicCounter.c" -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(SolidSyslogWindowsAtomicCounterStorage) <= TEST_ATOMIC_COUNTER_STORAGE_SIZE, - TestAtomicCounterStorage_too_small_for_Windows -); - -struct SolidSyslogAtomicCounter* TestAtomicCounter_Create(TestAtomicCounterStorage* storage) +struct SolidSyslogAtomicCounter* TestAtomicCounter_Create(void) { - return SolidSyslogWindowsAtomicCounter_Create((SolidSyslogWindowsAtomicCounterStorage*) storage); + return SolidSyslogWindowsAtomicCounter_Create(); } void TestAtomicCounter_Init(struct SolidSyslogAtomicCounter* base, uint32_t value) @@ -41,3 +37,8 @@ void TestAtomicCounter_Destroy(struct SolidSyslogAtomicCounter* base) { SolidSyslogWindowsAtomicCounter_Destroy(base); } + +size_t TestAtomicCounter_PoolSize(void) +{ + return SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE; +}