diff --git a/Bdd/Targets/FreeRtos/CMakeLists.txt b/Bdd/Targets/FreeRtos/CMakeLists.txt index cbcf5018..63212898 100644 --- a/Bdd/Targets/FreeRtos/CMakeLists.txt +++ b/Bdd/Targets/FreeRtos/CMakeLists.txt @@ -176,10 +176,14 @@ add_executable(SolidSyslogBddTarget ${CMAKE_SOURCE_DIR}/Platform/FatFs/Source/SolidSyslogFatFsFile.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogAddress.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramStatic.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexStatic.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverStatic.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetInteractive.c ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetIps.c ${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetLanguage.c diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index e776e8e7..e955bdff 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -155,10 +155,6 @@ static struct SolidSyslogMessage testMessage = { static NetworkInterface_t networkInterface; static NetworkEndPoint_t networkEndPoint; -static SolidSyslogFreeRtosStaticResolverStorage resolverStorage; -static SolidSyslogFreeRtosDatagramStorage datagramStorage; -static SolidSyslogFreeRtosTcpStreamStorage tcpStreamStorage; - /* CircularBuffer + FreeRtosMutex composition for cross-task emission. * 8 max-sized messages is comfortably above the 3-message BDD scenarios * with headroom for a brief Service drain stall, and ~16 KB of .bss is @@ -169,14 +165,12 @@ enum }; static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)]; -static SolidSyslogFreeRtosMutexStorage mutexStorage; /* Lifecycle mutex serialises SolidSyslog_Service against the rebuild path * (`set store file` swaps NullStore for the file-backed BlockStore by * destroying and re-creating SolidSyslog mid-run). Service holds the lock * for one Service() call per iteration; the rebuild path holds it across * Destroy → BlockStore_Create → Create. */ -static SolidSyslogFreeRtosMutexStorage lifecycleMutexStorage; static struct SolidSyslogMutex* lifecycleMutex = NULL; static volatile bool solidSyslogReady; /* Signals Service to self-delete BEFORE Teardown destroys the lifecycle @@ -775,8 +769,8 @@ static void InteractiveTask(void* argument) { (void) argument; - resolver = SolidSyslogFreeRtosStaticResolver_Create(&resolverStorage, TEST_DESTINATION_IPV4); - datagram = SolidSyslogFreeRtosDatagram_Create(&datagramStorage); + resolver = SolidSyslogFreeRtosStaticResolver_Create(TEST_DESTINATION_IPV4); + datagram = SolidSyslogFreeRtosDatagram_Create(); struct SolidSyslogUdpSenderConfig udpConfig = { .Resolver = resolver, @@ -790,7 +784,7 @@ static void InteractiveTask(void* argument) * UDP endpoint callbacks because the BDD oracle (syslog-ng) listens on the * same host:port for both transports — the syslog-ng config in * Bdd/syslog-ng/syslog-ng.conf has a TCP listener on 5514 alongside UDP. */ - tcpStream = SolidSyslogFreeRtosTcpStream_Create(&tcpStreamStorage); + tcpStream = SolidSyslogFreeRtosTcpStream_Create(); struct SolidSyslogStreamSenderConfig tcpConfig = { .Resolver = resolver, .Stream = tcpStream, @@ -820,12 +814,12 @@ static void InteractiveTask(void* argument) * emission in S08.04 slice 3 will add more). The buffer's Read side * is the Service task; its Write side is whichever task calls * SolidSyslog_Log. */ - bufferMutex = SolidSyslogFreeRtosMutex_Create(&mutexStorage); + bufferMutex = SolidSyslogFreeRtosMutex_Create(); buffer = SolidSyslogCircularBuffer_Create(bufferMutex, bufferRing, sizeof(bufferRing)); /* Lifecycle mutex created up front so the Service task can take it * from its very first iteration without a NULL check. */ - lifecycleMutex = SolidSyslogFreeRtosMutex_Create(&lifecycleMutexStorage); + lifecycleMutex = SolidSyslogFreeRtosMutex_Create(); /* Default store is NullStore — flipped to FatFs/BlockStore by * `set store file` via RebuildWithFileStore(). */ diff --git a/Bdd/Targets/FreeRtos/solidsyslog_user_tunables.h b/Bdd/Targets/FreeRtos/solidsyslog_user_tunables.h index c0a3d2c9..1103077b 100644 --- a/Bdd/Targets/FreeRtos/solidsyslog_user_tunables.h +++ b/Bdd/Targets/FreeRtos/solidsyslog_user_tunables.h @@ -16,4 +16,13 @@ * generated Bdd/features/steps/solidsyslog_tunables.py mirror). */ #define SOLIDSYSLOG_MAX_MESSAGE_SIZE 512 +/* The BDD target creates two FreeRtosMutex instances: `bufferMutex` (gating + * the CircularBuffer producers against the Service task drain) and + * `lifecycleMutex` (serialising SolidSyslog_Service against the + * `set store file` rebuild path that destroys and re-creates SolidSyslog + * mid-run). The library default of 1 would silently fall the second Create + * back to NullMutex — Lock/Unlock would become no-ops and the rebuild path + * could race the Service task. */ +#define SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE 2U + #endif /* SOLIDSYSLOG_USER_TUNABLES_H */ diff --git a/CLAUDE.md b/CLAUDE.md index 0b991e95..fab64f07 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -326,15 +326,15 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `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) | +| `SolidSyslogFreeRtosStaticResolver.h` | System setup code on FreeRTOS targets pinned to a hardcoded IPv4 destination (no DNS) | `SolidSyslogFreeRtosStaticResolver_Create(ipv4Octets)`, `_Destroy(base)` (ignores host and transport at Resolve time; port is taken from each call). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullResolver`. | | `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`) | +| `SolidSyslogFreeRtosDatagram.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-TCP for UDP | `SolidSyslogFreeRtosDatagram_Create(void)`, `_Destroy(base)` (wraps `FreeRTOS_socket` / `FreeRTOS_sendto` / `FreeRTOS_closesocket`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullDatagram`. | | `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) | +| `SolidSyslogFreeRtosTcpStream.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-TCP for TCP | `SolidSyslogFreeRtosTcpStream_Create(void)`, `_Destroy(base)` (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). Instance struct lives in a library-internal static pool (E11); default pool size 2 to support the future TLS-via-mbedTLS + plain-TCP pair (S08.07). Pool-exhaustion fallback is the shared `SolidSyslogNullStream`. | | `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`. | @@ -350,7 +350,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `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 | `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`) | +| `SolidSyslogFreeRtosMutex.h` | System setup code on FreeRTOS targets needing thread-safe buffering | `SolidSyslogFreeRtosMutex_Create(void)`, `_Destroy(base)` (wraps `xSemaphoreCreateMutexStatic`; requires `configSUPPORT_STATIC_ALLOCATION=1`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullMutex`. | | `SolidSyslogStore.h` | Library internals consuming a store (Service algorithm) and integrator code querying capacity | `SolidSyslogStore_Write`, `_ReadNextUnsent`, `_MarkSent`, `_HasUnsent`, `_IsHalted`, `_GetTotalBytes`, `_GetUsedBytes` | | `SolidSyslogStoreDefinition.h` | Store implementors (extension point) | `SolidSyslogStore` vtable struct (`Write`, `ReadNextUnsent`, `MarkSent`, `HasUnsent`, `IsHalted`, `GetTotalBytes`, `GetUsedBytes`) | | `SolidSyslogNullStore.h` | System setup code (no store-and-forward) | `SolidSyslogNullStore_Get` | diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index ad7b2260..20439250 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -456,4 +456,89 @@ #error "SOLIDSYSLOG_WINSOCK_TCP_STREAM_POOL_SIZE must be >= 1" #endif +/* + * Number of SolidSyslogFreeRtosMutex instances the library's internal + * static pool can simultaneously hold. Each instance carries a + * StaticSemaphore_t — the kernel-primitive layout the adapter wraps + * via xSemaphoreCreateMutexStatic. + * + * Default 1 — almost all FreeRTOS integrators wire a single mutex + * 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_FREE_RTOS_MUTEX_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE < 1 +#error "SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogFreeRtosDatagram instances the library's + * internal static pool can simultaneously hold. Each instance carries + * a FreeRTOS-Plus-TCP Socket_t. + * + * Default 1 — almost all FreeRTOS integrators wire a single datagram + * 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_FREE_RTOS_DATAGRAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogFreeRtosStaticResolver instances the library's + * internal static pool can simultaneously hold. Each instance carries + * the 4-byte IPv4 octets the integrator pins it to. + * + * Default 1 — the static resolver pairs with a single hardcoded + * destination; the typical FreeRTOS integrator wires one. Bump via + * SOLIDSYSLOG_USER_TUNABLES_FILE if more than one is needed. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE < 1 +#error "SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE must be >= 1" +#endif + +/* + * Number of SolidSyslogFreeRtosTcpStream instances the library's + * internal static pool can simultaneously hold. Each instance carries + * a FreeRTOS-Plus-TCP Socket_t for the bounded-blocking-connect + * non-blocking TCP transport. + * + * Default 2 — matches the POSIX and Windows TCP stream pool defaults + * so a future TLS-via-mbedTLS path (S08.07) wrapping an underlying + * FreeRtosTcpStream does not silently fall back to NullStream on the + * second Create. Plain-TCP-only integrators pay 8 bytes of extra + * static state; mbedTLS integrators are spared an override. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE 2U +#endif + +#if SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE < 1 +#error "SOLIDSYSLOG_FREE_RTOS_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 8258ab2b..87563b8f 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -107,5 +107,21 @@ "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" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_POOL_EXHAUSTED \ + "SolidSyslogFreeRtosMutex_Create pool exhausted; returning fallback mutex" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_UNKNOWN_DESTROY \ + "SolidSyslogFreeRtosMutex_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_POOL_EXHAUSTED \ + "SolidSyslogFreeRtosDatagram_Create pool exhausted; returning fallback datagram" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_UNKNOWN_DESTROY \ + "SolidSyslogFreeRtosDatagram_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_POOL_EXHAUSTED \ + "SolidSyslogFreeRtosStaticResolver_Create pool exhausted; returning fallback resolver" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_UNKNOWN_DESTROY \ + "SolidSyslogFreeRtosStaticResolver_Destroy called with a handle not issued by this pool" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_POOL_EXHAUSTED \ + "SolidSyslogFreeRtosTcpStream_Create pool exhausted; returning fallback stream" +#define SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_UNKNOWN_DESTROY \ + "SolidSyslogFreeRtosTcpStream_Destroy called with a handle not issued by this pool" #endif /* SOLIDSYSLOGERRORMESSAGES_H */ diff --git a/DEVLOG.md b/DEVLOG.md index a13de82f..9d328bb7 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,206 @@ # Dev Log +## 2026-05-20 — S11.08: FreeRTOS adapters onto PoolAllocator + +Closes S11.08 (#410). The third platform-adapter sweep in E11 — applies +the canonical 3-TU split + `SolidSyslogPoolAllocator` to all four +stateful FreeRTOS adapter classes. Every FreeRTOS adapter is +storage-cast today, so the public-header surgery is uniform: drop the +`Storage` typedef and the `SOLIDSYSLOG_FREE_RTOS_*_SIZE` enum, +drop the `storage` parameter from `_Create`. `FreeRtosStaticResolver` +keeps its `ipv4Octets[4]` parameter — that's its runtime state, not +storage. The previous `_Destroy(base)` shape on all four matches the +new contract so no `_Destroy(void) → _Destroy(handle)` migrations are +needed. + +Four per-class commits on the branch, then DEVLOG. No new public GoF +nulls (all four needed — `NullMutex` `_Get`, `NullDatagram`, +`NullStream`, `NullResolver` — already landed in S11.06). + +- `3f4a43b` — `FreeRtosMutex` (storage-cast). `Initialise` guards + `xSemaphoreCreateMutexStatic` returning NULL (only happens when + `configSUPPORT_STATIC_ALLOCATION` is misconfigured, but mirrors + `PosixMutex`'s defence against `pthread_mutex_init` failure for + symmetry) and installs the shared `NullMutex` vtable on failure; + `Cleanup` keys `vSemaphoreDelete` off `Base.Lock == FreeRtosMutex_Lock` + so it doesn't delete an uninitialised semaphore. Refactor-only + otherwise. +- `4404745` — `FreeRtosDatagram` (storage-cast). The ARP-priming + yield-once pattern from S08.03 slice 3b.1.5 (see + `project_freertos_arp_first_packet`) is preserved verbatim. The + `DEFAULT_INSTANCE` / `DESTROYED_INSTANCE` static const seeds the + storage-cast variant used to clear the vtable + Socket are gone; that + job moves to `Initialise` (sets vtable, marks Socket + `FREERTOS_INVALID_SOCKET`) / `Cleanup` (closes any open socket, + overwrites with `NullDatagram`). +- `ef7a385` — `FreeRtosStaticResolver` (storage-cast). The only + FreeRTOS adapter with a `_Create` arg: `ipv4Octets[4]` plumbs through + to `Initialise`. The test's `RecreateResolverWith` helper now + Destroy/Create-cycles a slot rather than re-stomping caller storage. + The pre-migration `DestroyIsIdempotent` test is superseded by + `DestroyOfStaleHandleReportsWarning` in the new Pool group — double- + Destroy now emits a WARNING per the bad-setup contract. +- `4cb02ed` — `FreeRtosTcpStream` (storage-cast). Largest commit; the + bounded-200 ms SO_SNDTIMEO/SO_RCVTIMEO connect dance, the + post-connect timeout clear, the ARP-priming yield-once on cache miss, + and the `FreeRTOS_recv` RCVTIMEO=0 "would-block-as-zero" contract are + all preserved verbatim. **Refactor-only.** + +### Decisions + +- **No new public GoF nulls in this story.** All four needed + (`NullMutex` `_Get`, `NullDatagram`, `NullStream`, `NullResolver`) + landed in S11.06. S11.08 is a pure migration sweep, no new API + surface. + +- **`FreeRtosTcpStream` pool size default 2U**, matching the POSIX and + Windows TCP stream defaults rather than the FreeRTOS BDD target's + immediate need of 1. Rationale: when S08.07 (#272) lands TLS via + mbedTLS on FreeRTOS, the TLS-underlying-TCP path will need a second + concurrent TCP stream alongside the plain-TCP one. Pre-paying 8 bytes + of static state to spare every mbedTLS integrator an override is the + cleaner default. Same precedent as POSIX/Windows. + +- **`FreeRtosMutex_Initialise` partial-init guard.** Symmetric with + `PosixMutex_Initialise`. `xSemaphoreCreateMutexStatic`'s documented + NULL return is a `configSUPPORT_STATIC_ALLOCATION != 1` compile-time + config gate, not a runtime failure mode in practice, but the guard + costs nothing and keeps the platform-adapter pattern uniform. + +- **Pool-exhaustion fallback uses the shared GoF null per class.** + Same severity vocabulary as S11.06/S11.07 — ERROR on exhausted + Create, WARNING on unknown/stale Destroy. Error message constants + follow the `SOLIDSYSLOG_ERROR_MSG_FREERTOS<*>_POOL_EXHAUSTED` / + `_UNKNOWN_DESTROY` pattern verbatim. + +- **INTERFACE library shape preserved.** `Platform/FreeRtos/CMakeLists.txt` + still exports headers only — sources are recompiled per consumer. + Every consumer's source list gets the four new `*Static.c` files + added: `Bdd/Targets/FreeRtos/CMakeLists.txt`, and per-exe in + `Tests/FreeRtos/CMakeLists.txt`. The Mutex / Datagram / TcpStream / + StaticResolver test exes additionally gain `ConfigLockFake` and + `ErrorHandlerFake` links for the new Pool TEST_GROUPs (first time + the FreeRTOS test exes pull in those fakes). + +- **All host gates green from the freertos-host devcontainer:** debug + build + 9/9 ctest exes + 1131 unit tests, sanitize, coverage (99.5% + overall — same pre-existing gap as POSIX/Windows where the lcov + capture only runs the main `SolidSyslogTests` exe; FreeRTOS adapter + coverage is captured per-class by the separate test exes and is at + 100% locally per per-class test runs), analyze-tidy clean on every + new/migrated file, analyze-cppcheck clean, analyze-format clean. + Per-class Pool TEST_GROUPs add 37 tests overall (9 each for Mutex, + Datagram, TcpStream; 10 for StaticResolver — 9 canonical + 1 + class-specific `FallbackResolveReturnsFalse`). The pre-migration + `DestroyIsIdempotent` test on `FreeRtosStaticResolver` is superseded + by `DestroyOfStaleHandleReportsWarning` in the new Pool group, so + net test-count change is +36. See per-class breakdown below. + +### Per-class delta numbers + +- `FreeRtosMutex`: 13 tests total (4 existing + 9 Pool). +- `FreeRtosDatagram`: 30 tests total (21 existing + 9 Pool). +- `FreeRtosStaticResolver`: 18 tests total (9 existing − 1 superseded + `DestroyIsIdempotent` + 10 Pool, of which 9 mirror the canonical set + and 1 (`FallbackResolveReturnsFalse`) is class-specific). +- `FreeRtosTcpStream`: 46 tests total (37 existing + 9 Pool). + +### freertos-target devcontainer pass — cross-build + QEMU smoke + +After completing the host TDD work, switched into the `freertos-target` +devcontainer and validated: + +1. **`cmake --preset freertos-cross && cmake --build --preset + freertos-cross`** — clean ARM Cortex-M3 cross-build; all four new + `*Static.c` files compile under arm-none-eabi-gcc 12.2 and the + `SolidSyslogBddTarget.elf` links (~951 KB, statically linked, + `mps2-an385` cpu/board). + +2. **QEMU boot smoke** — `qemu-system-arm -M mps2-an385 -kernel + …BddTarget.elf` boots to the `SolidSyslog>` prompt over slirp; piped + `get host` / `get port` / `send 1` / `quit` produces the expected + `Sent 1 message` and the lifecycle teardown print + (`[stack-hwm] interactive=… service=…`). + +The smoke run caught one **real correctness regression** in the BDD +target's own setup, fixed inline in this branch's Mutex commit: + +- The FreeRTOS BDD target creates **two** `FreeRtosMutex` instances + (`bufferMutex` for the CircularBuffer, `lifecycleMutex` for the + Service-vs-rebuild critical section). The library default + `SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE = 1U` correctly serves the + typical single-mutex integrator, but the BDD target's second + `_Create` was silently falling back to `SolidSyslogNullMutex` — + Lock/Unlock would no-op and the rebuild path could race the Service + task. Symptom-only it surfaced at `quit` teardown as a `severity=4` + `SolidSyslogFreeRtosMutex_Destroy called with a handle not issued by + this pool` warning when Teardown destroyed the NullMutex handle as + if it were a pool slot. Fixed by adding + `#define SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE 2U` to + `Bdd/Targets/FreeRtos/solidsyslog_user_tunables.h`, alongside the + existing `MAX_MESSAGE_SIZE` override. Smoke clean after the fix — + no warning, both mutexes are real pool slots. + + Pre-S11.08 this same configuration worked because each mutex got + its own `Storage` blob; the migration's pool semantics are exactly + what surfaces the multi-instance need explicitly, which is the + intended E11 motivation. The library default stays at 1U — the BDD + target is the unusual case carrying its own override. + +### Handoff for the next devcontainer / WSL pass + +Two CI-gated checks remain unvalidated and need a context switch: + +1. **MSVC** (`msvc-debug` preset, Windows host). Per `feedback_local_msvc`, + a local MSVC build before push is the expectation. **Action for + next context: run from WSL/Windows host with VS 2026 + vcpkg.** + Expected pass; the FreeRTOS adapter pack is not built on MSVC + (it's a FreeRTOS-only INTERFACE target). The only MSVC-visible + changes are the new entries in + `Core/Interface/SolidSyslogTunablesDefaults.h` and + `Core/Source/SolidSyslogErrorMessages.h` plus the + `CLAUDE.md`/`DEVLOG.md` docs — none of those affect compile output. + +2. **`bdd-freertos-qemu`** — runs the actual BDD scenarios against the + QEMU-hosted `SolidSyslogBddTarget`. Validates that the migration + doesn't regress the FreeRTOS interactive UART command channel, + UDP/TCP S&F, FatFs store-and-forward. The QEMU boot + `send 1` + smoke above covers a sliver of this; the full scenarios test more + paths including `set store file` (which now exercises a real + lifecycle mutex thanks to the pool-size-2 override). **Action: + either `behave` devcontainer (the BDD scenarios run as a Python + harness against a QEMU-hosted target) or push the branch and let + CI run them.** Expected pass. + +3. **`analyze-iwyu`** — no `include-what-you-use` in either + freertos-host or freertos-target; CI will report. + +### Known stale findings (this devcontainer, pre-existing) + +- `Tests/FreeRtos/CmsdkUartFake.c` triggers a handful of tidy + `cppcoreguidelines-macro-to-enum` / + `bugprone-easily-swappable-parameters` errors under the `tidy` + preset in *this* devcontainer. The CI `analyze-tidy` job runs in the + `cpputest` container without `FREERTOS_KERNEL_PATH` set, so the + `Tests/FreeRtos` subdir is excluded entirely and these errors are + not gating. They predate S11.08 — confirmed by checking out main and + re-running tidy in this container. Out of scope for this story; the + fix is either to clean up `CmsdkUartFake.c` or to add + `freertos-host` to the CI analyze-tidy matrix. Not blocking the + push. + +### Deferred + +- **`FreeRtosGetAddrInfoResolver`** (DNS via `FreeRTOS_getaddrinfo`) + — tracked under S08.08 (#288). Not implemented yet, so nothing to + migrate. +- **`TlsStream` via mbedTLS** — S08.07 (#272). Will sweep alongside + the AtomicCounter family in S11.09. +- **`SolidSyslogAddress` (FreeRTOS variant)** — utility on a struct, + no Create / Destroy. Out of scope for E11. +- **Audience-table row consistency.** Updated for all four FreeRTOS + classes; same shape as POSIX/Windows rows updated in S11.06/S11.07. + ## 2026-05-20 — chore: `*Static.c` helper functions to `static inline` Mechanical sweep over every `*Static.c` file across Core, Posix and Windows diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosDatagram.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosDatagram.h index 3d6932e5..9758b449 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosDatagram.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosDatagram.h @@ -3,23 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogDatagram; - enum - { - SOLIDSYSLOG_FREE_RTOS_DATAGRAM_SIZE = sizeof(intptr_t) * 8U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_FREE_RTOS_DATAGRAM_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogFreeRtosDatagramStorage; - - struct SolidSyslogDatagram* SolidSyslogFreeRtosDatagram_Create(SolidSyslogFreeRtosDatagramStorage * storage); + struct SolidSyslogDatagram* SolidSyslogFreeRtosDatagram_Create(void); void SolidSyslogFreeRtosDatagram_Destroy(struct SolidSyslogDatagram * base); EXTERN_C_END diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h index 1d09d2d6..1e8f61a1 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h @@ -3,23 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogMutex; - enum - { - SOLIDSYSLOG_FREE_RTOS_MUTEX_SIZE = sizeof(intptr_t) * 20U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_FREE_RTOS_MUTEX_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogFreeRtosMutexStorage; - - struct SolidSyslogMutex* SolidSyslogFreeRtosMutex_Create(SolidSyslogFreeRtosMutexStorage * storage); + struct SolidSyslogMutex* SolidSyslogFreeRtosMutex_Create(void); void SolidSyslogFreeRtosMutex_Destroy(struct SolidSyslogMutex * base); EXTERN_C_END diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosStaticResolver.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosStaticResolver.h index a8dbf745..756b7dca 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosStaticResolver.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosStaticResolver.h @@ -9,20 +9,7 @@ EXTERN_C_BEGIN struct SolidSyslogResolver; - enum - { - SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_SIZE = sizeof(intptr_t) * 4U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogFreeRtosStaticResolverStorage; - - struct SolidSyslogResolver* SolidSyslogFreeRtosStaticResolver_Create( - SolidSyslogFreeRtosStaticResolverStorage * storage, - const uint8_t ipv4Octets[4] - ); + struct SolidSyslogResolver* SolidSyslogFreeRtosStaticResolver_Create(const uint8_t ipv4Octets[4]); void SolidSyslogFreeRtosStaticResolver_Destroy(struct SolidSyslogResolver * base); EXTERN_C_END diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h index 5b9a8666..b031c0bd 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosTcpStream.h @@ -3,23 +3,11 @@ #include "ExternC.h" -#include - EXTERN_C_BEGIN struct SolidSyslogStream; - enum - { - SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_SIZE = sizeof(intptr_t) * 8U - }; - - typedef struct - { - intptr_t slots[(SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_SIZE + sizeof(intptr_t) - 1U) / sizeof(intptr_t)]; - } SolidSyslogFreeRtosTcpStreamStorage; - - struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(SolidSyslogFreeRtosTcpStreamStorage * storage); + struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(void); void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream * base); EXTERN_C_END diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c index 729d25e5..48c87b0c 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c @@ -3,6 +3,10 @@ #include "SolidSyslogFreeRtosDatagram.h" +#include +#include +#include + #include "FreeRTOS.h" #include "FreeRTOS_ARP.h" #include "FreeRTOS_Sockets.h" @@ -10,22 +14,10 @@ #include "SolidSyslogAddressInternal.h" #include "SolidSyslogDatagramDefinition.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogFreeRtosDatagramPrivate.h" +#include "SolidSyslogNullDatagram.h" #include "SolidSyslogUdpPayload.h" -typedef struct SolidSyslogFreeRtosDatagram FreeRtosDatagram; - -struct SolidSyslogFreeRtosDatagram -{ - struct SolidSyslogDatagram Base; - Socket_t Socket; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(FreeRtosDatagram) <= SOLIDSYSLOG_FREE_RTOS_DATAGRAM_SIZE, - "SOLIDSYSLOG_FREE_RTOS_DATAGRAM_SIZE is too small for SolidSyslogFreeRtosDatagram layout" -); - /* Time the calling task yields after issuing an ARP probe so the IP task can * receive the reply and populate the cache before we attempt FreeRTOS_sendto. * 50 ms is generous against typical sub-millisecond LAN ARP RTT but short @@ -43,48 +35,36 @@ static enum SolidSyslogDatagramSendResult FreeRtosDatagram_SendTo( static size_t FreeRtosDatagram_MaxPayload(struct SolidSyslogDatagram* base); static void FreeRtosDatagram_Close(struct SolidSyslogDatagram* base); -static inline FreeRtosDatagram* FreeRtosDatagram_SelfFromStorage(SolidSyslogFreeRtosDatagramStorage* storage); -static inline FreeRtosDatagram* FreeRtosDatagram_SelfFromBase(struct SolidSyslogDatagram* base); -static inline bool FreeRtosDatagram_IsOpen(const FreeRtosDatagram* self); +static inline struct SolidSyslogFreeRtosDatagram* FreeRtosDatagram_SelfFromBase(struct SolidSyslogDatagram* base); +static inline bool FreeRtosDatagram_IsOpen(const struct SolidSyslogFreeRtosDatagram* self); static inline void FreeRtosDatagram_PrimeArpIfMissing(uint32_t ip); -static const FreeRtosDatagram DEFAULT_INSTANCE = { - {FreeRtosDatagram_Open, FreeRtosDatagram_SendTo, FreeRtosDatagram_MaxPayload, FreeRtosDatagram_Close}, - FREERTOS_INVALID_SOCKET, -}; - -static const FreeRtosDatagram DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL}, - FREERTOS_INVALID_SOCKET, -}; - -struct SolidSyslogDatagram* SolidSyslogFreeRtosDatagram_Create(SolidSyslogFreeRtosDatagramStorage* storage) -{ - FreeRtosDatagram* self = FreeRtosDatagram_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; -} - -static inline FreeRtosDatagram* FreeRtosDatagram_SelfFromStorage(SolidSyslogFreeRtosDatagramStorage* storage) +void FreeRtosDatagram_Initialise(struct SolidSyslogDatagram* base) { - return (FreeRtosDatagram*) storage; + struct SolidSyslogFreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); + self->Base.Open = FreeRtosDatagram_Open; + self->Base.SendTo = FreeRtosDatagram_SendTo; + self->Base.MaxPayload = FreeRtosDatagram_MaxPayload; + self->Base.Close = FreeRtosDatagram_Close; + self->Socket = FREERTOS_INVALID_SOCKET; } -void SolidSyslogFreeRtosDatagram_Destroy(struct SolidSyslogDatagram* base) +void FreeRtosDatagram_Cleanup(struct SolidSyslogDatagram* base) { - FreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); FreeRtosDatagram_Close(base); - *self = DESTROYED_INSTANCE; + /* 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 inline FreeRtosDatagram* FreeRtosDatagram_SelfFromBase(struct SolidSyslogDatagram* base) +static inline struct SolidSyslogFreeRtosDatagram* FreeRtosDatagram_SelfFromBase(struct SolidSyslogDatagram* base) { - return (FreeRtosDatagram*) base; + return (struct SolidSyslogFreeRtosDatagram*) base; } static bool FreeRtosDatagram_Open(struct SolidSyslogDatagram* base) { - FreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); + struct SolidSyslogFreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); if (!FreeRtosDatagram_IsOpen(self)) { self->Socket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP); @@ -99,7 +79,7 @@ static enum SolidSyslogDatagramSendResult FreeRtosDatagram_SendTo( const struct SolidSyslogAddress* addr ) { - FreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); + struct SolidSyslogFreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); enum SolidSyslogDatagramSendResult result = SOLIDSYSLOG_DATAGRAM_SEND_RESULT_FAILED; if (FreeRtosDatagram_IsOpen(self)) { @@ -129,7 +109,7 @@ static inline void FreeRtosDatagram_PrimeArpIfMissing(uint32_t ip) } } -static inline bool FreeRtosDatagram_IsOpen(const FreeRtosDatagram* self) +static inline bool FreeRtosDatagram_IsOpen(const struct SolidSyslogFreeRtosDatagram* self) { return self->Socket != FREERTOS_INVALID_SOCKET; } @@ -142,7 +122,7 @@ static size_t FreeRtosDatagram_MaxPayload(struct SolidSyslogDatagram* base) static void FreeRtosDatagram_Close(struct SolidSyslogDatagram* base) { - FreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); + struct SolidSyslogFreeRtosDatagram* self = FreeRtosDatagram_SelfFromBase(base); if (FreeRtosDatagram_IsOpen(self)) { (void) FreeRTOS_closesocket(self->Socket); diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramPrivate.h b/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramPrivate.h new file mode 100644 index 00000000..4c2cfa74 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramPrivate.h @@ -0,0 +1,18 @@ +#ifndef SOLIDSYSLOGFREERTOSDATAGRAMPRIVATE_H +#define SOLIDSYSLOGFREERTOSDATAGRAMPRIVATE_H + +#include "FreeRTOS.h" +#include "FreeRTOS_Sockets.h" + +#include "SolidSyslogDatagramDefinition.h" + +struct SolidSyslogFreeRtosDatagram +{ + struct SolidSyslogDatagram Base; + Socket_t Socket; +}; + +void FreeRtosDatagram_Initialise(struct SolidSyslogDatagram* base); +void FreeRtosDatagram_Cleanup(struct SolidSyslogDatagram* base); + +#endif /* SOLIDSYSLOGFREERTOSDATAGRAMPRIVATE_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramStatic.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramStatic.c new file mode 100644 index 00000000..78a85d48 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogFreeRtosDatagram.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogFreeRtosDatagramPrivate.h" +#include "SolidSyslogNullDatagram.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogDatagram; + +static inline size_t FreeRtosDatagram_IndexFromHandle(const struct SolidSyslogDatagram* base); +static inline void FreeRtosDatagram_CleanupAtIndex(size_t index, void* context); + +static bool FreeRtosDatagram_InUse[SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE]; +static struct SolidSyslogFreeRtosDatagram FreeRtosDatagram_Pool[SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator FreeRtosDatagram_Allocator = { + FreeRtosDatagram_InUse, + SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE +}; + +struct SolidSyslogDatagram* SolidSyslogFreeRtosDatagram_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&FreeRtosDatagram_Allocator); + struct SolidSyslogDatagram* handle = SolidSyslogNullDatagram_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosDatagram_Allocator, index)) + { + FreeRtosDatagram_Initialise(&FreeRtosDatagram_Pool[index].Base); + handle = &FreeRtosDatagram_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogFreeRtosDatagram_Destroy(struct SolidSyslogDatagram* base) +{ + size_t index = FreeRtosDatagram_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosDatagram_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&FreeRtosDatagram_Allocator, index, FreeRtosDatagram_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_UNKNOWN_DESTROY); + } +} + +static inline size_t FreeRtosDatagram_IndexFromHandle(const struct SolidSyslogDatagram* base) +{ + size_t result = SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE; poolIndex++) + { + if (base == &FreeRtosDatagram_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void FreeRtosDatagram_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + FreeRtosDatagram_Cleanup(&FreeRtosDatagram_Pool[index].Base); +} diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c index 28ff2429..30fef2f9 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c @@ -1,63 +1,58 @@ #include "SolidSyslogFreeRtosMutex.h" +#include + #include "FreeRTOS.h" #include "semphr.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogFreeRtosMutexPrivate.h" #include "SolidSyslogMutexDefinition.h" - -typedef struct SolidSyslogFreeRtosMutex FreeRtosMutex; - -/* xSemaphoreCreateMutexStatic returns a handle that is the same pointer - * as the StaticSemaphore_t passed in, so the per-instance struct doesn't - * carry a separate SemaphoreHandle_t — the kernel-primitive layout matches - * the Posix (pthread_mutex_t) and Windows (CRITICAL_SECTION) adapters. */ -struct SolidSyslogFreeRtosMutex -{ - struct SolidSyslogMutex Base; - StaticSemaphore_t Buffer; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(FreeRtosMutex) <= SOLIDSYSLOG_FREE_RTOS_MUTEX_SIZE, - "SOLIDSYSLOG_FREE_RTOS_MUTEX_SIZE is too small for SolidSyslogFreeRtosMutex layout" -); +#include "SolidSyslogNullMutex.h" static void FreeRtosMutex_Lock(struct SolidSyslogMutex* base); static void FreeRtosMutex_Unlock(struct SolidSyslogMutex* base); -static inline FreeRtosMutex* FreeRtosMutex_SelfFromStorage(SolidSyslogFreeRtosMutexStorage* storage); -static inline FreeRtosMutex* FreeRtosMutex_SelfFromBase(struct SolidSyslogMutex* base); -static inline SemaphoreHandle_t FreeRtosMutex_AsHandle(FreeRtosMutex* self); - -struct SolidSyslogMutex* SolidSyslogFreeRtosMutex_Create(SolidSyslogFreeRtosMutexStorage* storage) -{ - FreeRtosMutex* self = FreeRtosMutex_SelfFromStorage(storage); - self->Base.Lock = FreeRtosMutex_Lock; - self->Base.Unlock = FreeRtosMutex_Unlock; - (void) xSemaphoreCreateMutexStatic(&self->Buffer); - return &self->Base; -} +static inline struct SolidSyslogFreeRtosMutex* FreeRtosMutex_SelfFromBase(struct SolidSyslogMutex* base); +static inline SemaphoreHandle_t FreeRtosMutex_AsHandle(struct SolidSyslogFreeRtosMutex* self); -static inline FreeRtosMutex* FreeRtosMutex_SelfFromStorage(SolidSyslogFreeRtosMutexStorage* storage) +void FreeRtosMutex_Initialise(struct SolidSyslogMutex* base) { - return (FreeRtosMutex*) storage; + struct SolidSyslogFreeRtosMutex* self = FreeRtosMutex_SelfFromBase(base); + /* xSemaphoreCreateMutexStatic returns NULL only when + * configSUPPORT_STATIC_ALLOCATION is not 1 — a compile-time config + * gate, not a runtime failure mode. Guarded anyway so a misconfigured + * integrator falls back to the NullMutex vtable instead of corrupting + * Lock/Unlock with a dangling handle, mirroring PosixMutex's defence + * against pthread_mutex_init failure. */ + if (xSemaphoreCreateMutexStatic(&self->Buffer) != NULL) + { + self->Base.Lock = FreeRtosMutex_Lock; + self->Base.Unlock = FreeRtosMutex_Unlock; + } + else + { + *base = *SolidSyslogNullMutex_Get(); + } } -void SolidSyslogFreeRtosMutex_Destroy(struct SolidSyslogMutex* base) +void FreeRtosMutex_Cleanup(struct SolidSyslogMutex* base) { - FreeRtosMutex* self = FreeRtosMutex_SelfFromBase(base); - vSemaphoreDelete(FreeRtosMutex_AsHandle(self)); - self->Base.Lock = NULL; - self->Base.Unlock = NULL; + struct SolidSyslogFreeRtosMutex* self = FreeRtosMutex_SelfFromBase(base); + if (self->Base.Lock == FreeRtosMutex_Lock) + { + vSemaphoreDelete(FreeRtosMutex_AsHandle(self)); + } + /* 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 FreeRtosMutex* FreeRtosMutex_SelfFromBase(struct SolidSyslogMutex* base) +static inline struct SolidSyslogFreeRtosMutex* FreeRtosMutex_SelfFromBase(struct SolidSyslogMutex* base) { - return (FreeRtosMutex*) base; + return (struct SolidSyslogFreeRtosMutex*) base; } -static inline SemaphoreHandle_t FreeRtosMutex_AsHandle(FreeRtosMutex* self) +static inline SemaphoreHandle_t FreeRtosMutex_AsHandle(struct SolidSyslogFreeRtosMutex* self) { return (SemaphoreHandle_t) &self->Buffer; } diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h new file mode 100644 index 00000000..a1226698 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h @@ -0,0 +1,22 @@ +#ifndef SOLIDSYSLOGFREERTOSMUTEXPRIVATE_H +#define SOLIDSYSLOGFREERTOSMUTEXPRIVATE_H + +#include "FreeRTOS.h" +#include "semphr.h" + +#include "SolidSyslogMutexDefinition.h" + +/* xSemaphoreCreateMutexStatic returns a handle that is the same pointer + * as the StaticSemaphore_t passed in, so the per-instance struct doesn't + * carry a separate SemaphoreHandle_t — the kernel-primitive layout matches + * the Posix (pthread_mutex_t) and Windows (CRITICAL_SECTION) adapters. */ +struct SolidSyslogFreeRtosMutex +{ + struct SolidSyslogMutex Base; + StaticSemaphore_t Buffer; +}; + +void FreeRtosMutex_Initialise(struct SolidSyslogMutex* base); +void FreeRtosMutex_Cleanup(struct SolidSyslogMutex* base); + +#endif /* SOLIDSYSLOGFREERTOSMUTEXPRIVATE_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexStatic.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexStatic.c new file mode 100644 index 00000000..88ccb2c0 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexStatic.c @@ -0,0 +1,72 @@ +#include "SolidSyslogFreeRtosMutex.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogFreeRtosMutexPrivate.h" +#include "SolidSyslogNullMutex.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogMutex; + +static inline size_t FreeRtosMutex_IndexFromHandle(const struct SolidSyslogMutex* base); +static inline void FreeRtosMutex_CleanupAtIndex(size_t index, void* context); + +static bool FreeRtosMutex_InUse[SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE]; +static struct SolidSyslogFreeRtosMutex FreeRtosMutex_Pool[SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE]; +static struct SolidSyslogPoolAllocator FreeRtosMutex_Allocator = { + FreeRtosMutex_InUse, + SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE +}; + +struct SolidSyslogMutex* SolidSyslogFreeRtosMutex_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&FreeRtosMutex_Allocator); + struct SolidSyslogMutex* handle = SolidSyslogNullMutex_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosMutex_Allocator, index)) + { + FreeRtosMutex_Initialise(&FreeRtosMutex_Pool[index].Base); + handle = &FreeRtosMutex_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogFreeRtosMutex_Destroy(struct SolidSyslogMutex* base) +{ + size_t index = FreeRtosMutex_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosMutex_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&FreeRtosMutex_Allocator, index, FreeRtosMutex_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_UNKNOWN_DESTROY); + } +} + +static inline size_t FreeRtosMutex_IndexFromHandle(const struct SolidSyslogMutex* base) +{ + size_t result = SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE; poolIndex++) + { + if (base == &FreeRtosMutex_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void FreeRtosMutex_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + FreeRtosMutex_Cleanup(&FreeRtosMutex_Pool[index].Base); +} diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c index 13212b3d..c535c1f3 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c @@ -1,26 +1,18 @@ #include "SolidSyslogFreeRtosStaticResolver.h" +#include +#include +#include + #include "FreeRTOS.h" #include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" #include "SolidSyslogAddressInternal.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogFreeRtosStaticResolverPrivate.h" +#include "SolidSyslogNullResolver.h" #include "SolidSyslogResolverDefinition.h" -typedef struct SolidSyslogFreeRtosStaticResolver FreeRtosStaticResolver; - -struct SolidSyslogFreeRtosStaticResolver -{ - struct SolidSyslogResolver Base; - uint8_t Octets[4]; -}; - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(FreeRtosStaticResolver) <= SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_SIZE, - "SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_SIZE is too small for SolidSyslogFreeRtosStaticResolver layout" -); - static bool FreeRtosStaticResolver_Resolve( struct SolidSyslogResolver* base, enum SolidSyslogTransport transport, @@ -29,51 +21,33 @@ static bool FreeRtosStaticResolver_Resolve( struct SolidSyslogAddress* result ); -static inline FreeRtosStaticResolver* FreeRtosStaticResolver_SelfFromStorage( - SolidSyslogFreeRtosStaticResolverStorage* storage +static inline struct SolidSyslogFreeRtosStaticResolver* FreeRtosStaticResolver_SelfFromBase( + struct SolidSyslogResolver* base ); -static inline FreeRtosStaticResolver* FreeRtosStaticResolver_SelfFromBase(struct SolidSyslogResolver* base); - -static const FreeRtosStaticResolver DEFAULT_INSTANCE = { - {FreeRtosStaticResolver_Resolve}, - {0, 0, 0, 0}, -}; -static const FreeRtosStaticResolver DESTROYED_INSTANCE = { - {NULL}, - {0, 0, 0, 0}, -}; - -struct SolidSyslogResolver* SolidSyslogFreeRtosStaticResolver_Create( - SolidSyslogFreeRtosStaticResolverStorage* storage, - const uint8_t ipv4Octets[4] -) +void FreeRtosStaticResolver_Initialise(struct SolidSyslogResolver* base, const uint8_t ipv4Octets[4]) { - FreeRtosStaticResolver* self = FreeRtosStaticResolver_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; + struct SolidSyslogFreeRtosStaticResolver* self = FreeRtosStaticResolver_SelfFromBase(base); + self->Base.Resolve = FreeRtosStaticResolver_Resolve; self->Octets[0] = ipv4Octets[0]; self->Octets[1] = ipv4Octets[1]; self->Octets[2] = ipv4Octets[2]; self->Octets[3] = ipv4Octets[3]; - return &self->Base; } -static inline FreeRtosStaticResolver* FreeRtosStaticResolver_SelfFromStorage( - SolidSyslogFreeRtosStaticResolverStorage* storage -) +void FreeRtosStaticResolver_Cleanup(struct SolidSyslogResolver* base) { - return (FreeRtosStaticResolver*) storage; + /* Overwrite the abstract base with the shared NullResolver vtable so + * use-after-destroy resolves cleanly to a failed-lookup error path + * rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullResolver_Get(); } -void SolidSyslogFreeRtosStaticResolver_Destroy(struct SolidSyslogResolver* base) -{ - FreeRtosStaticResolver* self = FreeRtosStaticResolver_SelfFromBase(base); - *self = DESTROYED_INSTANCE; -} - -static inline FreeRtosStaticResolver* FreeRtosStaticResolver_SelfFromBase(struct SolidSyslogResolver* base) +static inline struct SolidSyslogFreeRtosStaticResolver* FreeRtosStaticResolver_SelfFromBase( + struct SolidSyslogResolver* base +) { - return (FreeRtosStaticResolver*) base; + return (struct SolidSyslogFreeRtosStaticResolver*) base; } static bool FreeRtosStaticResolver_Resolve( @@ -86,7 +60,7 @@ static bool FreeRtosStaticResolver_Resolve( { (void) transport; (void) host; - FreeRtosStaticResolver* self = FreeRtosStaticResolver_SelfFromBase(base); + struct SolidSyslogFreeRtosStaticResolver* self = FreeRtosStaticResolver_SelfFromBase(base); struct freertos_sockaddr* sockaddr = SolidSyslogAddress_AsFreertosSockaddr(result); sockaddr->sin_family = FREERTOS_AF_INET; sockaddr->sin_port = (uint16_t) FreeRTOS_htons(port); diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverPrivate.h b/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverPrivate.h new file mode 100644 index 00000000..09cbe8a7 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverPrivate.h @@ -0,0 +1,17 @@ +#ifndef SOLIDSYSLOGFREERTOSSTATICRESOLVERPRIVATE_H +#define SOLIDSYSLOGFREERTOSSTATICRESOLVERPRIVATE_H + +#include + +#include "SolidSyslogResolverDefinition.h" + +struct SolidSyslogFreeRtosStaticResolver +{ + struct SolidSyslogResolver Base; + uint8_t Octets[4]; +}; + +void FreeRtosStaticResolver_Initialise(struct SolidSyslogResolver* base, const uint8_t ipv4Octets[4]); +void FreeRtosStaticResolver_Cleanup(struct SolidSyslogResolver* base); + +#endif /* SOLIDSYSLOGFREERTOSSTATICRESOLVERPRIVATE_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverStatic.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverStatic.c new file mode 100644 index 00000000..c0d8dd52 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverStatic.c @@ -0,0 +1,78 @@ +#include "SolidSyslogFreeRtosStaticResolver.h" + +#include +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogFreeRtosStaticResolverPrivate.h" +#include "SolidSyslogNullResolver.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogResolver; + +static inline size_t FreeRtosStaticResolver_IndexFromHandle(const struct SolidSyslogResolver* base); +static inline void FreeRtosStaticResolver_CleanupAtIndex(size_t index, void* context); + +static bool FreeRtosStaticResolver_InUse[SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE]; +static struct SolidSyslogFreeRtosStaticResolver + FreeRtosStaticResolver_Pool[SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE]; +static struct SolidSyslogPoolAllocator FreeRtosStaticResolver_Allocator = { + FreeRtosStaticResolver_InUse, + SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE +}; + +struct SolidSyslogResolver* SolidSyslogFreeRtosStaticResolver_Create(const uint8_t ipv4Octets[4]) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&FreeRtosStaticResolver_Allocator); + struct SolidSyslogResolver* handle = SolidSyslogNullResolver_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosStaticResolver_Allocator, index)) + { + FreeRtosStaticResolver_Initialise(&FreeRtosStaticResolver_Pool[index].Base, ipv4Octets); + handle = &FreeRtosStaticResolver_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogFreeRtosStaticResolver_Destroy(struct SolidSyslogResolver* base) +{ + size_t index = FreeRtosStaticResolver_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosStaticResolver_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &FreeRtosStaticResolver_Allocator, + index, + FreeRtosStaticResolver_CleanupAtIndex, + NULL + ); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_UNKNOWN_DESTROY); + } +} + +static inline size_t FreeRtosStaticResolver_IndexFromHandle(const struct SolidSyslogResolver* base) +{ + size_t result = SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE; poolIndex++) + { + if (base == &FreeRtosStaticResolver_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void FreeRtosStaticResolver_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + FreeRtosStaticResolver_Cleanup(&FreeRtosStaticResolver_Pool[index].Base); +} diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c index a99ea25b..8d005acd 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c @@ -3,6 +3,10 @@ #include "SolidSyslogFreeRtosTcpStream.h" +#include +#include +#include + #include "FreeRTOS.h" #include "FreeRTOS_ARP.h" #include "FreeRTOS_IP.h" @@ -10,18 +14,11 @@ #include "task.h" #include "SolidSyslogAddressInternal.h" -#include "SolidSyslogMacros.h" +#include "SolidSyslogFreeRtosTcpStreamPrivate.h" +#include "SolidSyslogNullStream.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" -typedef struct SolidSyslogFreeRtosTcpStream FreeRtosTcpStream; - -struct SolidSyslogFreeRtosTcpStream -{ - struct SolidSyslogStream Base; - Socket_t Socket; -}; - /* 200 ms is short enough that the Service task keeps draining predictably * during an outage, long enough for a healthy peer to ACK over slirp/LAN. * Both SO_SNDTIMEO and SO_RCVTIMEO are set before FreeRTOS_connect — @@ -61,64 +58,63 @@ static bool FreeRtosTcpStream_Send(struct SolidSyslogStream* base, const void* b static SolidSyslogSsize FreeRtosTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size); static void FreeRtosTcpStream_Close(struct SolidSyslogStream* base); -static inline FreeRtosTcpStream* FreeRtosTcpStream_SelfFromStorage(SolidSyslogFreeRtosTcpStreamStorage* storage); -static inline FreeRtosTcpStream* FreeRtosTcpStream_SelfFromBase(struct SolidSyslogStream* base); -static inline bool FreeRtosTcpStream_IsOpen(const FreeRtosTcpStream* self); -static inline bool FreeRtosTcpStream_IsClosed(const FreeRtosTcpStream* self); -static void FreeRtosTcpStream_OpenSocket(FreeRtosTcpStream* self); -static void FreeRtosTcpStream_ConnectOrCloseOnFailure(FreeRtosTcpStream* self, const struct SolidSyslogAddress* addr); -static bool FreeRtosTcpStream_TryConnect(FreeRtosTcpStream* self, const struct SolidSyslogAddress* addr); +static inline struct SolidSyslogFreeRtosTcpStream* FreeRtosTcpStream_SelfFromBase(struct SolidSyslogStream* base); +static inline bool FreeRtosTcpStream_IsOpen(const struct SolidSyslogFreeRtosTcpStream* self); +static inline bool FreeRtosTcpStream_IsClosed(const struct SolidSyslogFreeRtosTcpStream* self); +static void FreeRtosTcpStream_OpenSocket(struct SolidSyslogFreeRtosTcpStream* self); +static void FreeRtosTcpStream_ConnectOrCloseOnFailure( + struct SolidSyslogFreeRtosTcpStream* self, + const struct SolidSyslogAddress* addr +); +static bool FreeRtosTcpStream_TryConnect( + struct SolidSyslogFreeRtosTcpStream* self, + const struct SolidSyslogAddress* addr +); +static inline void FreeRtosTcpStream_PrimeArpIfMissing(uint32_t ip); static void FreeRtosTcpStream_ClearTimeouts(Socket_t socket); static void FreeRtosTcpStream_SetSendTimeout(Socket_t socket, TickType_t ticks); static void FreeRtosTcpStream_SetRecvTimeout(Socket_t socket, TickType_t ticks); -static bool FreeRtosTcpStream_SendOrCloseOnFailure(FreeRtosTcpStream* self, const void* buffer, size_t size); -static bool FreeRtosTcpStream_TrySend(FreeRtosTcpStream* self, const void* buffer, size_t size); +static bool FreeRtosTcpStream_SendOrCloseOnFailure( + struct SolidSyslogFreeRtosTcpStream* self, + const void* buffer, + size_t size +); +static bool FreeRtosTcpStream_TrySend(struct SolidSyslogFreeRtosTcpStream* self, const void* buffer, size_t size); static bool FreeRtosTcpStream_AllBytesSent(BaseType_t sentCount, size_t expected); -static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure(FreeRtosTcpStream* self, void* buffer, size_t size); -static void FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream* self); - -SOLIDSYSLOG_STATIC_ASSERT( - sizeof(FreeRtosTcpStream) <= SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_SIZE, - "SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_SIZE is too small for SolidSyslogFreeRtosTcpStream layout" +static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure( + struct SolidSyslogFreeRtosTcpStream* self, + void* buffer, + size_t size ); +static void FreeRtosTcpStream_CloseSocket(struct SolidSyslogFreeRtosTcpStream* self); -static const FreeRtosTcpStream DEFAULT_INSTANCE = { - {FreeRtosTcpStream_Open, FreeRtosTcpStream_Send, FreeRtosTcpStream_Read, FreeRtosTcpStream_Close}, - FREERTOS_INVALID_SOCKET, -}; - -static const FreeRtosTcpStream DESTROYED_INSTANCE = { - {NULL, NULL, NULL, NULL}, - FREERTOS_INVALID_SOCKET, -}; - -struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(SolidSyslogFreeRtosTcpStreamStorage* storage) +void FreeRtosTcpStream_Initialise(struct SolidSyslogStream* base) { - FreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromStorage(storage); - *self = DEFAULT_INSTANCE; - return &self->Base; + struct SolidSyslogFreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); + self->Base.Open = FreeRtosTcpStream_Open; + self->Base.Send = FreeRtosTcpStream_Send; + self->Base.Read = FreeRtosTcpStream_Read; + self->Base.Close = FreeRtosTcpStream_Close; + self->Socket = FREERTOS_INVALID_SOCKET; } -static inline FreeRtosTcpStream* FreeRtosTcpStream_SelfFromStorage(SolidSyslogFreeRtosTcpStreamStorage* storage) +void FreeRtosTcpStream_Cleanup(struct SolidSyslogStream* base) { - return (FreeRtosTcpStream*) storage; -} - -void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream* base) -{ - FreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); + struct SolidSyslogFreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); FreeRtosTcpStream_CloseSocket(self); - *self = DESTROYED_INSTANCE; + /* Overwrite the abstract base with the shared NullStream vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullStream_Get(); } -static inline FreeRtosTcpStream* FreeRtosTcpStream_SelfFromBase(struct SolidSyslogStream* base) +static inline struct SolidSyslogFreeRtosTcpStream* FreeRtosTcpStream_SelfFromBase(struct SolidSyslogStream* base) { - return (FreeRtosTcpStream*) base; + return (struct SolidSyslogFreeRtosTcpStream*) base; } static bool FreeRtosTcpStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr) { - FreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); + struct SolidSyslogFreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); if (FreeRtosTcpStream_IsClosed(self)) { FreeRtosTcpStream_OpenSocket(self); @@ -130,22 +126,25 @@ static bool FreeRtosTcpStream_Open(struct SolidSyslogStream* base, const struct return FreeRtosTcpStream_IsOpen(self); } -static inline bool FreeRtosTcpStream_IsOpen(const FreeRtosTcpStream* self) +static inline bool FreeRtosTcpStream_IsOpen(const struct SolidSyslogFreeRtosTcpStream* self) { return self->Socket != FREERTOS_INVALID_SOCKET; } -static inline bool FreeRtosTcpStream_IsClosed(const FreeRtosTcpStream* self) +static inline bool FreeRtosTcpStream_IsClosed(const struct SolidSyslogFreeRtosTcpStream* self) { return !FreeRtosTcpStream_IsOpen(self); } -static void FreeRtosTcpStream_OpenSocket(FreeRtosTcpStream* self) +static void FreeRtosTcpStream_OpenSocket(struct SolidSyslogFreeRtosTcpStream* self) { self->Socket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM, FREERTOS_IPPROTO_TCP); } -static void FreeRtosTcpStream_ConnectOrCloseOnFailure(FreeRtosTcpStream* self, const struct SolidSyslogAddress* addr) +static void FreeRtosTcpStream_ConnectOrCloseOnFailure( + struct SolidSyslogFreeRtosTcpStream* self, + const struct SolidSyslogAddress* addr +) { if (FreeRtosTcpStream_TryConnect(self, addr)) { @@ -157,9 +156,10 @@ static void FreeRtosTcpStream_ConnectOrCloseOnFailure(FreeRtosTcpStream* self, c } } -static inline void FreeRtosTcpStream_PrimeArpIfMissing(uint32_t ip); - -static bool FreeRtosTcpStream_TryConnect(FreeRtosTcpStream* self, const struct SolidSyslogAddress* addr) +static bool FreeRtosTcpStream_TryConnect( + struct SolidSyslogFreeRtosTcpStream* self, + const struct SolidSyslogAddress* addr +) { const struct freertos_sockaddr* dest = SolidSyslogAddress_AsConstFreertosSockaddr(addr); FreeRtosTcpStream_PrimeArpIfMissing(dest->sin_address.ulIP_IPv4); @@ -200,7 +200,7 @@ static void FreeRtosTcpStream_SetRecvTimeout(Socket_t socket, TickType_t ticks) static bool FreeRtosTcpStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size) { - FreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); + struct SolidSyslogFreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); bool sent = false; if (FreeRtosTcpStream_IsOpen(self)) { @@ -209,7 +209,11 @@ static bool FreeRtosTcpStream_Send(struct SolidSyslogStream* base, const void* b return sent; } -static bool FreeRtosTcpStream_SendOrCloseOnFailure(FreeRtosTcpStream* self, const void* buffer, size_t size) +static bool FreeRtosTcpStream_SendOrCloseOnFailure( + struct SolidSyslogFreeRtosTcpStream* self, + const void* buffer, + size_t size +) { bool sent = FreeRtosTcpStream_TrySend(self, buffer, size); if (!sent) @@ -219,7 +223,7 @@ static bool FreeRtosTcpStream_SendOrCloseOnFailure(FreeRtosTcpStream* self, cons return sent; } -static bool FreeRtosTcpStream_TrySend(FreeRtosTcpStream* self, const void* buffer, size_t size) +static bool FreeRtosTcpStream_TrySend(struct SolidSyslogFreeRtosTcpStream* self, const void* buffer, size_t size) { BaseType_t sentCount = FreeRTOS_send(self->Socket, buffer, size, SEND_RECV_FLAGS_DEFAULT); return FreeRtosTcpStream_AllBytesSent(sentCount, size); @@ -235,7 +239,7 @@ static bool FreeRtosTcpStream_AllBytesSent(BaseType_t sentCount, size_t expected * Negative returns are real errors — close the socket and surface failure. */ static SolidSyslogSsize FreeRtosTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size) { - FreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); + struct SolidSyslogFreeRtosTcpStream* self = FreeRtosTcpStream_SelfFromBase(base); SolidSyslogSsize result = READ_FAILED; if (FreeRtosTcpStream_IsOpen(self)) { @@ -244,7 +248,11 @@ static SolidSyslogSsize FreeRtosTcpStream_Read(struct SolidSyslogStream* base, v return result; } -static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure(FreeRtosTcpStream* self, void* buffer, size_t size) +static SolidSyslogSsize FreeRtosTcpStream_ReceiveOrCloseOnFailure( + struct SolidSyslogFreeRtosTcpStream* self, + void* buffer, + size_t size +) { BaseType_t receivedCount = FreeRTOS_recv(self->Socket, buffer, size, SEND_RECV_FLAGS_DEFAULT); SolidSyslogSsize result = READ_FAILED; @@ -264,7 +272,7 @@ static void FreeRtosTcpStream_Close(struct SolidSyslogStream* base) FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream_SelfFromBase(base)); } -static void FreeRtosTcpStream_CloseSocket(FreeRtosTcpStream* self) +static void FreeRtosTcpStream_CloseSocket(struct SolidSyslogFreeRtosTcpStream* self) { if (FreeRtosTcpStream_IsOpen(self)) { diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h new file mode 100644 index 00000000..031e86c2 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamPrivate.h @@ -0,0 +1,18 @@ +#ifndef SOLIDSYSLOGFREERTOSTCPSTREAMPRIVATE_H +#define SOLIDSYSLOGFREERTOSTCPSTREAMPRIVATE_H + +#include "FreeRTOS.h" +#include "FreeRTOS_Sockets.h" + +#include "SolidSyslogStreamDefinition.h" + +struct SolidSyslogFreeRtosTcpStream +{ + struct SolidSyslogStream Base; + Socket_t Socket; +}; + +void FreeRtosTcpStream_Initialise(struct SolidSyslogStream* base); +void FreeRtosTcpStream_Cleanup(struct SolidSyslogStream* base); + +#endif /* SOLIDSYSLOGFREERTOSTCPSTREAMPRIVATE_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c new file mode 100644 index 00000000..8161ef99 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c @@ -0,0 +1,76 @@ +#include "SolidSyslogFreeRtosTcpStream.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogFreeRtosTcpStreamPrivate.h" +#include "SolidSyslogNullStream.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogStream; + +static inline size_t FreeRtosTcpStream_IndexFromHandle(const struct SolidSyslogStream* base); +static inline void FreeRtosTcpStream_CleanupAtIndex(size_t index, void* context); + +static bool FreeRtosTcpStream_InUse[SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE]; +static struct SolidSyslogFreeRtosTcpStream FreeRtosTcpStream_Pool[SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE]; +static struct SolidSyslogPoolAllocator FreeRtosTcpStream_Allocator = { + FreeRtosTcpStream_InUse, + SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE +}; + +struct SolidSyslogStream* SolidSyslogFreeRtosTcpStream_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&FreeRtosTcpStream_Allocator); + struct SolidSyslogStream* handle = SolidSyslogNullStream_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosTcpStream_Allocator, index)) + { + FreeRtosTcpStream_Initialise(&FreeRtosTcpStream_Pool[index].Base); + handle = &FreeRtosTcpStream_Pool[index].Base; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_POOL_EXHAUSTED); + } + return handle; +} + +void SolidSyslogFreeRtosTcpStream_Destroy(struct SolidSyslogStream* base) +{ + size_t index = FreeRtosTcpStream_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&FreeRtosTcpStream_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &FreeRtosTcpStream_Allocator, + index, + FreeRtosTcpStream_CleanupAtIndex, + NULL + ); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_UNKNOWN_DESTROY); + } +} + +static inline size_t FreeRtosTcpStream_IndexFromHandle(const struct SolidSyslogStream* base) +{ + size_t result = SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE; poolIndex++) + { + if (base == &FreeRtosTcpStream_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void FreeRtosTcpStream_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + FreeRtosTcpStream_Cleanup(&FreeRtosTcpStream_Pool[index].Base); +} diff --git a/Tests/FreeRtos/CMakeLists.txt b/Tests/FreeRtos/CMakeLists.txt index 6d868917..9e3b2e57 100644 --- a/Tests/FreeRtos/CMakeLists.txt +++ b/Tests/FreeRtos/CMakeLists.txt @@ -15,6 +15,7 @@ else() SolidSyslogFreeRtosDatagramTest.cpp main.cpp ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagramStatic.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosArpFake.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c @@ -22,6 +23,8 @@ else() target_link_libraries(SolidSyslogFreeRtosDatagramTest PRIVATE ${PROJECT_NAME} + ConfigLockFake + ErrorHandlerFake CppUTest CppUTestExt ) @@ -42,6 +45,7 @@ else() SolidSyslogFreeRtosTcpStreamTest.cpp main.cpp ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStreamStatic.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosArpFake.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c @@ -49,6 +53,8 @@ else() target_link_libraries(SolidSyslogFreeRtosTcpStreamTest PRIVATE ${PROJECT_NAME} + ConfigLockFake + ErrorHandlerFake CppUTest CppUTestExt ) @@ -70,10 +76,13 @@ else() SolidSyslogFreeRtosStaticResolverTest.cpp main.cpp ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolverStatic.c ) target_link_libraries(SolidSyslogFreeRtosStaticResolverTest PRIVATE ${PROJECT_NAME} + ConfigLockFake + ErrorHandlerFake CppUTest CppUTestExt ) @@ -98,11 +107,14 @@ add_executable(SolidSyslogFreeRtosMutexTest SolidSyslogFreeRtosMutexTest.cpp main.cpp ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexStatic.c ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosSemaphoreFake.c ) target_link_libraries(SolidSyslogFreeRtosMutexTest PRIVATE ${PROJECT_NAME} + ConfigLockFake + ErrorHandlerFake CppUTest CppUTestExt ) diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosDatagramTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosDatagramTest.cpp index 63afb770..965f049a 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosDatagramTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosDatagramTest.cpp @@ -4,9 +4,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 "SolidSyslogFreeRtosDatagram.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogUdpPayload.h" #include "FreeRtosArpFake.h" @@ -17,11 +23,26 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f #include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" +// 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) + static const uint16_t TEST_PORT = 514; TEST_GROUP(SolidSyslogFreeRtosDatagram) { - SolidSyslogFreeRtosDatagramStorage storage{}; struct SolidSyslogDatagram* datagram = nullptr; SolidSyslogAddressStorage addrStorage{}; struct SolidSyslogAddress* addr = nullptr; @@ -31,7 +52,7 @@ TEST_GROUP(SolidSyslogFreeRtosDatagram) FreeRtosSocketsFake_Reset(); FreeRtosArpFake_Reset(); FreeRtosTaskFake_Reset(); - datagram = SolidSyslogFreeRtosDatagram_Create(&storage); + datagram = SolidSyslogFreeRtosDatagram_Create(); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- char-type aliasing into platform layout, storage is intptr_t-aligned auto* sin = reinterpret_cast(&addrStorage); @@ -42,7 +63,12 @@ TEST_GROUP(SolidSyslogFreeRtosDatagram) addr = reinterpret_cast(&addrStorage); } - void openAndSendOnce() + void teardown() override + { + SolidSyslogFreeRtosDatagram_Destroy(datagram); + } + + void openAndSendOnce() const { SolidSyslogDatagram_Open(datagram); SolidSyslogDatagram_SendTo(datagram, "x", 1, addr); @@ -121,6 +147,7 @@ TEST(SolidSyslogFreeRtosDatagram, DestroyClosesOpenSocket) { SolidSyslogDatagram_Open(datagram); SolidSyslogFreeRtosDatagram_Destroy(datagram); + datagram = nullptr; CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); } @@ -143,6 +170,7 @@ TEST(SolidSyslogFreeRtosDatagram, DestroyAfterCloseDoesNotCloseAgain) SolidSyslogDatagram_Open(datagram); SolidSyslogDatagram_Close(datagram); SolidSyslogFreeRtosDatagram_Destroy(datagram); + datagram = nullptr; CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); } @@ -232,3 +260,154 @@ TEST(SolidSyslogFreeRtosDatagram, SendToForwardsLengthVerbatim) SolidSyslogDatagram_SendTo(datagram, TEST_BUFFER, 1232, addr); LONGS_EQUAL(1232, FreeRtosSocketsFake_LastSendtoLength()); } + +// clang-format off +TEST_GROUP(SolidSyslogFreeRtosDatagramPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogDatagram* pooled[SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE] = {}; + struct SolidSyslogDatagram* overflow = nullptr; + + void setup() override + { + FreeRtosSocketsFake_Reset(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogFreeRtosDatagram_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogFreeRtosDatagram_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogFreeRtosDatagram_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogFreeRtosDatagramPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogFreeRtosDatagram_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogFreeRtosDatagramPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogFreeRtosDatagram_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosDatagramPool, FallbackVtableMethodsAreNoOps) +{ + FillPool(); + overflow = SolidSyslogFreeRtosDatagram_Create(); + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&addrStorage); + FreeRtosSocketsFake_Reset(); + + /* NullDatagram's Open returns true so caller success paths are not + * tripped; no underlying FreeRTOS_socket is created. */ + CHECK_TRUE(SolidSyslogDatagram_Open(overflow)); + SolidSyslogDatagram_SendTo(overflow, "x", 1, addr); + SolidSyslogDatagram_Close(overflow); + + CALLED_FAKE(FreeRtosSocketsFake_Socket, NEVER); + CALLED_FAKE(FreeRtosSocketsFake_Sendto, NEVER); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); +} + +TEST(SolidSyslogFreeRtosDatagramPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogFreeRtosDatagram_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosDatagramPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogFreeRtosDatagram_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_DATAGRAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogFreeRtosDatagramPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogFreeRtosDatagram_Create(); + ConfigLockFake_Install(); + + SolidSyslogFreeRtosDatagram_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosDatagramPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogDatagram stranger = {}; + + SolidSyslogFreeRtosDatagram_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogFreeRtosDatagramPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogDatagram stranger = {}; + + SolidSyslogFreeRtosDatagram_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosDatagramPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogFreeRtosDatagram_Create(); + SolidSyslogFreeRtosDatagram_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogFreeRtosDatagram_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSDATAGRAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosMutexTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosMutexTest.cpp index 6f3d3f60..00d11a91 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosMutexTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosMutexTest.cpp @@ -4,34 +4,55 @@ 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 "FreeRtosSemaphoreFake.h" +#include "SolidSyslogErrorMessages.h" #include "SolidSyslogFreeRtosMutex.h" #include "SolidSyslogMutex.h" +#include "SolidSyslogMutexDefinition.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" #include "FreeRTOS.h" #include "semphr.h" +// 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(SolidSyslogFreeRtosMutex) { - SolidSyslogFreeRtosMutexStorage storage{}; - struct SolidSyslogMutex* mutex = nullptr; + struct SolidSyslogMutex* mutex = nullptr; void setup() override { FreeRtosSemaphoreFake_Reset(); // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros - mutex = SolidSyslogFreeRtosMutex_Create(&storage); + mutex = SolidSyslogFreeRtosMutex_Create(); + } + + void teardown() override + { + SolidSyslogFreeRtosMutex_Destroy(mutex); } }; // clang-format on -TEST(SolidSyslogFreeRtosMutex, HandleEqualsStorageAddress) -{ - POINTERS_EQUAL(&storage, mutex); -} - TEST(SolidSyslogFreeRtosMutex, CreateCallsCreateMutexStaticOnce) { CALLED_FAKE(FreeRtosSemaphoreFake_CreateMutexStatic, ONCE); @@ -54,6 +75,152 @@ TEST(SolidSyslogFreeRtosMutex, UnlockCallsSemaphoreGiveOnce) TEST(SolidSyslogFreeRtosMutex, DestroyCallsSemaphoreDeleteOnce) { SolidSyslogFreeRtosMutex_Destroy(mutex); + mutex = nullptr; CALLED_FAKE(FreeRtosSemaphoreFake_SemaphoreDelete, ONCE); } + +// clang-format off +TEST_GROUP(SolidSyslogFreeRtosMutexPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogMutex* pooled[SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE] = {}; + struct SolidSyslogMutex* overflow = nullptr; + + void setup() override + { + FreeRtosSemaphoreFake_Reset(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogFreeRtosMutex_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogFreeRtosMutex_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogFreeRtosMutex_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogFreeRtosMutexPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogFreeRtosMutex_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogFreeRtosMutexPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogFreeRtosMutex_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosMutexPool, FallbackLockUnlockAreNoOps) +{ + FillPool(); + FreeRtosSemaphoreFake_Reset(); + overflow = SolidSyslogFreeRtosMutex_Create(); + + SolidSyslogMutex_Lock(overflow); + SolidSyslogMutex_Unlock(overflow); + + CALLED_FAKE(FreeRtosSemaphoreFake_SemaphoreTake, NEVER); + CALLED_FAKE(FreeRtosSemaphoreFake_SemaphoreGive, NEVER); +} + +TEST(SolidSyslogFreeRtosMutexPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogFreeRtosMutex_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosMutexPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogFreeRtosMutex_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_MUTEX_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogFreeRtosMutexPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogFreeRtosMutex_Create(); + ConfigLockFake_Install(); + + SolidSyslogFreeRtosMutex_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosMutexPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogMutex stranger = {}; + + SolidSyslogFreeRtosMutex_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogFreeRtosMutexPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogMutex stranger = {}; + + SolidSyslogFreeRtosMutex_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosMutexPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogFreeRtosMutex_Create(); + SolidSyslogFreeRtosMutex_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogFreeRtosMutex_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSMUTEX_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosStaticResolverTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosStaticResolverTest.cpp index 31c7f6da..1b4d3ef2 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosStaticResolverTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosStaticResolverTest.cpp @@ -1,14 +1,40 @@ +#include "TestUtils.h" #include "CppUTest/TestHarness.h" +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 "SolidSyslogFreeRtosStaticResolver.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogResolver.h" +#include "SolidSyslogResolverDefinition.h" #include "SolidSyslogTransport.h" +#include "SolidSyslogTunables.h" #include "FreeRTOS.h" #include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" +// 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) + static const uint8_t TEST_OCTETS[4] = {10, 0, 2, 2}; static const uint16_t TEST_PORT = 514; static const uint16_t TEST_ALTERNATE_PORT = 9999; @@ -17,25 +43,30 @@ static const char* IGNORED_HOST = "ignored.example.com"; // clang-format off TEST_GROUP(SolidSyslogFreeRtosStaticResolver) { - SolidSyslogFreeRtosStaticResolverStorage storage{}; - struct SolidSyslogResolver* resolver = nullptr; - SolidSyslogAddressStorage addrStorage{}; - struct SolidSyslogAddress* addr = nullptr; + struct SolidSyslogResolver* resolver = nullptr; + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* addr = nullptr; void setup() override { - resolver = SolidSyslogFreeRtosStaticResolver_Create(&storage, TEST_OCTETS); + resolver = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); addr = SolidSyslogAddress_FromStorage(&addrStorage); } - bool Resolve(const char* host = IGNORED_HOST, uint16_t port = TEST_PORT, enum SolidSyslogTransport transport = SOLIDSYSLOG_TRANSPORT_UDP) + void teardown() override + { + SolidSyslogFreeRtosStaticResolver_Destroy(resolver); + } + + bool Resolve(const char* host = IGNORED_HOST, uint16_t port = TEST_PORT, enum SolidSyslogTransport transport = SOLIDSYSLOG_TRANSPORT_UDP) const { return SolidSyslogResolver_Resolve(resolver, transport, host, port, addr); } void RecreateResolverWith(const uint8_t octets[4]) { - resolver = SolidSyslogFreeRtosStaticResolver_Create(&storage, octets); + SolidSyslogFreeRtosStaticResolver_Destroy(resolver); + resolver = SolidSyslogFreeRtosStaticResolver_Create(octets); } // NOLINTNEXTLINE(modernize-use-nodiscard) -- used through accessor syntax in tests @@ -114,8 +145,139 @@ TEST(SolidSyslogFreeRtosStaticResolver, ResolveWritesAllOnesOctets) LONGS_EQUAL(FreeRTOS_inet_addr_quick(255, 255, 255, 255), Result()->sin_address.ulIP_IPv4); } -TEST(SolidSyslogFreeRtosStaticResolver, DestroyIsIdempotent) +// clang-format off +TEST_GROUP(SolidSyslogFreeRtosStaticResolverPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogResolver* pooled[SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE] = {}; + struct SolidSyslogResolver* overflow = nullptr; + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogFreeRtosStaticResolver_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogFreeRtosStaticResolver_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + } + } +}; + +// clang-format on + +TEST(SolidSyslogFreeRtosStaticResolverPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, FallbackResolveReturnsFalse) +{ + FillPool(); + overflow = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&addrStorage); + + CHECK_FALSE(SolidSyslogResolver_Resolve(overflow, SOLIDSYSLOG_TRANSPORT_UDP, "host", 514, addr)); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_STATIC_RESOLVER_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + ConfigLockFake_Install(); + + SolidSyslogFreeRtosStaticResolver_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogResolver stranger = {}; + + SolidSyslogFreeRtosStaticResolver_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, DestroyOfUnknownHandleReportsWarning) { - SolidSyslogFreeRtosStaticResolver_Destroy(resolver); - SolidSyslogFreeRtosStaticResolver_Destroy(resolver); + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogResolver stranger = {}; + + SolidSyslogFreeRtosStaticResolver_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosStaticResolverPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogFreeRtosStaticResolver_Create(TEST_OCTETS); + SolidSyslogFreeRtosStaticResolver_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogFreeRtosStaticResolver_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSSTATICRESOLVER_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); } diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp index f1e2dad7..33104b37 100644 --- a/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp +++ b/Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp @@ -4,9 +4,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 "SolidSyslogFreeRtosTcpStream.h" +#include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" +#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTunables.h" #include "FreeRtosArpFake.h" #include "FreeRtosSocketsFake.h" @@ -16,6 +22,22 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f #include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" +// 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) + static const uint16_t TEST_PORT = 514; static const char TEST_MESSAGE[] = "hello"; static const size_t TEST_MESSAGE_LEN = sizeof(TEST_MESSAGE) - 1U; @@ -25,7 +47,6 @@ static const BaseType_t TEST_READ_BYTES = 7; // clang-format off TEST_GROUP(SolidSyslogFreeRtosTcpStream) { - SolidSyslogFreeRtosTcpStreamStorage storage{}; struct SolidSyslogStream* stream = nullptr; SolidSyslogAddressStorage addrStorage{}; struct SolidSyslogAddress* addr = nullptr; @@ -36,7 +57,7 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) FreeRtosSocketsFake_Reset(); FreeRtosArpFake_Reset(); FreeRtosTaskFake_Reset(); - stream = SolidSyslogFreeRtosTcpStream_Create(&storage); + stream = SolidSyslogFreeRtosTcpStream_Create(); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- char-type aliasing into platform layout, storage is intptr_t-aligned auto* sin = reinterpret_cast(&addrStorage); @@ -49,7 +70,10 @@ TEST_GROUP(SolidSyslogFreeRtosTcpStream) void teardown() override { - SolidSyslogFreeRtosTcpStream_Destroy(stream); + if (stream != nullptr) + { + SolidSyslogFreeRtosTcpStream_Destroy(stream); + } } void openStream() const @@ -330,6 +354,7 @@ TEST(SolidSyslogFreeRtosTcpStream, DestroyClosesOpenSocket) { openStream(); SolidSyslogFreeRtosTcpStream_Destroy(stream); + stream = nullptr; CHECK_SOCKET_CLOSED_ONCE(); } @@ -338,5 +363,161 @@ TEST(SolidSyslogFreeRtosTcpStream, DestroyAfterCloseDoesNotCloseAgain) openStream(); SolidSyslogStream_Close(stream); SolidSyslogFreeRtosTcpStream_Destroy(stream); + stream = nullptr; CALLED_FAKE(FreeRtosSocketsFake_Closesocket, ONCE); } + +// clang-format off +TEST_GROUP(SolidSyslogFreeRtosTcpStreamPool) +{ + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslogStream* pooled[SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE] = {}; + struct SolidSyslogStream* overflow = nullptr; + + void setup() override + { + FreeRtosSocketsFake_Reset(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogFreeRtosTcpStream_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslogFreeRtosTcpStream_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogFreeRtosTcpStream_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogFreeRtosTcpStreamPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogFreeRtosTcpStream_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogFreeRtosTcpStream_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, FallbackVtableMethodsAreNoOps) +{ + FillPool(); + overflow = SolidSyslogFreeRtosTcpStream_Create(); + SolidSyslogAddressStorage addrStorage{}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&addrStorage); + char buf[8] = {0}; + FreeRtosSocketsFake_Reset(); + + /* NullStream's Open/Send/Read return safe values so the Service algorithm + * does not tear the (non-existent) connection down on the fallback. */ + CHECK_TRUE(SolidSyslogStream_Open(overflow, addr)); + CHECK_TRUE(SolidSyslogStream_Send(overflow, buf, sizeof(buf))); + LONGS_EQUAL(0, SolidSyslogStream_Read(overflow, buf, sizeof(buf))); + SolidSyslogStream_Close(overflow); + + CALLED_FAKE(FreeRtosSocketsFake_Socket, NEVER); + CALLED_FAKE(FreeRtosSocketsFake_Connect, NEVER); + CALLED_FAKE(FreeRtosSocketsFake_Send, NEVER); + CALLED_FAKE(FreeRtosSocketsFake_Recv, NEVER); + CALLED_FAKE(FreeRtosSocketsFake_Closesocket, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogFreeRtosTcpStream_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogFreeRtosTcpStream_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_FREE_RTOS_TCP_STREAM_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogFreeRtosTcpStream_Create(); + ConfigLockFake_Install(); + + SolidSyslogFreeRtosTcpStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogStream stranger = {}; + + SolidSyslogFreeRtosTcpStream_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogStream stranger = {}; + + SolidSyslogFreeRtosTcpStream_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogFreeRtosTcpStreamPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogFreeRtosTcpStream_Create(); + SolidSyslogFreeRtosTcpStream_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogFreeRtosTcpStream_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_FREERTOSTCPSTREAM_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +}