diff --git a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c index 29eb45ec..ca88d8dd 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_WinsockTcp.c @@ -10,7 +10,6 @@ struct SolidSyslogResolver; -static SolidSyslogWinsockTcpStreamStorage 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 = SolidSyslogWinsockTcpStream_Create(&underlyingStreamStorage); + underlyingStream = SolidSyslogWinsockTcpStream_Create(); static struct SolidSyslogTlsStreamConfig tlsStreamConfig; tlsStreamConfig = (struct SolidSyslogTlsStreamConfig) {0}; diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index ef1fb6d9..24b3aa81 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -64,14 +64,13 @@ enum static const char* const STORE_PATH_PREFIX = "Bdd/output/STORE"; static const char* const THRESHOLD_MARKER_PATH = "Bdd/output/solidsyslog_threshold_marker.log"; -static SolidSyslogWinsockTcpStreamStorage tcpStreamStorage; static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)]; -static SolidSyslogWindowsMutexStorage mutexStorage; static SolidSyslogWindowsAtomicCounterStorage counterStorage; static volatile bool shutdownFlag; /* Created in CreateSender, destroyed in DestroySender — held in file scope so teardown can reach them after the SwitchingSender wraps them all. */ +static struct SolidSyslogResolver* resolver; static struct SolidSyslogStream* plainTcpStream; static struct SolidSyslogSender* plainTcpSender; static struct SolidSyslogDatagram* udpDatagram; @@ -179,7 +178,7 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetWindowsOptio { bool mtlsModeActive = (strcmp(options->Transport, "mtls") == 0); - struct SolidSyslogResolver* resolver = SolidSyslogWinsockResolver_Create(); + resolver = SolidSyslogWinsockResolver_Create(); udpDatagram = SolidSyslogWinsockDatagram_Create(); static struct SolidSyslogUdpSenderConfig udpConfig = {0}; @@ -189,7 +188,7 @@ static struct SolidSyslogSender* CreateSender(const struct BddTargetWindowsOptio udpConfig.EndpointVersion = GetEndpointVersion; udpSender = SolidSyslogUdpSender_Create(&udpConfig); - plainTcpStream = SolidSyslogWinsockTcpStream_Create(&tcpStreamStorage); + plainTcpStream = SolidSyslogWinsockTcpStream_Create(); static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; tcpConfig.Resolver = resolver; tcpConfig.Stream = plainTcpStream; @@ -221,8 +220,8 @@ static void DestroySender(void) SolidSyslogStreamSender_Destroy(plainTcpSender); SolidSyslogWinsockTcpStream_Destroy(plainTcpStream); SolidSyslogUdpSender_Destroy(udpSender); - SolidSyslogWinsockDatagram_Destroy(); - SolidSyslogWinsockResolver_Destroy(); + SolidSyslogWinsockDatagram_Destroy(udpDatagram); + SolidSyslogWinsockResolver_Destroy(resolver); } static struct SolidSyslogStore* CreateStore(const struct BddTargetWindowsOptions* options) @@ -231,8 +230,7 @@ static struct SolidSyslogStore* CreateStore(const struct BddTargetWindowsOptions if (useFile) { - static SolidSyslogWindowsFileStorage fileStorage; - storeFile = SolidSyslogWindowsFile_Create(&fileStorage); + storeFile = SolidSyslogWindowsFile_Create(); storeBlockDevice = SolidSyslogFileBlockDevice_Create(storeFile, STORE_PATH_PREFIX); @@ -299,7 +297,7 @@ int BddTargetWindows_Run(int argc, char* argv[]) struct SolidSyslogSender* sender = CreateSender(&options); struct SolidSyslogStore* store = CreateStore(&options); - struct SolidSyslogMutex* mutex = SolidSyslogWindowsMutex_Create(&mutexStorage); + struct SolidSyslogMutex* mutex = SolidSyslogWindowsMutex_Create(); struct SolidSyslogBuffer* buffer = SolidSyslogCircularBuffer_Create(mutex, bufferRing, sizeof(bufferRing)); struct SolidSyslogAtomicCounter* counter = SolidSyslogWindowsAtomicCounter_Create(&counterStorage); struct SolidSyslogMetaSdConfig metaConfig = { diff --git a/CLAUDE.md b/CLAUDE.md index 2bd8194d..0b991e95 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -349,7 +349,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogMutexDefinition.h` | Mutex implementors (extension point) | `SolidSyslogMutex` vtable struct (`Lock`, `Unlock`) | | `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`) | +| `SolidSyslogWindowsMutex.h` | System setup code on Windows targets needing thread-safe buffering | `SolidSyslogWindowsMutex_Create(void)`, `_Destroy(base)` (wraps `CRITICAL_SECTION`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullMutex`. | | `SolidSyslogFreeRtosMutex.h` | System setup code on FreeRTOS targets needing thread-safe buffering | `SolidSyslogFreeRtosMutexStorage`, `SOLIDSYSLOG_FREERTOSMUTEX_SIZE`, `SolidSyslogFreeRtosMutex_Create(storage)`, `_Destroy(mutex)` (wraps `xSemaphoreCreateMutexStatic` — caller's `StaticSemaphore_t` lives inside the injected storage; requires `configSUPPORT_STATIC_ALLOCATION=1`) | | `SolidSyslogStore.h` | Library internals consuming a store (Service algorithm) and integrator code querying capacity | `SolidSyslogStore_Write`, `_ReadNextUnsent`, `_MarkSent`, `_HasUnsent`, `_IsHalted`, `_GetTotalBytes`, `_GetUsedBytes` | | `SolidSyslogStoreDefinition.h` | Store implementors (extension point) | `SolidSyslogStore` vtable struct (`Write`, `ReadNextUnsent`, `MarkSent`, `HasUnsent`, `IsHalted`, `GetTotalBytes`, `GetUsedBytes`) | @@ -367,7 +367,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `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(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` | +| `SolidSyslogWindowsFile.h` | System setup code using Windows file I/O (MSVC ``) | `SolidSyslogWindowsFile_Create(void)`, `_Destroy(base)`. Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. | | `SolidSyslogFatFsFile.h` | System setup code on targets using ChaN FatFs as the file layer (bare-metal flash / SD / eMMC, FreeRTOS, Zephyr, NuttX, …) | `SolidSyslogFatFsFileStorage`, `SOLIDSYSLOG_FATFS_FILE_SIZE`, `SolidSyslogFatFsFile_Create(storage)`, `_Destroy(file)` (wraps `f_open` / `f_close` / `f_read` / `f_write` with `f_sync` after every successful write so a power loss never loses a record the BlockStore claimed it stored; integrator supplies `diskio.c` and — if `FF_FS_REENTRANT=1` — `ffsystem.c`) | | `SolidSyslogPosixClock.h` | System setup code using POSIX clock | `SolidSyslogPosixClock_GetTimestamp` | | `SolidSyslogPosixHostname.h` | String callback implementor using POSIX hostname | `SolidSyslogPosixHostname_Get` (writes into `SolidSyslogFormatter*`) | diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 734675aa..ad7b2260 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -358,4 +358,102 @@ #error "SOLIDSYSLOG_ORIGIN_SD_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogWindowsMutex instances the library's internal + * static pool can simultaneously hold. Each instance carries a + * CRITICAL_SECTION. + * + * Default 1 — almost all integrators wire a single WindowsMutex 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_WINDOWS_MUTEX_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE < 1 +#error "SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogWinsockDatagram instances the library's internal + * static pool can simultaneously hold. Each instance carries the AF_INET + * SOCKET handle and a one-shot connect flag. + * + * Default 1 — almost all integrators wire a single WinsockDatagram 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_WINSOCK_DATAGRAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogWinsockResolver instances the library's internal + * static pool can simultaneously hold. The resolver is stateless (its + * slot just holds the vtable); the pool exists for lifecycle symmetry + * with the stateful FreeRtosStaticResolver / GetAddrInfoResolver siblings. + * + * Default 1. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogWindowsFile instances the library's internal + * static pool can simultaneously hold. Each instance carries an `int` + * MSVC CRT file descriptor. + * + * Default 1. Integrators using FileBlockDevice with BlockStore may want + * to bump this in line with SOLIDSYSLOG_BLOCK_STORE_POOL_SIZE. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE < 1 +#error "SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogWinsockTcpStream instances the library's internal + * static pool can simultaneously hold. Each instance carries the SOCKET + * for the non-blocking TCP connection. + * + * Default 2 — the BDD target needs a plain-TCP stream and a + * TLS-underlying-TCP stream concurrently, matching the POSIX pool size. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE 2U +#endif + +#if SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE must be >= 1" +#endif + #endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index b829e1f9..8258ab2b 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -87,5 +87,25 @@ "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" +#define SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_POOL_EXHAUSTED \ + "SolidSyslogWindowsMutex_Create pool exhausted; returning fallback mutex" +#define SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_UNKNOWN_DESTROY \ + "SolidSyslogWindowsMutex_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_POOL_EXHAUSTED \ + "SolidSyslogWinsockDatagram_Create pool exhausted; returning fallback datagram" +#define SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_UNKNOWN_DESTROY \ + "SolidSyslogWinsockDatagram_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_POOL_EXHAUSTED \ + "SolidSyslogWinsockResolver_Create pool exhausted; returning fallback resolver" +#define SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_UNKNOWN_DESTROY \ + "SolidSyslogWinsockResolver_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_POOL_EXHAUSTED \ + "SolidSyslogWindowsFile_Create pool exhausted; returning fallback file" +#define SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_UNKNOWN_DESTROY \ + "SolidSyslogWindowsFile_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_POOL_EXHAUSTED \ + "SolidSyslogWinsockTcpStream_Create pool exhausted; returning fallback stream" +#define SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_UNKNOWN_DESTROY \ + "SolidSyslogWinsockTcpStream_Destroy called with a handle not issued by this pool" #endif /* SOLIDSYSLOGERRORMESSAGES_H */ diff --git a/DEVLOG.md b/DEVLOG.md index fe24abab..2186c520 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,140 @@ # Dev Log +## 2026-05-20 — S11.07: Windows adapters onto PoolAllocator + +Closes S11.07 (#406). The second platform-adapter sweep in E11 — applies the +canonical 3-TU split + `SolidSyslogPoolAllocator` to all five stateful +Windows adapter classes (`WindowsMutex`, `WindowsFile`, `WinsockTcpStream` — +storage-cast; `WinsockDatagram`, `WinsockResolver` — file-scope singleton). + +Five per-class commits on the branch — no new public GoF nulls (all four +needed — `NullStream`, `NullDatagram`, `NullFile`, `NullResolver` — landed in +S11.06; `NullMutex` already in `_Get` shape). + +- `80dfd5b` — `WindowsMutex` (storage-cast). `InitializeCriticalSection` + has no failure mode in our flow so Initialise has no partial-init + branch (unlike `PosixMutex_Initialise` which guards `pthread_mutex_init`'s + return). Refactor-only otherwise. +- `8918100` — `WinsockDatagram` (singleton). The six file-scope + `Winsock_socket` / `_sendto` / `_closesocket` / `_connect` / `_setsockopt` + / `_getsockopt` function-pointer test seams stay in `WinsockDatagram.c` + per the C4232 `__declspec(dllimport)` forwarder workaround; the + `Internal.h` header continues to expose them to WinsockFake. + `Initialise` explicitly sets `Fd = INVALID_SOCKET` (not relying on pool + zero-init — `SOCKET 0` is a valid handle on Windows, not invalid). +- `1f3ac1f` — `WinsockResolver` (singleton). Truly stateless today (the + struct is just `{ Base }`); pool-migrated for symmetry with the upcoming + `FreeRtosStaticResolver` (S11.08) which carries IPv4 octet state. Same + decision precedent as S11.06's `GetAddrInfoResolver`. The two + `Winsock_getaddrinfo` / `_freeaddrinfo` seams stay in `WinsockResolver.c`. +- `766432a` — `WindowsFile` (storage-cast). The `DEFAULT_INSTANCE` / + `DESTROYED_INSTANCE` static const structs that the storage-cast variant + used to seed/clear the vtable + Fd are gone; that job moves to + `Initialise` / `Cleanup`. +- `209b767` — `WinsockTcpStream` (storage-cast). Largest commit; all ten + `WinsockTcpStream_*` seams preserved verbatim, all keepalive setsockopt + calls preserved, the non-blocking connect-with-select-timeout dance + preserved, the WSAEWOULDBLOCK / WSAECONNRESET branches in Read / Send + preserved. **Refactor-only.** Also fixes a latent header-order accident: + `mstcpip.h` was being parsed correctly only because `Internal.h` + transitively pulled in `winsock2.h` first; the new TU layout exposes the + ordering explicitly with a documented `#include ` ahead of + ``. + +### Decisions + +- **No new public GoF nulls in this story.** All four needed + (`NullStream`, `NullDatagram`, `NullFile`, `NullResolver`) landed in + S11.06; `NullMutex` migrated to `_Get` in S11.06's commit `bba44d9`. + S11.07 is a pure migration sweep, no new API surface. + +- **`WinsockResolver` pool-migrated, not `_Get`.** The class is truly + stateless today (struct is one field), and `_Get(void)` would be the + more honest shape. Rejected in favour of consistency with + `FreeRtosStaticResolver` (S11.08), which carries 4 bytes of IPv4 octet + state and needs the pool. Mixing `_Get` and `_Create`/`_Destroy` within + the Resolver base class would force integrators to remember which + sibling wants which lifecycle. Same decision was taken for + `GetAddrInfoResolver` in S11.06. + +- **`WinsockTcpStream` pool size default 2U**, not 1U like the other four + classes. Matches the POSIX sibling. The BDD target needs a plain-TCP + stream and a TLS-underlying-TCP stream concurrently; a default of 1 + would force every TLS integration to override the tunable. + +- **`WindowsMutex_Initialise` has no partial-init guard.** Unlike + `PosixMutex_Initialise` which keys `Cleanup` off `Base.Lock == + PosixMutex_Lock` to handle a failed `pthread_mutex_init`, + `InitializeCriticalSection` is `void` (documented non-failing on Vista+) + and the pre-migration code didn't handle failure either. Per + refactor-only, no new failure path introduced. If the + `InitializeCriticalSectionAndSpinCount` path's BOOL return is ever + needed (resource exhaustion under stress), that's an E12 concern. + +- **`HandleEqualsStorageAddress` / `CreateReturnsHandleInsideCallerSuppliedStorage` + tests dropped.** Three of the five tests pinned the invariant "returned + handle equals address of caller's storage" — meaningful for the + storage-cast shape, no longer meaningful when the slot is library-internal. + The remaining interface tests carry the regression net forward. + +- **WSAStartup / WSACleanup ownership unchanged.** None of the five `.c` + files calls `WSAStartup` today; the BDD target / caller owns the + Winsock lifecycle. Pool migration preserves that — no new lifecycle + calls introduced. + +- **Pool-exhaustion fallback uses the shared GoF null per class:** + `NullMutex` / `NullStream` / `NullDatagram` / `NullFile` / `NullResolver`. + Same severity vocabulary as S11.06 — ERROR on exhausted Create, + WARNING on unknown/stale Destroy. Error message constants follow the + `SOLIDSYSLOG_ERROR_MSG__POOL_EXHAUSTED` / `_UNKNOWN_DESTROY` + pattern verbatim. + +- **All local MSVC gates green.** `msvc-debug` preset: full 1131-test + suite passes at default pool sizes. Per-class Pool TEST_GROUPs add 45 + tests overall (9 × 5 classes). + +### Deferred + +- **`WindowsAtomicCounter` migration.** Out of scope here per the E11 + sequence — lands in S11.09 alongside `StdAtomicCounter` ("AtomicCounter + family + TLS + FatFs"). The two atomic counters share a contract test + and will likely share a `NullAtomicCounter` / class-private fallback + decision; that's cleaner as one PR. + +- **`SolidSyslogAddress` (Windows variant).** Utility on a struct, no + Create / Destroy. Out of scope for E11. + +- **Audience-table rows for `WinsockDatagram` / `WinsockResolver` / + `WinsockTcpStream`.** None today (only `WindowsFile` and `WindowsMutex` + carry audience rows on the Windows side); pre-existing inconsistency + with POSIX. Tracked for S11.11 wrap-up. + +- **`*Static.c` helper placement.** CodeRabbit flagged that + `IndexFromHandle` / `CleanupAtIndex` should be `static inline` and + defined immediately beneath the first caller per CLAUDE.md's helper + rule, rather than the forward-declare-at-top + define-at-end shape. + The rule has drifted across the entire `*Static.c` family — every + S11.01 / S11.04 / S11.05 / S11.06 / S11.07 `*Static.c` follows the + drifted shape today. Fixing S11.07 in isolation would leave the + already-merged stories inconsistent. Carved out as the next standalone + cleanup story (does not block S11.08). + +### Open questions + +- **`WinsockDatagram` `socket()` failure mode.** Today's pool Create + returns the slot unconditionally and stashes whatever the underlying + `socket()` returns (possibly `INVALID_SOCKET`); the migration preserves + that. Routing a `socket()` failure through `NullDatagram` would shift + the bad-setup contract — fits E12 better than this sweep. Mirrors + S11.06's `PosixMessageQueueBuffer` `mq_open` defer. + +- **`WinsockTcpStream`'s `select()`-bounded connect timeout** isn't + exercised by S11.07's pool tests (they only stress the slot allocator). + The existing TcpStream suite carries that contract forward unchanged — + no regression risk under refactor-only — but if the timeout semantics + ever drift, the failure mode would be a Service-loop wedge in BDD + outage scenarios. + ## 2026-05-19 — S11.06: POSIX adapters onto PoolAllocator Closes S11.06 (#405). The first platform-adapter sweep in E11 — applies the diff --git a/Platform/Windows/CMakeLists.txt b/Platform/Windows/CMakeLists.txt index 780d92b7..2d0ad9c2 100644 --- a/Platform/Windows/CMakeLists.txt +++ b/Platform/Windows/CMakeLists.txt @@ -8,8 +8,10 @@ if(HAVE_WINDOWS_PLATFORM) target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogWindowsClock.c Source/SolidSyslogWindowsFile.c + Source/SolidSyslogWindowsFileStatic.c Source/SolidSyslogWindowsHostname.c Source/SolidSyslogWindowsMutex.c + Source/SolidSyslogWindowsMutexStatic.c Source/SolidSyslogWindowsProcessId.c Source/SolidSyslogWindowsSleep.c Source/SolidSyslogWindowsSysUpTime.c @@ -28,8 +30,11 @@ if(SOLIDSYSLOG_WINSOCK) target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogAddress.c Source/SolidSyslogWinsockResolver.c + Source/SolidSyslogWinsockResolverStatic.c Source/SolidSyslogWinsockDatagram.c + Source/SolidSyslogWinsockDatagramStatic.c Source/SolidSyslogWinsockTcpStream.c + Source/SolidSyslogWinsockTcpStreamStatic.c ) target_link_libraries(${PROJECT_NAME} PUBLIC ws2_32) endif() diff --git a/Platform/Windows/Interface/SolidSyslogWindowsFile.h b/Platform/Windows/Interface/SolidSyslogWindowsFile.h index 889f21ed..e43c7266 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsFile.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsFile.h @@ -1,23 +1,13 @@ #ifndef SOLIDSYSLOGWINDOWSFILE_H #define SOLIDSYSLOGWINDOWSFILE_H -#include "SolidSyslogFile.h" - -#include +#include "ExternC.h" EXTERN_C_BEGIN - enum - { - SOLIDSYSLOG_WINDOWS_FILE_SIZE = sizeof(intptr_t) * 11U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_WINDOWS_FILE_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogWindowsFileStorage; + struct SolidSyslogFile; - struct SolidSyslogFile* SolidSyslogWindowsFile_Create(SolidSyslogWindowsFileStorage * storage); + struct SolidSyslogFile* SolidSyslogWindowsFile_Create(void); void SolidSyslogWindowsFile_Destroy(struct SolidSyslogFile * base); EXTERN_C_END diff --git a/Platform/Windows/Interface/SolidSyslogWindowsMutex.h b/Platform/Windows/Interface/SolidSyslogWindowsMutex.h index 2d517cb0..ebbfab75 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsMutex.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsMutex.h @@ -3,23 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogMutex; - enum - { - SOLIDSYSLOG_WINDOWS_MUTEX_SIZE = sizeof(intptr_t) * 10U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_WINDOWS_MUTEX_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogWindowsMutexStorage; - - struct SolidSyslogMutex* SolidSyslogWindowsMutex_Create(SolidSyslogWindowsMutexStorage * storage); + struct SolidSyslogMutex* SolidSyslogWindowsMutex_Create(void); void SolidSyslogWindowsMutex_Destroy(struct SolidSyslogMutex * base); EXTERN_C_END diff --git a/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h b/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h index ae387f15..e97339f6 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN and will call WSACleanup() on shutdown. The library does not manage Winsock lifecycle. */ struct SolidSyslogDatagram* SolidSyslogWinsockDatagram_Create(void); - void SolidSyslogWinsockDatagram_Destroy(void); + void SolidSyslogWinsockDatagram_Destroy(struct SolidSyslogDatagram * base); EXTERN_C_END diff --git a/Platform/Windows/Interface/SolidSyslogWinsockResolver.h b/Platform/Windows/Interface/SolidSyslogWinsockResolver.h index d0f08ee2..5ef1f83c 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockResolver.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockResolver.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN and will call WSACleanup() on shutdown. The library does not manage Winsock lifecycle. */ struct SolidSyslogResolver* SolidSyslogWinsockResolver_Create(void); - void SolidSyslogWinsockResolver_Destroy(void); + void SolidSyslogWinsockResolver_Destroy(struct SolidSyslogResolver * base); EXTERN_C_END diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h index 256a620d..a7590e79 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h @@ -3,24 +3,13 @@ #include "SolidSyslogStream.h" #include "SolidSyslogTransport.h" -#include EXTERN_C_BEGIN - enum - { - SOLIDSYSLOG_WINSOCK_TCP_STREAM_SIZE = sizeof(intptr_t) * 5U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_WINSOCK_TCP_STREAM_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogWinsockTcpStreamStorage; - /* Precondition: caller has invoked WSAStartup() before using the stream, and will call WSACleanup() on shutdown. The library does not manage Winsock lifecycle. */ - struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(SolidSyslogWinsockTcpStreamStorage * storage); + struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(void); void SolidSyslogWinsockTcpStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/Windows/Source/SolidSyslogWindowsFile.c b/Platform/Windows/Source/SolidSyslogWindowsFile.c index 70b0aa2e..e68f8dac 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsFile.c +++ b/Platform/Windows/Source/SolidSyslogWindowsFile.c @@ -1,13 +1,17 @@ #include "SolidSyslogWindowsFile.h" -#include "SolidSyslogFileDefinition.h" -#include "SolidSyslogMacros.h" #include #include #include +#include +#include #include #include +#include "SolidSyslogFileDefinition.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogWindowsFilePrivate.h" + /* _O_BINARY disables the MSVC CRT's CR/LF translation on read/write so * arbitrary binary content (e.g. SolidSyslogBlockStore frames) round-trips * unchanged. Without it the CRT substitutes 0x0D 0x0A for 0x0A on write @@ -33,61 +37,35 @@ static void WindowsFile_Truncate(struct SolidSyslogFile* base); static bool WindowsFile_Exists(struct SolidSyslogFile* base, const char* path); static bool WindowsFile_Delete(struct SolidSyslogFile* base, const char* path); -static inline struct SolidSyslogWindowsFile* WindowsFile_SelfFromStorage(SolidSyslogWindowsFileStorage* storage); static inline struct SolidSyslogWindowsFile* WindowsFile_SelfFromBase(struct SolidSyslogFile* base); -struct SolidSyslogWindowsFile +void WindowsFile_Initialise(struct SolidSyslogFile* base) { - struct SolidSyslogFile Base; - int Fd; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogWindowsFile) <= sizeof(SolidSyslogWindowsFileStorage), - "SOLIDSYSLOG_WINDOWS_FILE_SIZE is too small for struct SolidSyslogWindowsFile" -); - -static const struct SolidSyslogWindowsFile DEFAULT_INSTANCE = { - {WindowsFile_Open, - WindowsFile_Close, - WindowsFile_IsOpen, - WindowsFile_Read, - WindowsFile_Write, - WindowsFile_SeekTo, - WindowsFile_Size, - WindowsFile_Truncate, - WindowsFile_Exists, - WindowsFile_Delete}, - INVALID_FD, -}; - -static const struct SolidSyslogWindowsFile DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, - INVALID_FD, -}; - -struct SolidSyslogFile* SolidSyslogWindowsFile_Create(SolidSyslogWindowsFileStorage* storage) -{ - struct SolidSyslogWindowsFile* self = WindowsFile_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; -} - -static inline struct SolidSyslogWindowsFile* WindowsFile_SelfFromStorage(SolidSyslogWindowsFileStorage* storage) -{ - return (struct SolidSyslogWindowsFile*) storage; + struct SolidSyslogWindowsFile* self = WindowsFile_SelfFromBase(base); + self->Base.Open = WindowsFile_Open; + self->Base.Close = WindowsFile_Close; + self->Base.IsOpen = WindowsFile_IsOpen; + self->Base.Read = WindowsFile_Read; + self->Base.Write = WindowsFile_Write; + self->Base.SeekTo = WindowsFile_SeekTo; + self->Base.Size = WindowsFile_Size; + self->Base.Truncate = WindowsFile_Truncate; + self->Base.Exists = WindowsFile_Exists; + self->Base.Delete = WindowsFile_Delete; + self->Fd = INVALID_FD; } -void SolidSyslogWindowsFile_Destroy(struct SolidSyslogFile* base) +void WindowsFile_Cleanup(struct SolidSyslogFile* base) { struct SolidSyslogWindowsFile* self = WindowsFile_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 SolidSyslogWindowsFile* WindowsFile_SelfFromBase(struct SolidSyslogFile* base) diff --git a/Platform/Windows/Source/SolidSyslogWindowsFilePrivate.h b/Platform/Windows/Source/SolidSyslogWindowsFilePrivate.h new file mode 100644 index 00000000..494fc1c9 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWindowsFilePrivate.h @@ -0,0 +1,15 @@ +#ifndef SOLIDSYSLOGWINDOWSFILEPRIVATE_H +#define SOLIDSYSLOGWINDOWSFILEPRIVATE_H + +#include "SolidSyslogFileDefinition.h" + +struct SolidSyslogWindowsFile +{ + struct SolidSyslogFile Base; + int Fd; +}; + +void WindowsFile_Initialise(struct SolidSyslogFile* base); +void WindowsFile_Cleanup(struct SolidSyslogFile* base); + +#endif /* SOLIDSYSLOGWINDOWSFILEPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWindowsFileStatic.c b/Platform/Windows/Source/SolidSyslogWindowsFileStatic.c new file mode 100644 index 00000000..b537fe94 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWindowsFileStatic.c @@ -0,0 +1,69 @@ +#include "SolidSyslogWindowsFile.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWindowsFilePrivate.h" + +struct SolidSyslogFile; + +static size_t WindowsFile_IndexFromHandle(const struct SolidSyslogFile* base); +static void WindowsFile_CleanupAtIndex(size_t index, void* context); + +static bool WindowsFile_InUse[SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE]; +static struct SolidSyslogWindowsFile WindowsFile_Pool[SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE]; +static struct SolidSyslogPoolAllocator WindowsFile_Allocator = {WindowsFile_InUse, SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE}; + +struct SolidSyslogFile* SolidSyslogWindowsFile_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WindowsFile_Allocator); + struct SolidSyslogFile* handle = SolidSyslogNullFile_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&WindowsFile_Allocator, index)) + { + WindowsFile_Initialise(&WindowsFile_Pool[index].Base); + handle = &WindowsFile_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogWindowsFile_Destroy(struct SolidSyslogFile* base) +{ + size_t index = WindowsFile_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&WindowsFile_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&WindowsFile_Allocator, index, WindowsFile_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_UNKNOWN_DESTROY); + } +} + +static size_t WindowsFile_IndexFromHandle(const struct SolidSyslogFile* base) +{ + size_t result = SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE; poolIndex++) + { + if (base == &WindowsFile_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void WindowsFile_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + WindowsFile_Cleanup(&WindowsFile_Pool[index].Base); +} diff --git a/Platform/Windows/Source/SolidSyslogWindowsMutex.c b/Platform/Windows/Source/SolidSyslogWindowsMutex.c index 16e46319..b0164332 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsMutex.c +++ b/Platform/Windows/Source/SolidSyslogWindowsMutex.c @@ -3,46 +3,30 @@ #include #include -#include "SolidSyslogMacros.h" #include "SolidSyslogMutexDefinition.h" - -struct SolidSyslogWindowsMutex -{ - struct SolidSyslogMutex Base; - CRITICAL_SECTION Section; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogWindowsMutex) <= SOLIDSYSLOG_WINDOWS_MUTEX_SIZE, - "SOLIDSYSLOG_WINDOWS_MUTEX_SIZE is too small for SolidSyslogWindowsMutex layout" -); +#include "SolidSyslogNullMutex.h" +#include "SolidSyslogWindowsMutexPrivate.h" static void WindowsMutex_Lock(struct SolidSyslogMutex* base); static void WindowsMutex_Unlock(struct SolidSyslogMutex* base); -static inline struct SolidSyslogWindowsMutex* WindowsMutex_SelfFromStorage(SolidSyslogWindowsMutexStorage* storage); static inline struct SolidSyslogWindowsMutex* WindowsMutex_SelfFromBase(struct SolidSyslogMutex* base); -struct SolidSyslogMutex* SolidSyslogWindowsMutex_Create(SolidSyslogWindowsMutexStorage* storage) +void WindowsMutex_Initialise(struct SolidSyslogMutex* base) { - struct SolidSyslogWindowsMutex* self = WindowsMutex_SelfFromStorage(storage); + struct SolidSyslogWindowsMutex* self = WindowsMutex_SelfFromBase(base); self->Base.Lock = WindowsMutex_Lock; self->Base.Unlock = WindowsMutex_Unlock; InitializeCriticalSection(&self->Section); - return &self->Base; -} - -static inline struct SolidSyslogWindowsMutex* WindowsMutex_SelfFromStorage(SolidSyslogWindowsMutexStorage* storage) -{ - return (struct SolidSyslogWindowsMutex*) storage; } -void SolidSyslogWindowsMutex_Destroy(struct SolidSyslogMutex* base) +void WindowsMutex_Cleanup(struct SolidSyslogMutex* base) { struct SolidSyslogWindowsMutex* self = WindowsMutex_SelfFromBase(base); DeleteCriticalSection(&self->Section); - self->Base.Lock = NULL; - self->Base.Unlock = NULL; + /* 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 SolidSyslogWindowsMutex* WindowsMutex_SelfFromBase(struct SolidSyslogMutex* base) diff --git a/Platform/Windows/Source/SolidSyslogWindowsMutexPrivate.h b/Platform/Windows/Source/SolidSyslogWindowsMutexPrivate.h new file mode 100644 index 00000000..899f9543 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWindowsMutexPrivate.h @@ -0,0 +1,17 @@ +#ifndef SOLIDSYSLOGWINDOWSMUTEXPRIVATE_H +#define SOLIDSYSLOGWINDOWSMUTEXPRIVATE_H + +#include + +#include "SolidSyslogMutexDefinition.h" + +struct SolidSyslogWindowsMutex +{ + struct SolidSyslogMutex Base; + CRITICAL_SECTION Section; +}; + +void WindowsMutex_Initialise(struct SolidSyslogMutex* base); +void WindowsMutex_Cleanup(struct SolidSyslogMutex* base); + +#endif /* SOLIDSYSLOGWINDOWSMUTEXPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWindowsMutexStatic.c b/Platform/Windows/Source/SolidSyslogWindowsMutexStatic.c new file mode 100644 index 00000000..d666573e --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWindowsMutexStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogWindowsMutex.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullMutex.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWindowsMutexPrivate.h" + +struct SolidSyslogMutex; + +static size_t WindowsMutex_IndexFromHandle(const struct SolidSyslogMutex* base); +static void WindowsMutex_CleanupAtIndex(size_t index, void* context); + +static bool WindowsMutex_InUse[SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE]; +static struct SolidSyslogWindowsMutex WindowsMutex_Pool[SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE]; +static struct SolidSyslogPoolAllocator WindowsMutex_Allocator = { + WindowsMutex_InUse, + SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE +}; + +struct SolidSyslogMutex* SolidSyslogWindowsMutex_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WindowsMutex_Allocator); + struct SolidSyslogMutex* handle = SolidSyslogNullMutex_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&WindowsMutex_Allocator, index)) + { + WindowsMutex_Initialise(&WindowsMutex_Pool[index].Base); + handle = &WindowsMutex_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogWindowsMutex_Destroy(struct SolidSyslogMutex* base) +{ + size_t index = WindowsMutex_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&WindowsMutex_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&WindowsMutex_Allocator, index, WindowsMutex_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_UNKNOWN_DESTROY); + } +} + +static size_t WindowsMutex_IndexFromHandle(const struct SolidSyslogMutex* base) +{ + size_t result = SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE; poolIndex++) + { + if (base == &WindowsMutex_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void WindowsMutex_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + WindowsMutex_Cleanup(&WindowsMutex_Pool[index].Base); +} diff --git a/Platform/Windows/Source/SolidSyslogWinsockDatagram.c b/Platform/Windows/Source/SolidSyslogWinsockDatagram.c index 141ce8a8..96b4a0cf 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockDatagram.c +++ b/Platform/Windows/Source/SolidSyslogWinsockDatagram.c @@ -1,13 +1,16 @@ #include "SolidSyslogWinsockDatagram.h" -#include "SolidSyslogAddressInternal.h" -#include "SolidSyslogDatagramDefinition.h" -#include "SolidSyslogUdpPayload.h" -#include "SolidSyslogWinsockDatagramInternal.h" #include #include #include +#include "SolidSyslogAddressInternal.h" +#include "SolidSyslogDatagramDefinition.h" +#include "SolidSyslogNullDatagram.h" +#include "SolidSyslogUdpPayload.h" +#include "SolidSyslogWinsockDatagramInternal.h" +#include "SolidSyslogWinsockDatagramPrivate.h" + /* File-local forwarders. Taking the address of a __declspec(dllimport) Winsock function for static initialisation triggers MSVC C4232 (the address isn't a compile-time constant); forwarding through a static function whose @@ -58,13 +61,6 @@ static int WSAAPI WinsockDatagram_CallGetSockOpt(SOCKET s, int level, int optnam return getsockopt(s, level, optname, optval, optlen); } -struct SolidSyslogWinsockDatagram -{ - struct SolidSyslogDatagram Base; - SOCKET Fd; - bool Connected; -}; - static bool WinsockDatagram_Open(struct SolidSyslogDatagram* base); static enum SolidSyslogDatagramSendResult WinsockDatagram_SendTo( struct SolidSyslogDatagram* base, @@ -82,23 +78,29 @@ static inline bool WinsockDatagram_ConnectIfNeeded( ); static inline bool WinsockDatagram_IsSocketValid(SOCKET fd); -static struct SolidSyslogWinsockDatagram instance = {.Fd = INVALID_SOCKET}; - -struct SolidSyslogDatagram* SolidSyslogWinsockDatagram_Create(void) +void WinsockDatagram_Initialise(struct SolidSyslogDatagram* base) { - instance.Base.Open = WinsockDatagram_Open; - instance.Base.SendTo = WinsockDatagram_SendTo; - instance.Base.MaxPayload = WinsockDatagram_MaxPayload; - instance.Base.Close = WinsockDatagram_Close; - return &instance.Base; + struct SolidSyslogWinsockDatagram* self = WinsockDatagram_SelfFromBase(base); + self->Base.Open = WinsockDatagram_Open; + self->Base.SendTo = WinsockDatagram_SendTo; + self->Base.MaxPayload = WinsockDatagram_MaxPayload; + self->Base.Close = WinsockDatagram_Close; + self->Fd = INVALID_SOCKET; + self->Connected = false; } -void SolidSyslogWinsockDatagram_Destroy(void) +void WinsockDatagram_Cleanup(struct SolidSyslogDatagram* base) { - instance.Base.Open = NULL; - instance.Base.SendTo = NULL; - instance.Base.MaxPayload = NULL; - instance.Base.Close = NULL; + struct SolidSyslogWinsockDatagram* self = WinsockDatagram_SelfFromBase(base); + if (WinsockDatagram_IsSocketValid(self->Fd)) + { + Winsock_closesocket(self->Fd); + self->Fd = INVALID_SOCKET; + 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 WinsockDatagram_Open(struct SolidSyslogDatagram* base) diff --git a/Platform/Windows/Source/SolidSyslogWinsockDatagramPrivate.h b/Platform/Windows/Source/SolidSyslogWinsockDatagramPrivate.h new file mode 100644 index 00000000..728b78ec --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWinsockDatagramPrivate.h @@ -0,0 +1,19 @@ +#ifndef SOLIDSYSLOGWINSOCKDATAGRAMPRIVATE_H +#define SOLIDSYSLOGWINSOCKDATAGRAMPRIVATE_H + +#include +#include + +#include "SolidSyslogDatagramDefinition.h" + +struct SolidSyslogWinsockDatagram +{ + struct SolidSyslogDatagram Base; + SOCKET Fd; + bool Connected; +}; + +void WinsockDatagram_Initialise(struct SolidSyslogDatagram* base); +void WinsockDatagram_Cleanup(struct SolidSyslogDatagram* base); + +#endif /* SOLIDSYSLOGWINSOCKDATAGRAMPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWinsockDatagramStatic.c b/Platform/Windows/Source/SolidSyslogWinsockDatagramStatic.c new file mode 100644 index 00000000..b8895494 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWinsockDatagramStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogWinsockDatagram.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullDatagram.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWinsockDatagramPrivate.h" + +struct SolidSyslogDatagram; + +static size_t WinsockDatagram_IndexFromHandle(const struct SolidSyslogDatagram* base); +static void WinsockDatagram_CleanupAtIndex(size_t index, void* context); + +static bool WinsockDatagram_InUse[SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE]; +static struct SolidSyslogWinsockDatagram WinsockDatagram_Pool[SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator WinsockDatagram_Allocator = { + WinsockDatagram_InUse, + SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE +}; + +struct SolidSyslogDatagram* SolidSyslogWinsockDatagram_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WinsockDatagram_Allocator); + struct SolidSyslogDatagram* handle = SolidSyslogNullDatagram_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&WinsockDatagram_Allocator, index)) + { + WinsockDatagram_Initialise(&WinsockDatagram_Pool[index].Base); + handle = &WinsockDatagram_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogWinsockDatagram_Destroy(struct SolidSyslogDatagram* base) +{ + size_t index = WinsockDatagram_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&WinsockDatagram_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&WinsockDatagram_Allocator, index, WinsockDatagram_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_UNKNOWN_DESTROY); + } +} + +static size_t WinsockDatagram_IndexFromHandle(const struct SolidSyslogDatagram* base) +{ + size_t result = SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE; poolIndex++) + { + if (base == &WinsockDatagram_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void WinsockDatagram_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + WinsockDatagram_Cleanup(&WinsockDatagram_Pool[index].Base); +} diff --git a/Platform/Windows/Source/SolidSyslogWinsockResolver.c b/Platform/Windows/Source/SolidSyslogWinsockResolver.c index 280c551e..2bc1a7a1 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockResolver.c +++ b/Platform/Windows/Source/SolidSyslogWinsockResolver.c @@ -1,12 +1,16 @@ #include "SolidSyslogWinsockResolver.h" -#include "SolidSyslogAddressInternal.h" -#include "SolidSyslogResolverDefinition.h" -#include "SolidSyslogWinsockResolverInternal.h" #include #include #include +#include "SolidSyslogAddressInternal.h" +#include "SolidSyslogNullResolver.h" +#include "SolidSyslogResolverDefinition.h" +#include "SolidSyslogTransport.h" +#include "SolidSyslogWinsockResolverInternal.h" +#include "SolidSyslogWinsockResolverPrivate.h" + /* File-local forwarders. Taking the address of a __declspec(dllimport) Winsock function for static initialisation triggers MSVC C4232 (the address isn't a compile-time constant); forwarding through a static function whose @@ -43,22 +47,16 @@ static bool WinsockResolver_Resolve( ); static int WinsockResolver_MapTransport(enum SolidSyslogTransport transport); -struct SolidSyslogWinsockResolver -{ - struct SolidSyslogResolver Base; -}; - -static struct SolidSyslogWinsockResolver instance; - -struct SolidSyslogResolver* SolidSyslogWinsockResolver_Create(void) +void WinsockResolver_Initialise(struct SolidSyslogResolver* base) { - instance.Base.Resolve = WinsockResolver_Resolve; - return &instance.Base; + base->Resolve = WinsockResolver_Resolve; } -void SolidSyslogWinsockResolver_Destroy(void) +void WinsockResolver_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 WinsockResolver_Resolve( diff --git a/Platform/Windows/Source/SolidSyslogWinsockResolverPrivate.h b/Platform/Windows/Source/SolidSyslogWinsockResolverPrivate.h new file mode 100644 index 00000000..702692c9 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWinsockResolverPrivate.h @@ -0,0 +1,14 @@ +#ifndef SOLIDSYSLOGWINSOCKRESOLVERPRIVATE_H +#define SOLIDSYSLOGWINSOCKRESOLVERPRIVATE_H + +#include "SolidSyslogResolverDefinition.h" + +struct SolidSyslogWinsockResolver +{ + struct SolidSyslogResolver Base; +}; + +void WinsockResolver_Initialise(struct SolidSyslogResolver* base); +void WinsockResolver_Cleanup(struct SolidSyslogResolver* base); + +#endif /* SOLIDSYSLOGWINSOCKRESOLVERPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWinsockResolverStatic.c b/Platform/Windows/Source/SolidSyslogWinsockResolverStatic.c new file mode 100644 index 00000000..ec7820b0 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWinsockResolverStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogWinsockResolver.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullResolver.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWinsockResolverPrivate.h" + +struct SolidSyslogResolver; + +static size_t WinsockResolver_IndexFromHandle(const struct SolidSyslogResolver* base); +static void WinsockResolver_CleanupAtIndex(size_t index, void* context); + +static bool WinsockResolver_InUse[SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE]; +static struct SolidSyslogWinsockResolver WinsockResolver_Pool[SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator WinsockResolver_Allocator = { + WinsockResolver_InUse, + SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE +}; + +struct SolidSyslogResolver* SolidSyslogWinsockResolver_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WinsockResolver_Allocator); + struct SolidSyslogResolver* handle = SolidSyslogNullResolver_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&WinsockResolver_Allocator, index)) + { + WinsockResolver_Initialise(&WinsockResolver_Pool[index].Base); + handle = &WinsockResolver_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogWinsockResolver_Destroy(struct SolidSyslogResolver* base) +{ + size_t index = WinsockResolver_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&WinsockResolver_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&WinsockResolver_Allocator, index, WinsockResolver_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_UNKNOWN_DESTROY); + } +} + +static size_t WinsockResolver_IndexFromHandle(const struct SolidSyslogResolver* base) +{ + size_t result = SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE; poolIndex++) + { + if (base == &WinsockResolver_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void WinsockResolver_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + WinsockResolver_Cleanup(&WinsockResolver_Pool[index].Base); +} diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c index 5e107d98..b54f1817 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c @@ -1,12 +1,21 @@ #include "SolidSyslogWinsockTcpStream.h" -#include "SolidSyslogAddressInternal.h" -#include "SolidSyslogMacros.h" -#include "SolidSyslogStreamDefinition.h" -#include "SolidSyslogWinsockTcpStreamInternal.h" +/* winsock2.h must precede mstcpip.h: mstcpip.h's TCP_KEEPIDLE / TCP_KEEPINTVL / + TCP_KEEPCNT constants use SOCKET-derived types declared in winsock2.h. */ +#include +/* clang-format off */ #include +/* clang-format on */ + +#include #include +#include "SolidSyslogAddressInternal.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogWinsockTcpStreamInternal.h" +#include "SolidSyslogWinsockTcpStreamPrivate.h" + /* File-local forwarders. Taking the address of a __declspec(dllimport) Winsock function for static initialisation triggers MSVC C4232 (the address isn't a compile-time constant); forwarding through a static function whose @@ -113,20 +122,11 @@ enum KEEPALIVE_PROBE_COUNT = 4 }; -struct SolidSyslogWinsockTcpStream -{ - struct SolidSyslogStream Base; - SOCKET Fd; -}; - static bool WinsockTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static bool WinsockTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); static SolidSyslogSsize WinsockTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void WinsockTcpStream_Close(struct SolidSyslogStream* base); -static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromStorage( - SolidSyslogWinsockTcpStreamStorage* storage -); static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromBase(struct SolidSyslogStream* base); static SOCKET WinsockTcpStream_OpenAndConfigureSocket(void); @@ -145,40 +145,22 @@ static bool WinsockTcpStream_ReadDeferredConnectError(SOCKET fd); static bool WinsockTcpStream_WroteAllBytes(int sent, size_t expected); static inline bool WinsockTcpStream_WouldBlock(int wsaError); -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(struct SolidSyslogWinsockTcpStream) <= SOLIDSYSLOG_WINSOCK_TCP_STREAM_SIZE, - "SOLIDSYSLOG_WINSOCK_TCP_STREAM_SIZE is too small for struct SolidSyslogWinsockTcpStream" -); - -static const struct SolidSyslogWinsockTcpStream DEFAULT_INSTANCE = { - {WinsockTcpStream_Open, WinsockTcpStream_Send, WinsockTcpStream_Read, WinsockTcpStream_Close}, - INVALID_SOCKET, -}; - -static const struct SolidSyslogWinsockTcpStream DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL}, - INVALID_SOCKET, -}; - -struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(SolidSyslogWinsockTcpStreamStorage* storage) +void WinsockTcpStream_Initialise(struct SolidSyslogStream* base) { - struct SolidSyslogWinsockTcpStream* self = WinsockTcpStream_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; -} - -static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromStorage( - SolidSyslogWinsockTcpStreamStorage* storage -) -{ - return (struct SolidSyslogWinsockTcpStream*) storage; + struct SolidSyslogWinsockTcpStream* self = WinsockTcpStream_SelfFromBase(base); + self->Base.Open = WinsockTcpStream_Open; + self->Base.Send = WinsockTcpStream_Send; + self->Base.Read = WinsockTcpStream_Read; + self->Base.Close = WinsockTcpStream_Close; + self->Fd = INVALID_SOCKET; } -void SolidSyslogWinsockTcpStream_Destroy(struct SolidSyslogStream* base) +void WinsockTcpStream_Cleanup(struct SolidSyslogStream* base) { - struct SolidSyslogWinsockTcpStream* self = WinsockTcpStream_SelfFromBase(base); WinsockTcpStream_Close(base); - *self = DESTROYED_INSTANCE; + /* Overwrite the abstract base with the shared NullStream vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullStream_Get(); } static inline struct SolidSyslogWinsockTcpStream* WinsockTcpStream_SelfFromBase(struct SolidSyslogStream* base) diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h new file mode 100644 index 00000000..1396a39a --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamPrivate.h @@ -0,0 +1,17 @@ +#ifndef SOLIDSYSLOGWINSOCKTCPSTREAMPRIVATE_H +#define SOLIDSYSLOGWINSOCKTCPSTREAMPRIVATE_H + +#include + +#include "SolidSyslogStreamDefinition.h" + +struct SolidSyslogWinsockTcpStream +{ + struct SolidSyslogStream Base; + SOCKET Fd; +}; + +void WinsockTcpStream_Initialise(struct SolidSyslogStream* base); +void WinsockTcpStream_Cleanup(struct SolidSyslogStream* base); + +#endif /* SOLIDSYSLOGWINSOCKTCPSTREAMPRIVATE_H */ diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c new file mode 100644 index 00000000..73686b48 --- /dev/null +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogWinsockTcpStream.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "SolidSyslogWinsockTcpStreamPrivate.h" + +struct SolidSyslogStream; + +static size_t WinsockTcpStream_IndexFromHandle(const struct SolidSyslogStream* base); +static void WinsockTcpStream_CleanupAtIndex(size_t index, void* context); + +static bool WinsockTcpStream_InUse[SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE]; +static struct SolidSyslogWinsockTcpStream WinsockTcpStream_Pool[SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator WinsockTcpStream_Allocator = { + WinsockTcpStream_InUse, + SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE +}; + +struct SolidSyslogStream* SolidSyslogWinsockTcpStream_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&WinsockTcpStream_Allocator); + struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&WinsockTcpStream_Allocator, index)) + { + WinsockTcpStream_Initialise(&WinsockTcpStream_Pool[index].Base); + handle = &WinsockTcpStream_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogWinsockTcpStream_Destroy(struct SolidSyslogStream* base) +{ + size_t index = WinsockTcpStream_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&WinsockTcpStream_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&WinsockTcpStream_Allocator, index, WinsockTcpStream_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_UNKNOWN_DESTROY); + } +} + +static size_t WinsockTcpStream_IndexFromHandle(const struct SolidSyslogStream* base) +{ + size_t result = SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE; poolIndex++) + { + if (base == &WinsockTcpStream_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static void WinsockTcpStream_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + WinsockTcpStream_Cleanup(&WinsockTcpStream_Pool[index].Base); +} diff --git a/Tests/SolidSyslogWindowsFileTest.cpp b/Tests/SolidSyslogWindowsFileTest.cpp index 5c93a9a4..82167ca2 100644 --- a/Tests/SolidSyslogWindowsFileTest.cpp +++ b/Tests/SolidSyslogWindowsFileTest.cpp @@ -1,6 +1,14 @@ #include "CppUTest/TestHarness.h" + +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogFile.h" +#include "SolidSyslogFileDefinition.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogWindowsFile.h" +#include "TestUtils.h" #include #include @@ -8,6 +16,24 @@ #define WIN32_LEAN_AND_MEAN #include +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 static std::string MakeTempPath(const char* filename) @@ -19,7 +45,6 @@ static std::string MakeTempPath(const char* filename) TEST_GROUP(SolidSyslogWindowsFile) { - SolidSyslogWindowsFileStorage storage = {}; // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros struct SolidSyslogFile* file = nullptr; std::string testPath; @@ -29,7 +54,7 @@ TEST_GROUP(SolidSyslogWindowsFile) testPath = MakeTempPath("test_windows_file.dat"); std::remove(testPath.c_str()); // cppcheck-suppress unreadVariable -- used in tests; cppcheck does not model CppUTest macros - file = SolidSyslogWindowsFile_Create(&storage); + file = SolidSyslogWindowsFile_Create(); } void teardown() override @@ -51,14 +76,6 @@ TEST(SolidSyslogWindowsFile, CreateReturnsNonNull) CHECK_TRUE(file != nullptr); } -TEST(SolidSyslogWindowsFile, CreateReturnsHandleInsideCallerSuppliedStorage) -{ - SolidSyslogWindowsFileStorage localStorage{}; - struct SolidSyslogFile* localFile = SolidSyslogWindowsFile_Create(&localStorage); - POINTERS_EQUAL(&localStorage, localFile); - SolidSyslogWindowsFile_Destroy(localFile); -} - TEST(SolidSyslogWindowsFile, IsOpenReturnsFalseBeforeOpen) { CHECK_FALSE(SolidSyslogFile_IsOpen(file)); @@ -142,3 +159,138 @@ TEST(SolidSyslogWindowsFile, DeleteReturnsFalseForNonexistentFile) { CHECK_FALSE(SolidSyslogFile_Delete(file, MakeTempPath("nonexistent_windows_file.dat").c_str())); } + +// clang-format off +TEST_GROUP(SolidSyslogWindowsFilePool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogFile* pooled[SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE] = {}; + struct SolidSyslogFile* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogWindowsFile_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogWindowsFile_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogWindowsFile_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogWindowsFilePool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogWindowsFile_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogWindowsFilePool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogWindowsFile_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWindowsFilePool, FallbackOpenReturnsFalse) +{ + FillPool(); + overflow = SolidSyslogWindowsFile_Create(); + + CHECK_FALSE(SolidSyslogFile_Open(overflow, "any_path")); +} + +TEST(SolidSyslogWindowsFilePool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogWindowsFile_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWindowsFilePool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogWindowsFile_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_WINDOWS_FILE_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogWindowsFilePool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogWindowsFile_Create(); + ConfigLockFake_Install(); + + SolidSyslogWindowsFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWindowsFilePool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogFile stranger = {}; + + SolidSyslogWindowsFile_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogWindowsFilePool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogFile stranger = {}; + + SolidSyslogWindowsFile_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWindowsFilePool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogWindowsFile_Create(); + SolidSyslogWindowsFile_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogWindowsFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSFILE_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogWindowsMutexTest.cpp b/Tests/SolidSyslogWindowsMutexTest.cpp index bfc606e0..f0bd4648 100644 --- a/Tests/SolidSyslogWindowsMutexTest.cpp +++ b/Tests/SolidSyslogWindowsMutexTest.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 "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogWindowsMutex.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(SolidSyslogWindowsMutex) { - SolidSyslogWindowsMutexStorage 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 = SolidSyslogWindowsMutex_Create(&storage); + mutex = SolidSyslogWindowsMutex_Create(); } void teardown() override @@ -26,13 +51,144 @@ TEST(SolidSyslogWindowsMutex, CreateDestroyDoesNotCrash) { } -TEST(SolidSyslogWindowsMutex, HandleEqualsStorageAddress) -{ - POINTERS_EQUAL(&storage, mutex); -} - TEST(SolidSyslogWindowsMutex, LockUnlockDoesNotCrash) { SolidSyslogMutex_Lock(mutex); SolidSyslogMutex_Unlock(mutex); } + +// clang-format off +TEST_GROUP(SolidSyslogWindowsMutexPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogMutex* pooled[SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE] = {}; + struct SolidSyslogMutex* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogWindowsMutex_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogWindowsMutex_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogWindowsMutex_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogWindowsMutexPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogWindowsMutex_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogWindowsMutexPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogWindowsMutex_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWindowsMutexPool, FallbackLockUnlockAreNoOps) +{ + FillPool(); + overflow = SolidSyslogWindowsMutex_Create(); + + SolidSyslogMutex_Lock(overflow); + SolidSyslogMutex_Unlock(overflow); +} + +TEST(SolidSyslogWindowsMutexPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogWindowsMutex_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWindowsMutexPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogWindowsMutex_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_WINDOWS_MUTEX_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogWindowsMutexPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogWindowsMutex_Create(); + ConfigLockFake_Install(); + + SolidSyslogWindowsMutex_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWindowsMutexPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogMutex stranger = {}; + + SolidSyslogWindowsMutex_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogWindowsMutexPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogMutex stranger = {}; + + SolidSyslogWindowsMutex_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWindowsMutexPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogWindowsMutex_Create(); + SolidSyslogWindowsMutex_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogWindowsMutex_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINDOWSMUTEX_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogWinsockDatagramTest.cpp b/Tests/SolidSyslogWinsockDatagramTest.cpp index deb29881..b03fe7ec 100644 --- a/Tests/SolidSyslogWinsockDatagramTest.cpp +++ b/Tests/SolidSyslogWinsockDatagramTest.cpp @@ -3,8 +3,14 @@ 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 "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogUdpPayload.h" #include "SolidSyslogWinsockDatagram.h" #include "SolidSyslogWinsockDatagramInternal.h" @@ -13,6 +19,22 @@ 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 + +// 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 static const char* const TEST_MESSAGE = "hello"; static const size_t TEST_MESSAGE_LEN = 5; @@ -51,7 +73,7 @@ TEST_GROUP(SolidSyslogWinsockDatagram) void teardown() override { - SolidSyslogWinsockDatagram_Destroy(); + SolidSyslogWinsockDatagram_Destroy(datagram); } }; @@ -255,3 +277,146 @@ TEST(SolidSyslogWinsockDatagram, MaxPayloadFallsBackWhenIpMtuLookupFails) WinsockFake_SetIpMtuLookupFails(true); LONGS_EQUAL(SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD, SolidSyslogDatagram_MaxPayload(datagram)); } + +// clang-format off +TEST_GROUP(SolidSyslogWinsockDatagramPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogDatagram* pooled[SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE] = {}; + struct SolidSyslogDatagram* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogWinsockDatagram_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogWinsockDatagram_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogWinsockDatagram_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogWinsockDatagramPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogWinsockDatagram_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogWinsockDatagramPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogWinsockDatagram_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWinsockDatagramPool, FallbackSendToIsNoOp) +{ + FillPool(); + overflow = SolidSyslogWinsockDatagram_Create(); + SolidSyslogAddressStorage addrStorage{}; + // 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 + auto* sin = reinterpret_cast(bytes); + sin->sin_family = AF_INET; + sin->sin_port = htons(514); + struct SolidSyslogAddress* nullAddr = SolidSyslogAddress_FromStorage(&addrStorage); + + LONGS_EQUAL(SOLIDSYSLOG_DATAGRAM_SEND_RESULT_SENT, SolidSyslogDatagram_SendTo(overflow, "x", 1, nullAddr)); +} + +TEST(SolidSyslogWinsockDatagramPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogWinsockDatagram_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWinsockDatagramPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogWinsockDatagram_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_DATAGRAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogWinsockDatagramPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogWinsockDatagram_Create(); + ConfigLockFake_Install(); + + SolidSyslogWinsockDatagram_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWinsockDatagramPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogDatagram stranger = {}; + + SolidSyslogWinsockDatagram_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogWinsockDatagramPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogDatagram stranger = {}; + + SolidSyslogWinsockDatagram_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWinsockDatagramPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogWinsockDatagram_Create(); + SolidSyslogWinsockDatagram_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogWinsockDatagram_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKDATAGRAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogWinsockResolverTest.cpp b/Tests/SolidSyslogWinsockResolverTest.cpp index 84753684..f0d2b674 100644 --- a/Tests/SolidSyslogWinsockResolverTest.cpp +++ b/Tests/SolidSyslogWinsockResolverTest.cpp @@ -3,8 +3,14 @@ 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 "SolidSyslogPrival.h" #include "SolidSyslogResolver.h" +#include "SolidSyslogResolverDefinition.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogWinsockResolver.h" #include "SolidSyslogWinsockResolverInternal.h" #include "WinsockFake.h" @@ -12,6 +18,22 @@ 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 + +// 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 static const char* const TEST_HOST = "127.0.0.1"; static const uint16_t TEST_PORT = 514; @@ -35,7 +57,7 @@ TEST_GROUP(SolidSyslogWinsockResolver) void teardown() override { - SolidSyslogWinsockResolver_Destroy(); + SolidSyslogWinsockResolver_Destroy(resolver); } bool Resolve(const char* host, uint16_t port, enum SolidSyslogTransport transport = SOLIDSYSLOG_TRANSPORT_UDP) @@ -122,3 +144,140 @@ TEST(SolidSyslogWinsockResolver, FreesAddrInfoOnSuccess) Resolve(TEST_HOST, TEST_PORT); CALLED_FAKE(WinsockFake_FreeAddrInfo, ONCE); } + +// clang-format off +TEST_GROUP(SolidSyslogWinsockResolverPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogResolver* pooled[SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE] = {}; + struct SolidSyslogResolver* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogWinsockResolver_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogWinsockResolver_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogWinsockResolver_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogWinsockResolverPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogWinsockResolver_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogWinsockResolverPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogWinsockResolver_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWinsockResolverPool, FallbackResolveIsNoOp) +{ + FillPool(); + overflow = SolidSyslogWinsockResolver_Create(); + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* address = SolidSyslogAddress_FromStorage(&addrStorage); + + CHECK_FALSE(SolidSyslogResolver_Resolve(overflow, SOLIDSYSLOG_TRANSPORT_UDP, "h", 1, address)); +} + +TEST(SolidSyslogWinsockResolverPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogWinsockResolver_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWinsockResolverPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogWinsockResolver_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_RESOLVER_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogWinsockResolverPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogWinsockResolver_Create(); + ConfigLockFake_Install(); + + SolidSyslogWinsockResolver_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWinsockResolverPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogResolver stranger = {}; + + SolidSyslogWinsockResolver_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogWinsockResolverPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogResolver stranger = {}; + + SolidSyslogWinsockResolver_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWinsockResolverPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogWinsockResolver_Create(); + SolidSyslogWinsockResolver_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogWinsockResolver_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKRESOLVER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogWinsockTcpStreamTest.cpp b/Tests/SolidSyslogWinsockTcpStreamTest.cpp index d1ed1ef3..d8395167 100644 --- a/Tests/SolidSyslogWinsockTcpStreamTest.cpp +++ b/Tests/SolidSyslogWinsockTcpStreamTest.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 "SolidSyslogErrorMessages.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" +#include "SolidSyslogStreamDefinition.h" #include "SolidSyslogTransport.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogWinsockTcpStream.h" #include "SolidSyslogWinsockTcpStreamInternal.h" #include "WinsockFake.h" @@ -13,6 +19,22 @@ 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 + +// 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 static const char* const TEST_MESSAGE = "hello"; static const size_t TEST_MESSAGE_LEN = 5; @@ -21,7 +43,6 @@ static const int TEST_PORT = 514; TEST_GROUP(SolidSyslogWinsockTcpStream) { - SolidSyslogWinsockTcpStreamStorage streamStorage{}; // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros struct SolidSyslogStream* stream = nullptr; SolidSyslogAddressStorage addrStorage{}; @@ -42,7 +63,7 @@ TEST_GROUP(SolidSyslogWinsockTcpStream) UT_PTR_SET(WinsockTcpStream_select, WinsockFake_select); UT_PTR_SET(WinsockTcpStream_WSAGetLastError, WinsockFake_WSAGetLastError); // cppcheck-suppress unreadVariable -- used in tests; cppcheck does not model CppUTest macros - stream = SolidSyslogWinsockTcpStream_Create(&streamStorage); + stream = SolidSyslogWinsockTcpStream_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 @@ -82,14 +103,6 @@ TEST(SolidSyslogWinsockTcpStream, CreateDestroyWorksWithoutCrashing) { } -TEST(SolidSyslogWinsockTcpStream, CreateReturnsHandleInsideCallerSuppliedStorage) -{ - SolidSyslogWinsockTcpStreamStorage storage{}; - struct SolidSyslogStream* localStream = SolidSyslogWinsockTcpStream_Create(&storage); - POINTERS_EQUAL(&storage, localStream); - SolidSyslogWinsockTcpStream_Destroy(localStream); -} - TEST(SolidSyslogWinsockTcpStream, OpenCallsSocketOnce) { SolidSyslogStream_Open(stream, addr); @@ -469,6 +482,141 @@ TEST(SolidSyslogWinsockTcpStream, DestroyClosesOpenSocket) CALLED_FAKE(WinsockFake_Close, ONCE); } +// clang-format off +TEST_GROUP(SolidSyslogWinsockTcpStreamPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogStream* pooled[SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE] = {}; + struct SolidSyslogStream* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogWinsockTcpStream_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogWinsockTcpStream_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogWinsockTcpStream_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogWinsockTcpStreamPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogWinsockTcpStream_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogWinsockTcpStreamPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogWinsockTcpStream_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWinsockTcpStreamPool, FallbackSendIsNoOp) +{ + FillPool(); + overflow = SolidSyslogWinsockTcpStream_Create(); + + CHECK_TRUE(SolidSyslogStream_Send(overflow, "x", 1)); +} + +TEST(SolidSyslogWinsockTcpStreamPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogWinsockTcpStream_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWinsockTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogWinsockTcpStream_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogWinsockTcpStream_Create(); + ConfigLockFake_Install(); + + SolidSyslogWinsockTcpStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogStream stranger = {}; + + SolidSyslogWinsockTcpStream_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogStream stranger = {}; + + SolidSyslogWinsockTcpStream_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogWinsockTcpStreamPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogWinsockTcpStream_Create(); + SolidSyslogWinsockTcpStream_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogWinsockTcpStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_WINSOCKTCPSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + TEST(SolidSyslogWinsockTcpStream, DestroyClosesWithSocketFd) { SolidSyslogStream_Open(stream, addr);