From a1b1e2ced48f1f2c0f5620655e96e5aa3642a29b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 10 May 2026 09:05:12 +0100 Subject: [PATCH 1/2] feat: S08.03 slice 9 timeQuality SD + no-RTC FreeRTOS example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire timeQuality SD into the FreeRTOS SingleTask example and switch the example to a no-RTC product stance per RFC 5424 §6.2.3.1. Bundles a syslog-ng pin (4.11 latest aborts on STATS over the control socket and takes the dev-container network down). Closes #312. Co-Authored-By: Claude Opus 4.7 (1M context) --- .devcontainer/docker-compose.yml | 4 +-- .github/workflows/ci.yml | 2 +- Bdd/README.md | 6 ++-- Bdd/features/origin.feature | 14 ++++++++- Bdd/features/time_quality.feature | 22 ++++++++++++-- Bdd/features/timestamp.feature | 9 ++++-- Example/FreeRtos/SingleTask/main.c | 48 ++++++++++++++---------------- ci/docker-compose.bdd.yml | 8 ++--- docs/containers.md | 2 +- 9 files changed, 75 insertions(+), 40 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 67352e72..0a675efd 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -27,7 +27,7 @@ services: # if a future workflow runs both pairs simultaneously; see # ci/docker-compose.bdd.yml for the rationale. syslog-ng-linux: - image: balabit/syslog-ng:latest + image: balabit/syslog-ng:4.8.2 volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro @@ -124,7 +124,7 @@ services: # network namespace so QEMU's slirp gateway 10.0.2.2 NATs to the pair's # loopback, where syslog-ng-freertos is listening on 0.0.0.0:5514. syslog-ng-freertos: - image: balabit/syslog-ng:latest + image: balabit/syslog-ng:4.8.2 volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a75aa7e..12b4e1ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -580,7 +580,7 @@ jobs: ORACLE_FORMAT: otel-jsonl run: > behave --junit --junit-directory Bdd/junit - --tags='not @wip and not @windows_wip' + --tags='not @wip and not @windows_wip and not @no_rtc' Bdd/features/ - name: BDD Test Report (Windows) diff --git a/Bdd/README.md b/Bdd/README.md index ee5eccd4..3c7ee0d3 100644 --- a/Bdd/README.md +++ b/Bdd/README.md @@ -22,7 +22,9 @@ for the compose layout. |---|---| | `@udp` / `@tcp` / `@tls` / `@mtls` | Transport-specific scenario; CI jobs filter by these. | | `@windows_wip` | Skipped on the Windows runner (typically OS-specific behaviour the OTel oracle doesn't model). | -| `@freertoswip` | Skipped on the FreeRTOS-on-QEMU runner. The FreeRTOS SingleTask example bakes hard-coded `TEST_*` values for hostname, PROCID, and timestamp; consumes config via the interactive `set NAME VALUE` command rather than getopt; and does not yet wire `meta` / `origin` / `timeQuality` SD. Scenarios that depend on any of these are tagged today and the tag is removed slice-by-slice as each gap is closed. | +| `@freertoswip` | Skipped on the FreeRTOS-on-QEMU runner. The FreeRTOS SingleTask example bakes hard-coded `TEST_*` values for hostname and PROCID and consumes config via the interactive `set NAME VALUE` command rather than getopt. Scenarios that depend on any of these are tagged today and the tag is removed slice-by-slice as each gap is closed. | +| `@rtc` | Scenario assumes the device has an RTC and synchronised wall-clock time. Run on Linux/Windows (which have both). Skipped on FreeRTOS, which models a no-RTC product per RFC 5424 §6.2.3.1. | +| `@no_rtc` | Scenario asserts the no-RTC product behaviour over the wire (`tzKnown="0"`, `isSynced="0"`). Run on FreeRTOS. Skipped on Linux/Windows. The NILVALUE TIMESTAMP itself is not asserted via the oracle — syslog-ng silently substitutes receipt time for `${ISODATE}` / `${S_ISODATE}` when the wire timestamp is NILVALUE — so that case is covered by formatter unit tests. | | `@wip` | Globally skipped on every runner. | ## Running locally @@ -49,7 +51,7 @@ once and run Behave from inside the container: ```bash cmake --preset freertos-cross cmake --build --preset freertos-cross --target SolidSyslogFreeRtosSingleTask -behave --tags='not @wip and not @freertoswip and @udp' Bdd/features/ +behave --tags='not @wip and not @freertoswip and not @rtc and @udp' Bdd/features/ ``` `BDD_TARGET=freertos` and `EXAMPLE_BINARY=build/freertos-cross/...` are diff --git a/Bdd/features/origin.feature b/Bdd/features/origin.feature index cabbd2a6..114f8dc2 100644 --- a/Bdd/features/origin.feature +++ b/Bdd/features/origin.feature @@ -22,7 +22,7 @@ Feature: Structured data — origin When the example program sends a syslog message Then the structured data contains ip "192.0.2.1" - @freertoswip + @rtc Scenario: All standard structured data present Given the syslog oracle is running When the example program sends a syslog message @@ -32,3 +32,15 @@ Feature: Structured data — origin And the structured data contains swVersion "0.7.0" And the structured data contains enterpriseId "1.3.6.1.4.1.99999" And the structured data contains ip "192.0.2.1" + + @no_rtc + Scenario: All standard structured data present (no RTC) + Given the syslog oracle is running + When the example program sends a syslog message + Then the structured data contains sequenceId "1" + And the structured data contains tzKnown "0" + And the structured data contains isSynced "0" + And the structured data contains software "SolidSyslogExample" + And the structured data contains swVersion "0.7.0" + And the structured data contains enterpriseId "1.3.6.1.4.1.99999" + And the structured data contains ip "192.0.2.1" diff --git a/Bdd/features/time_quality.feature b/Bdd/features/time_quality.feature index 6d34fb88..1c30db75 100644 --- a/Bdd/features/time_quality.feature +++ b/Bdd/features/time_quality.feature @@ -1,16 +1,34 @@ @udp -@freertoswip Feature: Structured data — time quality - The library includes time quality metadata in structured data. + The library includes time quality metadata in structured data. RTC-equipped + targets (Linux, Windows) report tzKnown="1" and isSynced="1"; embedded + targets without an RTC report tzKnown="0" and isSynced="0", per the + no-RTC product stance modelled in the FreeRTOS reference example. + @rtc Scenario: Time quality appears in structured data Given the syslog oracle is running When the example program sends a syslog message Then the structured data contains tzKnown "1" And the structured data contains isSynced "1" + @rtc Scenario: Time quality and sequence ID coexist Given the syslog oracle is running When the example program sends a syslog message Then the structured data contains sequenceId "1" And the structured data contains tzKnown "1" + + @no_rtc + Scenario: Time quality reflects no RTC + Given the syslog oracle is running + When the example program sends a syslog message + Then the structured data contains tzKnown "0" + And the structured data contains isSynced "0" + + @no_rtc + Scenario: Time quality and sequence ID coexist (no RTC) + Given the syslog oracle is running + When the example program sends a syslog message + Then the structured data contains sequenceId "1" + And the structured data contains tzKnown "0" diff --git a/Bdd/features/timestamp.feature b/Bdd/features/timestamp.feature index 4e8f8d0e..358176af 100644 --- a/Bdd/features/timestamp.feature +++ b/Bdd/features/timestamp.feature @@ -1,8 +1,13 @@ @udp -@freertoswip +@rtc Feature: Timestamp encoding The library captures the timestamp at raise-time via an injected clock - and formats it as RFC 5424 FULL-DATE "T" FULL-TIME. + and formats it as RFC 5424 FULL-DATE "T" FULL-TIME. The no-RTC product + shape (config.clock = NULL → NILVALUE TIMESTAMP per RFC 5424 §6.2.3.1) + is covered by formatter unit tests; syslog-ng silently substitutes + receipt time for ${ISODATE} and ${S_ISODATE} when the wire TIMESTAMP + is NILVALUE, so a BDD assertion against the oracle would re-test what + the unit tests already cover, with no extra coverage. Scenario: Timestamp is received by syslog-ng Given the syslog oracle is running diff --git a/Example/FreeRtos/SingleTask/main.c b/Example/FreeRtos/SingleTask/main.c index 54d7b9a5..93ee704a 100644 --- a/Example/FreeRtos/SingleTask/main.c +++ b/Example/FreeRtos/SingleTask/main.c @@ -33,7 +33,8 @@ #include "SolidSyslogOriginSd.h" #include "SolidSyslogPrival.h" #include "SolidSyslogStdAtomicOps.h" -#include "SolidSyslogTimestamp.h" +#include "SolidSyslogTimeQuality.h" +#include "SolidSyslogTimeQualitySd.h" #include "SolidSyslogUdpSender.h" #include @@ -123,19 +124,6 @@ static struct SolidSyslogMessage g_message = { .msg = g_msg, }; -/* RFC 5424 publication date — placeholder until S08.03 slice 4+ injects a - * real RTC-backed clock callback. */ -static const struct SolidSyslogTimestamp TEST_TIMESTAMP = { - .year = 2009U, - .month = 3U, - .day = 23U, - .hour = 0U, - .minute = 0U, - .second = 0U, - .microsecond = 0U, - .utcOffsetMinutes = 0, -}; - /* Plus-TCP requires the network interface descriptor and its endpoint(s) * to outlive the IP stack. */ static NetworkInterface_t networkInterface; @@ -203,9 +191,17 @@ static void GetProcessId(struct SolidSyslogFormatter* formatter) SolidSyslogFormatter_BoundedString(formatter, g_processId, strlen(g_processId)); } -static void GetTimestamp(struct SolidSyslogTimestamp* timestamp) +/* No RTC and no time-sync on this reference target — the example models an + * embedded device that has no concept of wall-clock time. RFC 5424 §6.2.3.1 + * mandates NILVALUE TIMESTAMP in that case, and the timeQuality SD reports + * tzKnown=0, isSynced=0. SolidSyslogConfig.clock=NULL drops through to the + * library's NilClock; the resulting all-zero SolidSyslogTimestamp fails + * TimestampIsValid in Core/Source/SolidSyslog.c and emits "-" on the wire. */ +static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) { - *timestamp = TEST_TIMESTAMP; + timeQuality->tzKnown = false; + timeQuality->isSynced = false; + timeQuality->syncAccuracyMicroseconds = SOLIDSYSLOG_SYNC_ACCURACY_OMIT; } static void GetEndpoint(struct SolidSyslogEndpoint* endpoint) @@ -345,21 +341,22 @@ static void InteractiveTask(void* argument) .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* metaSd = SolidSyslogMetaSd_Create(&metaConfig); + struct SolidSyslogStructuredData* timeQualitySd = SolidSyslogTimeQualitySd_Create(GetTimeQuality); + 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[] = {metaSd, originSd}; + struct SolidSyslogStructuredData* sdList[] = {metaSd, timeQualitySd, originSd}; struct SolidSyslogConfig config = { .buffer = buffer, .sender = NULL, - .clock = GetTimestamp, + .clock = NULL, .getHostname = GetHostname, .getAppName = GetAppName, .getProcessId = GetProcessId, @@ -373,6 +370,7 @@ static void InteractiveTask(void* argument) SolidSyslog_Destroy(); SolidSyslogOriginSd_Destroy(); + SolidSyslogTimeQualitySd_Destroy(); SolidSyslogMetaSd_Destroy(); SolidSyslogAtomicCounter_Destroy(); SolidSyslogStdAtomicOps_Destroy(); diff --git a/ci/docker-compose.bdd.yml b/ci/docker-compose.bdd.yml index da61eb58..2d300e83 100644 --- a/ci/docker-compose.bdd.yml +++ b/ci/docker-compose.bdd.yml @@ -1,6 +1,6 @@ services: syslog-ng-linux: - image: balabit/syslog-ng:latest + image: balabit/syslog-ng:4.8.2 volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro @@ -44,7 +44,7 @@ services: condition: service_healthy environment: - BDD_TARGET=linux - command: behave --junit --junit-directory Bdd/junit --tags='not @wip' Bdd/features/ + command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @no_rtc' Bdd/features/ # Each BDD target pairs with its own syslog-ng instance on its own # bridge network so the `syslog-ng` DNS alias is scoped per-pair — @@ -56,7 +56,7 @@ services: # 0.0.0.0:5514. Adding more targets is one more pair + one more # network block. syslog-ng-freertos: - image: balabit/syslog-ng:latest + image: balabit/syslog-ng:4.8.2 volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro @@ -101,7 +101,7 @@ services: environment: - BDD_TARGET=freertos - EXAMPLE_BINARY=build/freertos-cross/Example/FreeRtos/SingleTask/SolidSyslogFreeRtosSingleTask.elf - command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and @udp' Bdd/features/ + command: behave --junit --junit-directory Bdd/junit --tags='not @wip and not @freertoswip and not @rtc and @udp' Bdd/features/ volumes: bdd-output-linux: diff --git a/docs/containers.md b/docs/containers.md index 24a35b2e..62ec221f 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -8,7 +8,7 @@ | `ghcr.io/davidcozens/cpputest-clang` | `sha-7eac3ab` | `clang` compose service, `build-linux-clang` CI job, `analyze-iwyu` CI job | | `ghcr.io/davidcozens/cpputest-freertos` | `sha-10a14ae` | `freertos-host` compose service, `build-freertos-host-tdd` CI job — adds FreeRTOS-Kernel / Plus-TCP / Plus-FAT / Mbed TLS sources for host-TDD of FreeRTOS adapters against fakes | | `ghcr.io/davidcozens/cpputest-freertos-cross` | `sha-10a14ae` | `freertos-target` compose service, `build-freertos-target` CI job, `behave-freertos` BDD service, `bdd-freertos-qemu` CI job — adds `gcc-arm-none-eabi`, `libnewlib-arm-none-eabi`, `gdb-multiarch` (aliased as `arm-none-eabi-gdb`), `qemu-system-arm`, `python3` + `behave` for cross builds, on-QEMU runs, and BDD scenarios driving a QEMU target | -| `balabit/syslog-ng` | `latest` | `syslog-ng-linux` and `syslog-ng-freertos` services — BDD test oracles, one per target pair | +| `balabit/syslog-ng` | `4.8.2` | `syslog-ng-linux` and `syslog-ng-freertos` services — BDD test oracles, one per target pair. Pinned to the 4.8 LTS line; 4.11.0 (`latest` as of 2026-02-24) regressed by aborting on `STATS` over the control socket, which crashed the oracle and cascaded to the dev-container network when `freertos-target` shares the namespace. | | `ghcr.io/davidcozens/behave` | `sha-3faff14` | `behave-linux` service — Debian trixie + Python 3.12 + Behave for Linux BDD scenarios. The FreeRTOS BDD runner uses the `cpputest-freertos-cross` image instead (which carries QEMU + Behave). | ## Docker Compose setup From 136366c347dc19e825c6914a4a3c75b46966f822 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 10 May 2026 09:39:10 +0100 Subject: [PATCH 2/2] fix: replace U+2192 arrow with ASCII; assert isSynced in coexist scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - timestamp.feature: U+2192 (right arrow) in the feature description trips Windows behave's cp1252 stdout codec with UnicodeEncodeError, failing bdd-windows-otel before any scenario runs. § (U+00A7) survives because it's in cp1252; → is not. Use ASCII '->' instead. - time_quality.feature: extend the coexist scenarios (both @rtc and the new @no_rtc) to assert isSynced alongside tzKnown for parity with the dedicated time-quality scenarios. CodeRabbit nitpick on #313. Co-Authored-By: Claude Opus 4.7 (1M context) --- Bdd/features/time_quality.feature | 2 ++ Bdd/features/timestamp.feature | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Bdd/features/time_quality.feature b/Bdd/features/time_quality.feature index 1c30db75..88a19ccf 100644 --- a/Bdd/features/time_quality.feature +++ b/Bdd/features/time_quality.feature @@ -18,6 +18,7 @@ Feature: Structured data — time quality When the example program sends a syslog message Then the structured data contains sequenceId "1" And the structured data contains tzKnown "1" + And the structured data contains isSynced "1" @no_rtc Scenario: Time quality reflects no RTC @@ -32,3 +33,4 @@ Feature: Structured data — time quality When the example program sends a syslog message Then the structured data contains sequenceId "1" And the structured data contains tzKnown "0" + And the structured data contains isSynced "0" diff --git a/Bdd/features/timestamp.feature b/Bdd/features/timestamp.feature index 358176af..e817ea97 100644 --- a/Bdd/features/timestamp.feature +++ b/Bdd/features/timestamp.feature @@ -3,7 +3,7 @@ Feature: Timestamp encoding The library captures the timestamp at raise-time via an injected clock and formats it as RFC 5424 FULL-DATE "T" FULL-TIME. The no-RTC product - shape (config.clock = NULL → NILVALUE TIMESTAMP per RFC 5424 §6.2.3.1) + shape (config.clock = NULL -> NILVALUE TIMESTAMP per RFC 5424 §6.2.3.1) is covered by formatter unit tests; syslog-ng silently substitutes receipt time for ${ISODATE} and ${S_ISODATE} when the wire TIMESTAMP is NILVALUE, so a BDD assertion against the oracle would re-test what