diff --git a/Bdd/features/structured_data.feature b/Bdd/features/structured_data.feature index 2692d4a5..d4c21f04 100644 --- a/Bdd/features/structured_data.feature +++ b/Bdd/features/structured_data.feature @@ -1,5 +1,4 @@ @udp -@freertoswip Feature: Structured data — meta SD-ELEMENT The library populates the IANA-registered "meta" SD-ELEMENT with sequenceId, sysUpTime, and language per RFC 5424 §7.3. diff --git a/CLAUDE.md b/CLAUDE.md index eab08478..3027e2c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -365,6 +365,7 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogWindowsHostname.h` | String callback implementor using Windows hostname | `SolidSyslogWindowsHostname_Get` (writes into `SolidSyslogFormatter*`) | | `SolidSyslogWindowsProcessId.h` | String callback implementor using Windows process ID | `SolidSyslogWindowsProcessId_Get` (writes into `SolidSyslogFormatter*`) | | `SolidSyslogWindowsSysUpTime.h` | SysUpTime callback implementor using `GetTickCount64` | `SolidSyslogWindowsSysUpTime_Get` (returns `uint32_t` hundredths since boot), `WindowsSysUpTime_GetTickCount64` (function-pointer seam for unit tests) | +| `SolidSyslogFreeRtosSysUpTime.h` | SysUpTime callback implementor using `xTaskGetTickCount` on FreeRTOS targets | `SolidSyslogFreeRtosSysUpTime_Get` (returns `uint32_t` hundredths since boot, wraps per RFC 3418 `TimeTicks`; uint64 intermediate so the result is correct at any `configTICK_RATE_HZ`) | | `SolidSyslogWindowsSleep.h` | System setup code wiring a `SolidSyslogSleepFunction` on Windows targets | `SolidSyslogWindowsSleep` (wraps `Sleep`) | | `SolidSyslogSleep.h` | Any code passing or implementing a sleep callback | `SolidSyslogSleepFunction` typedef (used by `SolidSyslogTlsStreamConfig.sleep` for the bounded handshake retry) | | `SolidSyslogStructuredData.h` | Library internals (SD dispatch) | `SolidSyslogStructuredData_Format` (writes into `SolidSyslogFormatter*`) | diff --git a/DEVLOG.md b/DEVLOG.md index 7d0e794b..36203ae9 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,96 @@ # Dev Log +## 2026-05-10 — S08.03 slice 8 — Meta SD wiring + FreeRtosSysUpTime adapter (#310) + +Slice 7 wired the first SD-ELEMENT (Origin) and brought the FreeRTOS +BDD runner to 5 features / 14 scenarios green. Slice 8 adds the meta +SD-ELEMENT — sequenceId, sysUpTime, language — to +`Example/FreeRtos/SingleTask/main.c`, introduces a new +`SolidSyslogFreeRtosSysUpTime` platform adapter, and untags +`structured_data.feature`. The runner is now at **6 features / 17 +scenarios green** (slice-7 baseline 5 / 14; +1 feature, +3 scenarios). + +### Decisions + +- **StdAtomicOps on Cortex-M3, no new platform adapter.** Cross-toolchain + compiled `Platform/Atomics/Source/SolidSyslogStdAtomicOps.c` for + Cortex-M3 already (`build/freertos-cross/.../*.obj` was present), + so the question was whether linkage would succeed. It does — the + cross build links the new ELF cleanly. On Cortex-M3 + Thumb-2 GCC + inlines `_Atomic uint32_t` operations to LDREX/STREX with no + libatomic runtime, and the SolidSyslog counter is incremented only + inside `SolidSyslog_Log` (task context only on this single-task + example, never from an ISR), so the ARM exclusive monitor is + sufficient. No `SolidSyslogFreeRtosAtomicOps` adapter was needed; + if a future MultiTask example needs ISR-vs-task atomicity that + adapter is the natural extension. +- **Tick width stayed 32-bit.** The original handoff floated + `configTICK_TYPE_WIDTH_IN_BITS = TICK_TYPE_WIDTH_64_BITS` to keep + the SysUpTime arithmetic trivially overflow-safe, but the upstream + `ARM_CM3` portmacro hard-errors on 64-bit ticks (`portmacro.h:73`, + `#error configTICK_TYPE_WIDTH_IN_BITS set to unsupported tick type + width.`). Patching the vendor port wasn't worth it; instead the + adapter does `(uint64_t)xTaskGetTickCount() * 100 / configTICK_RATE_HZ` + with a uint64 intermediate so the formula stays correct at any + HZ, then casts to `uint32_t` for RFC 3418 `TimeTicks` wrap + semantics. Mirrors `SolidSyslogPosixSysUpTime` which already uses + the same uint64-inside / uint32-out shape. +- **Language string reuses `Example/Common/ExampleLanguage`.** The + Linux/Windows examples already use `ExampleLanguage_Get` returning + `en-GB`; reusing the same source on FreeRTOS keeps the BDD + assertion target-agnostic. One-line CMakeLists addition. No + divergent local getter. +- **Untagged `structured_data.feature` at feature level.** All three + scenarios pass (sequenceId 1, sequential sequenceId values, + sysUpTime + language). `time_quality.feature` stays tagged because + both its scenarios assert `tzKnown` (timeQuality SD — slice 9), and + `origin.feature::All standard structured data present` stays + tagged because it asserts sequenceId AND tzKnown together. +- **Kept SysUpTimeTest outside the Plus-TCP guard** in + `Tests/FreeRtos/CMakeLists.txt`. The adapter only needs FreeRTOS + kernel headers (`FreeRTOS.h`, `task.h` for `TickType_t`); the + existing comment in that file explicitly flags this scope split + ("a minimal `FREERTOS_KERNEL_PATH`-only environment can still + build the UART test"). The SysUpTime test now sits with + `CmsdkUartTest` in that same kernel-only scope. `FreeRtosTaskFake` + picked up a stubbable `xTaskGetTickCount` + `_SetTickCount` + accessor alongside the existing `vTaskDelay` stub. + +### Local verification + +- `cmake --build --preset debug` — full host build clean. All 6 host + test executables pass (1 + 4 FreeRTOS + 1 OpenSSL integration), + including the new `SolidSyslogFreeRtosSysUpTimeTest` (4 tests: + zero, one, mid-range, `UINT32_MAX` boundary). +- `cmake --build --preset freertos-cross --target + SolidSyslogFreeRtosSingleTask` — clean. **StdAtomicOps linked into + the Cortex-M3 ELF** without needing a FreeRTOS-specific adapter. +- `behave --tags='not @wip and not @freertoswip and @udp' + Bdd/features/structured_data.feature` against `syslog-ng-freertos`: + 3 of 3 scenarios pass. +- Full FreeRTOS BDD sweep: 6 features / 17 scenarios pass / 29 + skipped (slice 7 baseline 5 / 14 / 32; +1 feature, +3 scenarios, + -3 skipped). +- `clang-format --dry-run --Werror` on touched C / C++ files: clean. +- cppcheck / tidy / iwyu / sanitize / coverage / Windows: not run + locally (cross-only devcontainer); CI's responsibility. + +### Deferred + +- timeQuality SD wiring on FreeRTOS — slice 9. Will untag + `time_quality.feature` and the remaining + `origin.feature::All standard structured data present` scenario. +- The `udp_mtu.feature::Oversize` scenario — UDP path-MTU EMSGSIZE + on FreeRTOS-Plus-TCP, its own slice. +- `SolidSyslogFreeRtosAtomicOps` adapter wrapping + `taskENTER_CRITICAL` / `taskEXIT_CRITICAL` — not needed for the + single-task example (StdAtomicOps suffices); will be the right + primitive when a MultiTask example arrives in S08.04. + +### Open questions + +- None for this slice. + ## 2026-05-10 — S08.03 slice 7 — Origin SD wiring in FreeRTOS example (#308) Slice 6 closed the cmdline→`set` translation gap so the FreeRTOS BDD diff --git a/Example/FreeRtos/SingleTask/CMakeLists.txt b/Example/FreeRtos/SingleTask/CMakeLists.txt index 29c6601c..043e3441 100644 --- a/Example/FreeRtos/SingleTask/CMakeLists.txt +++ b/Example/FreeRtos/SingleTask/CMakeLists.txt @@ -146,8 +146,10 @@ add_executable(SolidSyslogFreeRtosSingleTask ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogAddress.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosDatagram.c ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosStaticResolver.c + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c ${CMAKE_SOURCE_DIR}/Example/Common/ExampleInteractive.c ${CMAKE_SOURCE_DIR}/Example/Common/ExampleIps.c + ${CMAKE_SOURCE_DIR}/Example/Common/ExampleLanguage.c $ ) diff --git a/Example/FreeRtos/SingleTask/main.c b/Example/FreeRtos/SingleTask/main.c index 32ad67d0..54d7b9a5 100644 --- a/Example/FreeRtos/SingleTask/main.c +++ b/Example/FreeRtos/SingleTask/main.c @@ -18,16 +18,21 @@ #include "ExampleEnterpriseId.h" #include "ExampleInteractive.h" #include "ExampleIps.h" +#include "ExampleLanguage.h" #include "SolidSyslog.h" +#include "SolidSyslogAtomicCounter.h" #include "SolidSyslogConfig.h" #include "SolidSyslogEndpoint.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogFreeRtosDatagram.h" #include "SolidSyslogFreeRtosStaticResolver.h" +#include "SolidSyslogFreeRtosSysUpTime.h" +#include "SolidSyslogMetaSd.h" #include "SolidSyslogNullBuffer.h" #include "SolidSyslogNullStore.h" #include "SolidSyslogOriginSd.h" #include "SolidSyslogPrival.h" +#include "SolidSyslogStdAtomicOps.h" #include "SolidSyslogTimestamp.h" #include "SolidSyslogUdpSender.h" @@ -334,15 +339,22 @@ static void InteractiveTask(void* argument) struct SolidSyslogBuffer* buffer = SolidSyslogNullBuffer_Create(sender); struct SolidSyslogStore* store = SolidSyslogNullStore_Create(); - struct SolidSyslogOriginSdConfig originConfig = { - .software = "SolidSyslogExample", - .swVersion = "0.7.0", - .enterpriseId = EXAMPLE_ENTERPRISE_ID, - .getIpCount = ExampleIps_Count, - .getIpAt = ExampleIps_At, + struct SolidSyslogAtomicCounter* counter = SolidSyslogAtomicCounter_Create(SolidSyslogStdAtomicOps_Create()); + struct SolidSyslogMetaSdConfig metaConfig = { + .counter = counter, + .getSysUpTime = SolidSyslogFreeRtosSysUpTime_Get, + .getLanguage = ExampleLanguage_Get, + }; + struct SolidSyslogStructuredData* metaSd = SolidSyslogMetaSd_Create(&metaConfig); + struct SolidSyslogOriginSdConfig originConfig = { + .software = "SolidSyslogExample", + .swVersion = "0.7.0", + .enterpriseId = EXAMPLE_ENTERPRISE_ID, + .getIpCount = ExampleIps_Count, + .getIpAt = ExampleIps_At, }; struct SolidSyslogStructuredData* originSd = SolidSyslogOriginSd_Create(&originConfig); - struct SolidSyslogStructuredData* sdList[] = {originSd}; + struct SolidSyslogStructuredData* sdList[] = {metaSd, originSd}; struct SolidSyslogConfig config = { .buffer = buffer, @@ -361,6 +373,9 @@ static void InteractiveTask(void* argument) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); + SolidSyslogMetaSd_Destroy(); + SolidSyslogAtomicCounter_Destroy(); + SolidSyslogStdAtomicOps_Destroy(); SolidSyslogNullStore_Destroy(); SolidSyslogNullBuffer_Destroy(); SolidSyslogUdpSender_Destroy(); diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h new file mode 100644 index 00000000..f8fff022 --- /dev/null +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h @@ -0,0 +1,14 @@ +#ifndef SOLIDSYSLOGFREERTOSSYSUPTIME_H +#define SOLIDSYSLOGFREERTOSSYSUPTIME_H + +#include "ExternC.h" + +#include + +EXTERN_C_BEGIN + + uint32_t SolidSyslogFreeRtosSysUpTime_Get(void); // NOLINT(modernize-redundant-void-arg) -- C idiom + +EXTERN_C_END + +#endif /* SOLIDSYSLOGFREERTOSSYSUPTIME_H */ diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c new file mode 100644 index 00000000..20a2b751 --- /dev/null +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c @@ -0,0 +1,17 @@ +#include "SolidSyslogFreeRtosSysUpTime.h" + +#include "FreeRTOS.h" +#include "task.h" + +enum +{ + HUNDREDTHS_PER_SECOND = 100 +}; + +uint32_t SolidSyslogFreeRtosSysUpTime_Get(void) +{ + /* uint64 intermediate so the formula stays correct at any + * configTICK_RATE_HZ; the cast wraps per RFC 3418 TimeTicks. */ + uint64_t hundredths = ((uint64_t) xTaskGetTickCount() * HUNDREDTHS_PER_SECOND) / configTICK_RATE_HZ; + return (uint32_t) hundredths; +} diff --git a/Tests/FreeRtos/CMakeLists.txt b/Tests/FreeRtos/CMakeLists.txt index 5e1d65f3..a30ad145 100644 --- a/Tests/FreeRtos/CMakeLists.txt +++ b/Tests/FreeRtos/CMakeLists.txt @@ -61,6 +61,31 @@ else() add_test(NAME SolidSyslogFreeRtosStaticResolverTest COMMAND SolidSyslogFreeRtosStaticResolverTest) endif() +# SolidSyslogFreeRtosSysUpTime — kernel-only adapter; needs FREERTOS_KERNEL_PATH +# but not Plus-TCP. Sits outside the Plus-TCP guard for the same reason the +# UART test does: a minimal kernel-only environment must still build it. +add_executable(SolidSyslogFreeRtosSysUpTimeTest + SolidSyslogFreeRtosSysUpTimeTest.cpp + main.cpp + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c + ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c +) + +target_link_libraries(SolidSyslogFreeRtosSysUpTimeTest PRIVATE + ${PROJECT_NAME} + CppUTest + CppUTestExt +) + +target_include_directories(SolidSyslogFreeRtosSysUpTimeTest PRIVATE + ${CMAKE_SOURCE_DIR}/Platform/FreeRtos/Interface + ${CMAKE_SOURCE_DIR}/Core/Interface + ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Interface + $ENV{FREERTOS_KERNEL_PATH}/include +) + +add_test(NAME SolidSyslogFreeRtosSysUpTimeTest COMMAND SolidSyslogFreeRtosSysUpTimeTest) + # CMSDK UART driver host tests — recompiles the driver source from the # Example/FreeRtos/Common board glue against an in-memory fake register # block. No FreeRTOS-Plus-TCP involvement, so the include path is intentionally diff --git a/Tests/FreeRtos/SolidSyslogFreeRtosSysUpTimeTest.cpp b/Tests/FreeRtos/SolidSyslogFreeRtosSysUpTimeTest.cpp new file mode 100644 index 00000000..a6a7239b --- /dev/null +++ b/Tests/FreeRtos/SolidSyslogFreeRtosSysUpTimeTest.cpp @@ -0,0 +1,45 @@ +#include "CppUTest/TestHarness.h" + +#include "FreeRtosTaskFake.h" +#include "SolidSyslogFreeRtosSysUpTime.h" + +#include "FreeRTOS.h" + +// clang-format off +TEST_GROUP(SolidSyslogFreeRtosSysUpTime) +{ + void setup() override + { + FreeRtosTaskFake_Reset(); + } +}; + +// clang-format on + +TEST(SolidSyslogFreeRtosSysUpTime, ReturnsZeroWhenTicksAreZero) +{ + FreeRtosTaskFake_SetTickCount(0); + + UNSIGNED_LONGS_EQUAL(0U, SolidSyslogFreeRtosSysUpTime_Get()); +} + +TEST(SolidSyslogFreeRtosSysUpTime, ReturnsOneWhenTicksAreOne) +{ + FreeRtosTaskFake_SetTickCount(1); + + UNSIGNED_LONGS_EQUAL(1U, SolidSyslogFreeRtosSysUpTime_Get()); +} + +TEST(SolidSyslogFreeRtosSysUpTime, ReturnsTickCountAtMidRange) +{ + FreeRtosTaskFake_SetTickCount(12345U); + + UNSIGNED_LONGS_EQUAL(12345U, SolidSyslogFreeRtosSysUpTime_Get()); +} + +TEST(SolidSyslogFreeRtosSysUpTime, ReturnsUint32MaxWhenTicksAreUint32Max) +{ + FreeRtosTaskFake_SetTickCount(UINT32_MAX); + + UNSIGNED_LONGS_EQUAL(UINT32_MAX, SolidSyslogFreeRtosSysUpTime_Get()); +} diff --git a/Tests/Support/FreeRtosFakes/Interface/FreeRtosTaskFake.h b/Tests/Support/FreeRtosFakes/Interface/FreeRtosTaskFake.h index 81b43c20..35d4d668 100644 --- a/Tests/Support/FreeRtosFakes/Interface/FreeRtosTaskFake.h +++ b/Tests/Support/FreeRtosFakes/Interface/FreeRtosTaskFake.h @@ -14,6 +14,9 @@ EXTERN_C_BEGIN unsigned FreeRtosTaskFake_VTaskDelayCallCount(void); TickType_t FreeRtosTaskFake_LastVTaskDelayTicks(void); + /* xTaskGetTickCount stub */ + void FreeRtosTaskFake_SetTickCount(TickType_t ticks); + EXTERN_C_END #endif /* FREERTOSTASKFAKE_H */ diff --git a/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c b/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c index 3c08dc34..9abf3501 100644 --- a/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c +++ b/Tests/Support/FreeRtosFakes/Source/FreeRtosTaskFake.c @@ -2,11 +2,13 @@ static unsigned vTaskDelayCallCount = 0; static TickType_t lastVTaskDelayTicks = 0; +static TickType_t tickCount = 0; void FreeRtosTaskFake_Reset(void) { vTaskDelayCallCount = 0; lastVTaskDelayTicks = 0; + tickCount = 0; } unsigned FreeRtosTaskFake_VTaskDelayCallCount(void) @@ -19,8 +21,18 @@ TickType_t FreeRtosTaskFake_LastVTaskDelayTicks(void) return lastVTaskDelayTicks; } +void FreeRtosTaskFake_SetTickCount(TickType_t ticks) +{ + tickCount = ticks; +} + void vTaskDelay(const TickType_t xTicksToDelay) { ++vTaskDelayCallCount; lastVTaskDelayTicks = xTicksToDelay; } + +TickType_t xTaskGetTickCount(void) +{ + return tickCount; +}