diff --git a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c index 648b1768..ab836e2e 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c @@ -10,7 +10,6 @@ struct SolidSyslogResolver; -static SolidSyslogPosixTcpStreamStorage underlyingStreamStorage; static struct SolidSyslogStream* underlyingStream; static SolidSyslogTlsStreamStorage tlsStreamStorage; static struct SolidSyslogStream* tlsStream; @@ -18,7 +17,7 @@ static struct SolidSyslogSender* sender; struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* resolver, bool mtls) { - underlyingStream = SolidSyslogPosixTcpStream_Create(&underlyingStreamStorage); + underlyingStream = SolidSyslogPosixTcpStream_Create(); static struct SolidSyslogTlsStreamConfig tlsStreamConfig; tlsStreamConfig = (struct SolidSyslogTlsStreamConfig) {0}; diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 82283747..360ed3a1 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -51,11 +51,12 @@ static const char* const STORE_PATH_PREFIX = "/tmp/STORE"; static const char* const THRESHOLD_MARKER_PATH = "/tmp/solidsyslog_threshold_marker.log"; static struct SolidSyslogFile* storeFile; static struct SolidSyslogBlockDevice* storeBlockDevice; -static SolidSyslogPosixTcpStreamStorage plainTcpStreamStorage; static struct SolidSyslogStream* plainTcpStream; static struct SolidSyslogSender* plainTcpSender; static struct SolidSyslogSender* udpSender; static struct SolidSyslogSender* switchingSender; +static struct SolidSyslogDatagram* udpDatagram; +static struct SolidSyslogResolver* sharedResolver; static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) { @@ -77,16 +78,18 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetOptions* opt { bool mtlsModeActive = (strcmp(options->Transport, "mtls") == 0); - struct SolidSyslogResolver* resolver = SolidSyslogGetAddrInfoResolver_Create(); + sharedResolver = SolidSyslogGetAddrInfoResolver_Create(); + struct SolidSyslogResolver* resolver = sharedResolver; + udpDatagram = SolidSyslogPosixDatagram_Create(); static struct SolidSyslogUdpSenderConfig udpConfig = {0}; udpConfig.Resolver = resolver; - udpConfig.Datagram = SolidSyslogPosixDatagram_Create(); + udpConfig.Datagram = udpDatagram; udpConfig.Endpoint = BddTargetUdpConfig_GetEndpoint; udpConfig.EndpointVersion = BddTargetUdpConfig_GetEndpointVersion; udpSender = SolidSyslogUdpSender_Create(&udpConfig); - plainTcpStream = SolidSyslogPosixTcpStream_Create(&plainTcpStreamStorage); + plainTcpStream = SolidSyslogPosixTcpStream_Create(); static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; tcpConfig.Resolver = resolver; tcpConfig.Stream = plainTcpStream; @@ -157,8 +160,7 @@ static struct SolidSyslogStore* CreateStore(const struct BddTargetOptions* optio if (useFile) { - static SolidSyslogPosixFileStorage fileStorage; - storeFile = SolidSyslogPosixFile_Create(&fileStorage); + storeFile = SolidSyslogPosixFile_Create(); storeBlockDevice = SolidSyslogFileBlockDevice_Create(storeFile, STORE_PATH_PREFIX); @@ -188,8 +190,8 @@ static void DestroySender(void) SolidSyslogStreamSender_Destroy(plainTcpSender); SolidSyslogPosixTcpStream_Destroy(plainTcpStream); SolidSyslogUdpSender_Destroy(udpSender); - SolidSyslogPosixDatagram_Destroy(); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogPosixDatagram_Destroy(udpDatagram); + SolidSyslogGetAddrInfoResolver_Destroy(sharedResolver); } static void DestroyStore(struct SolidSyslogStore* store, const struct BddTargetOptions* options) @@ -289,7 +291,7 @@ int main(int argc, char* argv[]) SolidSyslogMetaSd_Destroy(metaSd); SolidSyslogStdAtomicCounter_Destroy(counter); DestroyStore(store, &options); - SolidSyslogPosixMessageQueueBuffer_Destroy(); + SolidSyslogPosixMessageQueueBuffer_Destroy(buffer); DestroySender(); return 0; diff --git a/CLAUDE.md b/CLAUDE.md index 240652d6..2bd8194d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -325,26 +325,30 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogTransport.h` | Any code selecting a transport or needing default port constants | `SolidSyslogTransport` enum (`UDP`, `TCP`), `SOLIDSYSLOG_UDP_DEFAULT_PORT`, `SOLIDSYSLOG_TCP_DEFAULT_PORT` | | `SolidSyslogResolver.h` | Any code that needs to resolve a destination | `SolidSyslogResolver_Resolve(resolver, transport, host, port, *out)` | | `SolidSyslogResolverDefinition.h` | Resolver implementors (extension point) | `SolidSyslogResolver` vtable struct (`Resolve`) | +| `SolidSyslogNullResolver.h` | Any code installing a no-op resolver slot (Resolve returns `false` so the caller's unresolved-host error path runs naturally) | `SolidSyslogNullResolver_Get` | | `SolidSyslogFreeRtosStaticResolver.h` | System setup code on FreeRTOS targets pinned to a hardcoded IPv4 destination (no DNS) | `SolidSyslogFreeRtosStaticResolverStorage`, `SOLIDSYSLOG_FREERTOSSTATICRESOLVER_SIZE`, `SolidSyslogFreeRtosStaticResolver_Create(storage, ipv4Octets)`, `_Destroy(resolver)` (ignores host and transport at Resolve time; port is taken from each call) | | `SolidSyslogDatagram.h` | Sender implementors using datagram transport | `SolidSyslogDatagram_Open`, `_SendTo`, `_Close` | | `SolidSyslogDatagramDefinition.h` | Datagram implementors (extension point) | `SolidSyslogDatagram` vtable struct (`Open`, `SendTo`, `Close`) | +| `SolidSyslogNullDatagram.h` | Any code installing a no-op datagram slot (Open/Close are no-ops, SendTo returns `SENT` to drop on the floor so the Store does not fill with undeliverables, MaxPayload returns the IPv6-safe default) | `SolidSyslogNullDatagram_Get` | | `SolidSyslogFreeRtosDatagram.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-TCP for UDP | `SolidSyslogFreeRtosDatagramStorage`, `SOLIDSYSLOG_FREERTOSDATAGRAM_SIZE`, `SolidSyslogFreeRtosDatagram_Create(storage)`, `_Destroy(datagram)` (wraps `FreeRTOS_socket` / `FreeRTOS_sendto` / `FreeRTOS_closesocket`) | | `SolidSyslogStream.h` | Sender implementors using stream transport | `SolidSyslogStream_Open`, `_Send`, `_Read`, `_Close`, `SolidSyslogSsize` | | `SolidSyslogStreamDefinition.h` | Stream implementors (extension point) | `SolidSyslogStream` vtable struct (`Open`, `Send`, `Read`, `Close`) | +| `SolidSyslogNullStream.h` | Any code installing a no-op stream slot (Open/Close are no-ops, Send returns `true` to drop on the floor so the Store does not fill with undeliverables, Read returns `0` for would-block so the caller does not tear the connection down) | `SolidSyslogNullStream_Get` | | `SolidSyslogFreeRtosTcpStream.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-TCP for TCP | `SolidSyslogFreeRtosTcpStreamStorage`, `SOLIDSYSLOG_FREERTOSTCPSTREAM_SIZE`, `SolidSyslogFreeRtosTcpStream_Create(storage)`, `_Destroy(stream)` (wraps `FreeRTOS_socket` / `FreeRTOS_connect` / `FreeRTOS_send` / `FreeRTOS_recv` / `FreeRTOS_closesocket`; bounded blocking connect via `SO_SNDTIMEO=200ms`, cleared post-connect so Send/Read are non-blocking) | | `SolidSyslogUdpSender.h` | System setup code using UDP transport | `SolidSyslogUdpSenderConfig` (resolver, datagram, endpoint, endpointVersion), `SolidSyslogUdpSender_Create(config)`, `_Destroy(sender)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create`. Pool-exhaustion and bad-config fallback is the shared `SolidSyslogNullSender`. | +| `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`. | | `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` | | `SolidSyslogBufferDefinition.h` | Buffer implementors (extension point) | `SolidSyslogBuffer` vtable struct | | `SolidSyslogPassthroughBuffer.h` | System setup code (single-task, no buffering) | `SolidSyslogPassthroughBuffer_Create(sender)`, `_Destroy(buffer)`. Instance struct lives in a library-internal static pool (E11); `_Destroy` takes the handle returned by `_Create` to release the slot. Pool exhaustion resolves to the shared `SolidSyslogNullBuffer`. | -| `SolidSyslogPosixMessageQueueBuffer.h` | System setup code using POSIX message queue buffer | `SolidSyslogPosixMessageQueueBuffer_Create`, `_Destroy` | +| `SolidSyslogPosixMessageQueueBuffer.h` | System setup code using POSIX message queue buffer | `SolidSyslogPosixMessageQueueBuffer_Create(maxMessageSize, maxMessages)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11); each slot's queue name is `/solidsyslog__` so multiple in-process slots remain isolated. Pool-exhaustion fallback is the shared `SolidSyslogNullBuffer`. | | `SolidSyslogCircularBuffer.h` | System setup code using an in-memory ring buffer (bare-metal / RTOS / Windows) | `SOLIDSYSLOG_CIRCULAR_BUFFER_HEADER_BYTES`, `SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(maxMessages)` (friendly: N max-sized messages → ring bytes), `SolidSyslogCircularBuffer_Create(mutex, ring, ringBytes)`, `_Destroy(buffer)` (uint16-length-prefixed records, drop-newest on overflow, no-split wrap, mutex-injected synchronisation). Instance struct lives in a library-internal static pool (E11); caller supplies the ring memory only. Pool exhaustion resolves to the shared `SolidSyslogNullBuffer`. | | `SolidSyslogMutex.h` | Any code holding a mutex handle | `SolidSyslogMutex_Lock`, `_Unlock` | | `SolidSyslogMutexDefinition.h` | Mutex implementors (extension point) | `SolidSyslogMutex` vtable struct (`Lock`, `Unlock`) | -| `SolidSyslogNullMutex.h` | System setup code (single-task, no synchronisation) | `SolidSyslogNullMutex_Create`, `_Destroy` | -| `SolidSyslogPosixMutex.h` | System setup code on POSIX targets needing thread-safe buffering | `SolidSyslogPosixMutexStorage`, `SOLIDSYSLOG_POSIXMUTEX_SIZE`, `SolidSyslogPosixMutex_Create(storage)`, `_Destroy(mutex)` (wraps `pthread_mutex_t`) | +| `SolidSyslogNullMutex.h` | System setup code (single-task, no synchronisation) | `SolidSyslogNullMutex_Get` | +| `SolidSyslogPosixMutex.h` | System setup code on POSIX targets needing thread-safe buffering | `SolidSyslogPosixMutex_Create(void)`, `_Destroy(base)` (wraps `pthread_mutex_t`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullMutex`. | | `SolidSyslogWindowsMutex.h` | System setup code on Windows targets needing thread-safe buffering | `SolidSyslogWindowsMutexStorage`, `SOLIDSYSLOG_WINDOWSMUTEX_SIZE`, `SolidSyslogWindowsMutex_Create(storage)`, `_Destroy(mutex)` (wraps `CRITICAL_SECTION`) | | `SolidSyslogFreeRtosMutex.h` | System setup code on FreeRTOS targets needing thread-safe buffering | `SolidSyslogFreeRtosMutexStorage`, `SOLIDSYSLOG_FREERTOSMUTEX_SIZE`, `SolidSyslogFreeRtosMutex_Create(storage)`, `_Destroy(mutex)` (wraps `xSemaphoreCreateMutexStatic` — caller's `StaticSemaphore_t` lives inside the injected storage; requires `configSUPPORT_STATIC_ALLOCATION=1`) | | `SolidSyslogStore.h` | Library internals consuming a store (Service algorithm) and integrator code querying capacity | `SolidSyslogStore_Write`, `_ReadNextUnsent`, `_MarkSent`, `_HasUnsent`, `_IsHalted`, `_GetTotalBytes`, `_GetUsedBytes` | @@ -352,6 +356,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogNullStore.h` | System setup code (no store-and-forward) | `SolidSyslogNullStore_Get` | | `SolidSyslogFileDefinition.h` | File implementors (extension point) | `SolidSyslogFile` vtable struct | | `SolidSyslogFile.h` | Any code using the file abstraction | `SolidSyslogFile_Open`, `_Close`, `_IsOpen`, `_Read`, `_Write`, `_SeekTo`, `_Size`, `_Truncate` | +| `SolidSyslogNullFile.h` | Any code installing a no-op file slot (Open/IsOpen/Read/Exists return `false`, Write/Delete return `true` so callers' success paths are not tripped, SeekTo/Truncate/Close are no-ops, Size returns `0`) | `SolidSyslogNullFile_Get` | | `SolidSyslogBlockDevice.h` | Library internals consuming a block device (BlockSequence inside BlockStore) and integrator code addressing blocks directly | `SolidSyslogBlockDevice_Acquire`, `_Dispose`, `_Exists`, `_Read`, `_Append`, `_WriteAt`, `_Size` (block-indexed I/O; Acquire makes a block ready for fresh writes, Dispose releases it) | | `SolidSyslogNullBlockDevice.h` | Any code installing a no-op block device slot (every method returns `false` / `0` — disk doesn't exist) | `SolidSyslogNullBlockDevice_Get` | | `SolidSyslogBlockDeviceDefinition.h` | BlockDevice implementors (extension point) | `SolidSyslogBlockDevice` vtable struct (`Acquire`, `Dispose`, `Exists`, `Read`, `Append`, `WriteAt`, `Size`) | @@ -361,7 +366,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogNullSecurityPolicy.h` | System setup code (no integrity checking) | `SolidSyslogNullSecurityPolicy_Get` | | `SolidSyslogCrc16Policy.h` | System setup code using CRC-16 integrity | `SolidSyslogCrc16Policy_Create`, `_Destroy` | | `SolidSyslogCrc16.h` | Any code needing CRC-16 computation | `SolidSyslogCrc16_Compute` | -| `SolidSyslogPosixFile.h` | System setup code using POSIX file I/O | `SolidSyslogPosixFile_Create`, `_Destroy` | +| `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`, `_Destroy` | | `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`) | | `SolidSyslogPosixClock.h` | System setup code using POSIX clock | `SolidSyslogPosixClock_GetTimestamp` | diff --git a/Core/Interface/SolidSyslogNullDatagram.h b/Core/Interface/SolidSyslogNullDatagram.h new file mode 100644 index 00000000..a5d128a2 --- /dev/null +++ b/Core/Interface/SolidSyslogNullDatagram.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLDATAGRAM_H +#define SOLIDSYSLOGNULLDATAGRAM_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogDatagram* SolidSyslogNullDatagram_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLDATAGRAM_H */ diff --git a/Core/Interface/SolidSyslogNullFile.h b/Core/Interface/SolidSyslogNullFile.h new file mode 100644 index 00000000..593b788d --- /dev/null +++ b/Core/Interface/SolidSyslogNullFile.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLFILE_H +#define SOLIDSYSLOGNULLFILE_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogFile* SolidSyslogNullFile_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLFILE_H */ diff --git a/Core/Interface/SolidSyslogNullMutex.h b/Core/Interface/SolidSyslogNullMutex.h index 4482e26c..d78cd890 100644 --- a/Core/Interface/SolidSyslogNullMutex.h +++ b/Core/Interface/SolidSyslogNullMutex.h @@ -5,10 +5,7 @@ EXTERN_C_BEGIN - struct SolidSyslogMutex; - - struct SolidSyslogMutex* SolidSyslogNullMutex_Create(void); - void SolidSyslogNullMutex_Destroy(void); + struct SolidSyslogMutex* SolidSyslogNullMutex_Get(void); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogNullResolver.h b/Core/Interface/SolidSyslogNullResolver.h new file mode 100644 index 00000000..6db03731 --- /dev/null +++ b/Core/Interface/SolidSyslogNullResolver.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLRESOLVER_H +#define SOLIDSYSLOGNULLRESOLVER_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogResolver* SolidSyslogNullResolver_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLRESOLVER_H */ diff --git a/Core/Interface/SolidSyslogNullStream.h b/Core/Interface/SolidSyslogNullStream.h new file mode 100644 index 00000000..17daa419 --- /dev/null +++ b/Core/Interface/SolidSyslogNullStream.h @@ -0,0 +1,12 @@ +#ifndef SOLIDSYSLOGNULLSTREAM_H +#define SOLIDSYSLOGNULLSTREAM_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogStream* SolidSyslogNullStream_Get(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGNULLSTREAM_H */ diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index f709c28a..734675aa 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -50,6 +50,133 @@ #error "SOLIDSYSLOG_CIRCULAR_BUFFER_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogPosixMutex instances the library's internal + * static pool can simultaneously hold. Each instance carries a + * pthread_mutex_t. + * + * Default 1 — almost all integrators wire a single PosixMutex into + * a CircularBuffer or other thread-safe primitive. Bump via + * SOLIDSYSLOG_USER_TUNABLES_FILE if more than one is genuinely + * needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE < 1 +#error "SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogPosixDatagram instances the library's internal + * static pool can simultaneously hold. Each instance carries the + * AF_INET socket FD and a one-shot connect flag. + * + * Default 1 — almost all integrators wire a single PosixDatagram into + * a UdpSender. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more than + * one is genuinely needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogGetAddrInfoResolver instances the library's + * internal static pool can simultaneously hold. The class is + * effectively stateless today — the pool slot carries the vtable + * holder. + * + * Default 1 — almost all integrators wire a single resolver into + * one or more Senders. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if + * more than one is genuinely needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogPosixFile instances the library's internal + * static pool can simultaneously hold. Each instance carries an + * int file descriptor. + * + * Default 1 — almost all integrators wire a single PosixFile into a + * FileBlockDevice. Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more + * than one is genuinely needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_POSIX_FILE_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_POSIX_FILE_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_POSIX_FILE_POOL_SIZE < 1 +#error "SOLIDSYSLOG_POSIX_FILE_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogPosixTcpStream instances the library's internal + * static pool can simultaneously hold. Each instance carries an + * int file descriptor. + * + * Default 2 — the Linux BDD target wires *two* stream-framed senders + * behind a SwitchingSender (plain TCP, plus TLS that wraps another + * underlying PosixTcpStream as its transport), so default 1 would + * silently fall back to NullStream on the second Create. Matches the + * SOLIDSYSLOG_STREAM_SENDER_POOL_SIZE default for the same reason. + * Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if more are needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE 2U +#endif + +#if SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogPosixMessageQueueBuffer instances the library's + * internal static pool can simultaneously hold. Each instance carries + * an mqd_t plus the per-process queue name (Formatter storage). + * + * Default 1 — almost all integrators wire a single MQ-backed buffer. + * The queue name derives from the process ID, so bumping above 1 in + * the same process would race multiple slots onto the same + * `/solidsyslog_` name; an integrator needing N > 1 must + * additionally distinguish the names (out of scope today). + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE must be >= 1" +#endif + /* * Number of SolidSyslogPassthroughBuffer instances the library's * internal static pool can simultaneously hold. Each instance is diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 0aff1dce..dbdee9c5 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -26,12 +26,14 @@ set(SOURCES SolidSyslogCrc16.c SolidSyslogCrc16Policy.c SolidSyslogStream.c + SolidSyslogNullStream.c SolidSyslogStructuredData.c SolidSyslogTimeQualitySd.c SolidSyslogTimeQualitySdStatic.c SolidSyslogOriginSd.c SolidSyslogOriginSdStatic.c SolidSyslogFile.c + SolidSyslogNullFile.c SolidSyslogBlockStore.c SolidSyslogBlockStoreStatic.c RecordStore.c @@ -56,7 +58,9 @@ endif() if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_WINSOCK OR DEFINED ENV{FREERTOS_KERNEL_PATH}) list(APPEND SOURCES SolidSyslogResolver.c + SolidSyslogNullResolver.c SolidSyslogDatagram.c + SolidSyslogNullDatagram.c SolidSyslogUdpSender.c SolidSyslogUdpSenderStatic.c SolidSyslogStreamSender.c diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index 48150c1f..b829e1f9 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -63,5 +63,29 @@ "SolidSyslogBlockStore_Create pool exhausted; returning fallback store" #define SOLIDSYSLOG_ERROR_MSG_BLOCKSTORE_UNKNOWN_DESTROY \ "SolidSyslogBlockStore_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_POOL_EXHAUSTED \ + "SolidSyslogPosixMutex_Create pool exhausted; returning fallback mutex" +#define SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_UNKNOWN_DESTROY \ + "SolidSyslogPosixMutex_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_POOL_EXHAUSTED \ + "SolidSyslogPosixDatagram_Create pool exhausted; returning fallback datagram" +#define SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_UNKNOWN_DESTROY \ + "SolidSyslogPosixDatagram_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_POOL_EXHAUSTED \ + "SolidSyslogGetAddrInfoResolver_Create pool exhausted; returning fallback resolver" +#define SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_UNKNOWN_DESTROY \ + "SolidSyslogGetAddrInfoResolver_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_POSIXFILE_POOL_EXHAUSTED \ + "SolidSyslogPosixFile_Create pool exhausted; returning fallback file" +#define SOLIDSYSLOG_ERROR_MSG_POSIXFILE_UNKNOWN_DESTROY \ + "SolidSyslogPosixFile_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_POOL_EXHAUSTED \ + "SolidSyslogPosixTcpStream_Create pool exhausted; returning fallback stream" +#define SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_UNKNOWN_DESTROY \ + "SolidSyslogPosixTcpStream_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_POOL_EXHAUSTED \ + "SolidSyslogPosixMessageQueueBuffer_Create pool exhausted; returning fallback buffer" +#define SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_UNKNOWN_DESTROY \ + "SolidSyslogPosixMessageQueueBuffer_Destroy called with a handle not issued by this pool" #endif /* SOLIDSYSLOGERRORMESSAGES_H */ diff --git a/Core/Source/SolidSyslogNullDatagram.c b/Core/Source/SolidSyslogNullDatagram.c new file mode 100644 index 00000000..19f69b6d --- /dev/null +++ b/Core/Source/SolidSyslogNullDatagram.c @@ -0,0 +1,66 @@ +#include "SolidSyslogNullDatagram.h" + +#include +#include + +#include "SolidSyslogDatagram.h" +#include "SolidSyslogDatagramDefinition.h" +#include "SolidSyslogUdpPayload.h" + +struct SolidSyslogAddress; + +static bool NullDatagram_Open(struct SolidSyslogDatagram* base); +static enum SolidSyslogDatagramSendResult NullDatagram_SendTo( + struct SolidSyslogDatagram* base, + const void* buffer, + size_t size, + const struct SolidSyslogAddress* addr +); +static size_t NullDatagram_MaxPayload(struct SolidSyslogDatagram* base); +static void NullDatagram_Close(struct SolidSyslogDatagram* base); + +static struct SolidSyslogDatagram instance = { + .Open = NullDatagram_Open, + .SendTo = NullDatagram_SendTo, + .MaxPayload = NullDatagram_MaxPayload, + .Close = NullDatagram_Close, +}; + +struct SolidSyslogDatagram* SolidSyslogNullDatagram_Get(void) +{ + return &instance; +} + +static bool NullDatagram_Open(struct SolidSyslogDatagram* base) +{ + (void) base; + return true; +} + +/* SendTo returns SENT so the Service algorithm treats the message as + * delivered and drops it from the Store, rather than retaining an + * undeliverable. Mirrors NullSender_Send's drop-on-the-floor contract. */ +static enum SolidSyslogDatagramSendResult NullDatagram_SendTo( + struct SolidSyslogDatagram* base, + const void* buffer, + size_t size, + const struct SolidSyslogAddress* addr +) +{ + (void) base; + (void) buffer; + (void) size; + (void) addr; + return SOLIDSYSLOG_DATAGRAM_SEND_RESULT_SENT; +} + +static size_t NullDatagram_MaxPayload(struct SolidSyslogDatagram* base) +{ + (void) base; + return SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD; +} + +static void NullDatagram_Close(struct SolidSyslogDatagram* base) +{ + (void) base; +} diff --git a/Core/Source/SolidSyslogNullFile.c b/Core/Source/SolidSyslogNullFile.c new file mode 100644 index 00000000..0c053b56 --- /dev/null +++ b/Core/Source/SolidSyslogNullFile.c @@ -0,0 +1,109 @@ +#include "SolidSyslogNullFile.h" + +#include +#include + +#include "SolidSyslogFileDefinition.h" + +static bool NullFile_Open(struct SolidSyslogFile* base, const char* path); +static void NullFile_Close(struct SolidSyslogFile* base); +static bool NullFile_IsOpen(struct SolidSyslogFile* base); +static bool NullFile_Read(struct SolidSyslogFile* base, void* buf, size_t count); +static bool NullFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count); +static void NullFile_SeekTo(struct SolidSyslogFile* base, size_t offset); +static size_t NullFile_Size(struct SolidSyslogFile* base); +static void NullFile_Truncate(struct SolidSyslogFile* base); +static bool NullFile_Exists(struct SolidSyslogFile* base, const char* path); +static bool NullFile_Delete(struct SolidSyslogFile* base, const char* path); + +static struct SolidSyslogFile instance = { + .Open = NullFile_Open, + .Close = NullFile_Close, + .IsOpen = NullFile_IsOpen, + .Read = NullFile_Read, + .Write = NullFile_Write, + .SeekTo = NullFile_SeekTo, + .Size = NullFile_Size, + .Truncate = NullFile_Truncate, + .Exists = NullFile_Exists, + .Delete = NullFile_Delete, +}; + +struct SolidSyslogFile* SolidSyslogNullFile_Get(void) +{ + return &instance; +} + +/* Open returns false so callers see a consistently non-functional file. + * NullFile is the fallback when PosixFile / WindowsFile / FatFsFile + * Create exhausts the pool — at that point the wider chain + * (BlockStore → NullStore) is already broken; presenting "open failed" + * lets the consumer's existing error path handle it cleanly. */ +static bool NullFile_Open(struct SolidSyslogFile* base, const char* path) +{ + (void) base; + (void) path; + return false; +} + +static void NullFile_Close(struct SolidSyslogFile* base) +{ + (void) base; +} + +static bool NullFile_IsOpen(struct SolidSyslogFile* base) +{ + (void) base; + return false; +} + +static bool NullFile_Read(struct SolidSyslogFile* base, void* buf, size_t count) +{ + (void) base; + (void) buf; + (void) count; + return false; +} + +/* Write returns true so the BlockStore-side caller treats the bytes as + * persisted and does not retry. Mirrors NullSender_Send's drop-on-the-floor. */ +static bool NullFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count) +{ + (void) base; + (void) buf; + (void) count; + return true; +} + +static void NullFile_SeekTo(struct SolidSyslogFile* base, size_t offset) +{ + (void) base; + (void) offset; +} + +static size_t NullFile_Size(struct SolidSyslogFile* base) +{ + (void) base; + return 0U; +} + +static void NullFile_Truncate(struct SolidSyslogFile* base) +{ + (void) base; +} + +static bool NullFile_Exists(struct SolidSyslogFile* base, const char* path) +{ + (void) base; + (void) path; + return false; +} + +/* Delete returns true ("succeeded vacuously") so callers that treat + * delete-of-nonexistent as a no-op are not tripped by the null object. */ +static bool NullFile_Delete(struct SolidSyslogFile* base, const char* path) +{ + (void) base; + (void) path; + return true; +} diff --git a/Core/Source/SolidSyslogNullMutex.c b/Core/Source/SolidSyslogNullMutex.c index 4dddc0b8..b48937bd 100644 --- a/Core/Source/SolidSyslogNullMutex.c +++ b/Core/Source/SolidSyslogNullMutex.c @@ -1,25 +1,18 @@ #include "SolidSyslogNullMutex.h" -#include - #include "SolidSyslogMutexDefinition.h" static void NullMutex_Lock(struct SolidSyslogMutex* base); static void NullMutex_Unlock(struct SolidSyslogMutex* base); -static struct SolidSyslogMutex NullMutex_Instance; - -struct SolidSyslogMutex* SolidSyslogNullMutex_Create(void) -{ - NullMutex_Instance.Lock = NullMutex_Lock; - NullMutex_Instance.Unlock = NullMutex_Unlock; - return &NullMutex_Instance; -} +static struct SolidSyslogMutex instance = { + .Lock = NullMutex_Lock, + .Unlock = NullMutex_Unlock, +}; -void SolidSyslogNullMutex_Destroy(void) +struct SolidSyslogMutex* SolidSyslogNullMutex_Get(void) { - NullMutex_Instance.Lock = NULL; - NullMutex_Instance.Unlock = NULL; + return &instance; } static void NullMutex_Lock(struct SolidSyslogMutex* base) diff --git a/Core/Source/SolidSyslogNullResolver.c b/Core/Source/SolidSyslogNullResolver.c new file mode 100644 index 00000000..885c9f14 --- /dev/null +++ b/Core/Source/SolidSyslogNullResolver.c @@ -0,0 +1,44 @@ +#include "SolidSyslogNullResolver.h" + +#include +#include + +#include "SolidSyslogResolverDefinition.h" +#include "SolidSyslogTransport.h" + +struct SolidSyslogAddress; + +static bool NullResolver_Resolve( + struct SolidSyslogResolver* base, + enum SolidSyslogTransport transport, + const char* host, + uint16_t port, + struct SolidSyslogAddress* result +); + +static struct SolidSyslogResolver instance = {.Resolve = NullResolver_Resolve}; + +struct SolidSyslogResolver* SolidSyslogNullResolver_Get(void) +{ + return &instance; +} + +/* Resolve returns false ("could not resolve") so callers' existing + * unresolved-host error path runs naturally. In the pool-exhausted + * fallback case the matching Sender is already on NullSender, so the + * send chain is broken end-to-end. */ +static bool NullResolver_Resolve( + struct SolidSyslogResolver* base, + enum SolidSyslogTransport transport, + const char* host, + uint16_t port, + struct SolidSyslogAddress* result +) +{ + (void) base; + (void) transport; + (void) host; + (void) port; + (void) result; + return false; +} diff --git a/Core/Source/SolidSyslogNullStream.c b/Core/Source/SolidSyslogNullStream.c new file mode 100644 index 00000000..24f211c1 --- /dev/null +++ b/Core/Source/SolidSyslogNullStream.c @@ -0,0 +1,59 @@ +#include "SolidSyslogNullStream.h" + +#include +#include + +#include "SolidSyslogStream.h" +#include "SolidSyslogStreamDefinition.h" + +struct SolidSyslogAddress; + +static bool NullStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); +static bool NullStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); +static SolidSyslogSsize NullStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); +static void NullStream_Close(struct SolidSyslogStream* base); + +static struct SolidSyslogStream instance = { + .Open = NullStream_Open, + .Send = NullStream_Send, + .Read = NullStream_Read, + .Close = NullStream_Close, +}; + +struct SolidSyslogStream* SolidSyslogNullStream_Get(void) +{ + return &instance; +} + +static bool NullStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr) +{ + (void) base; + (void) addr; + return true; +} + +/* Send returns true so the StreamSender treats the message as delivered + * and the Store does not retain undeliverables. Mirrors NullSender_Send + * and NullDatagram_SendTo. */ +static bool NullStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size) +{ + (void) base; + (void) buffer; + (void) size; + return true; +} + +/* Read returns 0 ("would-block, nothing available") rather than < 0 (EOF / error) + * so StreamSender does not flag a broken connection and tear it down. */ +static SolidSyslogSsize NullStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size) +{ + (void) base; + (void) buffer; + (void) size; + return 0; +} + +static void NullStream_Close(struct SolidSyslogStream* base) +{ + (void) base; +} diff --git a/DEVLOG.md b/DEVLOG.md index a11b3b60..fe24abab 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,156 @@ # Dev Log +## 2026-05-19 — S11.06: POSIX adapters onto PoolAllocator + +Closes S11.06 (#405). The first platform-adapter sweep in E11 — applies the +canonical 3-TU split + `SolidSyslogPoolAllocator` to all six stateful POSIX +adapter classes (`PosixMutex`, `PosixFile`, `PosixTcpStream` — storage-cast; +`PosixDatagram`, `GetAddrInfoResolver`, `PosixMessageQueueBuffer` — +file-scope singleton). + +Twelve commits on the branch: + +- `17101b7 … fce49bb` — four new public GoF nulls land first + (`SolidSyslogNullDatagram`, `_NullStream`, `_NullFile`, `_NullResolver`), + each reused by this story plus the upcoming S11.07/S11.08/S11.09 sweeps. +- `bba44d9` — `SolidSyslogNullMutex` migrated from `_Create`/`_Destroy` to + the `_Get(void)` shape. It was the last stateless GoF null still on the + old shape. Five caller test files updated. +- `f85c73c … 11d3996` — six per-class POSIX migrations, one commit each, + in the order from the issue body. Public `Storage` types and + `SOLIDSYSLOG_*_SIZE` enums deleted from `Platform/Posix/Interface/`; + `_Create` drops the `storage` parameter; `_Destroy(base)` shape adopted + for the three previously `_Destroy(void)` singletons. +- `6957ca1` — local gate sweep cleanup (iwyu, cppcheck CTU false positives, + tidy NOLINT for an `_Initialise` that mirrors a `_Create`'s existing + `bugprone-easily-swappable-parameters` suppression). + +### Decisions + +- **Four GoF nulls bundled in this story up-front.** Same precedent as + S11.04 (`NullSender` + `NullSd`). The pool-exhaustion fallback for + `Datagram` / `Stream` / `File` / `Resolver` is needed *now* by S11.06's + six classes, and by every platform sweep that follows. Bundling the + nulls into this story avoids preceding-story bookkeeping (a la + `NullBuffer` rename in S11.03) and lets the cross-sweep types ship on + the same review surface as their first consumer. + +- **NullDatagram MaxPayload returns `SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD`, + not 0.** MaxPayload is reachable from `UdpSender_RetryAfterOversize`, + and only that path — but since the call site uses the value as a + clip limit, a 0 there would silently drop everything. The IPv6-safe + default (1232) keeps any incidental caller of MaxPayload sane while + the surrounding SendTo-returns-SENT contract still drops the message + on the floor. + +- **NullStream Read returns 0 (would-block), not -1 (EOF/error).** A < 0 + return from Read would force `StreamSender` to flag a broken connection + and tear it down, defeating the "drop on the floor" contract. + Documented in the production source so future Stream impls follow the + same shape for their Null sibling. + +- **NullFile per-method semantics chosen for clean-degrade.** `Open` / + `IsOpen` / `Read` / `Exists` return `false` (presents consistently as a + closed, empty, non-existent file). `Write` / `Delete` return `true` + (drop-on-the-floor for the Write, vacuous-success for the Delete). + In the realistic pool-exhausted scenario the wider chain is already + broken (`BlockStore` on `NullStore`) by the time `NullFile` is + reached, but the semantics here are defensible in isolation too — + any direct caller of `NullFile` degrades cleanly rather than crashing + or spinning on a contradictory state. + +- **NullMutex `_Get` migration sits inside S11.06, not a separate + cleanup.** Asked pre-flight; rolled in here because `PosixMutex`'s + pool-exhaustion fallback wiring needs `NullMutex_Get()` in five test + files anyway, and the other Null-* helpers landing in this story are + already on `_Get`. `Crc16Policy` is the only remaining Null-shaped + class still on `_Create`/`_Destroy` (deliberate — it's not a null + object, it's a stateless policy); deferred to a future small cleanup + if it's worth doing at all. + +- **`GetAddrInfoResolver` stays pool-allocated, not `_Get`.** Mid-sweep + call: the class is *truly stateless today* (its slot just holds the + vtable), and `_Get` would be the more honest shape. Rejected in + favour of symmetry with `FreeRtosStaticResolver` (S11.08) which + carries 4 bytes of IPv4 octet state and therefore needs the pool. + Splitting the two `Resolver` impls onto different lifecycles would + cost long-term consistency for a marginal short-term simplification. + Recorded as an open question in the issue body and explicitly closed + here. + +- **`PosixDatagram` Initialise sets `Fd = INVALID_FD` and + `Connected = false` explicitly.** Pre-migration the file-scope + singleton's `Fd = INVALID_FD` initial value came from the + declaration, and `_Create` never reset it. After Close + a second + Create, the slot relied on Close having set `Fd = -1` and + `Connected = false`. Pool slots are zero-initialised — `Fd = 0` + is a valid FD (stdin) — so the pool migration had to set both + fields explicitly in `_Initialise`. Latent re-Create bug closed + by the migration. + +- **`HandleEqualsStorageAddress` test dropped from + `SolidSyslogPosixMutexTest.cpp`.** Pre-migration it pinned the + invariant "the returned handle equals the address of the storage the + caller supplied" — meaningful for the storage-cast shape, no longer + meaningful when the slot is library-internal. The other existing + tests carry through as the regression net. + +- **Pool-size override validated at 3 for every new tunable.** Built and + ran the full suite at + `SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE=3` / + `_POSIX_DATAGRAM_POOL_SIZE=3` / + `_GETADDRINFO_RESOLVER_POOL_SIZE=3` / + `_POSIX_FILE_POOL_SIZE=3` / + `_POSIX_TCP_STREAM_POOL_SIZE=3` / + `_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE=3` (single + `SOLIDSYSLOG_USER_TUNABLES_FILE` override). All 1258 tests green. + +- **Verified in `cpputest-freertos` container per + `feedback_verify_in_freertos_host_image`.** Full POSIX suite + the + five FreeRTOS-specific host tests (`FreeRtosDatagram`, `FreeRtosMutex`, + `FreeRtosStaticResolver`, `FreeRtosSysUpTime`, `FreeRtosTcpStream`) + all green; the new tunables propagate cleanly through the FreeRTOS + build's CMake config. + +- **All local gates green.** debug + clang-debug + sanitize + coverage + + tidy + cppcheck + iwyu + clang-format. Coverage 100% line on every + new and migrated file (lcov per-file table confirms). cppcheck-misra + produced no new findings vs `main` — the per-class storage-cast + deviations for `PosixMutex` / `PosixFile` / `PosixTcpStream` + evaporated (the bonus E11 promised). + +### Deferred + +- **`Crc16Policy` shape cleanup.** Last class still on `_Create`/ + `_Destroy` for a stateless thing. Not a null object so doesn't need + `_Get`; could become a constexpr-style policy struct or a function + pair. Not worth its own story today; will fall out of S11.10 / S11.11 + cleanups if anything touches it. + +- **POSIX function-adapter consistency review.** `PosixHostname`, + `PosixProcessId`, `PosixSysUpTime`, `PosixSleep`, `PosixClock` all + use bare function names (`SolidSyslogPosix_Get` / + `_GetTimestamp`). Out of scope per E11 (stateless, no Create), but + worth a glance at S11.11 wrap-up to confirm the audience-table rows + still read consistently after the platform sweeps. + +### Open questions + +- **`PosixMessageQueueBuffer` `mq_open` failure mode.** Today's pool + Create returns the slot unconditionally and stashes whatever + `mqd_t` comes back from `mq_open`; the migration preserves that. + An `mq_open` failure should arguably route to the shared + `SolidSyslogNullBuffer` with a separate error message — but that + shifts the bad-setup contract and belongs in E12, not this sweep. + Flagged in the issue body, deliberately deferred. + +- **`PosixTcpStream`'s `SO_SNDTIMEO` bounded-connect timeout** wasn't + exercised by S11.06's pool tests (they only stress the slot + allocator, not the underlying socket logic). The existing TcpStream + suite carries that contract forward unchanged — no regression risk — + but if the timeout semantics ever drift, the failure mode would be + a Service-loop wedge that's not easy to catch in unit tests. + ## 2026-05-19 — BDD target stderr handler: fatal exit on ERROR severity Follow-up from S11.05 part B post-merge. The PR's final CI run took 23m diff --git a/Platform/Posix/CMakeLists.txt b/Platform/Posix/CMakeLists.txt index 20c8e64b..5e673d3f 100644 --- a/Platform/Posix/CMakeLists.txt +++ b/Platform/Posix/CMakeLists.txt @@ -2,15 +2,21 @@ target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogPosixClock.c Source/SolidSyslogPosixHostname.c Source/SolidSyslogPosixMessageQueueBuffer.c + Source/SolidSyslogPosixMessageQueueBufferStatic.c Source/SolidSyslogPosixMutex.c + Source/SolidSyslogPosixMutexStatic.c Source/SolidSyslogPosixProcessId.c Source/SolidSyslogPosixSysUpTime.c Source/SolidSyslogPosixSleep.c Source/SolidSyslogAddress.c Source/SolidSyslogGetAddrInfoResolver.c + Source/SolidSyslogGetAddrInfoResolverStatic.c Source/SolidSyslogPosixDatagram.c + Source/SolidSyslogPosixDatagramStatic.c Source/SolidSyslogPosixTcpStream.c + Source/SolidSyslogPosixTcpStreamStatic.c Source/SolidSyslogPosixFile.c + Source/SolidSyslogPosixFileStatic.c ) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Interface) diff --git a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h index db883525..8590496d 100644 --- a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h +++ b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h @@ -5,8 +5,10 @@ EXTERN_C_BEGIN + struct SolidSyslogResolver; + struct SolidSyslogResolver* SolidSyslogGetAddrInfoResolver_Create(void); - void SolidSyslogGetAddrInfoResolver_Destroy(void); + void SolidSyslogGetAddrInfoResolver_Destroy(struct SolidSyslogResolver * base); EXTERN_C_END diff --git a/Platform/Posix/Interface/SolidSyslogPosixDatagram.h b/Platform/Posix/Interface/SolidSyslogPosixDatagram.h index d8e5fbcd..a4d06896 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixDatagram.h +++ b/Platform/Posix/Interface/SolidSyslogPosixDatagram.h @@ -5,8 +5,10 @@ EXTERN_C_BEGIN + struct SolidSyslogDatagram; + struct SolidSyslogDatagram* SolidSyslogPosixDatagram_Create(void); - void SolidSyslogPosixDatagram_Destroy(void); + void SolidSyslogPosixDatagram_Destroy(struct SolidSyslogDatagram * base); EXTERN_C_END diff --git a/Platform/Posix/Interface/SolidSyslogPosixFile.h b/Platform/Posix/Interface/SolidSyslogPosixFile.h index 472e39f9..245504a5 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixFile.h +++ b/Platform/Posix/Interface/SolidSyslogPosixFile.h @@ -1,25 +1,13 @@ #ifndef SOLIDSYSLOGPOSIXFILE_H #define SOLIDSYSLOGPOSIXFILE_H -#include - #include "ExternC.h" struct SolidSyslogFile; EXTERN_C_BEGIN - enum - { - SOLIDSYSLOG_POSIX_FILE_SIZE = sizeof(intptr_t) * 11U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_POSIX_FILE_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogPosixFileStorage; - - struct SolidSyslogFile* SolidSyslogPosixFile_Create(SolidSyslogPosixFileStorage * storage); + struct SolidSyslogFile* SolidSyslogPosixFile_Create(void); void SolidSyslogPosixFile_Destroy(struct SolidSyslogFile * base); EXTERN_C_END diff --git a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h index a513f116..a8fafa98 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h @@ -7,8 +7,10 @@ EXTERN_C_BEGIN + struct SolidSyslogBuffer; + struct SolidSyslogBuffer* SolidSyslogPosixMessageQueueBuffer_Create(size_t maxMessageSize, long maxMessages); - void SolidSyslogPosixMessageQueueBuffer_Destroy(void); + void SolidSyslogPosixMessageQueueBuffer_Destroy(struct SolidSyslogBuffer * base); EXTERN_C_END diff --git a/Platform/Posix/Interface/SolidSyslogPosixMutex.h b/Platform/Posix/Interface/SolidSyslogPosixMutex.h index 1d9c5a52..c16b91ce 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMutex.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMutex.h @@ -3,23 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogMutex; - enum - { - SOLIDSYSLOG_POSIX_MUTEX_SIZE = sizeof(intptr_t) * 10U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_POSIX_MUTEX_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogPosixMutexStorage; - - struct SolidSyslogMutex* SolidSyslogPosixMutex_Create(SolidSyslogPosixMutexStorage * storage); + struct SolidSyslogMutex* SolidSyslogPosixMutex_Create(void); void SolidSyslogPosixMutex_Destroy(struct SolidSyslogMutex * base); EXTERN_C_END diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h index 6e9b3697..f1983a1c 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h @@ -1,25 +1,13 @@ #ifndef SOLIDSYSLOGPOSIXTCPSTREAM_H #define SOLIDSYSLOGPOSIXTCPSTREAM_H -#include - #include "ExternC.h" -struct SolidSyslogStream; - EXTERN_C_BEGIN - enum - { - SOLIDSYSLOG_POSIX_TCP_STREAM_SIZE = sizeof(intptr_t) * 5U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_POSIX_TCP_STREAM_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogPosixTcpStreamStorage; + struct SolidSyslogStream; - struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(SolidSyslogPosixTcpStreamStorage * storage); + struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void); void SolidSyslogPosixTcpStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c b/Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c index 74ad459b..79c132b8 100644 --- a/Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c +++ b/Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c @@ -1,14 +1,16 @@ #include "SolidSyslogGetAddrInfoResolver.h" +#include #include +#include #include #include -#include -#include -#include #include +#include #include "SolidSyslogAddressInternal.h" +#include "SolidSyslogGetAddrInfoResolverPrivate.h" +#include "SolidSyslogNullResolver.h" #include "SolidSyslogResolverDefinition.h" #include "SolidSyslogTransport.h" @@ -28,22 +30,16 @@ static bool GetAddrInfoResolver_Resolve( ); static int GetAddrInfoResolver_MapTransport(enum SolidSyslogTransport transport); -struct SolidSyslogGetAddrInfoResolver -{ - struct SolidSyslogResolver Base; -}; - -static struct SolidSyslogGetAddrInfoResolver instance; - -struct SolidSyslogResolver* SolidSyslogGetAddrInfoResolver_Create(void) +void GetAddrInfoResolver_Initialise(struct SolidSyslogResolver* base) { - instance.Base.Resolve = GetAddrInfoResolver_Resolve; - return &instance.Base; + base->Resolve = GetAddrInfoResolver_Resolve; } -void SolidSyslogGetAddrInfoResolver_Destroy(void) +void GetAddrInfoResolver_Cleanup(struct SolidSyslogResolver* base) { - instance.Base.Resolve = NULL; + /* Overwrite the abstract base with the shared NullResolver vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullResolver_Get(); } static bool GetAddrInfoResolver_Resolve( diff --git a/Platform/Posix/Source/SolidSyslogGetAddrInfoResolverPrivate.h b/Platform/Posix/Source/SolidSyslogGetAddrInfoResolverPrivate.h new file mode 100644 index 00000000..2aeb8df6 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogGetAddrInfoResolverPrivate.h @@ -0,0 +1,14 @@ +#ifndef SOLIDSYSLOGGETADDRINFORESOLVERPRIVATE_H +#define SOLIDSYSLOGGETADDRINFORESOLVERPRIVATE_H + +#include "SolidSyslogResolverDefinition.h" + +struct SolidSyslogGetAddrInfoResolver +{ + struct SolidSyslogResolver Base; +}; + +void GetAddrInfoResolver_Initialise(struct SolidSyslogResolver* base); +void GetAddrInfoResolver_Cleanup(struct SolidSyslogResolver* base); + +#endif /* SOLIDSYSLOGGETADDRINFORESOLVERPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogGetAddrInfoResolverStatic.c b/Platform/Posix/Source/SolidSyslogGetAddrInfoResolverStatic.c new file mode 100644 index 00000000..bcc8a73f --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogGetAddrInfoResolverStatic.c @@ -0,0 +1,76 @@ +#include "SolidSyslogGetAddrInfoResolver.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogGetAddrInfoResolverPrivate.h" +#include "SolidSyslogNullResolver.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogResolver; + +static size_t GetAddrInfoResolver_IndexFromHandle(const struct SolidSyslogResolver* base); +static void GetAddrInfoResolver_CleanupAtIndex(size_t index, void* context); + +static bool GetAddrInfoResolver_InUse[SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE]; +static struct SolidSyslogGetAddrInfoResolver GetAddrInfoResolver_Pool[SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator GetAddrInfoResolver_Allocator = { + GetAddrInfoResolver_InUse, + SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE +}; + +struct SolidSyslogResolver* SolidSyslogGetAddrInfoResolver_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&GetAddrInfoResolver_Allocator); + struct SolidSyslogResolver* handle = SolidSyslogNullResolver_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&GetAddrInfoResolver_Allocator, index)) + { + GetAddrInfoResolver_Initialise(&GetAddrInfoResolver_Pool[index].Base); + handle = &GetAddrInfoResolver_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogGetAddrInfoResolver_Destroy(struct SolidSyslogResolver* base) +{ + size_t index = GetAddrInfoResolver_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&GetAddrInfoResolver_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &GetAddrInfoResolver_Allocator, + index, + GetAddrInfoResolver_CleanupAtIndex, + NULL + ); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_UNKNOWN_DESTROY); + } +} + +static size_t GetAddrInfoResolver_IndexFromHandle(const struct SolidSyslogResolver* base) +{ + size_t result = SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE; poolIndex++) + { + if (base == &GetAddrInfoResolver_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void GetAddrInfoResolver_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + GetAddrInfoResolver_Cleanup(&GetAddrInfoResolver_Pool[index].Base); +} diff --git a/Platform/Posix/Source/SolidSyslogPosixDatagram.c b/Platform/Posix/Source/SolidSyslogPosixDatagram.c index 9d30381a..6a8253be 100644 --- a/Platform/Posix/Source/SolidSyslogPosixDatagram.c +++ b/Platform/Posix/Source/SolidSyslogPosixDatagram.c @@ -5,13 +5,15 @@ #include #include #include -#include #include +#include #include "SolidSyslogAddressInternal.h" +#include "SolidSyslogDatagram.h" #include "SolidSyslogDatagramDefinition.h" +#include "SolidSyslogNullDatagram.h" +#include "SolidSyslogPosixDatagramPrivate.h" #include "SolidSyslogUdpPayload.h" -#include "SolidSyslogDatagram.h" struct SolidSyslogAddress; @@ -20,13 +22,6 @@ enum INVALID_FD = -1 }; -struct SolidSyslogPosixDatagram -{ - struct SolidSyslogDatagram Base; - int Fd; - bool Connected; -}; - static bool PosixDatagram_Open(struct SolidSyslogDatagram* base); static enum SolidSyslogDatagramSendResult PosixDatagram_SendTo( struct SolidSyslogDatagram* base, @@ -44,23 +39,29 @@ static inline bool PosixDatagram_ConnectIfNeeded( ); static inline bool PosixDatagram_IsFileDescriptorValid(int fd); -static struct SolidSyslogPosixDatagram instance = {.Fd = INVALID_FD}; - -struct SolidSyslogDatagram* SolidSyslogPosixDatagram_Create(void) +void PosixDatagram_Initialise(struct SolidSyslogDatagram* base) { - instance.Base.Open = PosixDatagram_Open; - instance.Base.SendTo = PosixDatagram_SendTo; - instance.Base.MaxPayload = PosixDatagram_MaxPayload; - instance.Base.Close = PosixDatagram_Close; - return &instance.Base; + struct SolidSyslogPosixDatagram* self = PosixDatagram_SelfFromBase(base); + self->Base.Open = PosixDatagram_Open; + self->Base.SendTo = PosixDatagram_SendTo; + self->Base.MaxPayload = PosixDatagram_MaxPayload; + self->Base.Close = PosixDatagram_Close; + self->Fd = INVALID_FD; + self->Connected = false; } -void SolidSyslogPosixDatagram_Destroy(void) +void PosixDatagram_Cleanup(struct SolidSyslogDatagram* base) { - instance.Base.Open = NULL; - instance.Base.SendTo = NULL; - instance.Base.MaxPayload = NULL; - instance.Base.Close = NULL; + struct SolidSyslogPosixDatagram* self = PosixDatagram_SelfFromBase(base); + if (PosixDatagram_IsFileDescriptorValid(self->Fd)) + { + close(self->Fd); + self->Fd = INVALID_FD; + self->Connected = false; + } + /* Overwrite the abstract base with the shared NullDatagram vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullDatagram_Get(); } static bool PosixDatagram_Open(struct SolidSyslogDatagram* base) diff --git a/Platform/Posix/Source/SolidSyslogPosixDatagramPrivate.h b/Platform/Posix/Source/SolidSyslogPosixDatagramPrivate.h new file mode 100644 index 00000000..bf9951c8 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixDatagramPrivate.h @@ -0,0 +1,18 @@ +#ifndef SOLIDSYSLOGPOSIXDATAGRAMPRIVATE_H +#define SOLIDSYSLOGPOSIXDATAGRAMPRIVATE_H + +#include + +#include "SolidSyslogDatagramDefinition.h" + +struct SolidSyslogPosixDatagram +{ + struct SolidSyslogDatagram Base; + int Fd; + bool Connected; +}; + +void PosixDatagram_Initialise(struct SolidSyslogDatagram* base); +void PosixDatagram_Cleanup(struct SolidSyslogDatagram* base); + +#endif /* SOLIDSYSLOGPOSIXDATAGRAMPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogPosixDatagramStatic.c b/Platform/Posix/Source/SolidSyslogPosixDatagramStatic.c new file mode 100644 index 00000000..afd0666f --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixDatagramStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogPosixDatagram.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullDatagram.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPosixDatagramPrivate.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogDatagram; + +static size_t PosixDatagram_IndexFromHandle(const struct SolidSyslogDatagram* base); +static void PosixDatagram_CleanupAtIndex(size_t index, void* context); + +static bool PosixDatagram_InUse[SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE]; +static struct SolidSyslogPosixDatagram PosixDatagram_Pool[SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator PosixDatagram_Allocator = { + PosixDatagram_InUse, + SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE +}; + +struct SolidSyslogDatagram* SolidSyslogPosixDatagram_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PosixDatagram_Allocator); + struct SolidSyslogDatagram* handle = SolidSyslogNullDatagram_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&PosixDatagram_Allocator, index)) + { + PosixDatagram_Initialise(&PosixDatagram_Pool[index].Base); + handle = &PosixDatagram_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogPosixDatagram_Destroy(struct SolidSyslogDatagram* base) +{ + size_t index = PosixDatagram_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&PosixDatagram_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&PosixDatagram_Allocator, index, PosixDatagram_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_UNKNOWN_DESTROY); + } +} + +static size_t PosixDatagram_IndexFromHandle(const struct SolidSyslogDatagram* base) +{ + size_t result = SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE; poolIndex++) + { + if (base == &PosixDatagram_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void PosixDatagram_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PosixDatagram_Cleanup(&PosixDatagram_Pool[index].Base); +} diff --git a/Platform/Posix/Source/SolidSyslogPosixFile.c b/Platform/Posix/Source/SolidSyslogPosixFile.c index 9a8f5956..cb8c9423 100644 --- a/Platform/Posix/Source/SolidSyslogPosixFile.c +++ b/Platform/Posix/Source/SolidSyslogPosixFile.c @@ -1,14 +1,15 @@ #include "SolidSyslogPosixFile.h" #include -#include -#include #include #include +#include #include +#include #include "SolidSyslogFileDefinition.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogPosixFilePrivate.h" #define OWNER_READ_WRITE (S_IRUSR | S_IWUSR) #define DEFAULT_FILE_PERMISSIONS OWNER_READ_WRITE @@ -29,61 +30,35 @@ static void PosixFile_Truncate(struct SolidSyslogFile* base); static bool PosixFile_Exists(struct SolidSyslogFile* base, const char* path); static bool PosixFile_Delete(struct SolidSyslogFile* base, const char* path); -static inline struct SolidSyslogPosixFile* PosixFile_SelfFromStorage(SolidSyslogPosixFileStorage* storage); static inline struct SolidSyslogPosixFile* PosixFile_SelfFromBase(struct SolidSyslogFile* base); -struct SolidSyslogPosixFile +void PosixFile_Initialise(struct SolidSyslogFile* base) { - struct SolidSyslogFile Base; - int Fd; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogPosixFile) <= sizeof(SolidSyslogPosixFileStorage), - "SOLIDSYSLOG_POSIX_FILE_SIZE is too small for struct SolidSyslogPosixFile" -); - -static const struct SolidSyslogPosixFile DEFAULT_INSTANCE = { - {PosixFile_Open, - PosixFile_Close, - PosixFile_IsOpen, - PosixFile_Read, - PosixFile_Write, - PosixFile_SeekTo, - PosixFile_Size, - PosixFile_Truncate, - PosixFile_Exists, - PosixFile_Delete}, - INVALID_FD, -}; - -static const struct SolidSyslogPosixFile DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, - INVALID_FD, -}; - -struct SolidSyslogFile* SolidSyslogPosixFile_Create(SolidSyslogPosixFileStorage* storage) -{ - struct SolidSyslogPosixFile* self = PosixFile_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; -} - -static inline struct SolidSyslogPosixFile* PosixFile_SelfFromStorage(SolidSyslogPosixFileStorage* storage) -{ - return (struct SolidSyslogPosixFile*) storage; + struct SolidSyslogPosixFile* self = PosixFile_SelfFromBase(base); + self->Base.Open = PosixFile_Open; + self->Base.Close = PosixFile_Close; + self->Base.IsOpen = PosixFile_IsOpen; + self->Base.Read = PosixFile_Read; + self->Base.Write = PosixFile_Write; + self->Base.SeekTo = PosixFile_SeekTo; + self->Base.Size = PosixFile_Size; + self->Base.Truncate = PosixFile_Truncate; + self->Base.Exists = PosixFile_Exists; + self->Base.Delete = PosixFile_Delete; + self->Fd = INVALID_FD; } -void SolidSyslogPosixFile_Destroy(struct SolidSyslogFile* base) +void PosixFile_Cleanup(struct SolidSyslogFile* base) { struct SolidSyslogPosixFile* self = PosixFile_SelfFromBase(base); - if (self->Fd != INVALID_FD) { close(self->Fd); + self->Fd = INVALID_FD; } - - *self = DESTROYED_INSTANCE; + /* 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(); } static inline struct SolidSyslogPosixFile* PosixFile_SelfFromBase(struct SolidSyslogFile* base) @@ -101,7 +76,6 @@ static bool PosixFile_Open(struct SolidSyslogFile* base, const char* path) static void PosixFile_Close(struct SolidSyslogFile* base) { struct SolidSyslogPosixFile* self = PosixFile_SelfFromBase(base); - if (self->Fd != INVALID_FD) { close(self->Fd); diff --git a/Platform/Posix/Source/SolidSyslogPosixFilePrivate.h b/Platform/Posix/Source/SolidSyslogPosixFilePrivate.h new file mode 100644 index 00000000..2ed3b584 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixFilePrivate.h @@ -0,0 +1,15 @@ +#ifndef SOLIDSYSLOGPOSIXFILEPRIVATE_H +#define SOLIDSYSLOGPOSIXFILEPRIVATE_H + +#include "SolidSyslogFileDefinition.h" + +struct SolidSyslogPosixFile +{ + struct SolidSyslogFile Base; + int Fd; +}; + +void PosixFile_Initialise(struct SolidSyslogFile* base); +void PosixFile_Cleanup(struct SolidSyslogFile* base); + +#endif /* SOLIDSYSLOGPOSIXFILEPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogPosixFileStatic.c b/Platform/Posix/Source/SolidSyslogPosixFileStatic.c new file mode 100644 index 00000000..a2580a04 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixFileStatic.c @@ -0,0 +1,68 @@ +#include "SolidSyslogPosixFile.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPosixFilePrivate.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogFile; + +static size_t PosixFile_IndexFromHandle(const struct SolidSyslogFile* base); +static void PosixFile_CleanupAtIndex(size_t index, void* context); + +static bool PosixFile_InUse[SOLIDSYSLOG_POSIX_FILE_POOL_SIZE]; +static struct SolidSyslogPosixFile PosixFile_Pool[SOLIDSYSLOG_POSIX_FILE_POOL_SIZE]; +static struct SolidSyslogPoolAllocator PosixFile_Allocator = {PosixFile_InUse, SOLIDSYSLOG_POSIX_FILE_POOL_SIZE}; + +struct SolidSyslogFile* SolidSyslogPosixFile_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PosixFile_Allocator); + struct SolidSyslogFile* handle = SolidSyslogNullFile_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&PosixFile_Allocator, index)) + { + PosixFile_Initialise(&PosixFile_Pool[index].Base); + handle = &PosixFile_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_POSIXFILE_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogPosixFile_Destroy(struct SolidSyslogFile* base) +{ + size_t index = PosixFile_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&PosixFile_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&PosixFile_Allocator, index, PosixFile_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_POSIXFILE_UNKNOWN_DESTROY); + } +} + +static size_t PosixFile_IndexFromHandle(const struct SolidSyslogFile* base) +{ + size_t result = SOLIDSYSLOG_POSIX_FILE_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_POSIX_FILE_POOL_SIZE; poolIndex++) + { + if (base == &PosixFile_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void PosixFile_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PosixFile_Cleanup(&PosixFile_Pool[index].Base); +} diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c index 5711857b..91342f5a 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c @@ -1,17 +1,20 @@ #include "SolidSyslogPosixMessageQueueBuffer.h" -#include #include +#include #include +#include +#include #include #include "SolidSyslogBufferDefinition.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogNullBuffer.h" +#include "SolidSyslogPosixMessageQueueBufferPrivate.h" #include "SolidSyslogPosixProcessId.h" enum { - MAX_NAME_SIZE = 64, /* 0600 in octal — owner read/write, equivalent to S_IRUSR | S_IWUSR. Hex form avoids MISRA 7.1. */ OWNER_READ_WRITE = 0x180U }; @@ -22,54 +25,54 @@ static void PosixMessageQueueBuffer_Write(struct SolidSyslogBuffer* base, const static inline struct SolidSyslogPosixMessageQueueBuffer* PosixMessageQueueBuffer_SelfFromBase( struct SolidSyslogBuffer* base ); - -struct SolidSyslogPosixMessageQueueBuffer -{ - struct SolidSyslogBuffer Base; - mqd_t Mq; - SolidSyslogFormatterStorage NameStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(MAX_NAME_SIZE)]; - size_t MaxMessageSize; -}; - -static struct SolidSyslogPosixMessageQueueBuffer PosixMessageQueueBuffer_Instance; - -static inline const char* PosixMessageQueueBuffer_QueueName(void) -{ - return SolidSyslogFormatter_AsFormattedBuffer( - SolidSyslogFormatter_FromStorage(PosixMessageQueueBuffer_Instance.NameStorage) - ); -} - -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- distinct semantic meaning; struct wrapper would over-engineer -struct SolidSyslogBuffer* SolidSyslogPosixMessageQueueBuffer_Create(size_t maxMessageSize, long maxMessages) +static inline const char* PosixMessageQueueBuffer_QueueName(struct SolidSyslogPosixMessageQueueBuffer* self); + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) -- distinct semantic meaning; mirrors the public _Create signature plus a per-slot discriminator +void PosixMessageQueueBuffer_Initialise( + struct SolidSyslogBuffer* base, + size_t maxMessageSize, + long maxMessages, + size_t slotIndex +) +// NOLINTEND(bugprone-easily-swappable-parameters) { static const char queueNamePrefix[] = "/solidsyslog_"; - PosixMessageQueueBuffer_Instance = (struct SolidSyslogPosixMessageQueueBuffer) {0}; + struct SolidSyslogPosixMessageQueueBuffer* self = PosixMessageQueueBuffer_SelfFromBase(base); + /* Queue name: /solidsyslog__. The pid keeps the name + * unique per process; the slot index keeps multiple in-process pool + * entries from aliasing onto the same kernel queue object. */ struct SolidSyslogFormatter* name = - SolidSyslogFormatter_Create(PosixMessageQueueBuffer_Instance.NameStorage, MAX_NAME_SIZE); + SolidSyslogFormatter_Create(self->NameStorage, POSIX_MESSAGE_QUEUE_BUFFER_MAX_NAME_SIZE); SolidSyslogFormatter_BoundedString(name, queueNamePrefix, sizeof(queueNamePrefix) - 1U); SolidSyslogPosixProcessId_Get(name); + SolidSyslogFormatter_AsciiCharacter(name, '_'); + SolidSyslogFormatter_Uint32(name, (uint32_t) slotIndex); struct mq_attr attr = {0}; attr.mq_maxmsg = maxMessages; attr.mq_msgsize = (long) maxMessageSize; - PosixMessageQueueBuffer_Instance.Mq = - mq_open(PosixMessageQueueBuffer_QueueName(), O_CREAT | O_RDWR | O_NONBLOCK, OWNER_READ_WRITE, &attr); - PosixMessageQueueBuffer_Instance.MaxMessageSize = maxMessageSize; - PosixMessageQueueBuffer_Instance.Base.Write = PosixMessageQueueBuffer_Write; - PosixMessageQueueBuffer_Instance.Base.Read = PosixMessageQueueBuffer_Read; + self->Mq = mq_open(PosixMessageQueueBuffer_QueueName(self), O_CREAT | O_RDWR | O_NONBLOCK, OWNER_READ_WRITE, &attr); + self->MaxMessageSize = maxMessageSize; + self->Base.Write = PosixMessageQueueBuffer_Write; + self->Base.Read = PosixMessageQueueBuffer_Read; +} - return &PosixMessageQueueBuffer_Instance.Base; +void PosixMessageQueueBuffer_Cleanup(struct SolidSyslogBuffer* base) +{ + struct SolidSyslogPosixMessageQueueBuffer* self = PosixMessageQueueBuffer_SelfFromBase(base); + mq_close(self->Mq); + mq_unlink(PosixMessageQueueBuffer_QueueName(self)); + /* Overwrite the abstract base with the shared NullBuffer vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullBuffer_Get(); } -void SolidSyslogPosixMessageQueueBuffer_Destroy(void) +static inline const char* PosixMessageQueueBuffer_QueueName(struct SolidSyslogPosixMessageQueueBuffer* self) { - mq_close(PosixMessageQueueBuffer_Instance.Mq); - mq_unlink(PosixMessageQueueBuffer_QueueName()); - PosixMessageQueueBuffer_Instance = (struct SolidSyslogPosixMessageQueueBuffer) {0}; + return SolidSyslogFormatter_AsFormattedBuffer(SolidSyslogFormatter_FromStorage(self->NameStorage)); } static bool PosixMessageQueueBuffer_Read(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h new file mode 100644 index 00000000..43a2272d --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferPrivate.h @@ -0,0 +1,32 @@ +#ifndef SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERPRIVATE_H +#define SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERPRIVATE_H + +#include +#include + +#include "SolidSyslogBufferDefinition.h" +#include "SolidSyslogFormatter.h" + +enum +{ + POSIX_MESSAGE_QUEUE_BUFFER_MAX_NAME_SIZE = 64 +}; + +struct SolidSyslogPosixMessageQueueBuffer +{ + struct SolidSyslogBuffer Base; + mqd_t Mq; + SolidSyslogFormatterStorage + NameStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(POSIX_MESSAGE_QUEUE_BUFFER_MAX_NAME_SIZE)]; + size_t MaxMessageSize; +}; + +void PosixMessageQueueBuffer_Initialise( + struct SolidSyslogBuffer* base, + size_t maxMessageSize, + long maxMessages, + size_t slotIndex +); +void PosixMessageQueueBuffer_Cleanup(struct SolidSyslogBuffer* base); + +#endif /* SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c new file mode 100644 index 00000000..88435a28 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBufferStatic.c @@ -0,0 +1,88 @@ +#include "SolidSyslogPosixMessageQueueBuffer.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullBuffer.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPosixMessageQueueBufferPrivate.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogBuffer; + +/* Each pool slot's queue name is `/solidsyslog__`. The + * pid keeps the name unique per process; the slot index keeps multiple + * in-process pool entries from aliasing onto the same kernel queue + * object when the pool tunable is bumped above 1. */ + +static size_t PosixMessageQueueBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base); +static void PosixMessageQueueBuffer_CleanupAtIndex(size_t index, void* context); + +static bool PosixMessageQueueBuffer_InUse[SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE]; +static struct SolidSyslogPosixMessageQueueBuffer + PosixMessageQueueBuffer_Pool[SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator PosixMessageQueueBuffer_Allocator = { + PosixMessageQueueBuffer_InUse, + SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE +}; + +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- distinct semantic meaning; struct wrapper would over-engineer +struct SolidSyslogBuffer* SolidSyslogPosixMessageQueueBuffer_Create(size_t maxMessageSize, long maxMessages) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PosixMessageQueueBuffer_Allocator); + struct SolidSyslogBuffer* handle = SolidSyslogNullBuffer_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&PosixMessageQueueBuffer_Allocator, index)) + { + PosixMessageQueueBuffer_Initialise( + &PosixMessageQueueBuffer_Pool[index].Base, + maxMessageSize, + maxMessages, + index + ); + handle = &PosixMessageQueueBuffer_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogPosixMessageQueueBuffer_Destroy(struct SolidSyslogBuffer* base) +{ + size_t index = PosixMessageQueueBuffer_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&PosixMessageQueueBuffer_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &PosixMessageQueueBuffer_Allocator, + index, + PosixMessageQueueBuffer_CleanupAtIndex, + NULL + ); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_UNKNOWN_DESTROY); + } +} + +static size_t PosixMessageQueueBuffer_IndexFromHandle(const struct SolidSyslogBuffer* base) +{ + size_t result = SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE; poolIndex++) + { + if (base == &PosixMessageQueueBuffer_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void PosixMessageQueueBuffer_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PosixMessageQueueBuffer_Cleanup(&PosixMessageQueueBuffer_Pool[index].Base); +} diff --git a/Platform/Posix/Source/SolidSyslogPosixMutex.c b/Platform/Posix/Source/SolidSyslogPosixMutex.c index b90c43be..523ff809 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMutex.c +++ b/Platform/Posix/Source/SolidSyslogPosixMutex.c @@ -3,46 +3,43 @@ #include #include -#include "SolidSyslogMacros.h" #include "SolidSyslogMutexDefinition.h" - -struct SolidSyslogPosixMutex -{ - struct SolidSyslogMutex Base; - pthread_mutex_t Mutex; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogPosixMutex) <= SOLIDSYSLOG_POSIX_MUTEX_SIZE, - "SOLIDSYSLOG_POSIX_MUTEX_SIZE is too small for SolidSyslogPosixMutex layout" -); +#include "SolidSyslogNullMutex.h" +#include "SolidSyslogPosixMutexPrivate.h" static void PosixMutex_Lock(struct SolidSyslogMutex* base); static void PosixMutex_Unlock(struct SolidSyslogMutex* base); -static inline struct SolidSyslogPosixMutex* PosixMutex_SelfFromStorage(SolidSyslogPosixMutexStorage* storage); static inline struct SolidSyslogPosixMutex* PosixMutex_SelfFromBase(struct SolidSyslogMutex* base); -struct SolidSyslogMutex* SolidSyslogPosixMutex_Create(SolidSyslogPosixMutexStorage* storage) +void PosixMutex_Initialise(struct SolidSyslogMutex* base) { - struct SolidSyslogPosixMutex* self = PosixMutex_SelfFromStorage(storage); - self->Base.Lock = PosixMutex_Lock; - self->Base.Unlock = PosixMutex_Unlock; - pthread_mutex_init(&self->Mutex, NULL); - return &self->Base; -} - -static inline struct SolidSyslogPosixMutex* PosixMutex_SelfFromStorage(SolidSyslogPosixMutexStorage* storage) -{ - return (struct SolidSyslogPosixMutex*) storage; + struct SolidSyslogPosixMutex* self = PosixMutex_SelfFromBase(base); + if (pthread_mutex_init(&self->Mutex, NULL) == 0) + { + self->Base.Lock = PosixMutex_Lock; + self->Base.Unlock = PosixMutex_Unlock; + } + else + { + /* pthread_mutex_init failed (ENOMEM, EAGAIN). Install the shared + * NullMutex vtable so Lock/Unlock are safe no-ops; Cleanup will + * see the marker and skip pthread_mutex_destroy on the + * uninitialised native handle. */ + *base = *SolidSyslogNullMutex_Get(); + } } -void SolidSyslogPosixMutex_Destroy(struct SolidSyslogMutex* base) +void PosixMutex_Cleanup(struct SolidSyslogMutex* base) { struct SolidSyslogPosixMutex* self = PosixMutex_SelfFromBase(base); - pthread_mutex_destroy(&self->Mutex); - self->Base.Lock = NULL; - self->Base.Unlock = NULL; + if (self->Base.Lock == PosixMutex_Lock) + { + pthread_mutex_destroy(&self->Mutex); + } + /* Overwrite the abstract base with the shared NullMutex vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullMutex_Get(); } static inline struct SolidSyslogPosixMutex* PosixMutex_SelfFromBase(struct SolidSyslogMutex* base) diff --git a/Platform/Posix/Source/SolidSyslogPosixMutexPrivate.h b/Platform/Posix/Source/SolidSyslogPosixMutexPrivate.h new file mode 100644 index 00000000..94e9b87e --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixMutexPrivate.h @@ -0,0 +1,17 @@ +#ifndef SOLIDSYSLOGPOSIXMUTEXPRIVATE_H +#define SOLIDSYSLOGPOSIXMUTEXPRIVATE_H + +#include + +#include "SolidSyslogMutexDefinition.h" + +struct SolidSyslogPosixMutex +{ + struct SolidSyslogMutex Base; + pthread_mutex_t Mutex; +}; + +void PosixMutex_Initialise(struct SolidSyslogMutex* base); +void PosixMutex_Cleanup(struct SolidSyslogMutex* base); + +#endif /* SOLIDSYSLOGPOSIXMUTEXPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogPosixMutexStatic.c b/Platform/Posix/Source/SolidSyslogPosixMutexStatic.c new file mode 100644 index 00000000..18cbcbe8 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixMutexStatic.c @@ -0,0 +1,68 @@ +#include "SolidSyslogPosixMutex.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullMutex.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPosixMutexPrivate.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogMutex; + +static size_t PosixMutex_IndexFromHandle(const struct SolidSyslogMutex* base); +static void PosixMutex_CleanupAtIndex(size_t index, void* context); + +static bool PosixMutex_InUse[SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE]; +static struct SolidSyslogPosixMutex PosixMutex_Pool[SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE]; +static struct SolidSyslogPoolAllocator PosixMutex_Allocator = {PosixMutex_InUse, SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE}; + +struct SolidSyslogMutex* SolidSyslogPosixMutex_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PosixMutex_Allocator); + struct SolidSyslogMutex* handle = SolidSyslogNullMutex_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&PosixMutex_Allocator, index)) + { + PosixMutex_Initialise(&PosixMutex_Pool[index].Base); + handle = &PosixMutex_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogPosixMutex_Destroy(struct SolidSyslogMutex* base) +{ + size_t index = PosixMutex_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&PosixMutex_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&PosixMutex_Allocator, index, PosixMutex_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_UNKNOWN_DESTROY); + } +} + +static size_t PosixMutex_IndexFromHandle(const struct SolidSyslogMutex* base) +{ + size_t result = SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE; poolIndex++) + { + if (base == &PosixMutex_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void PosixMutex_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PosixMutex_Cleanup(&PosixMutex_Pool[index].Base); +} diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 4aec7319..8bd40368 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -13,7 +13,8 @@ #include #include "SolidSyslogAddressInternal.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogPosixTcpStreamPrivate.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" @@ -36,19 +37,11 @@ enum USER_TIMEOUT_MILLISECONDS = 30000 }; -struct SolidSyslogPosixTcpStream -{ - struct SolidSyslogStream Base; - int Fd; -}; - static bool PosixTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static bool PosixTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize PosixTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void PosixTcpStream_Close(struct SolidSyslogStream* base); -static inline struct SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromStorage(SolidSyslogPosixTcpStreamStorage* storage -); static inline struct SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromBase(struct SolidSyslogStream* base); static int PosixTcpStream_OpenAndConfigureSocket(void); @@ -67,39 +60,27 @@ static bool PosixTcpStream_ReadDeferredConnectError(int fd); static bool PosixTcpStream_WroteAllBytes(ssize_t sent, size_t expected); static inline bool PosixTcpStream_WouldBlock(void); -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogPosixTcpStream) <= SOLIDSYSLOG_POSIX_TCP_STREAM_SIZE, - "SOLIDSYSLOG_POSIX_TCP_STREAM_SIZE is too small for struct SolidSyslogPosixTcpStream" -); - -static const struct SolidSyslogPosixTcpStream DEFAULT_INSTANCE = { - {PosixTcpStream_Open, PosixTcpStream_Send, PosixTcpStream_Read, PosixTcpStream_Close}, - INVALID_FD, -}; - -static const struct SolidSyslogPosixTcpStream DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL}, - INVALID_FD, -}; - -struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(SolidSyslogPosixTcpStreamStorage* storage) +void PosixTcpStream_Initialise(struct SolidSyslogStream* base) { - struct SolidSyslogPosixTcpStream* self = PosixTcpStream_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; -} - -static inline struct SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromStorage(SolidSyslogPosixTcpStreamStorage* storage -) -{ - return (struct SolidSyslogPosixTcpStream*) storage; + struct SolidSyslogPosixTcpStream* self = PosixTcpStream_SelfFromBase(base); + self->Base.Open = PosixTcpStream_Open; + self->Base.Send = PosixTcpStream_Send; + self->Base.Read = PosixTcpStream_Read; + self->Base.Close = PosixTcpStream_Close; + self->Fd = INVALID_FD; } -void SolidSyslogPosixTcpStream_Destroy(struct SolidSyslogStream* base) +void PosixTcpStream_Cleanup(struct SolidSyslogStream* base) { struct SolidSyslogPosixTcpStream* self = PosixTcpStream_SelfFromBase(base); - PosixTcpStream_Close(base); - *self = DESTROYED_INSTANCE; + if (PosixTcpStream_IsFileDescriptorValid(self->Fd)) + { + close(self->Fd); + self->Fd = INVALID_FD; + } + /* 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 SolidSyslogPosixTcpStream* PosixTcpStream_SelfFromBase(struct SolidSyslogStream* base) diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h b/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h new file mode 100644 index 00000000..3b37a165 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStreamPrivate.h @@ -0,0 +1,15 @@ +#ifndef SOLIDSYSLOGPOSIXTCPSTREAMPRIVATE_H +#define SOLIDSYSLOGPOSIXTCPSTREAMPRIVATE_H + +#include "SolidSyslogStreamDefinition.h" + +struct SolidSyslogPosixTcpStream +{ + struct SolidSyslogStream Base; + int Fd; +}; + +void PosixTcpStream_Initialise(struct SolidSyslogStream* base); +void PosixTcpStream_Cleanup(struct SolidSyslogStream* base); + +#endif /* SOLIDSYSLOGPOSIXTCPSTREAMPRIVATE_H */ diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c b/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c new file mode 100644 index 00000000..4a9a2727 --- /dev/null +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStreamStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogPosixTcpStream.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPosixTcpStreamPrivate.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogStream; + +static size_t PosixTcpStream_IndexFromHandle(const struct SolidSyslogStream* base); +static void PosixTcpStream_CleanupAtIndex(size_t index, void* context); + +static bool PosixTcpStream_InUse[SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE]; +static struct SolidSyslogPosixTcpStream PosixTcpStream_Pool[SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator PosixTcpStream_Allocator = { + PosixTcpStream_InUse, + SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE +}; + +struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PosixTcpStream_Allocator); + struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&PosixTcpStream_Allocator, index)) + { + PosixTcpStream_Initialise(&PosixTcpStream_Pool[index].Base); + handle = &PosixTcpStream_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogPosixTcpStream_Destroy(struct SolidSyslogStream* base) +{ + size_t index = PosixTcpStream_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&PosixTcpStream_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&PosixTcpStream_Allocator, index, PosixTcpStream_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_UNKNOWN_DESTROY); + } +} + +static size_t PosixTcpStream_IndexFromHandle(const struct SolidSyslogStream* base) +{ + size_t result = SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE; poolIndex++) + { + if (base == &PosixTcpStream_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void PosixTcpStream_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PosixTcpStream_Cleanup(&PosixTcpStream_Pool[index].Base); +} diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index 381cfefa..46459094 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -49,11 +49,13 @@ static uint32_t BddTargetEndpointVersion() // NOLINT(modernize-use-trailing-retu // clang-format off TEST_GROUP(BddTargetServiceThread) { - struct SolidSyslogSender* sender = nullptr; - struct SolidSyslogBuffer* buffer = nullptr; - struct SolidSyslogStore* store = nullptr; + struct SolidSyslogSender* sender = nullptr; + struct SolidSyslogBuffer* buffer = nullptr; + struct SolidSyslogStore* store = nullptr; + struct SolidSyslogDatagram* datagram = nullptr; + struct SolidSyslogResolver* resolver = nullptr; // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro - volatile bool shutdown; + volatile bool shutdown; void setup() override { @@ -65,7 +67,9 @@ TEST_GROUP(BddTargetServiceThread) lastSleepMs = 0; sleepShutdownFlag = nullptr; - SolidSyslogUdpSenderConfig udpConfig = {SolidSyslogGetAddrInfoResolver_Create(), SolidSyslogPosixDatagram_Create(), BddTargetEndpoint, BddTargetEndpointVersion}; + resolver = SolidSyslogGetAddrInfoResolver_Create(); + datagram = SolidSyslogPosixDatagram_Create(); + SolidSyslogUdpSenderConfig udpConfig = {resolver, datagram, BddTargetEndpoint, BddTargetEndpointVersion}; sender = SolidSyslogUdpSender_Create(&udpConfig); buffer = SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); store = SolidSyslogNullStore_Get(); @@ -77,10 +81,10 @@ TEST_GROUP(BddTargetServiceThread) void teardown() override { SolidSyslog_Destroy(); - SolidSyslogPosixMessageQueueBuffer_Destroy(); + SolidSyslogPosixMessageQueueBuffer_Destroy(buffer); SolidSyslogUdpSender_Destroy(sender); - SolidSyslogPosixDatagram_Destroy(); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogPosixDatagram_Destroy(datagram); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } static void Log() diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 84155c17..e4a84e33 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -28,6 +28,10 @@ set(TEST_SOURCES SolidSyslogPoolAllocatorTest.cpp SolidSyslogNullBlockDeviceTest.cpp SolidSyslogNullBufferTest.cpp + SolidSyslogNullDatagramTest.cpp + SolidSyslogNullFileTest.cpp + SolidSyslogNullResolverTest.cpp + SolidSyslogNullStreamTest.cpp SolidSyslogNullMutexTest.cpp SolidSyslogNullSdTest.cpp SolidSyslogNullSenderTest.cpp diff --git a/Tests/Fixtures/SmallMessageSizeTunables.h b/Tests/Fixtures/SmallMessageSizeTunables.h index a65901ce..a515b06d 100644 --- a/Tests/Fixtures/SmallMessageSizeTunables.h +++ b/Tests/Fixtures/SmallMessageSizeTunables.h @@ -1,5 +1,13 @@ -/* Test fixture: drives a smaller-than-default SOLIDSYSLOG_MAX_MESSAGE_SIZE - * via the SOLIDSYSLOG_USER_TUNABLES_FILE override mechanism. Wired in by - * the `tunable-override-debug` CMake preset to prove that the user-config - * override path actually flows through to the compiler. */ +/* Test fixture: drives override values via the SOLIDSYSLOG_USER_TUNABLES_FILE + * mechanism. Wired in by the `tunable-override-debug` CMake preset to + * prove that the user-config override path actually flows through to the + * compiler. + * + * - SOLIDSYSLOG_MAX_MESSAGE_SIZE smaller than the default proves the + * message-size knob. + * - 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). */ #define SOLIDSYSLOG_MAX_MESSAGE_SIZE 512 +#define SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE 2U diff --git a/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp b/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp index 6b213b51..79b67a09 100644 --- a/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp +++ b/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp @@ -229,7 +229,7 @@ TEST_GROUP_BASE(ServiceDrainInterleave, DrainTestFixtureBase) void setup() override { setupBlockDeviceAndPolicy(); - mutex = SolidSyslogNullMutex_Create(); + mutex = SolidSyslogNullMutex_Get(); buffer = SolidSyslogCircularBuffer_Create(mutex, bufferRing, sizeof(bufferRing)); SenderSpy_Init(spy); } @@ -243,7 +243,6 @@ TEST_GROUP_BASE(ServiceDrainInterleave, DrainTestFixtureBase) store = nullptr; } SolidSyslogCircularBuffer_Destroy(buffer); - SolidSyslogNullMutex_Destroy(); teardownBlockDeviceAndPolicy(); } diff --git a/Tests/SolidSyslogBlockStorePosixTest.cpp b/Tests/SolidSyslogBlockStorePosixTest.cpp index 4b6a7607..ce08a458 100644 --- a/Tests/SolidSyslogBlockStorePosixTest.cpp +++ b/Tests/SolidSyslogBlockStorePosixTest.cpp @@ -37,7 +37,6 @@ TEST_GROUP(SolidSyslogBlockStorePosix) static const size_t RECORD_OVERHEAD = 5; /* 2 (magic) + 2 (length) + 1 (sent flag) */ static const size_t ONE_MAX_MSG_RECORD = SOLIDSYSLOG_MAX_MESSAGE_SIZE + RECORD_OVERHEAD; - SolidSyslogPosixFileStorage fileStorage = {}; struct SolidSyslogFile* file = nullptr; struct SolidSyslogBlockDevice* device = nullptr; struct SolidSyslogStore* store = nullptr; @@ -46,7 +45,7 @@ TEST_GROUP(SolidSyslogBlockStorePosix) void setup() override { CleanStoreFiles(); - file = SolidSyslogPosixFile_Create(&fileStorage); + file = SolidSyslogPosixFile_Create(); device = SolidSyslogFileBlockDevice_Create(file, TEST_PATH_PREFIX); std::memset(maxMsg, 'A', sizeof(maxMsg)); } diff --git a/Tests/SolidSyslogCircularBufferTest.cpp b/Tests/SolidSyslogCircularBufferTest.cpp index 8207c6b1..3b10d567 100644 --- a/Tests/SolidSyslogCircularBufferTest.cpp +++ b/Tests/SolidSyslogCircularBufferTest.cpp @@ -92,13 +92,12 @@ TEST_GROUP_BASE(SolidSyslogCircularBuffer, CircularBufferFixture) void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - buffer = SolidSyslogCircularBuffer_Create(SolidSyslogNullMutex_Create(), ring, sizeof(ring)); + buffer = SolidSyslogCircularBuffer_Create(SolidSyslogNullMutex_Get(), ring, sizeof(ring)); } void teardown() override { SolidSyslogCircularBuffer_Destroy(buffer); - SolidSyslogNullMutex_Destroy(); } }; @@ -119,7 +118,7 @@ TEST(SolidSyslogCircularBuffer, UseAfterDestroyIsCrashSafeViaNullBufferVtable) CHECK_FALSE(Read()); LONGS_EQUAL(0, readSize); - buffer = SolidSyslogCircularBuffer_Create(SolidSyslogNullMutex_Create(), ring, sizeof(ring)); // for teardown + buffer = SolidSyslogCircularBuffer_Create(SolidSyslogNullMutex_Get(), ring, sizeof(ring)); // for teardown } TEST(SolidSyslogCircularBuffer, ReadFromEmptyReturnsFalse) @@ -279,13 +278,12 @@ TEST_GROUP_BASE(SolidSyslogCircularBufferSmallRing, CircularBufferFixture) void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - buffer = SolidSyslogCircularBuffer_Create(SolidSyslogNullMutex_Create(), ring, sizeof(ring)); + buffer = SolidSyslogCircularBuffer_Create(SolidSyslogNullMutex_Get(), ring, sizeof(ring)); } void teardown() override { SolidSyslogCircularBuffer_Destroy(buffer); - SolidSyslogNullMutex_Destroy(); } }; @@ -390,7 +388,7 @@ TEST_GROUP(SolidSyslogCircularBufferPool) void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - mutex = SolidSyslogNullMutex_Create(); + mutex = SolidSyslogNullMutex_Get(); } void teardown() override @@ -406,7 +404,6 @@ TEST_GROUP(SolidSyslogCircularBufferPool) { SolidSyslogCircularBuffer_Destroy(overflow); } - SolidSyslogNullMutex_Destroy(); ConfigLockFake_Uninstall(); ErrorHandlerFake_Uninstall(); } diff --git a/Tests/SolidSyslogGetAddrInfoResolverTest.cpp b/Tests/SolidSyslogGetAddrInfoResolverTest.cpp index 4cf99346..4b453aaf 100644 --- a/Tests/SolidSyslogGetAddrInfoResolverTest.cpp +++ b/Tests/SolidSyslogGetAddrInfoResolverTest.cpp @@ -3,11 +3,17 @@ #include #include +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" #include "SolidSyslogAddress.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogGetAddrInfoResolver.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogResolver.h" +#include "SolidSyslogResolverDefinition.h" #include "SocketFake.h" #include "SolidSyslogTransport.h" +#include "SolidSyslogTunables.h" #include "TestUtils.h" #include "CppUTest/TestHarness.h" @@ -35,7 +41,7 @@ TEST_GROUP(SolidSyslogGetAddrInfoResolver) void teardown() override { - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } bool Resolve(const char* host, uint16_t port, enum SolidSyslogTransport transport = SOLIDSYSLOG_TRANSPORT_UDP) @@ -122,3 +128,159 @@ TEST(SolidSyslogGetAddrInfoResolver, FreesAddrInfoOnSuccess) Resolve(TEST_HOST, TEST_PORT); CALLED_FAKE(SocketFake_FreeAddrInfo, ONCE); } + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site +#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(SolidSyslogGetAddrInfoResolverPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogResolver* pooled[SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE] = {}; + struct SolidSyslogResolver* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogGetAddrInfoResolver_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogGetAddrInfoResolver_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogGetAddrInfoResolver_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogGetAddrInfoResolverPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogGetAddrInfoResolver_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogGetAddrInfoResolver_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, FallbackResolveReturnsFalse) +{ + FillPool(); + overflow = SolidSyslogGetAddrInfoResolver_Create(); + + SolidSyslogAddressStorage resultStorage{}; + CHECK_FALSE(SolidSyslogResolver_Resolve( + overflow, + SOLIDSYSLOG_TRANSPORT_UDP, + TEST_HOST, + TEST_PORT, + SolidSyslogAddress_FromStorage(&resultStorage) + )); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogGetAddrInfoResolver_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogGetAddrInfoResolver_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_GETADDRINFO_RESOLVER_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogGetAddrInfoResolver_Create(); + ConfigLockFake_Install(); + + SolidSyslogGetAddrInfoResolver_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogResolver stranger = {}; + + SolidSyslogGetAddrInfoResolver_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogResolver stranger = {}; + + SolidSyslogGetAddrInfoResolver_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogGetAddrInfoResolverPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogGetAddrInfoResolver_Create(); + SolidSyslogGetAddrInfoResolver_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogGetAddrInfoResolver_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_GETADDRINFORESOLVER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogNullDatagramTest.cpp b/Tests/SolidSyslogNullDatagramTest.cpp new file mode 100644 index 00000000..e9893aaa --- /dev/null +++ b/Tests/SolidSyslogNullDatagramTest.cpp @@ -0,0 +1,40 @@ +#include + +#include "CppUTest/TestHarness.h" +#include "SolidSyslogDatagram.h" +#include "SolidSyslogNullDatagram.h" +#include "SolidSyslogUdpPayload.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullDatagram) +{ + struct SolidSyslogDatagram* datagram = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + datagram = SolidSyslogNullDatagram_Get(); + } +}; + +// clang-format on + +TEST(SolidSyslogNullDatagram, SendToReturnsSentToDropOnTheFloor) +{ + LONGS_EQUAL(SOLIDSYSLOG_DATAGRAM_SEND_RESULT_SENT, SolidSyslogDatagram_SendTo(datagram, "x", 1, nullptr)); +} + +TEST(SolidSyslogNullDatagram, OpenReturnsTrue) +{ + CHECK_TRUE(SolidSyslogDatagram_Open(datagram)); +} + +TEST(SolidSyslogNullDatagram, MaxPayloadReturnsIpv6SafeDefault) +{ + UNSIGNED_LONGS_EQUAL((size_t) SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD, SolidSyslogDatagram_MaxPayload(datagram)); +} + +TEST(SolidSyslogNullDatagram, CloseDoesNotCrash) +{ + SolidSyslogDatagram_Close(datagram); +} diff --git a/Tests/SolidSyslogNullFileTest.cpp b/Tests/SolidSyslogNullFileTest.cpp new file mode 100644 index 00000000..8d5d7e00 --- /dev/null +++ b/Tests/SolidSyslogNullFileTest.cpp @@ -0,0 +1,68 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogFile.h" +#include "SolidSyslogNullFile.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullFile) +{ + struct SolidSyslogFile* file = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + file = SolidSyslogNullFile_Get(); + } +}; + +// clang-format on + +TEST(SolidSyslogNullFile, OpenReturnsFalse) +{ + CHECK_FALSE(SolidSyslogFile_Open(file, "anywhere")); +} + +TEST(SolidSyslogNullFile, CloseDoesNotCrash) +{ + SolidSyslogFile_Close(file); +} + +TEST(SolidSyslogNullFile, IsOpenReturnsFalse) +{ + CHECK_FALSE(SolidSyslogFile_IsOpen(file)); +} + +TEST(SolidSyslogNullFile, ReadReturnsFalse) +{ + char buffer[1] = {0}; + CHECK_FALSE(SolidSyslogFile_Read(file, buffer, sizeof(buffer))); +} + +TEST(SolidSyslogNullFile, WriteReturnsTrueToDropOnTheFloor) +{ + CHECK_TRUE(SolidSyslogFile_Write(file, "x", 1)); +} + +TEST(SolidSyslogNullFile, SeekToDoesNotCrash) +{ + SolidSyslogFile_SeekTo(file, 0U); +} + +TEST(SolidSyslogNullFile, SizeReturnsZero) +{ + UNSIGNED_LONGS_EQUAL(0U, SolidSyslogFile_Size(file)); +} + +TEST(SolidSyslogNullFile, TruncateDoesNotCrash) +{ + SolidSyslogFile_Truncate(file); +} + +TEST(SolidSyslogNullFile, ExistsReturnsFalse) +{ + CHECK_FALSE(SolidSyslogFile_Exists(file, "anywhere")); +} + +TEST(SolidSyslogNullFile, DeleteReturnsTrueAsVacuouslySucceeded) +{ + CHECK_TRUE(SolidSyslogFile_Delete(file, "anywhere")); +} diff --git a/Tests/SolidSyslogNullMutexTest.cpp b/Tests/SolidSyslogNullMutexTest.cpp index 5deaaeb9..d6520693 100644 --- a/Tests/SolidSyslogNullMutexTest.cpp +++ b/Tests/SolidSyslogNullMutexTest.cpp @@ -10,19 +10,15 @@ TEST_GROUP(SolidSyslogNullMutex) void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - mutex = SolidSyslogNullMutex_Create(); - } - - void teardown() override - { - SolidSyslogNullMutex_Destroy(); + mutex = SolidSyslogNullMutex_Get(); } }; // clang-format on -TEST(SolidSyslogNullMutex, CreateDestroyDoesNotCrash) +TEST(SolidSyslogNullMutex, GetReturnsSameInstance) { + POINTERS_EQUAL(mutex, SolidSyslogNullMutex_Get()); } TEST(SolidSyslogNullMutex, LockUnlockDoesNotCrash) diff --git a/Tests/SolidSyslogNullResolverTest.cpp b/Tests/SolidSyslogNullResolverTest.cpp new file mode 100644 index 00000000..61c8c40c --- /dev/null +++ b/Tests/SolidSyslogNullResolverTest.cpp @@ -0,0 +1,23 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogNullResolver.h" +#include "SolidSyslogResolver.h" +#include "SolidSyslogTransport.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullResolver) +{ + struct SolidSyslogResolver* resolver = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + resolver = SolidSyslogNullResolver_Get(); + } +}; + +// clang-format on + +TEST(SolidSyslogNullResolver, ResolveReturnsFalse) +{ + CHECK_FALSE(SolidSyslogResolver_Resolve(resolver, SOLIDSYSLOG_TRANSPORT_UDP, "anywhere", 514U, nullptr)); +} diff --git a/Tests/SolidSyslogNullStreamTest.cpp b/Tests/SolidSyslogNullStreamTest.cpp new file mode 100644 index 00000000..06d6ba00 --- /dev/null +++ b/Tests/SolidSyslogNullStreamTest.cpp @@ -0,0 +1,38 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogStream.h" + +// clang-format off +TEST_GROUP(SolidSyslogNullStream) +{ + struct SolidSyslogStream* stream = nullptr; + + void setup() override + { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + stream = SolidSyslogNullStream_Get(); + } +}; + +// clang-format on + +TEST(SolidSyslogNullStream, SendReturnsTrueToDropOnTheFloor) +{ + CHECK_TRUE(SolidSyslogStream_Send(stream, "x", 1)); +} + +TEST(SolidSyslogNullStream, OpenReturnsTrue) +{ + CHECK_TRUE(SolidSyslogStream_Open(stream, nullptr)); +} + +TEST(SolidSyslogNullStream, ReadReturnsZeroForWouldBlock) +{ + char buffer[1] = {0}; + LONGS_EQUAL(0, SolidSyslogStream_Read(stream, buffer, sizeof(buffer))); +} + +TEST(SolidSyslogNullStream, CloseDoesNotCrash) +{ + SolidSyslogStream_Close(stream); +} diff --git a/Tests/SolidSyslogPosixDatagramTest.cpp b/Tests/SolidSyslogPosixDatagramTest.cpp index 0308a271..c1eaa50a 100644 --- a/Tests/SolidSyslogPosixDatagramTest.cpp +++ b/Tests/SolidSyslogPosixDatagramTest.cpp @@ -3,9 +3,15 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* // macros +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" #include "SolidSyslogAddress.h" #include "SolidSyslogDatagram.h" +#include "SolidSyslogDatagramDefinition.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogPosixDatagram.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogUdpPayload.h" #include "SocketFake.h" #include @@ -15,6 +21,19 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f #include #include +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site +#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 static const char* const TEST_MESSAGE = "hello"; static const size_t TEST_MESSAGE_LEN = 5; @@ -47,7 +66,7 @@ TEST_GROUP(SolidSyslogPosixDatagram) void teardown() override { - SolidSyslogPosixDatagram_Destroy(); + SolidSyslogPosixDatagram_Destroy(datagram); } }; @@ -251,3 +270,138 @@ TEST(SolidSyslogPosixDatagram, SendToReturnsFailedWhenConnectFails) SolidSyslogDatagram_SendTo(datagram, TEST_MESSAGE, TEST_MESSAGE_LEN, addr) ); } + +// clang-format off +TEST_GROUP(SolidSyslogPosixDatagramPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogDatagram* pooled[SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE] = {}; + struct SolidSyslogDatagram* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPosixDatagram_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogPosixDatagram_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogPosixDatagram_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPosixDatagramPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogPosixDatagram_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPosixDatagramPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogPosixDatagram_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixDatagramPool, FallbackSendToReturnsSent) +{ + FillPool(); + overflow = SolidSyslogPosixDatagram_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_DATAGRAM_SEND_RESULT_SENT, SolidSyslogDatagram_SendTo(overflow, "x", 1, nullptr)); +} + +TEST(SolidSyslogPosixDatagramPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogPosixDatagram_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixDatagramPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogPosixDatagram_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_POSIX_DATAGRAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPosixDatagramPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogPosixDatagram_Create(); + ConfigLockFake_Install(); + + SolidSyslogPosixDatagram_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixDatagramPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogDatagram stranger = {}; + + SolidSyslogPosixDatagram_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPosixDatagramPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogDatagram stranger = {}; + + SolidSyslogPosixDatagram_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixDatagramPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogPosixDatagram_Create(); + SolidSyslogPosixDatagram_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPosixDatagram_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXDATAGRAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogPosixFileTest.cpp b/Tests/SolidSyslogPosixFileTest.cpp index 46703ba4..3c6faf65 100644 --- a/Tests/SolidSyslogPosixFileTest.cpp +++ b/Tests/SolidSyslogPosixFileTest.cpp @@ -1,16 +1,39 @@ +#include "ConfigLockFake.h" #include "CppUTest/TestHarness.h" +#include "ErrorHandlerFake.h" +#include "SocketFake.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogFile.h" +#include "SolidSyslogFileDefinition.h" #include "SolidSyslogPosixFile.h" -#include "SocketFake.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "TestUtils.h" #include +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* + // macros + static const char* const TEST_PATH = "/tmp/test_posix_file.dat"; +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site +#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(SolidSyslogPosixFile) { - SolidSyslogPosixFileStorage storage = {}; struct SolidSyslogFile* file = nullptr; void setup() override @@ -18,7 +41,7 @@ TEST_GROUP(SolidSyslogPosixFile) SocketFake_Reset(); remove(TEST_PATH); // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - file = SolidSyslogPosixFile_Create(&storage); + file = SolidSyslogPosixFile_Create(); } void teardown() override @@ -40,14 +63,6 @@ TEST(SolidSyslogPosixFile, CreateReturnsNonNull) CHECK_TRUE(file != nullptr); } -TEST(SolidSyslogPosixFile, CreateReturnsHandleInsideCallerSuppliedStorage) -{ - SolidSyslogPosixFileStorage localStorage{}; - struct SolidSyslogFile* localFile = SolidSyslogPosixFile_Create(&localStorage); - POINTERS_EQUAL(&localStorage, localFile); - SolidSyslogPosixFile_Destroy(localFile); -} - TEST(SolidSyslogPosixFile, IsOpenReturnsFalseBeforeOpen) { CHECK_FALSE(SolidSyslogFile_IsOpen(file)); @@ -115,3 +130,138 @@ TEST(SolidSyslogPosixFile, DeleteReturnsFalseForNonexistentFile) { CHECK_FALSE(SolidSyslogFile_Delete(file, "/tmp/nonexistent_posix_file.dat")); } + +// clang-format off +TEST_GROUP(SolidSyslogPosixFilePool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogFile* pooled[SOLIDSYSLOG_POSIX_FILE_POOL_SIZE] = {}; + struct SolidSyslogFile* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPosixFile_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogPosixFile_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogPosixFile_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPosixFilePool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogPosixFile_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPosixFilePool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogPosixFile_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXFILE_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixFilePool, FallbackOpenReturnsFalse) +{ + FillPool(); + overflow = SolidSyslogPosixFile_Create(); + + CHECK_FALSE(SolidSyslogFile_Open(overflow, TEST_PATH)); +} + +TEST(SolidSyslogPosixFilePool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogPosixFile_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixFilePool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogPosixFile_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_POSIX_FILE_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_POSIX_FILE_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPosixFilePool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogPosixFile_Create(); + ConfigLockFake_Install(); + + SolidSyslogPosixFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixFilePool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogFile stranger = {}; + + SolidSyslogPosixFile_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPosixFilePool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogFile stranger = {}; + + SolidSyslogPosixFile_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXFILE_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixFilePool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogPosixFile_Create(); + SolidSyslogPosixFile_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPosixFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXFILE_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp index e2484c12..71de18ee 100644 --- a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp +++ b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp @@ -5,7 +5,11 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* // macros +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" #include "SolidSyslogBuffer.h" +#include "SolidSyslogBufferDefinition.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogPosixMessageQueueBuffer.h" #include "SolidSyslog.h" #include "SolidSyslogConfig.h" @@ -36,7 +40,7 @@ TEST_GROUP(SolidSyslogPosixMessageQueueBuffer) void teardown() override { - SolidSyslogPosixMessageQueueBuffer_Destroy(); + SolidSyslogPosixMessageQueueBuffer_Destroy(buffer); } void Write() const @@ -131,3 +135,194 @@ IGNORE_TEST(SolidSyslogPosixMessageQueueBuffer, HappyPathOnly) // Blocking mode not yet implemented — see S4.5 or later // Read blocks waiting for a message (O_NONBLOCK removed) } + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) +#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(SolidSyslogPosixMessageQueueBufferPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogBuffer* pooled[SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE] = {}; + struct SolidSyslogBuffer* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPosixMessageQueueBuffer_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogPosixMessageQueueBuffer_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + static struct SolidSyslogBuffer* MakeBuffer() + { + return SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = MakeBuffer(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPosixMessageQueueBufferPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = MakeBuffer(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = MakeBuffer(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, FallbackReadAndWriteAreNoOps) +{ + FillPool(); + overflow = MakeBuffer(); + + SolidSyslogBuffer_Write(overflow, "hello", 5); + + char readBuffer[16] = {}; + size_t bytesRead = 99; + CHECK_FALSE(SolidSyslogBuffer_Read(overflow, readBuffer, sizeof(readBuffer), &bytesRead)); + LONGS_EQUAL(0, bytesRead); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = MakeBuffer(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = MakeBuffer(); + + LONGS_EQUAL(SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = MakeBuffer(); + ConfigLockFake_Install(); + + SolidSyslogPosixMessageQueueBuffer_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogBuffer stranger = {}; + + SolidSyslogPosixMessageQueueBuffer_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogBuffer stranger = {}; + + SolidSyslogPosixMessageQueueBuffer_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixMessageQueueBufferPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = MakeBuffer(); + SolidSyslogPosixMessageQueueBuffer_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPosixMessageQueueBuffer_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXMESSAGEQUEUEBUFFER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +/* Slot-indexed queue names are only observable with at least two pool + * slots. The default build runs at pool size 1; the + * `tunable-override-debug` preset bumps it to 2 (see + * Tests/Fixtures/SmallMessageSizeTunables.h). When the runtime pool + * size can't host a second slot, print a notice and exit cleanly via + * TEST_EXIT so the test is honestly accounted for in the run rather + * than compiled out. Local pointers (not fixture's `pooled[]`) so the + * second handle has compile-time storage even at pool size 1. */ +TEST(SolidSyslogPosixMessageQueueBufferPool, EachPooledHandleHasIsolatedQueue) +{ + if (SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE < 2U) + { + UT_PRINT("Pool size < 2 — slot isolation only observable under tunable-override-debug"); + TEST_EXIT; + } + + struct SolidSyslogBuffer* slotZero = MakeBuffer(); + struct SolidSyslogBuffer* slotOne = MakeBuffer(); + + SolidSyslogBuffer_Write(slotZero, "slot0", 5U); + + char readBuffer[SOLIDSYSLOG_MAX_MESSAGE_SIZE] = {}; + size_t bytesRead = 99U; + bool readSucceeded = SolidSyslogBuffer_Read(slotOne, readBuffer, sizeof(readBuffer), &bytesRead); + + SolidSyslogPosixMessageQueueBuffer_Destroy(slotZero); + SolidSyslogPosixMessageQueueBuffer_Destroy(slotOne); + + CHECK_FALSE(readSucceeded); + LONGS_EQUAL(0, bytesRead); +} diff --git a/Tests/SolidSyslogPosixMutexTest.cpp b/Tests/SolidSyslogPosixMutexTest.cpp index 04b96237..7658c8f6 100644 --- a/Tests/SolidSyslogPosixMutexTest.cpp +++ b/Tests/SolidSyslogPosixMutexTest.cpp @@ -1,17 +1,42 @@ #include "CppUTest/TestHarness.h" + +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogMutex.h" +#include "SolidSyslogMutexDefinition.h" #include "SolidSyslogPosixMutex.h" +#include "SolidSyslogPrival.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(SolidSyslogPosixMutex) { - SolidSyslogPosixMutexStorage storage{}; - struct SolidSyslogMutex* mutex = nullptr; + struct SolidSyslogMutex* mutex = nullptr; void setup() override { // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - mutex = SolidSyslogPosixMutex_Create(&storage); + mutex = SolidSyslogPosixMutex_Create(); } void teardown() override @@ -26,13 +51,144 @@ TEST(SolidSyslogPosixMutex, CreateDestroyDoesNotCrash) { } -TEST(SolidSyslogPosixMutex, HandleEqualsStorageAddress) -{ - POINTERS_EQUAL(&storage, mutex); -} - TEST(SolidSyslogPosixMutex, LockUnlockDoesNotCrash) { SolidSyslogMutex_Lock(mutex); SolidSyslogMutex_Unlock(mutex); } + +// clang-format off +TEST_GROUP(SolidSyslogPosixMutexPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogMutex* pooled[SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE] = {}; + struct SolidSyslogMutex* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPosixMutex_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogPosixMutex_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogPosixMutex_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPosixMutexPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogPosixMutex_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPosixMutexPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogPosixMutex_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixMutexPool, FallbackLockUnlockAreNoOps) +{ + FillPool(); + overflow = SolidSyslogPosixMutex_Create(); + + SolidSyslogMutex_Lock(overflow); + SolidSyslogMutex_Unlock(overflow); +} + +TEST(SolidSyslogPosixMutexPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogPosixMutex_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixMutexPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogPosixMutex_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_POSIX_MUTEX_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPosixMutexPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogPosixMutex_Create(); + ConfigLockFake_Install(); + + SolidSyslogPosixMutex_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixMutexPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogMutex stranger = {}; + + SolidSyslogPosixMutex_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPosixMutexPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogMutex stranger = {}; + + SolidSyslogPosixMutex_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixMutexPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogPosixMutex_Create(); + SolidSyslogPosixMutex_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPosixMutex_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXMUTEX_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index e234658d..c99d1e2f 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -11,10 +11,16 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings NEVER/ONCE/TWICE/THRICE into scope for the CALLED_* // macros +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" #include "SolidSyslogAddress.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogPosixTcpStream.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" +#include "SolidSyslogStreamDefinition.h" #include "SolidSyslogTransport.h" +#include "SolidSyslogTunables.h" #include "SocketFake.h" // clang-format off @@ -25,7 +31,6 @@ static const int TEST_PORT = 514; TEST_GROUP(SolidSyslogPosixTcpStream) { - SolidSyslogPosixTcpStreamStorage streamStorage{}; // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros struct SolidSyslogStream* stream = nullptr; SolidSyslogAddressStorage addrStorage{}; @@ -36,7 +41,7 @@ TEST_GROUP(SolidSyslogPosixTcpStream) { SocketFake_Reset(); // cppcheck-suppress unreadVariable -- used in tests; cppcheck does not model CppUTest macros - stream = SolidSyslogPosixTcpStream_Create(&streamStorage); + stream = SolidSyslogPosixTcpStream_Create(); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- char-type aliasing, legal and necessary auto* bytes = reinterpret_cast(&addrStorage); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- reinterpret to platform layout, storage is intptr_t-aligned @@ -76,14 +81,6 @@ TEST(SolidSyslogPosixTcpStream, CreateDestroyWorksWithoutCrashing) { } -TEST(SolidSyslogPosixTcpStream, CreateReturnsHandleInsideCallerSuppliedStorage) -{ - SolidSyslogPosixTcpStreamStorage storage{}; - struct SolidSyslogStream* localStream = SolidSyslogPosixTcpStream_Create(&storage); - POINTERS_EQUAL(&storage, localStream); - SolidSyslogPosixTcpStream_Destroy(localStream); -} - TEST(SolidSyslogPosixTcpStream, OpenCallsSocketOnce) { SolidSyslogStream_Open(stream, addr); @@ -513,3 +510,152 @@ TEST(SolidSyslogPosixTcpStream, ReadReturnsNegativeOneOnErrorAndClosesSocket) LONGS_EQUAL(-1, Read16ByteBuffer()); CHECK_SOCKET_CLOSED_ONCE(); } + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) +#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(SolidSyslogPosixTcpStreamPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogStream* pooled[SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE] = {}; + struct SolidSyslogStream* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPosixTcpStream_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogPosixTcpStream_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogPosixTcpStream_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPosixTcpStreamPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogPosixTcpStream_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPosixTcpStreamPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogPosixTcpStream_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixTcpStreamPool, FallbackSendReturnsTrue) +{ + FillPool(); + overflow = SolidSyslogPosixTcpStream_Create(); + + CHECK_TRUE(SolidSyslogStream_Send(overflow, "x", 1)); +} + +TEST(SolidSyslogPosixTcpStreamPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogPosixTcpStream_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogPosixTcpStream_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_POSIX_TCP_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPosixTcpStreamPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogPosixTcpStream_Create(); + ConfigLockFake_Install(); + + SolidSyslogPosixTcpStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPosixTcpStreamPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogStream stranger = {}; + + SolidSyslogPosixTcpStream_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPosixTcpStreamPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogStream stranger = {}; + + SolidSyslogPosixTcpStream_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPosixTcpStreamPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogPosixTcpStream_Create(); + SolidSyslogPosixTcpStream_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPosixTcpStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_POSIXTCPSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogStreamSenderTest.cpp b/Tests/SolidSyslogStreamSenderTest.cpp index 2d5c26ad..57feb9ad 100644 --- a/Tests/SolidSyslogStreamSenderTest.cpp +++ b/Tests/SolidSyslogStreamSenderTest.cpp @@ -87,7 +87,6 @@ static uint32_t TestEndpointVersion() // NOLINT(modernize-use-trailing-return-ty TEST_GROUP(SolidSyslogStreamSender) { struct SolidSyslogResolver* resolver = nullptr; - SolidSyslogPosixTcpStreamStorage streamStorage{}; struct SolidSyslogStream* stream = nullptr; struct SolidSyslogStreamSenderConfig config; // cppcheck-suppress constVariablePointer -- Send requires non-const self; false positive from macro expansion @@ -101,7 +100,7 @@ TEST_GROUP(SolidSyslogStreamSender) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(&streamStorage); + stream = SolidSyslogPosixTcpStream_Create(); config = {resolver, stream, TestEndpoint, TestEndpointVersion}; // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle sender = SolidSyslogStreamSender_Create(&config); @@ -111,7 +110,7 @@ TEST_GROUP(SolidSyslogStreamSender) { SolidSyslogStreamSender_Destroy(sender); SolidSyslogPosixTcpStream_Destroy(stream); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } void Send() const @@ -150,7 +149,6 @@ TEST(SolidSyslogStreamSender, FirstSendSetsTcpNoDelay) TEST_GROUP(SolidSyslogStreamSenderDestroy) { struct SolidSyslogResolver* resolver = nullptr; - SolidSyslogPosixTcpStreamStorage streamStorage{}; struct SolidSyslogStream* stream = nullptr; struct SolidSyslogStreamSenderConfig config; @@ -161,7 +159,7 @@ TEST_GROUP(SolidSyslogStreamSenderDestroy) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(&streamStorage); + stream = SolidSyslogPosixTcpStream_Create(); // cppcheck-suppress unreadVariable -- used in test bodies; cppcheck does not model CppUTest macros config = {resolver, stream, TestEndpoint, TestEndpointVersion}; } @@ -169,7 +167,7 @@ TEST_GROUP(SolidSyslogStreamSenderDestroy) void teardown() override { SolidSyslogPosixTcpStream_Destroy(stream); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } void CreateAndDestroy() @@ -337,8 +335,8 @@ TEST_GROUP(SolidSyslogStreamSenderConfig) const char* (*getHostFn)(void) = GetHost; // NOLINT(modernize-redundant-void-arg) -- C idiom // cppcheck-suppress unreadVariable -- assigned in CreateSender; cppcheck does not model CppUTest macros int (*getPortFn)(void) = GetPort; // NOLINT(modernize-redundant-void-arg) -- C idiom - SolidSyslogPosixTcpStreamStorage streamStorage{}; - struct SolidSyslogStream* stream = nullptr; + struct SolidSyslogStream* stream = nullptr; + struct SolidSyslogResolver* resolver = nullptr; // cppcheck-suppress constVariablePointer -- Send requires non-const self; false positive from macro expansion // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros struct SolidSyslogSender* sender = nullptr; @@ -357,15 +355,15 @@ TEST_GROUP(SolidSyslogStreamSenderConfig) { SolidSyslogStreamSender_Destroy(sender); SolidSyslogPosixTcpStream_Destroy(stream); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } void CreateSender() { endpointGetHost = getHostFn; endpointGetPort = getPortFn; - struct SolidSyslogResolver* resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(&streamStorage); + resolver = SolidSyslogGetAddrInfoResolver_Create(); + stream = SolidSyslogPosixTcpStream_Create(); struct SolidSyslogStreamSenderConfig config = {resolver, stream, TestEndpoint, TestEndpointVersion}; // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle sender = SolidSyslogStreamSender_Create(&config); @@ -443,7 +441,6 @@ TEST(SolidSyslogStreamSenderConfig, GetAddrInfoCalledWithHostname) TEST_GROUP(SolidSyslogStreamSenderFailure) { struct SolidSyslogResolver* resolver = nullptr; - SolidSyslogPosixTcpStreamStorage streamStorage{}; struct SolidSyslogStream* stream = nullptr; struct SolidSyslogStreamSenderConfig config; // cppcheck-suppress constVariablePointer -- Send requires non-const self; false positive from macro expansion @@ -457,7 +454,7 @@ TEST_GROUP(SolidSyslogStreamSenderFailure) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(&streamStorage); + stream = SolidSyslogPosixTcpStream_Create(); config = {resolver, stream, TestEndpoint, TestEndpointVersion}; // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle sender = SolidSyslogStreamSender_Create(&config); @@ -467,7 +464,7 @@ TEST_GROUP(SolidSyslogStreamSenderFailure) { SolidSyslogStreamSender_Destroy(sender); SolidSyslogPosixTcpStream_Destroy(stream); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } // NOLINTNEXTLINE(modernize-use-nodiscard) -- test helper; return value intentionally ignored in some tests @@ -647,7 +644,6 @@ TEST(SolidSyslogStreamSenderFailure, NoEndpointConfiguredConnectsToPortZero) TEST_GROUP(SolidSyslogStreamSenderPool) { struct SolidSyslogResolver* resolver = nullptr; - SolidSyslogPosixTcpStreamStorage streamStorage{}; struct SolidSyslogStream* stream = nullptr; struct SolidSyslogStreamSenderConfig config; struct SolidSyslogSender* pooled[SOLIDSYSLOG_STREAM_SENDER_POOL_SIZE] = {}; @@ -660,7 +656,7 @@ TEST_GROUP(SolidSyslogStreamSenderPool) endpointVersion = 0; endpointGetPort = GetPort; resolver = SolidSyslogGetAddrInfoResolver_Create(); - stream = SolidSyslogPosixTcpStream_Create(&streamStorage); + stream = SolidSyslogPosixTcpStream_Create(); // cppcheck-suppress unreadVariable -- read by MakeSender; cppcheck does not model CppUTest macros config = {resolver, stream, TestEndpoint, TestEndpointVersion}; } @@ -679,7 +675,7 @@ TEST_GROUP(SolidSyslogStreamSenderPool) SolidSyslogStreamSender_Destroy(overflow); } SolidSyslogPosixTcpStream_Destroy(stream); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } struct SolidSyslogSender* MakeSender() diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index 8955b92b..c87cd440 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -1543,7 +1543,7 @@ TEST_GROUP(SolidSyslogServiceEagerDrain) fakeSender = SenderFake_Create(); circularBuffer = SolidSyslogCircularBuffer_Create( - SolidSyslogNullMutex_Create(), bufferRing, sizeof(bufferRing)); + SolidSyslogNullMutex_Get(), bufferRing, sizeof(bufferRing)); fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = {}; @@ -1558,7 +1558,6 @@ TEST_GROUP(SolidSyslogServiceEagerDrain) SolidSyslog_Destroy(); StoreFake_Destroy(); SolidSyslogCircularBuffer_Destroy(circularBuffer); - SolidSyslogNullMutex_Destroy(); SenderFake_Destroy(fakeSender); } }; diff --git a/Tests/SolidSyslogUdpSenderTest.cpp b/Tests/SolidSyslogUdpSenderTest.cpp index 438db5d2..3ab3143e 100644 --- a/Tests/SolidSyslogUdpSenderTest.cpp +++ b/Tests/SolidSyslogUdpSenderTest.cpp @@ -125,16 +125,16 @@ TEST_BASE(UdpSenderTestBase) config = {resolver, datagram, TestEndpoint, TestEndpointVersion}; } - static void teardownFakesWithPosixDatagram() + void teardownFakesWithPosixDatagram() const { - SolidSyslogPosixDatagram_Destroy(); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogPosixDatagram_Destroy(datagram); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } void teardownFakesWithDatagramFake() const { DatagramFake_Destroy(datagram); - SolidSyslogGetAddrInfoResolver_Destroy(); + SolidSyslogGetAddrInfoResolver_Destroy(resolver); } // NOLINTNEXTLINE(modernize-use-nodiscard) -- many test bodies intentionally discard the return