From 2d71e4b4192b9c3bfac954e2acaeaf23b7fa358b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 12 May 2026 10:01:56 +0100 Subject: [PATCH 1/3] feat: S21.01 build-time tunables mechanism via SOLIDSYSLOG_MAX_MESSAGE_SIZE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the build-time tunables override mechanism — defaults header + central header + optional user-config file + CMake INTERFACE target + BDD configure_file bridge. SOLIDSYSLOG_MAX_MESSAGE_SIZE is the first tunable; default 2048 unchanged, so no behavioural change on main. mbedTLS / FreeRTOS-Kernel / lwIP pattern: integrators set SOLIDSYSLOG_USER_TUNABLES_FILE to the absolute path of their own header containing #define overrides. The SolidSyslogTunables INTERFACE target propagates the macro to the library; the library #ifndef-guards each default so the user's value wins. Compile-time floor (#if/#error) rejects sub-floor values. Replaces the hardcoded "remember to bump this" SOLIDSYSLOG_MAX_MESSAGE_SIZE mirror in Bdd/features/steps/syslog_steps.py: CMake parses the defaults header (and the user-config file if set) at configure time and writes Bdd/features/steps/solidsyslog_tunables.py (gitignored) from a .in template. Single source of truth — drift impossible. New tunable-override-debug preset + build-linux-tunable-override CI job prove the override path flows end-to-end (static_assert in Tests/SolidSyslogTunablesTest.cpp fails the build if it doesn't). Fixes two latent test-infra bugs surfaced by the override: - Tests/SolidSyslogUdpSenderTest.cpp hardcoded TEST_MAX_MESSAGE_SIZE to 1024 despite its name claiming "max size"; now tracks the macro. - Tests/Support/SocketFake.c reserved a null-terminator byte inside a buffer sized exactly to MAX, silently clipping the last byte of a true max-size message; buffer is now MAX + 1. Closes #347. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 40 ++++++- .gitignore | 4 + Bdd/Targets/Common/BddTargetInteractive.c | 1 + Bdd/Targets/FreeRtos/main.c | 1 + Bdd/Targets/Linux/main.c | 1 + Bdd/features/steps/solidsyslog_tunables.py.in | 7 ++ Bdd/features/steps/syslog_steps.py | 15 ++- CMakeLists.txt | 34 ++++++ CMakePresets.json | 10 ++ Core/Interface/SolidSyslog.h | 5 - Core/Interface/SolidSyslogBlockStore.h | 1 + Core/Interface/SolidSyslogCircularBuffer.h | 2 +- Core/Interface/SolidSyslogTunables.h | 10 ++ Core/Interface/SolidSyslogTunablesDefaults.h | 27 +++++ Core/Source/CMakeLists.txt | 2 + Core/Source/RecordStore.c | 1 + Core/Source/RecordStore.h | 2 +- Core/Source/SolidSyslog.c | 1 + Core/Source/SolidSyslogBlockStore.c | 2 +- Core/Source/SolidSyslogCircularBuffer.c | 2 +- DEVLOG.md | 106 ++++++++++++++++++ .../Targets/BddTargetServiceThreadTest.cpp | 1 + Tests/CMakeLists.txt | 8 ++ Tests/FileFake.c | 2 +- Tests/Fixtures/SmallMessageSizeTunables.h | 5 + Tests/SenderFake.c | 2 +- Tests/SolidSyslogBlockStorePosixTest.cpp | 2 +- Tests/SolidSyslogBlockStoreTest.cpp | 2 +- Tests/SolidSyslogCircularBufferTest.cpp | 2 +- Tests/SolidSyslogCrc16Test.cpp | 2 +- ...SolidSyslogPosixMessageQueueBufferTest.cpp | 1 + Tests/SolidSyslogTest.cpp | 1 + Tests/SolidSyslogTunablesTest.cpp | 19 ++++ Tests/SolidSyslogUdpSenderTest.cpp | 3 +- Tests/Support/SocketFake.c | 6 +- Tests/Support/WinsockFake.c | 2 +- 36 files changed, 306 insertions(+), 26 deletions(-) create mode 100644 Bdd/features/steps/solidsyslog_tunables.py.in create mode 100644 Core/Interface/SolidSyslogTunables.h create mode 100644 Core/Interface/SolidSyslogTunablesDefaults.h create mode 100644 Tests/Fixtures/SmallMessageSizeTunables.h create mode 100644 Tests/SolidSyslogTunablesTest.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8cd25c3e..7018bbb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,6 +165,44 @@ jobs: path: build/msvc-debug/Tests/OpenSslIntegration/Debug/cpputest_*.xml retention-days: 1 + build-linux-tunable-override: + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + container: + image: ghcr.io/davidcozens/cpputest:sha-18f19e1 + options: --user root + env: + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: safe.directory + GIT_CONFIG_VALUE_0: '*' + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Configure with tunable override + run: cmake --preset tunable-override-debug + + - name: Build and test + run: cmake --build --preset tunable-override-debug --target junit + + - name: Test Report + uses: dorny/test-reporter@v3 + if: success() || failure() + with: + name: Test Results (Tunable Override) + path: build/tunable-override-debug/cpputest_*.xml + reporter: java-junit + + - name: Upload JUnit XML + if: success() || failure() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: junit-build-linux-tunable-override + path: build/tunable-override-debug/cpputest_*.xml + retention-days: 1 + build-linux-clang: runs-on: ubuntu-latest permissions: @@ -776,7 +814,7 @@ jobs: summary: if: always() && github.event_name == 'pull_request' - needs: [build-linux-gcc, build-linux-clang, sanitize-linux-gcc, coverage-linux-gcc, analyze-tidy, analyze-cppcheck, analyze-format, analyze-iwyu, bdd-linux-syslog-ng, build-windows-msvc, bdd-windows-otel, integration-linux-openssl, integration-windows-openssl, build-freertos-host-tdd, build-freertos-target, bdd-freertos-qemu] + needs: [build-linux-gcc, build-linux-tunable-override, build-linux-clang, sanitize-linux-gcc, coverage-linux-gcc, analyze-tidy, analyze-cppcheck, analyze-format, analyze-iwyu, bdd-linux-syslog-ng, build-windows-msvc, bdd-windows-otel, integration-linux-openssl, integration-windows-openssl, build-freertos-host-tdd, build-freertos-target, bdd-freertos-qemu] runs-on: ubuntu-latest permissions: contents: read diff --git a/.gitignore b/.gitignore index 43fd77cf..2e4e333c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,10 @@ Bdd/otel/bin/ __pycache__/ *.pyc +# BDD-side mirror of build-time tunables, generated by CMake configure_file +# from Bdd/features/steps/solidsyslog_tunables.py.in +Bdd/features/steps/solidsyslog_tunables.py + # Claude Code local config .claude/ pr*-comments.json diff --git a/Bdd/Targets/Common/BddTargetInteractive.c b/Bdd/Targets/Common/BddTargetInteractive.c index aedc1763..e12a5e1a 100644 --- a/Bdd/Targets/Common/BddTargetInteractive.c +++ b/Bdd/Targets/Common/BddTargetInteractive.c @@ -6,6 +6,7 @@ #include #include "SolidSyslog.h" +#include "SolidSyslogTunables.h" static const char* const PROMPT = "SolidSyslog> "; diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 65bd5766..ed307b3e 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -25,6 +25,7 @@ #include "BddTargetSwitchConfig.h" #include "SolidSyslog.h" #include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogCircularBuffer.h" #include "SolidSyslogConfig.h" #include "SolidSyslogEndpoint.h" diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 35ab2049..f2a696ec 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -21,6 +21,7 @@ #include "BddTargetUdpConfig.h" #include "SolidSyslog.h" #include "SolidSyslogAtomicCounter.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogConfig.h" #include "SolidSyslogCrc16Policy.h" #include "SolidSyslogFileBlockDevice.h" diff --git a/Bdd/features/steps/solidsyslog_tunables.py.in b/Bdd/features/steps/solidsyslog_tunables.py.in new file mode 100644 index 00000000..7c805c09 --- /dev/null +++ b/Bdd/features/steps/solidsyslog_tunables.py.in @@ -0,0 +1,7 @@ +# Auto-generated by CMake from Bdd/features/steps/solidsyslog_tunables.py.in. +# Mirrors the build-time tunable values used in CMakeLists.txt and parsed +# from Core/Interface/SolidSyslogTunablesDefaults.h (or the user-supplied +# override file if SOLIDSYSLOG_USER_TUNABLES_FILE is set). Reconfigure CMake +# to refresh. Do not edit; do not commit (see .gitignore). + +SOLIDSYSLOG_MAX_MESSAGE_SIZE = @SOLIDSYSLOG_BDD_MAX_MESSAGE_SIZE@ diff --git a/Bdd/features/steps/syslog_steps.py b/Bdd/features/steps/syslog_steps.py index bc00ff5b..072fb1d7 100644 --- a/Bdd/features/steps/syslog_steps.py +++ b/Bdd/features/steps/syslog_steps.py @@ -61,12 +61,15 @@ def per_transport_log(context, transport): SYSLOG_NG_FULL_CONF = "Bdd/syslog-ng/syslog-ng-full.conf" SYSLOG_NG_UDP_ONLY_CONF = "Bdd/syslog-ng/syslog-ng-udp-only.conf" -# Mirrors SOLIDSYSLOG_MAX_MESSAGE_SIZE from Core/Interface/SolidSyslog.h. Bump -# the two together. The store_capacity scenarios depend on it because production -# clamps max-block-size up to MAX + RECORD_OVERHEAD + integritySize at runtime, -# so the block size used by the block store is MAX-coupled even when the feature -# file specifies a smaller value. -SOLIDSYSLOG_MAX_MESSAGE_SIZE = 2048 +# Sourced from the build's configured tunables via the CMake-generated +# solidsyslog_tunables module (configure_file in top-level CMakeLists.txt +# parses Core/Interface/SolidSyslogTunablesDefaults.h, or the user override +# if SOLIDSYSLOG_USER_TUNABLES_FILE is set). The store_capacity scenarios +# depend on this value because production clamps max-block-size up to +# MAX + RECORD_OVERHEAD + integritySize at runtime, so the block size used +# by the block store is MAX-coupled even when the feature file specifies a +# smaller value. +from solidsyslog_tunables import SOLIDSYSLOG_MAX_MESSAGE_SIZE # RFC 5424 §6.2.4 IP fallback for the FreeRTOS reference example. With no # FQDN (no DNS resolver), no integrator-supplied hostname, and no DHCP, the diff --git a/CMakeLists.txt b/CMakeLists.txt index 6365f67a..64559e19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,40 @@ if(SOLIDSYSLOG_OPENSSL) endif() endif() +# Build-time tunables (E21: Port-Time Configurability). Integrators override +# library defaults by setting SOLIDSYSLOG_USER_TUNABLES_FILE to the absolute +# path of their own header containing #define lines for whichever tunables +# they want to change. Defaults live in Core/Interface/SolidSyslogTunablesDefaults.h. +add_library(SolidSyslogTunables INTERFACE) +target_include_directories(SolidSyslogTunables INTERFACE + ${CMAKE_SOURCE_DIR}/Core/Interface) +if(SOLIDSYSLOG_USER_TUNABLES_FILE) + target_compile_definitions(SolidSyslogTunables INTERFACE + SOLIDSYSLOG_USER_TUNABLES_FILE="${SOLIDSYSLOG_USER_TUNABLES_FILE}") +endif() + +# BDD-side mirror of the configured tunables — eliminates the hardcoded +# "remember to bump this" mirror that used to live in syslog_steps.py. +# Parse the default from SolidSyslogTunablesDefaults.h; if a user override +# is in effect and defines the macro, use its value instead. The generated +# file is written to the source tree because behave's import discovery +# expects modules under Bdd/features/steps/; it is gitignored. +file(READ "${CMAKE_SOURCE_DIR}/Core/Interface/SolidSyslogTunablesDefaults.h" _solidsyslog_defaults) +string(REGEX MATCH "#define[ \t]+SOLIDSYSLOG_MAX_MESSAGE_SIZE[ \t]+([0-9]+)" _ "${_solidsyslog_defaults}") +set(SOLIDSYSLOG_BDD_MAX_MESSAGE_SIZE "${CMAKE_MATCH_1}") +if(SOLIDSYSLOG_USER_TUNABLES_FILE AND EXISTS "${SOLIDSYSLOG_USER_TUNABLES_FILE}") + file(READ "${SOLIDSYSLOG_USER_TUNABLES_FILE}" _solidsyslog_user_tunables) + string(REGEX MATCH "#define[ \t]+SOLIDSYSLOG_MAX_MESSAGE_SIZE[ \t]+([0-9]+)" _ "${_solidsyslog_user_tunables}") + if(CMAKE_MATCH_1) + set(SOLIDSYSLOG_BDD_MAX_MESSAGE_SIZE "${CMAKE_MATCH_1}") + endif() +endif() +configure_file( + "${CMAKE_SOURCE_DIR}/Bdd/features/steps/solidsyslog_tunables.py.in" + "${CMAKE_SOURCE_DIR}/Bdd/features/steps/solidsyslog_tunables.py" + @ONLY +) + add_subdirectory(Core/Source) if(HAVE_STDATOMIC_H) diff --git a/CMakePresets.json b/CMakePresets.json index 8a8ca8eb..1b3c386c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -36,6 +36,15 @@ "SANITIZE": "ON" } }, + { + "name": "tunable-override-debug", + "displayName": "Tunable Override (mechanism end-to-end)", + "inherits": "debug", + "cacheVariables": { + "SOLIDSYSLOG_USER_TUNABLES_FILE": "${sourceDir}/Tests/Fixtures/SmallMessageSizeTunables.h", + "SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE": "512" + } + }, { "name": "coverage", "displayName": "Coverage", @@ -112,6 +121,7 @@ { "name": "debug", "configurePreset": "debug" }, { "name": "release", "configurePreset": "release" }, { "name": "sanitize", "configurePreset": "sanitize" }, + { "name": "tunable-override-debug", "configurePreset": "tunable-override-debug" }, { "name": "coverage", "configurePreset": "coverage" }, { "name": "tidy", "configurePreset": "tidy" }, { "name": "cppcheck", "configurePreset": "cppcheck" }, diff --git a/Core/Interface/SolidSyslog.h b/Core/Interface/SolidSyslog.h index dcb3b164..ae574a50 100644 --- a/Core/Interface/SolidSyslog.h +++ b/Core/Interface/SolidSyslog.h @@ -6,11 +6,6 @@ EXTERN_C_BEGIN - enum - { - SOLIDSYSLOG_MAX_MESSAGE_SIZE = 2048 - }; - struct SolidSyslogMessage { enum SolidSyslog_Facility facility; diff --git a/Core/Interface/SolidSyslogBlockStore.h b/Core/Interface/SolidSyslogBlockStore.h index 55641b2c..3b3ed2c3 100644 --- a/Core/Interface/SolidSyslogBlockStore.h +++ b/Core/Interface/SolidSyslogBlockStore.h @@ -6,6 +6,7 @@ #include "SolidSyslog.h" #include "SolidSyslogSecurityPolicyDefinition.h" +#include "SolidSyslogTunables.h" #include "ExternC.h" struct SolidSyslogStore; diff --git a/Core/Interface/SolidSyslogCircularBuffer.h b/Core/Interface/SolidSyslogCircularBuffer.h index a3bea317..cf305a48 100644 --- a/Core/Interface/SolidSyslogCircularBuffer.h +++ b/Core/Interface/SolidSyslogCircularBuffer.h @@ -6,7 +6,7 @@ #include #include -#include "SolidSyslog.h" // IWYU pragma: keep -- macro body references SOLIDSYSLOG_MAX_MESSAGE_SIZE +#include "SolidSyslogTunables.h" EXTERN_C_BEGIN diff --git a/Core/Interface/SolidSyslogTunables.h b/Core/Interface/SolidSyslogTunables.h new file mode 100644 index 00000000..0c1ecd25 --- /dev/null +++ b/Core/Interface/SolidSyslogTunables.h @@ -0,0 +1,10 @@ +#ifndef SOLIDSYSLOG_TUNABLES_H +#define SOLIDSYSLOG_TUNABLES_H + +#if defined(SOLIDSYSLOG_USER_TUNABLES_FILE) +/* cppcheck-suppress preprocessorErrorDirective -- macro expands to a header path supplied via -D at build time; the #if guard makes it active only then. */ +#include SOLIDSYSLOG_USER_TUNABLES_FILE +#endif +#include "SolidSyslogTunablesDefaults.h" + +#endif /* SOLIDSYSLOG_TUNABLES_H */ diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h new file mode 100644 index 00000000..5c6ad0c0 --- /dev/null +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -0,0 +1,27 @@ +#ifndef SOLIDSYSLOG_TUNABLES_DEFAULTS_H +#define SOLIDSYSLOG_TUNABLES_DEFAULTS_H + +/* + * Maximum bytes the library will format for a single syslog message. + * + * Caps the per-Log Formatter buffer (stack-allocated inside SolidSyslog_Log), + * the FileStore record size, and the minimum BlockStore block size. Per + * RFC 5424 section 6.1, receivers SHOULD accept up to 2048 bytes; transports + * may impose lower limits beyond which truncation kicks in. Lower values save + * stack and store footprint; higher values reduce truncation. + * + * Floor: smallest size that can hold a meaningful RFC 5424 security event. + * A legal all-NILVALUE message is 16 bytes; this allows enough headroom + * for a real PRI, hostname, MSGID, and short payload. + * Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_MAX_MESSAGE_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 2048 /* RFC 5424 section 6.1 SHOULD value */ +#endif + +#if SOLIDSYSLOG_MAX_MESSAGE_SIZE < 64 +#error "SOLIDSYSLOG_MAX_MESSAGE_SIZE must be >= 64" +#endif + +#endif /* SOLIDSYSLOG_TUNABLES_DEFAULTS_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 4b59f953..ef10c038 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -48,6 +48,8 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ) +target_link_libraries(${PROJECT_NAME} PUBLIC SolidSyslogTunables) + if(SOLIDSYSLOG_POSIX) target_link_libraries(${PROJECT_NAME} PUBLIC rt) endif() diff --git a/Core/Source/RecordStore.c b/Core/Source/RecordStore.c index 1a2c0f44..a6e54240 100644 --- a/Core/Source/RecordStore.c +++ b/Core/Source/RecordStore.c @@ -1,6 +1,7 @@ #include "RecordStore.h" #include "SolidSyslogBlockDevice.h" +#include "SolidSyslogTunables.h" #include diff --git a/Core/Source/RecordStore.h b/Core/Source/RecordStore.h index 713aa265..77509259 100644 --- a/Core/Source/RecordStore.h +++ b/Core/Source/RecordStore.h @@ -1,8 +1,8 @@ #ifndef SOLIDSYSLOG_RECORDSTORE_H #define SOLIDSYSLOG_RECORDSTORE_H -#include "SolidSyslog.h" #include "SolidSyslogSecurityPolicyDefinition.h" +#include "SolidSyslogTunables.h" #include #include diff --git a/Core/Source/SolidSyslog.c b/Core/Source/SolidSyslog.c index 2fbb3e0f..56dcc008 100644 --- a/Core/Source/SolidSyslog.c +++ b/Core/Source/SolidSyslog.c @@ -17,6 +17,7 @@ #include "SolidSyslogStringFunction.h" #include "SolidSyslogStructuredData.h" #include "SolidSyslogTimestamp.h" +#include "SolidSyslogTunables.h" struct SolidSyslogFormatter; struct SolidSyslogStructuredData; diff --git a/Core/Source/SolidSyslogBlockStore.c b/Core/Source/SolidSyslogBlockStore.c index 117a5e12..a137eabf 100644 --- a/Core/Source/SolidSyslogBlockStore.c +++ b/Core/Source/SolidSyslogBlockStore.c @@ -4,7 +4,7 @@ #include "BlockSequence.h" #include "RecordStore.h" -#include "SolidSyslog.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogBlockDevice.h" #include "SolidSyslogMacros.h" #include "SolidSyslogNullSecurityPolicy.h" diff --git a/Core/Source/SolidSyslogCircularBuffer.c b/Core/Source/SolidSyslogCircularBuffer.c index a599e63f..41eaed6d 100644 --- a/Core/Source/SolidSyslogCircularBuffer.c +++ b/Core/Source/SolidSyslogCircularBuffer.c @@ -5,10 +5,10 @@ #include #include -#include "SolidSyslog.h" #include "SolidSyslogBufferDefinition.h" #include "SolidSyslogMacros.h" #include "SolidSyslogMutex.h" +#include "SolidSyslogTunables.h" enum { diff --git a/DEVLOG.md b/DEVLOG.md index 10eb29b6..8347466b 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,111 @@ # Dev Log +## 2026-05-12 — S21.01 SOLIDSYSLOG_MAX_MESSAGE_SIZE as the first build-time tunable (#347) + +First slice of E21 (Port-Time Configurability). Introduces the mechanism +for integrators to override library defaults at build time; no +behavioural change on `main` because the default value of +`SOLIDSYSLOG_MAX_MESSAGE_SIZE` is unchanged at 2048. + +### Decisions + +- **mbedTLS-style C-side single source of truth.** Defaults live in + `Core/Interface/SolidSyslogTunablesDefaults.h`. The central header + `Core/Interface/SolidSyslogTunables.h` optionally `#include`s a + user-supplied override file (controlled by the + `SOLIDSYSLOG_USER_TUNABLES_FILE` macro), then falls through to the + defaults. Each tunable is `#ifndef`-guarded so the user's value wins. + Familiar pattern to embedded developers coming from mbedTLS, + FreeRTOS-Kernel, lwIP. +- **CMake INTERFACE target carries the override.** Top-level + `CMakeLists.txt` declares `SolidSyslogTunables` (INTERFACE) and adds + `SOLIDSYSLOG_USER_TUNABLES_FILE="${SOLIDSYSLOG_USER_TUNABLES_FILE}"` + as a compile definition when the cache variable is set. The library + links it `PUBLIC` so the macro propagates to consumers. Per Ben + Boeckel's CMake-Discourse guidance — usages that morph the library + itself break CMake's identity model; an INTERFACE target the + consumer can link against does not. +- **Compile-time floor guard, not runtime test.** The defaults header + enforces `SOLIDSYSLOG_MAX_MESSAGE_SIZE >= 64` via `#if`/`#error` + outside the `#ifndef` block, so the check evaluates whatever value + is in effect (user override or default). Floor rationale: a legal + all-NILVALUE RFC 5424 message is 16 bytes; 64 leaves room for a + real PRI, hostname, MSGID, short payload. One-off manual + verification — directive is self-evidently correct, no need to + automate the negative-build case. +- **BDD-Python bridge via `configure_file`.** Replaces the hardcoded + `SOLIDSYSLOG_MAX_MESSAGE_SIZE = 2048` mirror in + `Bdd/features/steps/syslog_steps.py` (the "remember to bump this" + comment was a known maintenance burden). CMake parses the defaults + header at configure time, optionally overrides from the user-config + file, and writes + `Bdd/features/steps/solidsyslog_tunables.py` (gitignored). BDD + steps import the value. Single source of truth — both sides come + from the same CMake variable in the same configure run, so drift + is structurally impossible. +- **`tunable-override-debug` preset proves the path end-to-end.** New + CMake preset inherits `debug` and sets + `SOLIDSYSLOG_USER_TUNABLES_FILE` to a test fixture + (`Tests/Fixtures/SmallMessageSizeTunables.h`, MAX = 512) plus + `SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE=512`. A `static_assert` + in `Tests/SolidSyslogTunablesTest.cpp` confirms the override + actually reached the compiler — the build fails if it didn't, no + runtime ceremony. New CI job `build-linux-tunable-override` runs + this preset; added to the `summary` gate's `needs:` list. Required + branch-protection check is a separate GitHub-side step. +- **Two latent bugs surfaced and fixed during the unit-test audit.** + (a) `Tests/SolidSyslogUdpSenderTest.cpp` hardcoded + `TEST_MAX_MESSAGE_SIZE = 1024` — the test name claims "max size + transmitted without truncation" but the constant was a static + literal. Now tracks `SOLIDSYSLOG_MAX_MESSAGE_SIZE` so the assertion + holds at any configured size. (b) `Tests/Support/SocketFake.c` + reserved one byte for a null terminator inside a buffer sized to + the configured MAX, silently clipping the last byte when a true + max-size message was sent. Buffer is now MAX + 1. +- **IWYU-clean include migration.** Files that previously included + `SolidSyslog.h` *only* for `SOLIDSYSLOG_MAX_MESSAGE_SIZE` (verified + by grep for `SolidSyslogMessage`/`SolidSyslog_Log`/`SolidSyslog_Service` + symbols) now include `SolidSyslogTunables.h` directly and drop + `SolidSyslog.h`. Files that genuinely use both keep both. The IWYU + pragma-keep on `SolidSyslogCircularBuffer.h` is gone — no longer + needed. + +### Deferred + +- **Actual FreeRTOS tuning down** — S21.02. This slice ships the + mechanism with the default unchanged. S21.02 will set + `SOLIDSYSLOG_USER_TUNABLES_FILE` on the `freertos-cross` preset and + audit BDD scenarios for hardcoded message-size assumptions + (`udp_mtu.feature` and `store_capacity` scenarios are the obvious + candidates). +- **Further tunables** (`SEND_TIMEOUT_*`, buffer capacities, SD + limits) — S21.03+. The mechanism extends to each by adding a + `#ifndef`-guarded `#define` to the defaults header. +- **`--dump-config` audit-evidence check.** Belt-and-braces idea from + the research brief: have the BDD target binary print its compiled + tunables, a behave step at session start cross-checks against the + generated Python module. With `configure_file` as a single source, + there's no drift surface, so this is purely audit-paperwork + value — natural fit under #155 (E19 CRA-Ready Cybersecurity + Reporting). +- **Porting guide.** Header comments are the docs for now. A + dedicated porting page (platform components, compilers, locations, + tuning) will land later and point at the header rather than + duplicate. +- **Strict-alphabetic sort of `#include` blocks.** Project doesn't + set `SortIncludes: true`; the new `SolidSyslogTunables.h` lands + next to its sibling `SolidSyslog.h` in each file, which is + semantically grouped but not strictly alphabetic. Cleanup pass + optional. + +### Open questions + +- **Branch-protection update for the new CI job.** The + `build-linux-tunable-override` job is added to the `summary` gate's + `needs:` list, so the aggregate signal stays correct. Adding it as + a *required* check on `main` is a separate GitHub-side + configuration step David owns. + ## 2026-05-11 — S08.11 switching_transport BDD scenario (UDP↔TCP) on QEMU (#340) Third and final slice of S08.06. With the TCP stream (S08.09) and TCP diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index aea8ae44..bc0c2f6f 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -11,6 +11,7 @@ #include "SolidSyslogPosixDatagram.h" #include "SolidSyslogUdpSender.h" #include "SolidSyslogNullStore.h" +#include "SolidSyslogTunables.h" #include "SocketFake.h" #include "ClockFake.h" #include "SolidSyslogPrival.h" diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 757dfa20..080e0d46 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -13,6 +13,7 @@ endif() set(TEST_SOURCES SolidSyslogTest.cpp + SolidSyslogTunablesTest.cpp SolidSyslogSwitchingSenderTest.cpp SolidSyslogMetaSdTest.cpp SolidSyslogTimeQualitySdTest.cpp @@ -120,6 +121,13 @@ add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES}) target_link_libraries(${PROJECT_NAME}Tests PRIVATE ${PROJECT_NAME} ErrorHandlerFake CppUTest CppUTestExt Threads::Threads) target_include_directories(${PROJECT_NAME}Tests PRIVATE ${CMAKE_SOURCE_DIR}/Core/Source ${CMAKE_CURRENT_SOURCE_DIR}/Support) +# Drives the static_assert in SolidSyslogTunablesTest.cpp when a tunable-override +# preset is active. Default builds leave it undefined; the assertion compiles out. +if(DEFINED SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE) + target_compile_definitions(${PROJECT_NAME}Tests PRIVATE + SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE=${SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE}) +endif() + if(SOLIDSYSLOG_POSIX) target_link_libraries(${PROJECT_NAME}Tests PRIVATE PosixFakes) endif() diff --git a/Tests/FileFake.c b/Tests/FileFake.c index 9d9c6c8e..7dc76736 100644 --- a/Tests/FileFake.c +++ b/Tests/FileFake.c @@ -4,9 +4,9 @@ #include #include "SafeString.h" -#include "SolidSyslog.h" #include "SolidSyslogFileDefinition.h" #include "SolidSyslogMacros.h" +#include "SolidSyslogTunables.h" #include "TestAssert.h" enum diff --git a/Tests/Fixtures/SmallMessageSizeTunables.h b/Tests/Fixtures/SmallMessageSizeTunables.h new file mode 100644 index 00000000..a65901ce --- /dev/null +++ b/Tests/Fixtures/SmallMessageSizeTunables.h @@ -0,0 +1,5 @@ +/* Test fixture: drives a smaller-than-default SOLIDSYSLOG_MAX_MESSAGE_SIZE + * via the SOLIDSYSLOG_USER_TUNABLES_FILE override mechanism. Wired in by + * the `tunable-override-debug` CMake preset to prove that the user-config + * override path actually flows through to the compiler. */ +#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 512 diff --git a/Tests/SenderFake.c b/Tests/SenderFake.c index 3a769555..f63b79ce 100644 --- a/Tests/SenderFake.c +++ b/Tests/SenderFake.c @@ -4,8 +4,8 @@ #include #include -#include "SolidSyslog.h" #include "SolidSyslogSenderDefinition.h" +#include "SolidSyslogTunables.h" #include "MinSize.h" struct SenderFake diff --git a/Tests/SolidSyslogBlockStorePosixTest.cpp b/Tests/SolidSyslogBlockStorePosixTest.cpp index fe49770e..df22d645 100644 --- a/Tests/SolidSyslogBlockStorePosixTest.cpp +++ b/Tests/SolidSyslogBlockStorePosixTest.cpp @@ -8,7 +8,7 @@ #include "SolidSyslogBlockStore.h" #include "SolidSyslogPosixFile.h" #include "SolidSyslogStore.h" -#include "SolidSyslog.h" +#include "SolidSyslogTunables.h" static const char* const TEST_PATH_PREFIX = "/tmp/test_posix_store"; diff --git a/Tests/SolidSyslogBlockStoreTest.cpp b/Tests/SolidSyslogBlockStoreTest.cpp index 035f3747..36312408 100644 --- a/Tests/SolidSyslogBlockStoreTest.cpp +++ b/Tests/SolidSyslogBlockStoreTest.cpp @@ -9,7 +9,7 @@ #include "SolidSyslogFileBlockDevice.h" #include "SolidSyslogSecurityPolicyDefinition.h" #include "SolidSyslogStore.h" -#include "SolidSyslog.h" +#include "SolidSyslogTunables.h" #include "FileFake.h" #include "TestUtils.h" diff --git a/Tests/SolidSyslogCircularBufferTest.cpp b/Tests/SolidSyslogCircularBufferTest.cpp index d6af1954..8c2b9d0e 100644 --- a/Tests/SolidSyslogCircularBufferTest.cpp +++ b/Tests/SolidSyslogCircularBufferTest.cpp @@ -2,10 +2,10 @@ #include "CppUTest/TestHarness.h" #include "MutexFake.h" -#include "SolidSyslog.h" #include "SolidSyslogBuffer.h" #include "SolidSyslogCircularBuffer.h" #include "SolidSyslogNullMutex.h" +#include "SolidSyslogTunables.h" enum { diff --git a/Tests/SolidSyslogCrc16Test.cpp b/Tests/SolidSyslogCrc16Test.cpp index 345cf390..42ec4782 100644 --- a/Tests/SolidSyslogCrc16Test.cpp +++ b/Tests/SolidSyslogCrc16Test.cpp @@ -2,7 +2,7 @@ #include #include "SolidSyslogCrc16.h" -#include "SolidSyslog.h" +#include "SolidSyslogTunables.h" #include "CppUTest/TestHarness.h" // clang-format off diff --git a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp index 6013eab0..0cc81c6e 100644 --- a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp +++ b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp @@ -9,6 +9,7 @@ using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-f #include "SolidSyslogPosixMessageQueueBuffer.h" #include "SolidSyslog.h" #include "SolidSyslogConfig.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogNullStore.h" #include "SenderFake.h" #include "SolidSyslogPrival.h" diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index a74e5994..3a10e9ca 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -13,6 +13,7 @@ #include "SolidSyslogNullStore.h" #include "SolidSyslogFormatter.h" #include "SolidSyslogStructuredDataDefinition.h" +#include "SolidSyslogTunables.h" #include "BufferFake.h" #include "ErrorHandlerFake.h" #include "SolidSyslogErrorMessages.h" diff --git a/Tests/SolidSyslogTunablesTest.cpp b/Tests/SolidSyslogTunablesTest.cpp new file mode 100644 index 00000000..a4e5e6c1 --- /dev/null +++ b/Tests/SolidSyslogTunablesTest.cpp @@ -0,0 +1,19 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogTunables.h" + +/* When the build is configured with a user-tunables override (the + * tunable-override-debug preset), CMake also injects the expected + * value here. Asserting equality at compile time proves that the + * SOLIDSYSLOG_USER_TUNABLES_FILE override actually flowed through to + * the compiler. The default build leaves the macro undefined and this + * assertion is compiled out. */ +#ifdef SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE +static_assert(SOLIDSYSLOG_MAX_MESSAGE_SIZE == SOLIDSYSLOG_TEST_EXPECTED_MAX_MESSAGE_SIZE, "SOLIDSYSLOG_USER_TUNABLES_FILE override did not reach the compiler"); +#endif + +TEST_GROUP(SolidSyslogTunables){}; + +TEST(SolidSyslogTunables, MaxMessageSizeIsReachableViaTunablesHeader) +{ + CHECK(SOLIDSYSLOG_MAX_MESSAGE_SIZE > 0); +} diff --git a/Tests/SolidSyslogUdpSenderTest.cpp b/Tests/SolidSyslogUdpSenderTest.cpp index 3de392db..c3bcb8e1 100644 --- a/Tests/SolidSyslogUdpSenderTest.cpp +++ b/Tests/SolidSyslogUdpSenderTest.cpp @@ -9,6 +9,7 @@ #include "SolidSyslogFormatter.h" #include "SolidSyslogGetAddrInfoResolver.h" #include "SolidSyslogPosixDatagram.h" +#include "SolidSyslogTunables.h" #include "SolidSyslogUdpSender.h" #include "SolidSyslogSender.h" #include "SocketFake.h" @@ -25,7 +26,7 @@ static const size_t TEST_MESSAGE_LEN = 5; static const char* const TEST_DEFAULT_HOST = "127.0.0.1"; static const int TEST_DEFAULT_PORT = 514; static const int TEST_ALTERNATE_PORT = 9999; -static const size_t TEST_MAX_MESSAGE_SIZE = 1024; +static const size_t TEST_MAX_MESSAGE_SIZE = SOLIDSYSLOG_MAX_MESSAGE_SIZE; // clang-format on static int GetDefaultPort() diff --git a/Tests/Support/SocketFake.c b/Tests/Support/SocketFake.c index 27115062..9ecca6a8 100644 --- a/Tests/Support/SocketFake.c +++ b/Tests/Support/SocketFake.c @@ -1,6 +1,6 @@ #include "SocketFake.h" #include "SafeString.h" -#include "SolidSyslog.h" +#include "SolidSyslogTunables.h" #include #include #include @@ -16,7 +16,9 @@ struct timeval; enum { - SOCKETFAKE_MAX_BUFFER_SIZE = SOLIDSYSLOG_MAX_MESSAGE_SIZE + /* +1 reserves a null terminator slot so LastBufAsString stays valid + * even when a full SOLIDSYSLOG_MAX_MESSAGE_SIZE-byte message is sent. */ + SOCKETFAKE_MAX_BUFFER_SIZE = SOLIDSYSLOG_MAX_MESSAGE_SIZE + 1 }; static bool sendtoFails; diff --git a/Tests/Support/WinsockFake.c b/Tests/Support/WinsockFake.c index 89d7a16d..09b2e280 100644 --- a/Tests/Support/WinsockFake.c +++ b/Tests/Support/WinsockFake.c @@ -1,6 +1,6 @@ #include "WinsockFake.h" #include "SafeString.h" -#include "SolidSyslog.h" +#include "SolidSyslogTunables.h" #include #include #include From 95aa232248fb3112598cbadcf8acf3e34609bee4 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 12 May 2026 10:09:26 +0100 Subject: [PATCH 2/3] fix: IWYU umbrella-header pragmas + carry generated tunables.py as BDD artefact Two follow-up fixes from CI on the S21.01 PR: - analyze-iwyu was steering callers to include SolidSyslogTunablesDefaults.h directly, which would bypass the optional SOLIDSYSLOG_USER_TUNABLES_FILE override. Add `// IWYU pragma: private, include "SolidSyslogTunables.h"` on the defaults header and `// IWYU pragma: export` on the include from the umbrella, the canonical mbedTLS-shaped fix for the umbrella-header case. - The BDD jobs (linux-syslog-ng, windows-otel, freertos-qemu) only check out the repo + download the BDD target binary; they don't run cmake, so the gitignored Bdd/features/steps/solidsyslog_tunables.py wasn't present and behave failed with `ModuleNotFoundError: No module named 'solidsyslog_tunables'`. Upload the generated file as a per-target artefact from each build job and download it into Bdd/features/steps/ in the matching BDD job (bdd-tunables-{linux,windows,freertos}). Per-target artefacts so S21.02's FreeRTOS preset override flows independently. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 39 ++++++++++++++++++++ Core/Interface/SolidSyslogTunables.h | 2 +- Core/Interface/SolidSyslogTunablesDefaults.h | 4 ++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7018bbb9..d0a88bff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,16 @@ jobs: retention-days: 1 compression-level: 0 + # BDD-side mirror of build-time tunables, generated by configure_file. + # bdd-linux-syslog-ng downloads this into Bdd/features/steps/ so behave + # can `import solidsyslog_tunables` (the file is gitignored, not in checkout). + - name: Upload BDD Python tunables (Linux) + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: bdd-tunables-linux + path: Bdd/features/steps/solidsyslog_tunables.py + retention-days: 1 + integration-linux-openssl: runs-on: ubuntu-latest permissions: @@ -536,6 +546,13 @@ jobs: retention-days: 1 compression-level: 0 + - name: Upload BDD Python tunables (Windows) + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: bdd-tunables-windows + path: Bdd/features/steps/solidsyslog_tunables.py + retention-days: 1 + bdd-windows-otel: needs: build-windows-msvc runs-on: windows-2025 @@ -554,6 +571,11 @@ jobs: name: solid-syslog-bdd-target-windows path: build/msvc-debug/Bdd/Targets/Debug/ + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: bdd-tunables-windows + path: Bdd/features/steps/ + - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: @@ -659,6 +681,11 @@ jobs: name: solid-syslog-bdd-targets path: build/debug/Bdd/Targets/ + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: bdd-tunables-linux + path: Bdd/features/steps/ + - name: Make example binary executable run: chmod +x build/debug/Bdd/Targets/SolidSyslogBddTarget @@ -765,6 +792,13 @@ jobs: retention-days: 1 compression-level: 0 + - name: Upload BDD Python tunables (FreeRTOS) + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: bdd-tunables-freertos + path: Bdd/features/steps/solidsyslog_tunables.py + retention-days: 1 + bdd-freertos-qemu: needs: build-freertos-target runs-on: ubuntu-latest @@ -780,6 +814,11 @@ jobs: name: solid-syslog-bdd-target-freertos path: build/freertos-cross/Bdd/Targets/FreeRtos/ + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: bdd-tunables-freertos + path: Bdd/features/steps/ + - name: Make ELF readable run: chmod +r build/freertos-cross/Bdd/Targets/FreeRtos/SolidSyslogBddTarget.elf diff --git a/Core/Interface/SolidSyslogTunables.h b/Core/Interface/SolidSyslogTunables.h index 0c1ecd25..b329b42f 100644 --- a/Core/Interface/SolidSyslogTunables.h +++ b/Core/Interface/SolidSyslogTunables.h @@ -5,6 +5,6 @@ /* cppcheck-suppress preprocessorErrorDirective -- macro expands to a header path supplied via -D at build time; the #if guard makes it active only then. */ #include SOLIDSYSLOG_USER_TUNABLES_FILE #endif -#include "SolidSyslogTunablesDefaults.h" +#include "SolidSyslogTunablesDefaults.h" // IWYU pragma: export #endif /* SOLIDSYSLOG_TUNABLES_H */ diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 5c6ad0c0..9ff6b848 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -1,6 +1,10 @@ #ifndef SOLIDSYSLOG_TUNABLES_DEFAULTS_H #define SOLIDSYSLOG_TUNABLES_DEFAULTS_H +// IWYU pragma: private, include "SolidSyslogTunables.h" +// Defaults are reached through the SolidSyslogTunables.h umbrella so the optional +// SOLIDSYSLOG_USER_TUNABLES_FILE override gets a chance to win first. + /* * Maximum bytes the library will format for a single syslog message. * From 2515aea7192fa445189afc065bac361d47c6585a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 12 May 2026 11:43:23 +0100 Subject: [PATCH 3/3] fix: drop unused SolidSyslog.h from BlockStore.h + add tunable-override to Quality Monitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IWYU caught that Core/Interface/SolidSyslogBlockStore.h's #include of SolidSyslog.h is only referenced inside a documentation comment about NullBuffer recursion semantics, not by any code. My earlier audit grepped for SolidSyslogMessage/SolidSyslog_Log/SolidSyslog_Service symbols and counted the comment-mentions as hits — false positive. Remove the include. - CodeRabbit caught that build-linux-tunable-override was added to the summary gate's needs list but missing from the Quality Monitor config's tests.tools array, so its JUnit results would have been uploaded but not displayed in the Quality Summary panel. Add the matching tool entry. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 5 +++++ Core/Interface/SolidSyslogBlockStore.h | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0a88bff..f5e1be98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -950,6 +950,11 @@ jobs: "id": "junit", "name": "build-windows-msvc", "pattern": "**/junit-build-windows-msvc/cpputest_*.xml" + }, + { + "id": "junit", + "name": "build-linux-tunable-override", + "pattern": "**/junit-build-linux-tunable-override/cpputest_*.xml" } ] }, diff --git a/Core/Interface/SolidSyslogBlockStore.h b/Core/Interface/SolidSyslogBlockStore.h index 3b3ed2c3..eb140448 100644 --- a/Core/Interface/SolidSyslogBlockStore.h +++ b/Core/Interface/SolidSyslogBlockStore.h @@ -4,7 +4,6 @@ #include #include -#include "SolidSyslog.h" #include "SolidSyslogSecurityPolicyDefinition.h" #include "SolidSyslogTunables.h" #include "ExternC.h"