diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8cd25c3e..f5e1be98 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: @@ -165,6 +175,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: @@ -498,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 @@ -516,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: @@ -621,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 @@ -727,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 @@ -742,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 @@ -776,7 +853,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 @@ -873,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/.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..eb140448 100644 --- a/Core/Interface/SolidSyslogBlockStore.h +++ b/Core/Interface/SolidSyslogBlockStore.h @@ -4,8 +4,8 @@ #include #include -#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..b329b42f --- /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" // IWYU pragma: export + +#endif /* SOLIDSYSLOG_TUNABLES_H */ diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h new file mode 100644 index 00000000..9ff6b848 --- /dev/null +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -0,0 +1,31 @@ +#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. + * + * 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