From cbd69779f879288e57e42b9102c7a1060a18067b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 12:57:40 +0100 Subject: [PATCH 01/57] docs: S23.22 correct the RFC 5424 section numbers in the compliance matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header-field rows were each one subsection low, PRI cited the message-length section, and the three structured-data rows paired a 6.3.x citation with the correct 7.x one — 6.3.4 is Change Control and 6.3.5 is Examples, so neither defines origin or meta. Message size cited 8.1 (UNICODE) for what is 6.1, in both the section column and the note; PRINTUSASCII cited 9 (IANA Considerations) for what the section 6 ABNF defines. Verified against the RFC's own table of contents. Also state why non-transparent framing is excluded rather than calling octet counting "recommended" — RFC 6587 is Historic and recommends neither framing. Co-Authored-By: Claude Opus 5 (1M context) --- docs/rfc-compliance.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index c6134819..20e9dfb3 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -15,23 +15,23 @@ Status key: | Section | Requirement | Status | Notes | |---|---|---|---| -| 6.1 | PRI — facility * 8 + severity | Supported | Invalid values fall back to `syslog.err` (facility 5, severity 3) | -| 6.2.1 | VERSION = 1 | Supported | | -| 6.2.2 | TIMESTAMP — ISO 8601 with microseconds | Supported | 6-digit fractional seconds, UTC offset or `Z` | -| 6.2.2 | TIMESTAMP — NILVALUE when clock unavailable | Supported | NilClock produces `-` | -| 6.2.3 | HOSTNAME — max 255 chars, PRINTUSASCII | Supported | Truncated to 255. Non-PRINTUSASCII bytes substituted with `?`. Written through the public `SolidSyslogHeaderField` sink (`SolidSyslogHeaderField_PrintUsAscii`); the underlying formatter is library-private | -| 6.2.4 | APP-NAME — max 48 chars, PRINTUSASCII | Supported | Truncated to 48. Non-PRINTUSASCII bytes substituted with `?` | -| 6.2.5 | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | -| 6.2.6 | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | +| 6.2.1 | PRI — facility * 8 + severity | Supported | Invalid values fall back to `syslog.err` (facility 5, severity 3) | +| 6.2.2 | VERSION = 1 | Supported | | +| 6.2.3 | TIMESTAMP — ISO 8601 with microseconds | Supported | 6-digit fractional seconds, UTC offset or `Z` | +| 6.2.3 | TIMESTAMP — NILVALUE when clock unavailable | Supported | NilClock produces `-` | +| 6.2.4 | HOSTNAME — max 255 chars, PRINTUSASCII | Supported | Truncated to 255. Non-PRINTUSASCII bytes substituted with `?`. Written through the public `SolidSyslogHeaderField` sink (`SolidSyslogHeaderField_PrintUsAscii`); the underlying formatter is library-private | +| 6.2.5 | APP-NAME — max 48 chars, PRINTUSASCII | Supported | Truncated to 48. Non-PRINTUSASCII bytes substituted with `?` | +| 6.2.6 | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | +| 6.2.7 | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | | 6.3 | STRUCTURED-DATA — SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | | 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. SD-NAME / SD-ID syntax validation only matters once callers can supply names, via Custom Structured Data; the standard SDs (meta / timeQuality / origin) use compile-time-constant names | -| 6.3.3 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | -| 6.3.4, 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | -| 6.3.5, 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | -| 6.3.5, 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Supported | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | +| 7.1 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | +| 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | +| 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | +| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Supported | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | -| 8.1 | Message size — max 2048 recommended | Supported | Default `SOLIDSYSLOG_MAX_MESSAGE_SIZE` = 2048, matching the §8.1 SHOULD value; override it for memory-constrained MCUs via the standard tunable mechanism | -| 9 | PRINTUSASCII in header fields (codes 33-126) | Supported | Non-compliant bytes substituted with `?` at format time (HOSTNAME, APP-NAME, PROCID, MSGID) | +| 6.1 | Message size — max 2048 recommended | Supported | Default `SOLIDSYSLOG_MAX_MESSAGE_SIZE` = 2048, matching the largest message §6.1 says a transport receiver SHOULD accept; override it for memory-constrained MCUs via the standard tunable mechanism | +| 6 | PRINTUSASCII in header fields (codes 33-126) | Supported | Non-compliant bytes substituted with `?` at format time (HOSTNAME, APP-NAME, PROCID, MSGID) | ## RFC 5426 — Transmission of Syslog Messages over UDP @@ -51,7 +51,7 @@ Status key: | 3.2 | Sender initiates TCP connection | Supported | `SolidSyslogStreamSender` connects lazily on first send | | 3.2 | Default port 601 | Supported | `SOLIDSYSLOG_TCP_DEFAULT_PORT = 601` per IANA assignment (defined in `Core/Interface/SolidSyslogTransport.h`) | | 3.4.1 | Octet counting framing | Supported | `MSG-LEN SP MSG` prefix on every send | -| 3.4.2 | Non-transparent framing (LF trailer) | N/A | RFC 6587 octet counting (§3.4.1) is the recommended method and is what the library ships; non-transparent framing is the legacy alternative | +| 3.4.2 | Non-transparent framing (LF trailer) | N/A | Deliberately not implemented. RFC 6587 is Historic and describes both framings without recommending either, but §3.4 records that non-transparent framing has known problems and that octet counting does not; §3.4.1 is also the framing RFC 5425 mandates for TLS, so the library ships that alone rather than a mode selector | | 3.5 | Session closure handling | Supported | On send failure the stream is closed; the next Send transparently reconnects | | 3.5 | Handle receiver-initiated close | Supported | Detected via send failure path — same reconnect-on-next-Send mechanism | | 3.5 | Address rotation without app restart | Supported | App bumps `endpointVersion`; sender Disconnects and reconnects on next Send | From c440ac761c2aae705c3917704905a410c2884dd1 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 14:30:45 +0100 Subject: [PATCH 02/57] docs: S23.22 bring the CI job table back in step with the workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sixteen jobs were missing from the table and three were named that no longer exist — build-freertos-host-tdd, build-freertos-target and bdd-freertos-qemu were each split into plustcp and lwip variants. Add a table saying which adapter each lane exercises and whether it runs against fakes or the real thing, since the lane names carry the platform but not the adapter. Correct two claims about what a required check buys: analyze-iwyu* run continue-on-error, and feeding the summary aggregator does not make a lane blocking. Co-Authored-By: Claude Opus 5 (1M context) --- docs/ci.md | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/docs/ci.md b/docs/ci.md index 974e2cf7..6fd97c9f 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -19,21 +19,56 @@ without renaming what's already there. | `analyze-cppcheck` | `cppcheck` | cppcheck static analysis | | `analyze-codeql` | — | CodeQL over the library as a consumer builds it; findings in Security → Code scanning. Its own workflow (`codeql.yml`) — see *Code scanning* | | `analyze-format` | — | clang-format dry-run; fails if any file needs reformatting | -| `analyze-iwyu` | `iwyu` | include-what-you-use; fails on missing or unused `#include` directives | +| `analyze-iwyu` | `iwyu` | include-what-you-use; fails on missing or unused `#include` directives. Advisory — runs `continue-on-error` | +| `analyze-tidy-freertos-plustcp` | `tidy` | clang-tidy over the FreeRTOS / FreeRTOS-Plus-TCP / Mbed TLS / Plus-FAT trees, which the base `analyze-tidy` lane cannot reach | +| `analyze-tidy-freertos-lwip` | `tidy` | The same, over the lwIP and ChaN FatFs trees | +| `analyze-iwyu-freertos-plustcp` | `iwyu` | IWYU over the same FreeRTOS-Plus-TCP set. Advisory | +| `analyze-iwyu-freertos-lwip` | `iwyu` | IWYU over the same lwIP set. Advisory | +| `analyze-markdown` | — | `markdownlint-cli2` over every tracked `.md` file | | `integration-linux-openssl` | `debug` | Runs the in-process TLS integration tests against libssl (no network oracle) | +| `integration-linux-mbedtls` | `debug` | The same integration tests against Mbed TLS, exercising `SolidSyslogMbedTlsStream` and the Mbed TLS security policies | | `integration-windows-openssl` | `msvc-debug` | Same TLS integration tests on `windows-latest` against libssl from vcpkg | +| `build-linux-tunable-override` | `tunable-override-debug` | Builds against a user tunables header to prove `SOLIDSYSLOG_USER_TUNABLES_FILE` overrides the defaults | | `bdd-linux-syslog-ng` | — | End-to-end BDD test via Docker Compose (`syslog-ng-linux` + `behave-linux`), Linux runner | | `bdd-windows-otel` | — | Windows-eligible BDD scenarios driven against an OTel Collector oracle | -| `build-freertos-host-tdd` | `debug` | Host-TDD of FreeRTOS adapters against fakes; runs inside `cpputest-freertos` (FreeRTOS upstream sources at fixed paths) | -| `build-freertos-target` | `freertos-cross` | ARM cross-build (Cortex-M3, mps2-an385) of the BDD target ELF; uploads it as an artifact for `bdd-freertos-qemu` | -| `bdd-freertos-qemu` | — | Pulls the BDD target ELF artifact, brings up the freertos compose pair (`syslog-ng-freertos` + `behave-freertos`); Behave drives the target through `qemu-system-arm`'s UART | +| `build-freertos-host-tdd-plustcp` | `debug` | Host-TDD of the FreeRTOS, FreeRTOS-Plus-TCP, Plus-FAT, FatFs and Mbed TLS adapters against fakes; runs inside `cpputest-freertos` (upstream sources at fixed paths) | +| `build-freertos-target-plustcp` | `freertos-cross` | ARM cross-build (Cortex-M3, mps2-an385) of the BDD target ELF over FreeRTOS-Plus-TCP; uploads it as an artifact | +| `build-freertos-target-lwip` | `freertos-cross-lwip` | The same cross-build over lwIP with ChaN FatFs (`FreeRtos;LwipRaw;MbedTls;FatFs;Atomics`) | +| `bdd-freertos-qemu-plustcp` | — | Pulls the Plus-TCP target ELF, brings up the freertos compose pair (`syslog-ng-freertos` + `behave-freertos`); Behave drives the target through `qemu-system-arm`'s UART | +| `bdd-freertos-qemu-lwip` | — | The same scenarios against the lwIP target ELF | +| `consumer-smoke-linux` | — | Builds [`ci/consumer-smoke/`](../ci/consumer-smoke/) as a FetchContent consumer, proving the documented integration path still works | +| `consumer-smoke-freertos-cross` | — | The same consumer project cross-compiled for ARM with `LwipRaw;FreeRtos` | +| `verify-manifest` | — | Regenerates the Core and per-platform source manifests and fails if they differ from the committed ones | | `docs-build` | — | Builds the MkDocs + mkdoxy site with `mkdocs build --strict`; on `main`, `deploy-docs-pages` publishes it to GitHub Pages | +| `summary` | — | Aggregates the JUnit artifacts into a run summary. Declared `if: always()` and asserts nothing about the other jobs' results | ## Branch protection -Every job in `ci.yml` is a required status check, as are the two contexts code -scanning contributes. A PR cannot be merged unless all checks pass. Direct pushes -to `main` are blocked. Squash merge only. +Every job in `ci.yml` is a required status check except `deploy-docs-pages`, which +only runs on `main`, and so are the two contexts code scanning contributes. A PR +cannot be merged unless all checks pass. Direct pushes to `main` are blocked. Squash +merge only. + +Two qualifications on what "required" buys. The `analyze-iwyu*` lanes run +`continue-on-error`, so they are required contexts that report success whatever IWYU +finds — required in form, advisory in substance. And feeding the `summary` aggregator +does not make a lane blocking: `summary` is declared `if: always()` and asserts nothing +about `needs.*.result`, so a new lane gates merges only once its own context is added +to the required list. + +## What each lane exercises + +The lane names say the platform and toolchain but not the adapter, so: + +| Adapter | Where it is exercised | +|---|---| +| OpenSSL (`SolidSyslogTlsStream`, security policies) | `integration-linux-openssl`, `integration-windows-openssl` against real libssl | +| Mbed TLS (`SolidSyslogMbedTlsStream`, security policies) | `integration-linux-mbedtls` against real Mbed TLS; both FreeRTOS QEMU BDD lanes over a real handshake | +| FreeRTOS-Plus-TCP | `build-freertos-host-tdd-plustcp` against fakes; `bdd-freertos-qemu-plustcp` end to end under QEMU | +| lwIP | `bdd-freertos-qemu-lwip` end to end under QEMU; static analysis via the `*-freertos-lwip` lanes | +| ChaN FatFs | Built and analysed in the lwIP lanes; store-and-forward scenarios run in `bdd-freertos-qemu-lwip` | +| FreeRTOS-Plus-FAT | Host-TDD against fakes in `build-freertos-host-tdd-plustcp`, and built in the Plus-TCP cross lanes | +| POSIX, Windows | The `build-linux-*` and `build-windows-msvc` lanes, plus both host BDD lanes | ## Code scanning From d925f34bad3e4a34a946803ce4bce00c33bc6f22 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 14:35:05 +0100 Subject: [PATCH 03/57] docs: S23.22 correct the SBOM, MISRA register and footprint arithmetic The SBOM page said the top-level components array is empty; the template carries one entry, OpenSSL with scope: optional, which the same page and release-verification.md both describe correctly. State what the array holds, and record that the other optional upstreams the Platform packs bind to are not yet declared. The deviation register jumped D.003 to D.005. D.004 was Rule 18.4 pointer arithmetic in RecordStore.c, retired in S10.19 when the field helpers moved to &base[OFFSET]; restore it in the retired form D.014 already uses, so the register has no gap for an assessor to query. Say in the hardening path that per-stage figures are rounded up individually and so overshoot when summed, since a reader adding them finds ~13.9 KB against a measured 13.5 KB and concludes the headline is wrong. Co-Authored-By: Claude Opus 5 (1M context) --- docs/hardening-path.md | 6 ++++++ docs/misra-deviations.md | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/docs/hardening-path.md b/docs/hardening-path.md index e31a9b3d..1349d222 100644 --- a/docs/hardening-path.md +++ b/docs/hardening-path.md @@ -39,6 +39,12 @@ your compiler and its options, and what your application already links. The exam repositories carry the exact figures, the diff that produced each, and the reasoning behind it. +Because each stage is rounded up on its own, the stages deliberately do not add up to +the totals quoted elsewhere: twenty roundings-up accumulate, so the sum overshoots. +Measured cumulatively against the same baseline, the whole path costs about 13.5 KB of +flash rather than the ~13.9 KB the column sums to. Take a stage's figure as the cost of +that step and the example's own measurements as the cost of the path. + ## How to read this The order of the stages is a logical way to integrate syslog in small steps, each one diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index d22f0e50..0b22659f 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -317,6 +317,25 @@ Project owner — David Cozens. Recorded under --- +## D.004 — Rule 18.4: pointer arithmetic on record buffers (retired) + +**Retired in S10.19.** This deviation authorised `uint8_t*` pointer arithmetic in +`Core/Source/RecordStore.c`, where four field-offset helpers walked the +`[magic][length][message]` record layout by adding an offset to a base address. +S10.19 rewrote them to take the address of an indexed element (`&base[OFFSET]`) +instead. That is the same address by definition, but rule 18.4 fires on the `+`, +`-`, `+=` and `-=` operators specifically rather than on subscripting, so the +finding no longer arises and the deviation had nothing left to authorise. +cppcheck-misra reports no 18.4 finding in `RecordStore.c`; the suppression was +removed from `misra_suppressions.txt` at the same time. + +The entry is kept, rather than the number reused, so the register has no gaps and +a reader of an older revision can still resolve D.004. Approved by the project +owner — David Cozens. Recorded under +[#436](https://github.com/cososo-ltd/solid-syslog/pull/436). + +--- + ## D.005 — Rule 18.7: flexible array members ### Rule From 8a5c4f894fb6d219bcf09391ebab3307feb93b7c Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 14:58:24 +0100 Subject: [PATCH 04/57] docs: S23.22 use the Partial and Planned statuses in the RFC matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 38 requirements read Supported or N/A, which reads as unaudited when the file's own notes already admit two limitations. The sequenceId row becomes Partial: uniqueness and gap detection hold, but under concurrent raise adjacent IDs may invert in transmission, which the note has always said. SD-NAME / SD-ID syntax validation was a deferred item buried in the escaping row's note; it is now its own Planned row against 6.3.2, tracked to Custom Structured Data (#64), leaving the escaping row Supported because escaping genuinely is. Widen the N/A key to cover a deliberate exclusion, which is what RFC 6587 3.4.2 is — applicable to a sender, and not implemented. Summary arithmetic recounted: RFC 5424 is now 18 rows, 16/1/1/0. Co-Authored-By: Claude Opus 5 (1M context) --- docs/rfc-compliance.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index 20e9dfb3..294c297b 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -9,7 +9,8 @@ Status key: - Supported: implemented and tested - Partial: implemented with known limitations - Planned: tracked in an issue or epic -- N/A: not applicable to a sender implementation +- N/A: not applicable to a sender implementation, or applicable and deliberately + excluded — the note says which, and why ## RFC 5424 — The Syslog Protocol @@ -24,11 +25,12 @@ Status key: | 6.2.6 | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | | 6.2.7 | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | | 6.3 | STRUCTURED-DATA — SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | -| 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. SD-NAME / SD-ID syntax validation only matters once callers can supply names, via Custom Structured Data; the standard SDs (meta / timeQuality / origin) use compile-time-constant names | +| 6.3.2 | SD-ID / SD-NAME syntax validation | Planned | Not performed. It only bites once callers can supply their own names: the three standard SDs (meta / timeQuality / origin) use compile-time-constant names that are valid by construction. Tracked under Custom Structured Data ([#64](https://github.com/cososo-ltd/solid-syslog/issues/64)), which is what introduces caller-supplied SD-IDs and PARAM names | +| 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | | 7.1 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | | 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | | 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | -| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Supported | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | +| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Partial | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | | 6.1 | Message size — max 2048 recommended | Supported | Default `SOLIDSYSLOG_MAX_MESSAGE_SIZE` = 2048, matching the largest message §6.1 says a transport receiver SHOULD accept; override it for memory-constrained MCUs via the standard tunable mechanism | | 6 | PRINTUSASCII in header fields (codes 33-126) | Supported | Non-compliant bytes substituted with `?` at format time (HOSTNAME, APP-NAME, PROCID, MSGID) | @@ -75,7 +77,7 @@ The library ships two reference TLS adapters that satisfy this RFC: `SolidSyslog | RFC | Total requirements | Supported | Partial | Planned | N/A | |---|---|---|---|---|---| -| RFC 5424 | 17 | 17 | 0 | 0 | 0 | +| RFC 5424 | 18 | 16 | 1 | 1 | 0 | | RFC 5426 | 6 | 4 | 0 | 0 | 2 | | RFC 6587 | 8 | 7 | 0 | 0 | 1 | | RFC 5425 | 7 | 7 | 0 | 0 | 0 | From dc67af3397f8ae4143fb3808176641cc334cbbb9 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 14:59:41 +0100 Subject: [PATCH 05/57] docs: S23.22 stop the substrate sections equating mutual TLS with non-repudiation Both substrate bullets were labelled "Mutual TLS (non-repudiation, CR 2.12)", which contradicts the CR 2.12 row in the same file: mutual TLS authenticates the TLS peer and binds nothing about a given record to it, least of all across a relay. Label it as peer authentication and a partial contribution, and point at the row that explains why. Spell the crypto policy identifiers out. This page was the only one using the brace shorthand; at-rest-cryptography.md, both platform pages and hardening-path.md all use the full names. Co-Authored-By: Claude Opus 5 (1M context) --- docs/iec62443.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/iec62443.md b/docs/iec62443.md index f4569862..c76cc508 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -45,8 +45,8 @@ what the library does not do. | **CR 2.9** — Audit storage capacity | `SolidSyslogBlockStore` — rotating blocks, configurable `max-blocks` and `max-block-size`, configurable discard policy (`oldest` / `newest` / `halt`). Backed by `SolidSyslogFileBlockDevice` over `SolidSyslogPosixFile` (POSIX), `SolidSyslogWindowsFile` (Windows), `SolidSyslogFatFsFile` (ChaN FatFs) or `SolidSyslogPlusFatFile` (FreeRTOS-Plus-FAT), or an integrator-supplied `SolidSyslogBlockDevice` over flash. The control's own requirement enhancement calls for a warning when the storage threshold is reached: `SolidSyslogStoreThresholdFunction` + `SolidSyslogStoreThresholdCallback` provide it — edge-triggered, fires once when used-bytes crosses the threshold, re-arms when usage falls back below | Capacity has to be sized to the deployment's outage budget, which is yours to know; the library enforces the number you give it. Durability of the medium — flash wear, filesystem behaviour on power loss — belongs to the block device and the platform beneath it | | **CR 2.10** — Response to audit processing failures | `SolidSyslogStoreFullCallback` (halt policy) and the discard-policy enum. Caller picks the policy that fits the deployment's audit-loss tolerance. The early-warning threshold callback (CR 2.9) fires before discard / halt engages, giving the application time to act (notify operator, reduce verbosity, tighten retention); at 100% with HALT both fire on the same Write with threshold first then `onStoreFull`. Failures elsewhere in the path surface through the error handler — see [error severity](error-severity.md) | The response itself is your application's. The library reports the failure and applies the policy you configured; deciding what an operator is told, and whether the device keeps running, is above it | | **CR 2.11** — Timestamps | Caller-injected `SolidSyslogClockFunction` — `SolidSyslogPosixClock_GetTimestamp` (POSIX) or `SolidSyslogWindowsClock_GetTimestamp` (Windows). Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | -| **CR 2.12** — Non-repudiation | At the wire: `SolidSyslogTlsStream` mutual TLS cryptographically identifies the TLS peer to the receiver. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: cryptographic integrity via `SolidSyslog{OpenSsl,MbedTls}HmacSha256Policy`, or authenticated encryption via `SolidSyslog{OpenSsl,MbedTls}AesGcmPolicy`; `SolidSyslogCrc16Policy` remains for accidental-corruption detection | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the receiver authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Cryptographic at-rest policies are keyed — key custody, rotation, and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | -| **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed at-rest policy (`SolidSyslog{OpenSsl,MbedTls}HmacSha256Policy`) makes an unauthorised edit detectable. `SolidSyslogCrc16Policy` does not serve this control: it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit with `SolidSyslogTlsStream` and at rest with `SolidSyslog{OpenSsl,MbedTls}AesGcmPolicy`. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | +| **CR 2.12** — Non-repudiation | At the wire: `SolidSyslogTlsStream` mutual TLS cryptographically identifies the TLS peer to the receiver. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: cryptographic integrity via `SolidSyslogOpenSslHmacSha256Policy` / `SolidSyslogMbedTlsHmacSha256Policy`, or authenticated encryption via `SolidSyslogOpenSslAesGcmPolicy` / `SolidSyslogMbedTlsAesGcmPolicy`; `SolidSyslogCrc16Policy` remains for accidental-corruption detection | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the receiver authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Cryptographic at-rest policies are keyed — key custody, rotation, and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | +| **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed at-rest policy (`SolidSyslogOpenSslHmacSha256Policy` / `SolidSyslogMbedTlsHmacSha256Policy`) makes an unauthorised edit detectable. `SolidSyslogCrc16Policy` does not serve this control: it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit with `SolidSyslogTlsStream` and at rest with `SolidSyslogOpenSslAesGcmPolicy` / `SolidSyslogMbedTlsAesGcmPolicy`. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | | **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport `Send` synchronously on the caller's thread. The TCP socket is non-blocking from the moment it is opened (see [`SolidSyslogPosixTcpStream.c`](../Platform/Posix/Source/SolidSyslogPosixTcpStream.c) and [`SolidSyslogWinsockTcpStream.c`](../Platform/Windows/Source/SolidSyslogWinsockTcpStream.c)), so `Send` returns immediately on a wedged peer or a full kernel buffer; the bounded blocking surface is the initial `connect()`, sized by `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms) or runtime-overridable per Stream via `GetConnectTimeoutMs(ConnectTimeoutContext)`. On the TLS path the same applies to the handshake, via `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` (default 5000 ms) or `GetHandshakeTimeoutMs(HandshakeTimeoutContext)` for both OpenSSL and Mbed TLS adapters. Long-term silent peer death is detected out-of-band via kernel `TCP_KEEPALIVE` + `TCP_USER_TIMEOUT`. Suitable when the application has no real-time deadline or the transport latency is known to be sub-millisecond. **Buffered (`SolidSyslogCircularBuffer` with an injected `SolidSyslogMutex` — Posix / Windows / FreeRTOS / Null / caller-supplied RTOS primitive — or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` is non-blocking: it formats and enqueues, returning before any I/O. `SolidSyslog_Service`, called on the integrator's chosen thread (typically a dedicated service thread), performs the transport I/O against the same non-blocking socket; the bounded surface is the same connect / handshake budget as the single-task wiring. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | | **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment | @@ -80,14 +80,16 @@ with the client-side half of the transport-security controls: profile. A reasonable starting point is `"ECDHE+AESGCM:ECDHE+CHACHA20"` (TLS 1.2 AEAD with forward secrecy); tune to match the libssl build on the target platform. -- Mutual TLS (non-repudiation, CR 2.12). Optional +- Mutual TLS (peer authentication; a partial contribution to CR 2.12). Optional `clientCertChainPath` / `clientKeyPath` load a client cert and private key via `SSL_CTX_use_certificate_chain_file` + `SSL_CTX_use_PrivateKey_file`, with `SSL_CTX_check_private_key` confirming the pairing locally before any bytes hit the wire. Both fields are opt-in and all-or-nothing: supply both to enable mTLS, leave both NULL for server-auth TLS. Supplying only one is rejected at Open time, so this adapter cannot silently downgrade. The Mbed TLS - adapter behaves differently — see below. + adapter behaves differently — see below. It authenticates the TLS peer and does not + by itself evidence that a given record originated there; the CR 2.12 row above sets + out what it does and does not establish. - Certificate rotation (CR 1.5 authenticator refresh, CR 1.8 PKI update). `SolidSyslogTlsStream` rebuilds the `SSL_CTX` on every `Open`, re-reading the cert material from `caBundlePath` / `clientCertChainPath` / `clientKeyPath` @@ -121,13 +123,13 @@ store) is unchanged. - TLS 1.2+ floor. Inherited from `mbedtls_ssl_config_defaults(... PRESET_DEFAULT)`. TLS 1.3 negotiates automatically when both peers support it. -- Mutual TLS (non-repudiation, CR 2.12). Optional `ClientCertChain` +- Mutual TLS (peer authentication; a partial contribution to CR 2.12). Optional `ClientCertChain` / `ClientKey` handles. Both NULL = server-auth-only. Both non-NULL = mTLS. Supplying only one configures no client certificate and `Open` proceeds with server-authenticated TLS rather than failing — unlike the OpenSSL adapter above, which rejects partial credentials at `Open`. If a half-supplied credential must be an error on this adapter, the caller checks - before `Open`. + before `Open`. The CR 2.12 caveat above applies here equally. - Certificate rotation (CR 1.5 / CR 1.8). Because the adapter consumes pre-built handles rather than file paths, rotation is "parse a new `mbedtls_x509_crt`, destroy and recreate the adapter (or the From 79384dd18d2de6b643866a376fbf9755d86f9163 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 15:14:09 +0100 Subject: [PATCH 06/57] docs: S23.22 remove the last Security Level claims outside the guide A tree-wide sweep for SL1-SL4 left three instances, all outside docs/ and so missed by earlier passes. The mTLS feature description claimed to satisfy CR 2.12 at SL3+, which is wrong three ways: Table B.1 puts CR 2.12 at SL1-4, mutual TLS authenticates the peer rather than evidencing non-repudiation, and a scenario proving mTLS works does not satisfy a control. A Gherkin description reads as an assertion about what the test proves, so the claim goes entirely. The API-audiences diagram used SL1 to mean a minimal build; it now says that. The IEC 62443 page's meta description still advertised a mapping by Security Level, which the page no longer does. Co-Authored-By: Claude Opus 5 (1M context) --- Bdd/features/mtls_transport.feature | 8 ++++---- docs/assets/postit/api-audiences.svg | 2 +- hooks/page_descriptions.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Bdd/features/mtls_transport.feature b/Bdd/features/mtls_transport.feature index 67583fd0..cc49e5be 100644 --- a/Bdd/features/mtls_transport.feature +++ b/Bdd/features/mtls_transport.feature @@ -1,10 +1,10 @@ @mtls Feature: Mutual TLS message delivery The BDD target authenticates itself to the oracle with a client - certificate over RFC 5425 TLS, exercising mTLS end-to-end. Satisfies - IEC 62443 CR 2.12 (non-repudiation, SL3+) — mutual TLS cryptographically - identifies the sender, while the SIEM detects sequence gaps. Cross-platform: Linux runner uses syslog-ng, - Windows runner uses otelcol-contrib with client_ca_file. + certificate over RFC 5425 TLS, exercising mTLS end-to-end. Mutual TLS + cryptographically identifies the sender, while the SIEM detects sequence + gaps. Cross-platform: Linux runner uses syslog-ng, Windows runner uses + otelcol-contrib with client_ca_file. Scenario: Message delivered over mutual TLS Given the syslog oracle is running diff --git a/docs/assets/postit/api-audiences.svg b/docs/assets/postit/api-audiences.svg index ba5ad226..a435b3aa 100644 --- a/docs/assets/postit/api-audiences.svg +++ b/docs/assets/postit/api-audiences.svg @@ -155,7 +155,7 @@ Setup includes one header per component it wires. - The Linux reference target pulls around two dozen; a lean SL1 stack, a + The Linux reference target pulls around two dozen; a minimal build, a handful. Every file that logs or drains includes only SolidSyslog.h. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index b090324e..4cf0335d 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -74,7 +74,7 @@ ), "iec62443.md": ( "IEC 62443-4-2 and 62443-3-3 audit-logging controls mapped " - "control-by-control to SolidSyslog components, by Security Level." + "control-by-control to SolidSyslog components." ), "rfc-compliance.md": ( "Requirement-by-requirement status of SolidSyslog against the syslog " From a7063861fd32f03a9e52d1b96ae0ba57fb4b0796 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 15:19:22 +0100 Subject: [PATCH 07/57] docs: S23.22 correct the IEC 62443 page meta description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The description still advertised 62443-3-3 coverage, which the page dropped when its two SR rows were renumbered to CRs and it became consistently 4-2. It also said the controls map to "SolidSyslog components", which now reads against the standard's own meaning of component — a software application or device, not a library inside one. Say what the page's columns actually say instead, mirroring the CRA page's phrasing. Co-Authored-By: Claude Opus 5 (1M context) --- hooks/page_descriptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index 4cf0335d..fc086aa8 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -73,8 +73,8 @@ "stays yours." ), "iec62443.md": ( - "IEC 62443-4-2 and 62443-3-3 audit-logging controls mapped " - "control-by-control to SolidSyslog components." + "IEC 62443-4-2 audit-logging controls mapped control-by-control to " + "what SolidSyslog provides and what stays yours." ), "rfc-compliance.md": ( "Requirement-by-requirement status of SolidSyslog against the syslog " From 42b984de6339c58602cc315988b0cb3a84d172fc Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 15:47:34 +0100 Subject: [PATCH 08/57] docs: S23.22 identify MISRA guidelines by citation rather than reproducing them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twelve of the fourteen deviation entries quoted the guideline headline verbatim. MISRA C:2012 is copyrighted and not redistributable, and the project claims no compliance — the register exists to tell an integrator where our code knowingly departs, which does not require restating the standard. Each entry now names its guideline and category and points at a licensed copy; the rationale, risk and approval sections, which were always our own writing, are unchanged. A preamble section states the omission is deliberate, so a reader sees diligence rather than a gap. CLAUDE.md carried the Rule 15.6 headline in a parenthetical; it now describes what the formatter settings do instead. The suppressions files and source comments already carried no rule text and needed no change. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 8 +-- docs/misra-deviations.md | 126 +++++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 56 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8fbfd08b..060ce8ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -500,10 +500,10 @@ format-on-save: - **`InsertBraces: true`** combined with `AllowShortIfStatementsOnASingleLine: Never`, `AllowShortLoopsOnASingleLine: false`, `AllowShortFunctionsOnASingleLine: None`, and - `AllowShortBlocksOnASingleLine: Never` — formatter-side enforcement of **MISRA 15.6** - (the body of an iteration- or selection-statement shall be a compound-statement). clang-format - rewrites your code to add the braces if they are missing, and the `AllowShort*` settings - stop them being collapsed back onto a single line. + `AllowShortBlocksOnASingleLine: Never` — formatter-side enforcement of **MISRA 15.6**, + which is why every `if`, `else`, `for` and `while` body in this project is braced. + clang-format rewrites your code to add the braces if they are missing, and the + `AllowShort*` settings stop them being collapsed back onto a single line. - **`RemoveParentheses: Leave`** — keeps the project **MISRA 12.1 safe**. The advisory rule prefers explicit precedence parentheses; flipping this to `MultipleParentheses` would let clang-format strip them. diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 0b22659f..ad9f32ee 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -22,24 +22,46 @@ header comment. The format below is patterned on MISRA's own deviation record template (MISRA Compliance:2020 §4.2). +## Guideline text is not reproduced here + +Each entry identifies its guideline by number and category and then describes +**the construct in SolidSyslog that deviates** — not what the guideline says. +MISRA C:2012 is copyrighted and not redistributable, so its rule text, +amplification and examples are omitted deliberately rather than by oversight. +Read them in your own licensed copy, available from +[misra.org.uk](https://misra.org.uk/); Appendix A lists every guideline with its +category. + +Nothing is lost by this. A deviation record exists to show that the project +understood the guideline and reasoned about the risk of departing from it, and a +precise description of our own code demonstrates that better than a restated +headline an assessor already has in front of them. + --- ## D.001 — Rule 5.1 external identifier uniqueness relaxed to 63 characters -### Rule +### Guideline -> **Rule 5.1 (Required)** — External identifiers shall be distinct. - -The "distinct" requirement is parameterised by the implementation's -significant-character count for external identifiers. C99 §5.2.4.1 -specifies a minimum of 31 significant characters in external -identifiers — i.e. a conforming compiler may treat two external -identifiers that agree in the first 31 characters as the same identifier. +**MISRA C:2012 Rule 5.1** — Required. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation -SolidSyslog requires external identifiers to be distinct in the first -63 characters rather than the first 31. +SolidSyslog requires external identifiers to be distinct within their first +63 characters rather than their first 31. + +Thirty-one is the floor the C language sets: C99 §5.2.4.1 guarantees +only 31 significant characters in an external identifier, so a conforming +compiler is permitted to treat two identifiers that agree that far as the same +identifier. The project asserts a longer guarantee than the language requires, +and states the number rather than leaving it implicit. + +Every toolchain this project builds and tests on resolves external identifiers +well past 63 characters, and the table under *Rationale* below records what +each of them documents. Yours may not be among them: if you build SolidSyslog +with a different compiler or linker, confirm its significant-character limit +before relying on this deviation. ### Scope @@ -112,16 +134,13 @@ document under [S10.01](https://github.com/cososo-ltd/solid-syslog/issues/357). ## D.002 — Rules 11.2 / 11.3 / 11.5: vtable downcasts + Formatter -### Rule +### Guidelines + +- **MISRA C:2012 Rule 11.2** — Required. +- **MISRA C:2012 Rule 11.3** — Required. +- **MISRA C:2012 Rule 11.5** — Advisory. -> **Rule 11.3 (Required)** — A cast shall not be performed between a -> pointer to object type and a pointer to a different object type. -> -> **Rule 11.2 (Required)** — Conversions shall not be performed between -> a pointer to an incomplete type and any other type. -> -> **Rule 11.5 (Advisory)** — A conversion should not be performed from -> pointer to `void` into pointer to object. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -257,11 +276,12 @@ caller-supplied-storage exception. ## D.003 — Rule 5.7: repeating struct tags (no-typedef-struct convention) -### Rule +### Guideline -> **Rule 5.7 (Required)** — A tag name shall be a unique identifier. +**MISRA C:2012 Rule 5.7** — Required. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). -cppcheck-misra interprets this strictly — every repeated `struct X` +cppcheck-misra interprets Rule 5.7 strictly — every repeated `struct X` declaration counts as a non-unique tag, including forward declarations in headers and the matching definition in source. @@ -338,10 +358,10 @@ owner — David Cozens. Recorded under ## D.005 — Rule 18.7: flexible array members -### Rule +### Guideline -> **Rule 18.7 (Required)** — Flexible array members shall not be -> declared. +**MISRA C:2012 Rule 18.7** — Required. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -401,11 +421,10 @@ Project owner — David Cozens. Recorded under ## D.006 — Rule 11.8: `const` qualification under field access of `const struct*` -### Rule +### Guideline -> **Rule 11.8 (Required)** — A cast shall not be performed that -> removes any `const` or `volatile` qualification from the type -> pointed to by a pointer. +**MISRA C:2012 Rule 11.8** — Required. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -533,11 +552,13 @@ Project owner — David Cozens. Recorded under ## D.007 — Rule 21.10: transitive `` via `` -### Rule +### Guideline -> **Rule 21.10 (Required)** — The Standard Library time and date -> functions shall not be used. (cppcheck-misra also flags -> `` inclusion under this rule.) +**MISRA C:2012 Rule 21.10** — Required. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). + +cppcheck-misra also raises this rule for `` inclusion, which is what +brings the construct below into scope. ### Deviation @@ -586,10 +607,10 @@ Project owner — David Cozens. Recorded under ## D.008 — Rule 21.6: `` for `SEEK_SET` / `SEEK_END` only -### Rule +### Guideline -> **Rule 21.6 (Required)** — The Standard Library input/output -> functions shall not be used. +**MISRA C:2012 Rule 21.6** — Required. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -641,11 +662,12 @@ Project owner — David Cozens. Recorded under ## D.009 — Rules 2.4 / 5.7: anonymous `enum` used as named-constant container -### Rules +### Guidelines + +- **MISRA C:2012 Rule 2.4** — Advisory. +- **MISRA C:2012 Rule 5.7** — Required. -> **Rule 2.4 (Advisory)** — A project should not contain unused tag -> declarations. -> **Rule 5.7 (Required)** — A tag name shall be a unique identifier. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). cppcheck-misra interprets an anonymous `enum { ... };` declaration (no enum tag, no `typedef`) two ways: @@ -739,10 +761,10 @@ Project owner — David Cozens. Recorded under ## D.010 — Rule 20.10: `#` stringification in the `SOLIDSYSLOG_STATIC_ASSERT` polyfill -### Rule +### Guideline -> **Rule 20.10 (Advisory)** — The `#` and `##` preprocessor operators -> should not be used. +**MISRA C:2012 Rule 20.10** — Advisory. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -809,10 +831,10 @@ Project owner — David Cozens. Recorded under ## D.011 — Rule 2.5: public API macros consumed outside the cppcheck-misra scope -### Rule +### Guideline -> **Rule 2.5 (Advisory)** — A project should not contain unused macro -> definitions. +**MISRA C:2012 Rule 2.5** — Advisory. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -879,10 +901,10 @@ Project owner — David Cozens. Recorded under ## D.012 — Rule 8.9: file-scope `static const` referenced from a file-scope enum + one function -### Rule +### Guideline -> **Rule 8.9 (Advisory)** — An object should be defined at block scope -> if its identifier only appears in a single function. +**MISRA C:2012 Rule 8.9** — Advisory. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation @@ -962,10 +984,10 @@ Project owner — David Cozens. Recorded under ## D.013 — Rule 11.5: `void*` ↔ `unsigned char*` at third-party byte-buffer API boundaries -### Rule +### Guideline -> **Rule 11.5 (Advisory)** — A conversion should not be performed from -> pointer to `void` into pointer to object. +**MISRA C:2012 Rule 11.5** — Advisory. +**Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). ### Deviation From 57cf65cf42ed77942be391c19675f35978d712bc Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 19:58:53 +0100 Subject: [PATCH 09/57] docs: S23.22 remove unsupportable citations from the MISRA register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four references to MISRA Compliance:2020 cited a document the project does not hold, three of them to support a claim that inline tool suppressions are weaker than a central register — a sound engineering preference that had acquired an authority it cannot carry. All four go; the preference is stated as ours. D.006 attributed external-interface deviation guidance to Rule 11.8 that is not there, and rested fifteen of its seventeen sites on "the accepted interpretation in the MISRA community". Both are replaced by the argument from the C standard, which was already present and is stronger. Clause references are fixed to C99 and corrected: flexible array members are 6.7.2.1 paragraph 16, not 18, which is C11's numbering; the -> operator is 6.5.2.3 paragraph 4, not 3, in both editions. 6.7.2.1 renumbered when C11 added anonymous struct and union members, so mixing editions sends a reader to the wrong sentence — a new header fixes the edition and says why. D.010 claimed every build compiles the C11 branch, writing the C99 fallback and the C++ harness branch out of existence. All three are compiled and all three are named. Co-Authored-By: Claude Opus 5 (1M context) --- docs/misra-deviations.md | 75 ++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index ad9f32ee..c3162784 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -19,8 +19,9 @@ under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367) after the rule subset was curated; before then it carried only a header comment. -The format below is patterned on MISRA's own deviation record template -(MISRA Compliance:2020 §4.2). +Each entry follows a fixed shape: the guideline and its category, the construct +that deviates, the scope the deviation covers, the engineering rationale, the +residual risk and how it is mitigated, and a named approval. ## Guideline text is not reproduced here @@ -37,6 +38,18 @@ understood the guideline and reasoned about the risk of departing from it, and a precise description of our own code demonstrates that better than a restated headline an assessor already has in front of them. +## Language edition for clause references + +Unless otherwise stated, all clause references are to ISO/IEC 9899:1999 (C99), +which is the edition SolidSyslog conforms to. Where a deviation concerns a code +path that only a later edition compiles, that entry names the applicable edition +and cites both. + +Paragraph numbering differs between editions — §6.7.2.1 renumbered when C11 +added anonymous structure and union members — so a C11 paragraph number read +against a C99 copy lands on the wrong sentence. That is why the edition is fixed +here rather than left to the reader. + --- ## D.001 — Rule 5.1 external identifier uniqueness relaxed to 63 characters @@ -96,8 +109,9 @@ distinctness would either collapse identifier pairs that read identically up to a trailing word (`SolidSyslogPlusTcpResolver_Create` vs `SolidSyslogPlusTcpResolver_Destroy`) into a single name, or force unidiomatic abbreviation throughout the public -API. Neither outcome serves clarity or MISRA's underlying intent -("the reader can tell two identifiers apart"); 63 characters does. +API. Neither outcome serves clarity, and neither preserves the +distinguishability the guideline exists to protect. Sixty-three characters +preserves both. 63 was chosen rather than "unlimited" so the project still names a concrete number that every targeted toolchain comfortably exceeds. It @@ -394,7 +408,7 @@ declares a storage buffer of arbitrary size (with a minimum enforced by bookkeeping fields at the start, payload bytes filling the rest. The flexible array member is C99's standard mechanism for exactly this -shape (§6.7.2.1 ¶18). The alternatives all regress: +shape (§6.7.2.1 ¶16). The alternatives all regress: | Alternative | Why rejected | |-------------|--------------| @@ -443,14 +457,15 @@ Two distinct site categories trigger this rule: } ``` - Per C11 §6.5.2.3 ¶3, the result of `->` is the type of the named + Per §6.5.2.3 ¶4, the result of `->` is the type of the named member; `config->buffer` evaluates to `struct SolidSyslogBuffer *` with no `const` qualifier on the pointed-to object. cppcheck-misra nonetheless flags the field access as a const-strip — it tracks the outer `const` on `*config` rather than the type of the member - expression. The accepted interpretation in the MISRA community is - that 11.8 applies to *pointer casts*, not to member-access yielding - a non-`const`-qualified pointer rvalue. + expression. No cast is performed at these sites, so the project + assesses them as a tool limitation rather than a genuine departure + from the guideline, and records them here so that the assessment is + visible rather than silent. The same pattern recurs in `SolidSyslogMessageFormatter_Format(const struct @@ -525,9 +540,9 @@ false-positive would either drop the outer `const` qualifier on introduce a no-op `const_cast`-style explicit cast that the tool would still flag. A site-local deviation is the honest record. -The two platform-API sites are the canonical "forced by an external -interface" pattern MISRA itself calls out in the deviation guidance for -rule 11.8. Both upstream declarations (Microsoft's `select()` timeout, +The two platform-API sites are the standard case of a const-correct interior +forced to strip qualification at a fixed third-party API boundary. Both +upstream declarations (Microsoft's `select()` timeout, lwIP's `pbuf::payload`) are fixed by their vendors; the SolidSyslog seam keeps the const-correctness contract on the caller's side of the boundary. @@ -672,11 +687,11 @@ Project owner — David Cozens. Recorded under cppcheck-misra interprets an anonymous `enum { ... };` declaration (no enum tag, no `typedef`) two ways: -- as a "tag declared but unused" (2.4) — the enumerators are used - as named constants but the enum type itself is never referenced; -- as a non-unique tag (5.7) — every anonymous `enum` shares the - same empty tag identifier, so the second and subsequent ones - collide. +- under 2.4 it reports the enum tag as unused — the enumerators are + used as named constants but the enum type itself is never referenced; +- under 5.7 it reports the tag as non-unique — every anonymous `enum` + shares the same empty tag identifier, so the second and subsequent + ones collide. Both findings originate from the same syntactic shape — the anonymous-`enum` named-constant idiom — and are covered by a single @@ -796,12 +811,20 @@ declarations in one translation unit are compatible, so no `__LINE__` pasting ### Rationale +`SOLIDSYSLOG_STATIC_ASSERT` selects one of three expansions on +`__cplusplus` / `__STDC_VERSION__`, and all three are compiled: C++ +`static_assert` in the CppUTest harnesses, C11 `_Static_assert` in the default +build (`CMAKE_C_STANDARD` is 11 unless overridden), and the C99 +negative-array-size fallback under the `c99` preset, which is the pre-release +check that the portable surface is still C99 — see +[local checks](local-checks.md). The deviation is confined to the two +string-literal forms; only they need the message stringified, and the C99 +fallback uses no preprocessor operator at all. + C++ `static_assert` and C11 `_Static_assert` are the standard compile-time -assertion primitives, and the project compiles at `--std=c11` (so the -`_Static_assert` branch is what every normal and CI build — including the -cppcheck-misra lane — compiles). Their message argument is a string literal, -and there is no way to convert an arbitrary identifier-shaped message into one -without `#`. The alternatives all regress: +assertion primitives for their editions. Their message argument is a string +literal, and there is no way to convert an arbitrary identifier-shaped message +into one without `#`. The alternatives all regress: | Alternative | Why rejected | |-------------|--------------| @@ -878,7 +901,7 @@ The alternatives all regress: | Alternative | Why rejected | |-------------|--------------| -| Inline `cppcheck-suppress misra-c2012-2.5` at each macro | Inline suppressions are weaker by MISRA Compliance:2020 §4.2 (rationale scattered, not centrally auditable). Project preference is structural deviations in this document. | +| Inline `cppcheck-suppress misra-c2012-2.5` at each macro | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | | Widen the cppcheck-misra scan to include `Tests/` | Tests are the Consistency-only tier per E10's tier model; running MISRA there is out of scope by design. | | Move the macros into `Core/Source/` | Public API by definition lives under `Core/Interface/`. Moving them would break the audience-segregated header layout. | @@ -960,7 +983,7 @@ Summary: | Alternative | Why rejected | |-------------|--------------| -| Inline `cppcheck-suppress misra-c2012-8.9` at the declaration | Inline suppressions are weaker by MISRA Compliance:2020 §4.2 (rationale scattered, not centrally auditable). Project preference is structural deviations in this document. | +| Inline `cppcheck-suppress misra-c2012-8.9` at the declaration | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | | Inline the `".log"` literal at both use sites | DRY violation for a single-source-of-truth on-disk constant. | | Promote the dependent enum entries to `static const size_t` | Verified to not satisfy 8.9; instead surfaces a second false positive on the new constant. | | Promote to `#define FILE_EXTENSION ".log"` | Introduces a string macro inconsistent with the file-scope-const pattern used elsewhere in storage code. | @@ -1024,10 +1047,10 @@ The alternatives all regress: |-------------|--------------| | Refactor `SolidSyslogStream::Send`/`Read` to use `unsigned char*` | Public-API ABI change that propagates to every Stream implementation (Posix TCP, Winsock TCP, FreeRTOS TCP, OpenSSL TLS, mbedTLS TLS, NullStream) and every Stream caller (`SolidSyslogStreamSender`). The `void*` byte-buffer contract is the conventional C idiom for transport interfaces and matches POSIX `send`/`recv`, OpenSSL `SSL_write`/`SSL_read`, etc. Changing it for the sake of one third-party API's typing choice is the wrong direction. | | Copy through an `unsigned char` scratch buffer per call | Runtime cost on the hot send/receive path; adds a fixed-size scratch or a stack-allocated VLA in a critical-path function. Defeats the zero-copy intent of the Stream contract. | -| Inline `cppcheck-suppress misra-c2012-11.5` at each site | Inline suppressions are weaker by MISRA Compliance:2020 §4.2 (rationale scattered, not centrally auditable). Project preference is structural deviations in this document. | +| Inline `cppcheck-suppress misra-c2012-11.5` at each site | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | The cast is well-defined: `unsigned char` may alias any object type -(C99 §6.5 ¶7), so reinterpreting a `void*` byte buffer as +(§6.5 ¶7), so reinterpreting a `void*` byte buffer as `unsigned char*` and back is a no-op at the abstract-machine level. ### Risk and mitigation From 34f510e89ba624b68d0f4df72d0cd4407586ae86 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 20:11:05 +0100 Subject: [PATCH 10/57] docs: S23.22 close the structural gaps in the MISRA deviation register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dates on every entry, taken from the issue each cites rather than invented: an issue link is traceable only while the tracker exists, and Compliance-style records carry raised and approved dates. D.002 had no Scope at all, on the largest deviation in the register — 80 line-specific suppressions across ten platform packs and Core. It now states them by tier, count and rule. D.011, D.012 and D.013 pre-authorised instances nobody had reviewed: a future sweep "extends the deviation to those files". Each now says a new instance is reviewed on its merits and either amends the entry or gets its own. D.002's alignment argument covered both sub-cases and was the wrong one for the vtable downcasts, where the guarantee is 6.7.2.1 paragraph 13 — a pointer to a struct converts to a pointer to its first member and back. Alignment is load-bearing only for the Formatter, whose storage is a caller-declared array. The lwIP const-strip cited "handoff design decision #1", which no reader can resolve; it now links the published PBUF_REF contract. Header gains four notes: the register is process evidence rather than a compliance submission, the MISRA trademark attribution, that misra_suppressions.txt is the authoritative instance trace, and that one person raises and approves every entry. D.014 was missing its separator, and its story numbers read backwards because they are numbered by epic; the dates now carry the order. Co-Authored-By: Claude Opus 5 (1M context) --- docs/misra-deviations.md | 140 ++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 39 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index c3162784..81f78331 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -5,6 +5,17 @@ adopts a curated subset of MISRA C:2012 rules per tier (see `docs/NAMING.md` for the tier model). This document records every deliberate deviation from a rule the project otherwise enforces. +This register is published as evidence of process, not as a compliance +submission. It exists so that an integrator building SolidSyslog into a +MISRA-constrained product can see where the code knowingly departs from a +guideline, and judge each departure against their own risk posture. It makes no +conformance claim on your behalf, and it is an input to your compliance +documentation rather than a substitute for it. + +MISRA and MISRA C are registered trade marks of The MISRA Consortium Limited, +used here for identification only. This project is neither endorsed by nor +affiliated with MISRA. + Each deviation is paired with a matching entry in `misra_suppressions.txt` (the cppcheck-misra input). The two files are complementary: @@ -13,15 +24,25 @@ Each deviation is paired with a matching entry in `misra_suppressions.txt` | `misra_suppressions.txt` | cppcheck-misra | Machine-readable suppressions per rule / file / line | | `docs/misra-deviations.md` | Reviewers, auditors, integrators | Why each deviation exists, with rationale, scope, approval | -Each entry in `misra_suppressions.txt` shall reference the section of -this document that authorises it. The suppressions file was populated +`misra_suppressions.txt` is the authoritative instance-level trace: its entries +are line-specific, so every individual site a deviation authorises appears there +by rule, file and line, and each block back-references the deviation in this +document that authorises it. A finding on a line not listed is not covered by +any deviation and surfaces in CI. The suppressions file was populated under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367) after the rule subset was curated; before then it carried only a header comment. Each entry follows a fixed shape: the guideline and its category, the construct that deviates, the scope the deviation covers, the engineering rationale, the -residual risk and how it is mitigated, and a named approval. +residual risk and how it is mitigated, and a named approval with its dates. + +Every deviation here is raised and approved by the same person. SolidSyslog is +developed by a one-person consultancy, so the project owner holds the designated +technical authority for these decisions; there is no second reviewer and the +record does not pretend otherwise. An integrator running their own compliance +process should re-review each deviation against their own risk posture rather +than inheriting this approval. ## Guideline text is not reproduced here @@ -141,8 +162,9 @@ identifiers (§5.2.4.1) — a single number applies project-wide. ### Approval -Project owner — David Cozens. Recorded as the founding entry in this -document under [S10.01](https://github.com/cososo-ltd/solid-syslog/issues/357). +Raised and approved 2026-05-14 by the project owner, David Cozens. Recorded as +the founding entry in this document under +[S10.01](https://github.com/cososo-ltd/solid-syslog/issues/357). --- @@ -234,6 +256,24 @@ byte-buffer reinterpretation that crosses the same advisory rule. The third-party API contract (the public `Send` / `SendTo` interface) is `void*` for opacity; the byte-level work needs a concrete unit type. +### Scope + +- **Strict tier** — `Core/Source/`: the `SelfFromBase` helpers on every vtable + class, and the Formatter storage cast of sub-case (b). 14 sites. +- **Pragmatic tier** — `Platform/*/Source/`: the same `SelfFromBase` shape in + each adapter, the per-platform Address downcasts, and the callback `void*` + casts of sub-case (c). 66 sites, across the Atomics, FatFs, FreeRtos, + LwipRaw, MbedTls, OpenSsl, PlusFat, PlusTcp, Posix and Windows packs. + +80 line-specific suppressions in total — 12 against rule 11.2, 57 against 11.3 +and 11 against 11.5. The deviation does not extend to `Tests/` or `Bdd/`, where +these rules are not enforced. + +A new class added to any vtable role inherits this shape, and its suppressions +belong in this block; adding them is a review step, not an automatic +consequence, because the reviewer has to confirm the new site really is the +`SelfFromBase` pattern and not a different conversion wearing the same name. + ### Rationale Every class with a Create/Destroy lifecycle uses the static pool @@ -264,10 +304,20 @@ opaque-type design). `mbedtls_ssl_set_bio(..., self, ...)`) is the same pointer that comes back — the library is a pass-through; the cast can only succeed against the type the wrapper passed in. -- **Alignment** — Storage types are declared as `intptr_t storage[N]` - (or a struct of the same shape), giving alignment at least as strict - as any pointer or scalar the impl contains. The cast is therefore - well-defined per §6.3.2.3. +- **Validity of the conversion, sub-cases (a) and (c)** — These do not + rest on an alignment argument at all. The public base struct is the + first member of the concrete struct, and §6.7.2.1 ¶13 guarantees that + a pointer to a structure object, suitably converted, points to its + initial member and back again. The address is the same address by + definition, so no alignment question arises. Sub-case (c) is the same + guarantee reached through the library's own `void*` round trip: the + pointer that comes back is the one that went out. +- **Alignment, sub-case (b) only** — The Formatter is the case where + alignment is the load-bearing argument, because its storage is a + caller-declared array rather than a struct whose first member is the + base. Storage is declared as `intptr_t storage[N]` (or a struct of the + same shape), giving alignment at least as strict as any pointer or + scalar the impl contains. - **Static analysis** — These rules are advisory (11.5) or required (11.2, 11.3). All current findings are suppressed via `misra_suppressions.txt` referencing this section. The pattern is @@ -275,7 +325,7 @@ opaque-type design). ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367); scope narrowed under [S11.11](https://github.com/cososo-ltd/solid-syslog/issues/414) once @@ -346,7 +396,7 @@ and in the definition. The repetition is the convention, not a defect. ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367). --- @@ -364,8 +414,9 @@ cppcheck-misra reports no 18.4 finding in `RecordStore.c`; the suppression was removed from `misra_suppressions.txt` at the same time. The entry is kept, rather than the number reused, so the register has no gaps and -a reader of an older revision can still resolve D.004. Approved by the project -owner — David Cozens. Recorded under +a reader of an older revision can still resolve D.004. Raised 2026-05-14 and +approved 2026-05-15 by the project owner, David Cozens, alongside the other +founding entries; retired 2026-05-23 under [#436](https://github.com/cososo-ltd/solid-syslog/pull/436). --- @@ -428,7 +479,7 @@ shape (§6.7.2.1 ¶16). The alternatives all regress: ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367). --- @@ -502,8 +553,9 @@ Two distinct site categories trigger this rule: `const void*` — the contract is read-only inside the library. lwIP's `struct pbuf::payload` is declared `void*` (no `const` variant in the lwIP headers); `udp_sendto` only reads the - payload (the PBUF_REF zero-copy contract — see handoff design - decision #1) but the field type does not encode that. Assigning + payload — that is the `PBUF_REF` zero-copy contract, set out under + [Datagram — pbuf strategy](integrating-lwip.md#datagram--pbuf-strategy) + — but the field type does not encode that. Assigning our `const void*` parameter to lwIP's `void*` field strips the qualifier at the platform-API boundary, same shape as the Winsock `select()` site above. Alternatives considered and @@ -560,7 +612,7 @@ boundary. ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367). --- @@ -615,7 +667,7 @@ unavoidable on this platform. ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367). --- @@ -670,7 +722,7 @@ use. The deviation is narrow and visible. ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367). --- @@ -769,7 +821,7 @@ would not be substitutable for any other type. ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recorded under [S10.06](https://github.com/cososo-ltd/solid-syslog/issues/367). --- @@ -847,7 +899,7 @@ been since C89; it is neither opaque nor novel. ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recorded under [S10.10](https://github.com/cososo-ltd/solid-syslog/issues/375). --- @@ -873,11 +925,12 @@ checker. `Core/Interface/SolidSyslogCircularBuffer.h` — one macro definition. -A future per-component sweep may surface similar findings on other -public API macros (per the tier model, MISRA enforcement does not cross -into `Tests/` or `Bdd/`). When that happens, the deviation extends to -those files; the rule still catches genuinely-unused macros inside the -scanned scope. +This entry authorises that one macro and no other. Per the tier model, MISRA +enforcement does not cross into `Tests/` or `Bdd/`, so a future sweep may +surface the same shape on another public API macro. That does not extend this +deviation automatically: each new instance is reviewed on its merits and either +amends this entry with the file named, or is raised as its own. Until then the +rule still catches genuinely unused macros inside the scanned scope. ### Rationale @@ -917,7 +970,7 @@ The alternatives all regress: ### Approval -Project owner — David Cozens. Recorded under +Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recorded under [S10.10](https://github.com/cososo-ltd/solid-syslog/issues/375). --- @@ -954,11 +1007,12 @@ function would break the enum's compile-time `sizeof()` evaluation. `Core/Source/SolidSyslogFileBlockDevice.c:20` — one declaration. -A future per-component sweep may surface this shape on other -file-scope `static const` objects whose identifier is used by a -file-scope enum's `sizeof()`/value initialiser and exactly one -function. When that happens, the deviation extends to those files; -the rule still catches genuinely single-function-scoped objects. +This entry authorises that one declaration and no other. A future sweep may +surface the same shape elsewhere — a file-scope `static const` whose identifier +is read by a file-scope enum initialiser and exactly one function. Each such +instance is reviewed on its merits and either amends this entry with the file +named, or is raised as its own; it is not covered by this record until that +happens. The rule still catches genuinely single-function-scoped objects. ### Rationale @@ -1000,7 +1054,7 @@ Summary: ### Approval -Project owner — David Cozens. Recorded under +Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded under [S10.18](https://github.com/cososo-ltd/solid-syslog/issues/430). --- @@ -1032,9 +1086,10 @@ Rule 11.5 fires on each such adapter cast. `Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c` — two sites (`MbedTlsStream_Send`, `MbedTlsStream_Read`). -The deviation extends to any future Stream / Datagram / hash / MAC -implementation that wraps a byte-typed third-party C API -(`unsigned char*` rather than `void*`). The OpenSSL adapter +A future Stream, Datagram, hash or MAC implementation wrapping a byte-typed +third-party C API (`unsigned char*` rather than `void*`) will meet the same +boundary, but is not covered by this record until reviewed and added to it — +or given its own entry. The OpenSSL adapter (`Platform/OpenSsl/Source/SolidSyslogTlsStream.c`) does not fall under this deviation — `SSL_write` / `SSL_read` take `void*` and so no cast is needed. @@ -1068,9 +1123,11 @@ The cast is well-defined: `unsigned char` may alias any object type ### Approval -Project owner — David Cozens. Recorded under +Raised and approved 2026-05-23 by the project owner, David Cozens. Recorded under [S10.20](https://github.com/cososo-ltd/solid-syslog/issues/437). +--- + ## D.014 — Rule 8.7: public-API `SolidSyslogErrorSource` objects (retired) **Retired in S12.26.** This deviation covered the crypto-policy @@ -1084,5 +1141,10 @@ exactly the resolution this deviation's "Risk and mitigation" anticipated. cppcheck-misra reports no 8.7 finding for any error source; the suppression lines were removed. -Recorded under [S17.02](https://github.com/cososo-ltd/solid-syslog/issues/493), -retired under [S12.26](https://github.com/cososo-ltd/solid-syslog/issues/507). +Raised and approved 2026-05-31 by the project owner, David Cozens, under +[S17.02](https://github.com/cososo-ltd/solid-syslog/issues/493); retired +2026-06-03 under +[S12.26](https://github.com/cososo-ltd/solid-syslog/issues/507). The story +numbers run backwards because they are numbered by epic rather than +chronologically — E12 was elaborated after E17 — so read the dates, not the +labels, for the order of events. From 12d9b2e083b3ff21ca963b56c99d2c0856b37cf1 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 20:14:23 +0100 Subject: [PATCH 11/57] docs: S23.22 correct the toolchain and suppression-mapping claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D.009 described the rule 5.7 suppression migration as half finished, so the record-to-suppression mapping read as ambiguous. It is finished: 44 of the 45 entries sit in the D.009 block and the one in D.003 is a struct tag, not an anonymous enum, so it belongs there. Both blocks carry 5.7 lines permanently and the rule deciding which is a standing convention, not a transitional state. D.005 claimed every toolchain supports flexible array members and that CI proves it on every push. MSVC reports C4200 for the construct and the build carries /wd4200 for exactly this reason — without it /WX would fail the Windows lane. IAR and Keil are not built at all. Say both. D.001 said MSVC 2015+ where D.005 said 2013+, and carried an IAR row whose claim could not be verified. The table now separates what CI proves from what rests on vendor documentation, and the two lead-in sentences that generalised over "every supported target" are reconciled with it. Co-Authored-By: Claude Opus 5 (1M context) --- docs/misra-deviations.md | 42 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 81f78331..eee99a39 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -109,16 +109,15 @@ tiers (rule 5.1 is not enforced there at all). ### Rationale The C99 31-character limit is a legacy linker artifact from the late -1980s. Every toolchain that SolidSyslog targets — hosted or embedded — -supports external identifiers well in excess of 63 significant -characters: +1980s. No toolchain SolidSyslog is built with imposes anything close to +it: | Toolchain | External identifier behaviour | |-----------|-------------------------------| | GCC (incl. `arm-none-eabi-gcc`) | No compiler-imposed limit; identifier length is delegated to the target's linker, and all characters are significant on every linker SolidSyslog targets (ld, gold, lld, link.exe). See GCC manual, "Implementation-defined behavior". | | Clang / LLVM (incl. Arm Compiler 6 / armclang) | Same rule as GCC for external identifiers — no compiler-imposed limit. | -| MSVC 2015+ | Documented maximum identifier length **2,047 characters** ([Microsoft Learn — C Identifiers](https://learn.microsoft.com/en-us/cpp/c-language/c-identifiers)). | -| IAR Embedded Workbench | C/C++ compiler reference manual documents an identifier limit well above 63 characters in every currently shipping version (verify on the target SKU's compiler reference for ports of SolidSyslog to non-standard SKUs). | +| MSVC | Documented maximum identifier length **2,047 characters** ([Microsoft Learn — C Identifiers](https://learn.microsoft.com/en-us/cpp/c-language/c-identifiers)). CI builds with the `windows-latest` toolchain; older MSVC releases are not tested. | +| IAR Embedded Workbench, Keil ARMCC 6 | Not built in CI. Identifier limits are documented per compiler SKU; confirm against your SKU's reference at port time. | The Tier 1 naming scheme in `docs/NAMING.md` (form `SolidSyslogClass_Function`) routinely produces identifiers in the @@ -142,9 +141,9 @@ identifiers (§5.2.4.1) — a single number applies project-wide. ### Risk and mitigation - **Portability** — Constrained to toolchains that support ≥ 63 - significant characters in external identifiers. The table above - covers every supported target; adding a new target requires verifying - this constraint. + significant characters in external identifiers. The table above states + which of those are proven by CI and which rest on documentation; + adding a target requires verifying the constraint on it. - **Tooling** — cppcheck-misra applies its default 31-character window for rule 5.1. The deviation only matters when a real collision would resolve at 63 characters but not at 31 — at @@ -469,9 +468,14 @@ shape (§6.7.2.1 ¶16). The alternatives all regress: ### Risk and mitigation -- **Compiler support.** All target toolchains support C99 flexible - array members (gcc, clang, MSVC 2013+, IAR, Keil ARMCC 6). The - project's CI builds prove this on every push. +- **Compiler support.** GCC, Clang and the ARM cross-compilers accept + the construct as the C99 feature it is, and CI compiles it on every + push. MSVC compiles it too, but reports C4200 — it treats a trailing + unsized array as a nonstandard extension — so the build carries + `/wd4200` for this construct specifically (see `CMakeLists.txt`); + without it, `/WX` would fail the Windows lane. IAR and Keil ARMCC 6 + are not built in CI, so support there rests on their documentation + and is confirmed at port time rather than per push. - **Allocation surprise.** The `_Static_assert` accompanying each flexible-array struct pins the storage-type-to-impl-type relationship at build time; an undersized storage allocation is a @@ -769,15 +773,13 @@ site would add visual noise next to a project-wide intentional idiom — listing them in `misra_suppressions.txt` under this deviation keeps the source clean. -**Suppression-file layout.** 5.7 anonymous-enum suppressions -were historically grouped in the D.003 block (struct-tag -repetition) because D.003 was the original 5.7 deviation; per-group -conformance stories migrate them into the D.009 block as they -review their cluster (S10.16 moved the Senders-cluster ones). -Until every group has run, both blocks contain rule 5.7 lines -and the deviation that authorises each is determined by the kind -of identifier the cppcheck-misra finding lands on (struct tag → -D.003; anonymous enum → D.009). +**Suppression-file layout.** Two deviations authorise rule 5.7 findings, and +which one applies is decided by the identifier the finding lands on: a repeated +struct tag is D.003, an anonymous enum is D.009. That is a standing convention, +not a transitional state — both kinds of finding exist permanently in this +codebase, so both blocks permanently carry rule 5.7 lines. Each block in +`misra_suppressions.txt` is headed by the deviation that authorises its entries, +so the mapping is explicit per line rather than inferred. ### Scope From ae21c989f74167b4dbb324a8019304509d84ea05 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 20:34:03 +0100 Subject: [PATCH 12/57] docs: S23.22 fix the 11.8 reasoning and separate deviations from tool findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D.006 cited 6.5.2.3 paragraph 4 for half of what it says. The second half — that a pointer to a qualified type yields the so-qualified member type — reads against our conclusion until you notice the member is itself a pointer, so the qualification lands on the pointer and is discarded when it is copied to a parameter. Nothing is stripped from the pointed-to type. The paragraph is our strongest support once read whole, and it now says so. Not every entry is a deviation. Five are cases where the code complies and cppcheck-misra reports anyway. Each entry now classifies itself, and the header explains why both belong in one register: the suppression exists either way. Eight genuine departures, not fourteen. D.002 reported twelve rule 11.2 suppressions while describing only the Formatter as triggering it; eleven are the Address downcasts, which fire 11.2 because the public handle is deliberately an incomplete type. Said so. D.009 said "approximately 31" anonymous enums where the tree has 45 across 44 files. C99 as conformance baseline is now distinguished from C11 as default build configuration. Smaller fixes to D.001, D.011 and D.014. Co-Authored-By: Claude Opus 5 (1M context) --- docs/misra-deviations.md | 82 +++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index eee99a39..35730bc8 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -3,7 +3,16 @@ SolidSyslog is MISRA-informed, not certified-compliant. The project adopts a curated subset of MISRA C:2012 rules per tier (see `docs/NAMING.md` for the tier model). This document records every -deliberate deviation from a rule the project otherwise enforces. +deliberate deviation from a rule the project otherwise enforces, and every +suppression that is not a deviation at all. + +Those are two different things and each entry says which it is. A **deviation** +is a departure: the code does something the guideline forbids, and the entry +justifies it. A **tool limitation** is not: the code complies, cppcheck-misra +reports a finding anyway, and the entry explains why the report is wrong. Both +are recorded here because a suppression exists either way, and the authorisation +for a suppression belongs in one place. Counting the entries is therefore not a +count of departures — eight of them are. This register is published as evidence of process, not as a compliance submission. It exists so that an integrator building SolidSyslog into a @@ -61,10 +70,13 @@ headline an assessor already has in front of them. ## Language edition for clause references -Unless otherwise stated, all clause references are to ISO/IEC 9899:1999 (C99), -which is the edition SolidSyslog conforms to. Where a deviation concerns a code -path that only a later edition compiles, that entry names the applicable edition -and cites both. +Unless otherwise stated, all clause references are to ISO/IEC 9899:1999 (C99). +C99 is the conformance baseline — the edition the source is written against and +claims to be valid under. It is not the same thing as the build configuration: +the default build selects C11 (`CMAKE_C_STANDARD` is 11 unless overridden), and +the `c99` preset verifies the baseline. Where a deviation concerns a code path +that only a later edition compiles, that entry names the applicable edition and +cites both. Paragraph numbering differs between editions — §6.7.2.1 renumbered when C11 added anonymous structure and union members — so a C11 paragraph number read @@ -79,6 +91,7 @@ here rather than left to the reader. **MISRA C:2012 Rule 5.1** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -109,8 +122,7 @@ tiers (rule 5.1 is not enforced there at all). ### Rationale The C99 31-character limit is a legacy linker artifact from the late -1980s. No toolchain SolidSyslog is built with imposes anything close to -it: +1980s. No toolchain SolidSyslog targets imposes anything close to it: | Toolchain | External identifier behaviour | |-----------|-------------------------------| @@ -176,6 +188,7 @@ the founding entry in this document under - **MISRA C:2012 Rule 11.5** — Advisory. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -213,7 +226,10 @@ public type, fully defined per platform as `SolidSyslogAddress_AsConstFreertosSockaddr`) plus a `HandleFromIndex(size_t)` helper in `*AddressStatic.c` that converts a pool slot index back to the public handle type. Rule 11.3 fires on -every such cast. +every such cast, and rule 11.2 fires alongside it here but not on the +other vtable classes: `struct SolidSyslogAddress` is deliberately an +incomplete type in the public header, so these conversions involve a +pointer to an incomplete type as well as to a different object type. This is the standard OO-in-C "interface pointer back to derived implementation" cast. @@ -343,6 +359,7 @@ caller-supplied-storage exception. **MISRA C:2012 Rule 5.7** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Tool limitation — the code complies; cppcheck-misra reports a finding regardless. cppcheck-misra interprets Rule 5.7 strictly — every repeated `struct X` declaration counts as a non-unique tag, including forward declarations @@ -426,6 +443,7 @@ founding entries; retired 2026-05-23 under **MISRA C:2012 Rule 18.7** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -494,6 +512,8 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 11.8** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** both kinds, which is why they share an entry — category 1 +below is a tool limitation, category 2 is a genuine deviation. ### Deviation @@ -512,15 +532,23 @@ Two distinct site categories trigger this rule: } ``` - Per §6.5.2.3 ¶4, the result of `->` is the type of the named - member; `config->buffer` evaluates to `struct SolidSyslogBuffer *` - with no `const` qualifier on the pointed-to object. cppcheck-misra - nonetheless flags the field access as a const-strip — it tracks the + §6.5.2.3 ¶4 governs this, and it has to be read whole. The result of + `->` is the named member's type; and where the left operand is a + pointer to a qualified type, the result carries the so-qualified + version of that type. That second clause looks at first like it works + against us. It does not. The member's type here is + `struct SolidSyslogBuffer *`, so the so-qualified version is + `struct SolidSyslogBuffer * const` — the qualification attaches to the + pointer, not to the object the pointer designates. Passing it by value + to `InstallBuffer` copies the pointer, and a top-level qualifier on a + copied value is discarded. + + So no qualification is removed from the pointed-to type, and no cast + is performed. cppcheck-misra flags the access anyway, tracking the outer `const` on `*config` rather than the type of the member - expression. No cast is performed at these sites, so the project - assesses them as a tool limitation rather than a genuine departure - from the guideline, and records them here so that the assessment is - visible rather than silent. + expression. The project assesses this as a tool limitation rather + than a departure from the guideline, and records it here so the + assessment is visible rather than silent. The same pattern recurs in `SolidSyslogMessageFormatter_Format(const struct @@ -627,6 +655,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 21.10** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Tool limitation — the code complies; cppcheck-misra reports a finding regardless. cppcheck-misra also raises this rule for `` inclusion, which is what brings the construct below into scope. @@ -682,6 +711,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 21.6** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -739,6 +769,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor - **MISRA C:2012 Rule 5.7** — Required. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Tool limitation — the code complies; cppcheck-misra reports a finding regardless. cppcheck-misra interprets an anonymous `enum { ... };` declaration (no enum tag, no `typedef`) two ways: @@ -767,8 +798,8 @@ enum }; ``` -There are approximately 31 such declarations across `Core/` and -`Platform/*/Source/`. Adding inline-suppress comments at every +There are 45 such declarations across 44 files in `Core/` and +`Platform/`. Adding inline-suppress comments at every site would add visual noise next to a project-wide intentional idiom — listing them in `misra_suppressions.txt` under this deviation keeps the source clean. @@ -834,6 +865,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 20.10** — Advisory. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -912,6 +944,7 @@ Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 2.5** — Advisory. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -936,7 +969,7 @@ rule still catches genuinely unused macros inside the scanned scope. ### Rationale -The macros *are* used by integrators in `Tests/` and `Bdd/Targets/`. +The macro *is* used by integrators in `Tests/` and `Bdd/Targets/`. Verified by `grep` over the tree: ```text @@ -956,9 +989,9 @@ The alternatives all regress: | Alternative | Why rejected | |-------------|--------------| -| Inline `cppcheck-suppress misra-c2012-2.5` at each macro | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | +| Inline `cppcheck-suppress misra-c2012-2.5` at the macro | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | | Widen the cppcheck-misra scan to include `Tests/` | Tests are the Consistency-only tier per E10's tier model; running MISRA there is out of scope by design. | -| Move the macros into `Core/Source/` | Public API by definition lives under `Core/Interface/`. Moving them would break the audience-segregated header layout. | +| Move the macro into `Core/Source/` | Public API by definition lives under `Core/Interface/`. Moving it would break the audience-segregated header layout. | ### Risk and mitigation @@ -983,6 +1016,7 @@ Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 8.9** — Advisory. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Tool limitation — the code complies; cppcheck-misra reports a finding regardless. ### Deviation @@ -1067,6 +1101,7 @@ Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded unde **MISRA C:2012 Rule 11.5** — Advisory. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). +**Classification:** Deviation — the code departs from the guideline. ### Deviation @@ -1138,8 +1173,9 @@ Raised and approved 2026-05-23 by the project owner, David Cozens. Recorded unde a single translation unit. S12.26 decoupled error text from the library (deleting the `*Messages.c` message tables) and unwound the `_Report` wrapper, so each source is now defined in its class's vtable TU and referenced from both -that TU's emit sites and its `*Static.c` lifecycle code, genuinely cross-TU, -exactly the resolution this deviation's "Risk and mitigation" anticipated. +that TU's emit sites and its `*Static.c` lifecycle code, genuinely cross-TU — +which is the resolution the entry's risk analysis anticipated before it was +collapsed to this note; see the revision prior to retirement for that text. cppcheck-misra reports no 8.7 finding for any error source; the suppression lines were removed. From f8a86867c86e15a2e7f1ffb0a9bda0090191858b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 20:39:11 +0100 Subject: [PATCH 13/57] docs: S23.22 anchor the register's shape to MISRA Compliance:2020 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier pass removed every reference to Compliance:2020 because three of them supported a claim the document does not make. That took out the one accurate reference with them: the entries really do follow its deviation record structure, and saying so gives the register external structural authority rather than a shape we appear to have invented. Restored with the citation corrected — Section 4 for the deviation process, Appendix B for the example record — and framed as voluntary, since it is mandatory only from MISRA C:2023. Co-Authored-By: Claude Opus 5 (1M context) --- docs/misra-deviations.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 35730bc8..74cdece8 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -44,7 +44,10 @@ header comment. Each entry follows a fixed shape: the guideline and its category, the construct that deviates, the scope the deviation covers, the engineering rationale, the -residual risk and how it is mitigated, and a named approval with its dates. +residual risk and how it is mitigated, and a named approval with its dates. The +register follows the deviation record structure of MISRA Compliance:2020 +(Section 4, example record at Appendix B), adopted voluntarily; it is mandatory +only from MISRA C:2023 onward. Every deviation here is raised and approved by the same person. SolidSyslog is developed by a one-person consultancy, so the project owner holds the designated From d1bcc6c8318674ccd277652ed20e6c14207324d5 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 6 Aug 2026 20:57:14 +0100 Subject: [PATCH 14/57] docs: S23.22 reclassify D.011 and settle the deviation/limitation wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D.011 failed the test the header had just introduced. The macro is used — by four named consumers the entry itself lists — and cppcheck-misra cannot see them because they sit outside the scanned tier. That the invisibility comes from the scan configuration rather than from a tracker gap is a property of the scan, not of the code, so it is a tool limitation on the same reasoning as D.012. Seven departures, not eight. Every entry's body section is now Construct rather than Deviation: it describes the code either way, and a section headed Deviation inside an entry classified Tool limitation contradicted itself. D.006's rationale no longer calls its tool-limitation half a deviation, and the header says the word is used generically where the distinction does not matter. Co-Authored-By: Claude Opus 5 (1M context) --- docs/misra-deviations.md | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 74cdece8..20d0d04e 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -12,7 +12,9 @@ justifies it. A **tool limitation** is not: the code complies, cppcheck-misra reports a finding anyway, and the entry explains why the report is wrong. Both are recorded here because a suppression exists either way, and the authorisation for a suppression belongs in one place. Counting the entries is therefore not a -count of departures — eight of them are. +count of departures: seven of the entries record a departure. Where the +distinction does not matter, the words *deviation* and *entry* are used +interchangeably below. This register is published as evidence of process, not as a compliance submission. It exists so that an integrator building SolidSyslog into a @@ -43,7 +45,7 @@ after the rule subset was curated; before then it carried only a header comment. Each entry follows a fixed shape: the guideline and its category, the construct -that deviates, the scope the deviation covers, the engineering rationale, the +at issue, the scope the entry covers, the engineering rationale, the residual risk and how it is mitigated, and a named approval with its dates. The register follows the deviation record structure of MISRA Compliance:2020 (Section 4, example record at Appendix B), adopted voluntarily; it is mandatory @@ -59,7 +61,8 @@ than inheriting this approval. ## Guideline text is not reproduced here Each entry identifies its guideline by number and category and then describes -**the construct in SolidSyslog that deviates** — not what the guideline says. +**the construct in SolidSyslog that the finding lands on** — not what the +guideline says. MISRA C:2012 is copyrighted and not redistributable, so its rule text, amplification and examples are omitted deliberately rather than by oversight. Read them in your own licensed copy, available from @@ -96,7 +99,7 @@ here rather than left to the reader. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Deviation — the code departs from the guideline. -### Deviation +### Construct SolidSyslog requires external identifiers to be distinct within their first 63 characters rather than their first 31. @@ -193,7 +196,7 @@ the founding entry in this document under **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Deviation — the code departs from the guideline. -### Deviation +### Construct SolidSyslog accepts two structural pointer conversions that are identified in code as `SelfFromBase` (vtable) or `(struct X*) storage` @@ -368,7 +371,7 @@ cppcheck-misra interprets Rule 5.7 strictly — every repeated `struct X` declaration counts as a non-unique tag, including forward declarations in headers and the matching definition in source. -### Deviation +### Construct SolidSyslog uses `struct SolidSyslogX` directly throughout the public API and source rather than typedef'ing it (see `docs/NAMING.md`, Tier 1 @@ -448,7 +451,7 @@ founding entries; retired 2026-05-23 under **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Deviation — the code departs from the guideline. -### Deviation +### Construct `struct SolidSyslogFormatter` ends with a flexible array member that holds the caller-supplied backing storage: @@ -518,7 +521,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **Classification:** both kinds, which is why they share an entry — category 1 below is a tool limitation, category 2 is a genuine deviation. -### Deviation +### Construct Two distinct site categories trigger this rule: @@ -625,7 +628,8 @@ standard; reorganising the code to avoid the cppcheck-misra false-positive would either drop the outer `const` qualifier on `*config` / `*blockSequence` / `*config` (the wrong direction) or introduce a no-op `const_cast`-style explicit cast that the tool would -still flag. A site-local deviation is the honest record. +still flag. Recording the finding here, with the reasoning, is the honest +alternative to bending the code around a tool. The two platform-API sites are the standard case of a const-correct interior forced to strip qualification at a fixed third-party API boundary. Both @@ -663,7 +667,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor cppcheck-misra also raises this rule for `` inclusion, which is what brings the construct below into scope. -### Deviation +### Construct Three POSIX platform sources include `` for `struct timespec` and `clock_gettime`/`nanosleep`: @@ -716,7 +720,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Deviation — the code departs from the guideline. -### Deviation +### Construct `Platform/Windows/Source/SolidSyslogWindowsFile.c` includes `` solely to obtain the `SEEK_SET` and `SEEK_END` constants used by @@ -787,7 +791,7 @@ Both findings originate from the same syntactic shape — the anonymous-`enum` named-constant idiom — and are covered by a single deviation here. -### Deviation +### Construct SolidSyslog uses the anonymous-`enum` idiom across the codebase as a portable mechanism for declaring named integer constants in @@ -870,7 +874,7 @@ Raised 2026-05-14, approved 2026-05-15 by the project owner, David Cozens. Recor **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Deviation — the code departs from the guideline. -### Deviation +### Construct `Core/Source/SolidSyslogMacros.h` defines `SOLIDSYSLOG_STATIC_ASSERT`. The native C++/C11 expansions need a string-literal message, so the macro @@ -947,9 +951,9 @@ Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recor **MISRA C:2012 Rule 2.5** — Advisory. **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). -**Classification:** Deviation — the code departs from the guideline. +**Classification:** Tool limitation — the code complies; cppcheck-misra reports a finding regardless. -### Deviation +### Construct `Core/Interface/SolidSyslogCircularBuffer.h` declares one function-like macro — `SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES` — that integrator code @@ -1021,7 +1025,7 @@ Raised 2026-05-15, approved 2026-05-16 by the project owner, David Cozens. Recor **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Tool limitation — the code complies; cppcheck-misra reports a finding regardless. -### Deviation +### Construct `Core/Source/SolidSyslogFileBlockDevice.c:20` declares `static const char FILE_EXTENSION[] = ".log"`. The constant is the @@ -1106,7 +1110,7 @@ Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded unde **Rule text:** not reproduced (see [above](#guideline-text-is-not-reproduced-here)). **Classification:** Deviation — the code departs from the guideline. -### Deviation +### Construct `SolidSyslogStream::Send` takes `const void*` and `SolidSyslogStream::Read` takes `void*` — the project-wide byte-buffer contract used by every From 93d3e8c2ec51b94115d9ec97f8d285de0d407434 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 06:26:03 +0100 Subject: [PATCH 15/57] docs: S23.22 record the C99 lane now that it gates nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The c99 preset landed as a per-PR lane in #713, so the register's two present-tense claims that it is a pre-release check are stale. Both now name build-linux-c99, and the language-edition section adds what the lane actually proves: the claim is about Core, built alone at strict -std=c99, with the platform packs a best-effort drift check. docs/ci.md gains the lane, and its branch-protection paragraph is corrected. It said every job but deploy-docs-pages is required; the new lane is not, so a change breaking the C99 baseline can still merge with it red. Thirty of thirty-two, and the exception is named. local-checks.md and builds.md needed nothing — #713 moved that material itself. Co-Authored-By: Claude Opus 5 (1M context) --- docs/ci.md | 13 +++++++++---- docs/misra-deviations.md | 10 ++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/ci.md b/docs/ci.md index 6fd97c9f..fd5dca64 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -28,6 +28,7 @@ without renaming what's already there. | `integration-linux-openssl` | `debug` | Runs the in-process TLS integration tests against libssl (no network oracle) | | `integration-linux-mbedtls` | `debug` | The same integration tests against Mbed TLS, exercising `SolidSyslogMbedTlsStream` and the Mbed TLS security policies | | `integration-windows-openssl` | `msvc-debug` | Same TLS integration tests on `windows-latest` against libssl from vcpkg | +| `build-linux-c99` | `c99`, `c99-platforms` | Builds Core alone at strict `-std=c99` (`CMAKE_C_EXTENSIONS=OFF`, no tests), then the POSIX and OpenSSL packs at C99 as a drift check. Proves the C99 conformance claim per PR. **Not** a required check | | `build-linux-tunable-override` | `tunable-override-debug` | Builds against a user tunables header to prove `SOLIDSYSLOG_USER_TUNABLES_FILE` overrides the defaults | | `bdd-linux-syslog-ng` | — | End-to-end BDD test via Docker Compose (`syslog-ng-linux` + `behave-linux`), Linux runner | | `bdd-windows-otel` | — | Windows-eligible BDD scenarios driven against an OTel Collector oracle | @@ -44,10 +45,14 @@ without renaming what's already there. ## Branch protection -Every job in `ci.yml` is a required status check except `deploy-docs-pages`, which -only runs on `main`, and so are the two contexts code scanning contributes. A PR -cannot be merged unless all checks pass. Direct pushes to `main` are blocked. Squash -merge only. +Thirty of the thirty-two jobs in `ci.yml` are required status checks, as are the two +contexts code scanning contributes — `analyze-codeql` and `CodeQL`. A PR cannot be +merged unless all of them pass. Direct pushes to `main` are blocked. Squash merge only. + +The two that are not required are `deploy-docs-pages`, which only runs on `main`, and +`build-linux-c99`, which runs on every pull request but does not yet gate one. Until +its context is added to the required list, a change that breaks the C99 baseline can +merge with the lane red. Two qualifications on what "required" buys. The `analyze-iwyu*` lanes run `continue-on-error`, so they are required contexts that report success whatever IWYU diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 20d0d04e..21643645 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -80,7 +80,10 @@ Unless otherwise stated, all clause references are to ISO/IEC 9899:1999 (C99). C99 is the conformance baseline — the edition the source is written against and claims to be valid under. It is not the same thing as the build configuration: the default build selects C11 (`CMAKE_C_STANDARD` is 11 unless overridden), and -the `c99` preset verifies the baseline. Where a deviation concerns a code path +the `build-linux-c99` lane verifies the baseline on every pull request. The claim +is about `Core/`: the lane builds Core alone at strict `-std=c99`, and the +platform packs are a best-effort drift check rather than a guarantee — see +[builds.md](builds.md#c99-portability--c99). Where a deviation concerns a code path that only a later edition compiles, that entry names the applicable edition and cites both. @@ -908,9 +911,8 @@ declarations in one translation unit are compatible, so no `__LINE__` pasting `__cplusplus` / `__STDC_VERSION__`, and all three are compiled: C++ `static_assert` in the CppUTest harnesses, C11 `_Static_assert` in the default build (`CMAKE_C_STANDARD` is 11 unless overridden), and the C99 -negative-array-size fallback under the `c99` preset, which is the pre-release -check that the portable surface is still C99 — see -[local checks](local-checks.md). The deviation is confined to the two +negative-array-size fallback in the `build-linux-c99` lane, which compiles +`Core/` at strict `-std=c99` on every pull request. The deviation is confined to the two string-literal forms; only they need the message stringified, and the C99 fallback uses no preprocessor operator at all. From d770610e24abae6ed57bfce11069939a4d71a075 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 06:40:28 +0100 Subject: [PATCH 16/57] docs: S23.22 make the C99 lane a required check and say so MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build-linux-c99 was running on every pull request without gating one, so a change breaking the C99 baseline could merge with the lane red. Its context is now on the required list — 33 contexts, verified against branch protection — leaving deploy-docs-pages as the only job in ci.yml that is not required, because it only runs on main. docs/ci.md and CLAUDE.md's required-checks list both updated; the CLAUDE.md list now matches branch protection exactly, in both directions. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- docs/ci.md | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 060ce8ce..04daf592 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ what matters — it becomes the permanent commit message on `main` on squash mer **Branch protection rules (configured on GitHub):** - Direct pushes to `main` are blocked -- PRs require all status checks to pass before merging: CodeQL, analyze-codeql, analyze-cppcheck, analyze-format, analyze-iwyu, analyze-iwyu-freertos-lwip, analyze-iwyu-freertos-plustcp, analyze-markdown, analyze-tidy, analyze-tidy-freertos-lwip, analyze-tidy-freertos-plustcp, bdd-freertos-qemu-lwip, bdd-freertos-qemu-plustcp, bdd-linux-syslog-ng, bdd-windows-otel, build-freertos-host-tdd-plustcp, build-freertos-target-lwip, build-freertos-target-plustcp, build-linux-clang, build-linux-gcc, build-linux-tunable-override, build-windows-msvc, consumer-smoke-freertos-cross, consumer-smoke-linux, coverage-linux-gcc, docs-build, integration-linux-mbedtls, integration-linux-openssl, integration-windows-openssl, sanitize-linux-gcc, summary, verify-manifest +- PRs require all status checks to pass before merging: CodeQL, analyze-codeql, analyze-cppcheck, analyze-format, analyze-iwyu, analyze-iwyu-freertos-lwip, analyze-iwyu-freertos-plustcp, analyze-markdown, analyze-tidy, analyze-tidy-freertos-lwip, analyze-tidy-freertos-plustcp, bdd-freertos-qemu-lwip, bdd-freertos-qemu-plustcp, bdd-linux-syslog-ng, bdd-windows-otel, build-freertos-host-tdd-plustcp, build-freertos-target-lwip, build-freertos-target-plustcp, build-linux-c99, build-linux-clang, build-linux-gcc, build-linux-tunable-override, build-windows-msvc, consumer-smoke-freertos-cross, consumer-smoke-linux, coverage-linux-gcc, docs-build, integration-linux-mbedtls, integration-linux-openssl, integration-windows-openssl, sanitize-linux-gcc, summary, verify-manifest - The `analyze-iwyu*` lanes run `continue-on-error: true` — they are required contexts but advisory in substance, so they report success whatever IWYU finds - Code scanning contributes two contexts and both are required. `analyze-codeql` is the Actions job in `codeql.yml`, and proves the analysis ran; `CodeQL` is the code-scanning results check, and is the one that fails when a PR introduces a new alert. Requiring only the job would let a PR add findings and still merge green - Feeding the `summary` aggregator does **not** make a lane blocking. `summary` is declared `if: always()` and asserts nothing about `needs.*.result`, so a new lane gates merges only once its own context is added to the required list above diff --git a/docs/ci.md b/docs/ci.md index fd5dca64..5a99779d 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -28,7 +28,7 @@ without renaming what's already there. | `integration-linux-openssl` | `debug` | Runs the in-process TLS integration tests against libssl (no network oracle) | | `integration-linux-mbedtls` | `debug` | The same integration tests against Mbed TLS, exercising `SolidSyslogMbedTlsStream` and the Mbed TLS security policies | | `integration-windows-openssl` | `msvc-debug` | Same TLS integration tests on `windows-latest` against libssl from vcpkg | -| `build-linux-c99` | `c99`, `c99-platforms` | Builds Core alone at strict `-std=c99` (`CMAKE_C_EXTENSIONS=OFF`, no tests), then the POSIX and OpenSSL packs at C99 as a drift check. Proves the C99 conformance claim per PR. **Not** a required check | +| `build-linux-c99` | `c99`, `c99-platforms` | Builds Core alone at strict `-std=c99` (`CMAKE_C_EXTENSIONS=OFF`, no tests), then the POSIX and OpenSSL packs at C99 as a drift check. Proves the C99 conformance claim per PR | | `build-linux-tunable-override` | `tunable-override-debug` | Builds against a user tunables header to prove `SOLIDSYSLOG_USER_TUNABLES_FILE` overrides the defaults | | `bdd-linux-syslog-ng` | — | End-to-end BDD test via Docker Compose (`syslog-ng-linux` + `behave-linux`), Linux runner | | `bdd-windows-otel` | — | Windows-eligible BDD scenarios driven against an OTel Collector oracle | @@ -45,14 +45,10 @@ without renaming what's already there. ## Branch protection -Thirty of the thirty-two jobs in `ci.yml` are required status checks, as are the two -contexts code scanning contributes — `analyze-codeql` and `CodeQL`. A PR cannot be -merged unless all of them pass. Direct pushes to `main` are blocked. Squash merge only. - -The two that are not required are `deploy-docs-pages`, which only runs on `main`, and -`build-linux-c99`, which runs on every pull request but does not yet gate one. Until -its context is added to the required list, a change that breaks the C99 baseline can -merge with the lane red. +Every job in `ci.yml` is a required status check except `deploy-docs-pages`, which +only runs on `main`, and so are the two contexts code scanning contributes — +`analyze-codeql` and `CodeQL`. That is 33 required contexts. A PR cannot be merged +unless all of them pass. Direct pushes to `main` are blocked. Squash merge only. Two qualifications on what "required" buys. The `analyze-iwyu*` lanes run `continue-on-error`, so they are required contexts that report success whatever IWYU From 273f8cbb7899354030f6347e18c2fb7bf595f5ae Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 06:42:30 +0100 Subject: [PATCH 17/57] docs: S23.22 require and document the C89 public-header lane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build-linux-c89-headers landed in #716 without gating a PR. Its context is now required — 34 contexts, verified against branch protection in both directions — so deploy-docs-pages remains the only job in ci.yml that is not required, because it only runs on main. docs/ci.md gains the lane, including what it proves beyond the language level: each header is compiled alone, so one that needs a companion included first fails here too. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- docs/ci.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 04daf592..e84d89ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ what matters — it becomes the permanent commit message on `main` on squash mer **Branch protection rules (configured on GitHub):** - Direct pushes to `main` are blocked -- PRs require all status checks to pass before merging: CodeQL, analyze-codeql, analyze-cppcheck, analyze-format, analyze-iwyu, analyze-iwyu-freertos-lwip, analyze-iwyu-freertos-plustcp, analyze-markdown, analyze-tidy, analyze-tidy-freertos-lwip, analyze-tidy-freertos-plustcp, bdd-freertos-qemu-lwip, bdd-freertos-qemu-plustcp, bdd-linux-syslog-ng, bdd-windows-otel, build-freertos-host-tdd-plustcp, build-freertos-target-lwip, build-freertos-target-plustcp, build-linux-c99, build-linux-clang, build-linux-gcc, build-linux-tunable-override, build-windows-msvc, consumer-smoke-freertos-cross, consumer-smoke-linux, coverage-linux-gcc, docs-build, integration-linux-mbedtls, integration-linux-openssl, integration-windows-openssl, sanitize-linux-gcc, summary, verify-manifest +- PRs require all status checks to pass before merging: CodeQL, analyze-codeql, analyze-cppcheck, analyze-format, analyze-iwyu, analyze-iwyu-freertos-lwip, analyze-iwyu-freertos-plustcp, analyze-markdown, analyze-tidy, analyze-tidy-freertos-lwip, analyze-tidy-freertos-plustcp, bdd-freertos-qemu-lwip, bdd-freertos-qemu-plustcp, bdd-linux-syslog-ng, bdd-windows-otel, build-freertos-host-tdd-plustcp, build-freertos-target-lwip, build-freertos-target-plustcp, build-linux-c89-headers, build-linux-c99, build-linux-clang, build-linux-gcc, build-linux-tunable-override, build-windows-msvc, consumer-smoke-freertos-cross, consumer-smoke-linux, coverage-linux-gcc, docs-build, integration-linux-mbedtls, integration-linux-openssl, integration-windows-openssl, sanitize-linux-gcc, summary, verify-manifest - The `analyze-iwyu*` lanes run `continue-on-error: true` — they are required contexts but advisory in substance, so they report success whatever IWYU finds - Code scanning contributes two contexts and both are required. `analyze-codeql` is the Actions job in `codeql.yml`, and proves the analysis ran; `CodeQL` is the code-scanning results check, and is the one that fails when a PR introduces a new alert. Requiring only the job would let a PR add findings and still merge green - Feeding the `summary` aggregator does **not** make a lane blocking. `summary` is declared `if: always()` and asserts nothing about `needs.*.result`, so a new lane gates merges only once its own context is added to the required list above diff --git a/docs/ci.md b/docs/ci.md index 5a99779d..d294f4e7 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -28,6 +28,7 @@ without renaming what's already there. | `integration-linux-openssl` | `debug` | Runs the in-process TLS integration tests against libssl (no network oracle) | | `integration-linux-mbedtls` | `debug` | The same integration tests against Mbed TLS, exercising `SolidSyslogMbedTlsStream` and the Mbed TLS security policies | | `integration-windows-openssl` | `msvc-debug` | Same TLS integration tests on `windows-latest` against libssl from vcpkg | +| `build-linux-c89-headers` | — | Compiles every public header standalone as ISO C89 with `-pedantic-errors`, via `scripts/check_headers_c89.py`. Also proves each header is self-contained, since a header needing a companion first fails here | | `build-linux-c99` | `c99`, `c99-platforms` | Builds Core alone at strict `-std=c99` (`CMAKE_C_EXTENSIONS=OFF`, no tests), then the POSIX and OpenSSL packs at C99 as a drift check. Proves the C99 conformance claim per PR | | `build-linux-tunable-override` | `tunable-override-debug` | Builds against a user tunables header to prove `SOLIDSYSLOG_USER_TUNABLES_FILE` overrides the defaults | | `bdd-linux-syslog-ng` | — | End-to-end BDD test via Docker Compose (`syslog-ng-linux` + `behave-linux`), Linux runner | @@ -47,7 +48,7 @@ without renaming what's already there. Every job in `ci.yml` is a required status check except `deploy-docs-pages`, which only runs on `main`, and so are the two contexts code scanning contributes — -`analyze-codeql` and `CodeQL`. That is 33 required contexts. A PR cannot be merged +`analyze-codeql` and `CodeQL`. That is 34 required contexts. A PR cannot be merged unless all of them pass. Direct pushes to `main` are blocked. Squash merge only. Two qualifications on what "required" buys. The `analyze-iwyu*` lanes run From 54223d83fac9ee31801d2f14c58c6d5721587857 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 08:02:57 +0100 Subject: [PATCH 18/57] docs: S23.22 correct the two C99 comments outside docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both called the c99 check pre-release and pointed at docs/local-checks.md. It has been a per-PR lane since #713, and that commit moved the C99 material to builds.md — CMakeLists.txt even contradicted itself, its second C99 comment already citing builds.md. SolidSyslogMacros.h also described C99 as "the optional portability target" where the project now calls it the conformance baseline. Comments only: clang-format clean, debug build green (1494 tests), and the line counts are unchanged so the misra-c2012-20.10 suppression still lands on the same line. Co-Authored-By: Claude Opus 5 (1M context) --- CMakeLists.txt | 4 ++-- Core/Source/SolidSyslogMacros.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97d1b4fe..93f60d12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,8 +47,8 @@ if(SOLIDSYSLOG_IS_TOP_LEVEL) endif() # Default to C11 (for the optional C11 atomics counter), but let an externally -# supplied standard win so the pre-release C99 portability check can build the -# library at -std=c99 without editing this file. See docs/local-checks.md. +# supplied standard win so the `c99` preset can build the library at -std=c99 +# without editing this file. See docs/builds.md. if(NOT DEFINED CMAKE_C_STANDARD) set(CMAKE_C_STANDARD 11) endif() diff --git a/Core/Source/SolidSyslogMacros.h b/Core/Source/SolidSyslogMacros.h index 2eb0be95..27425184 100644 --- a/Core/Source/SolidSyslogMacros.h +++ b/Core/Source/SolidSyslogMacros.h @@ -2,9 +2,9 @@ #define SOLIDSYSLOGMACROS_H /* Compile-time assertion. C++ and C11 have native primitives that carry the - message into the diagnostic; a strict C99 toolchain — the optional - portability target, exercised by the pre-release `c99` preset (see - docs/local-checks.md) — has neither, so it falls back to declaring an array + message into the diagnostic; a strict C99 toolchain — the conformance + baseline, built on every pull request by the `build-linux-c99` lane (see + docs/builds.md) — has neither, so it falls back to declaring an array whose length goes negative (a constraint violation every C99 compiler rejects) when cond is false. The fallback uses a fixed name: repeated identical extern declarations in one translation unit are compatible, so no From 9962384f613644fb94d4454d44a6e41047e23f4c Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 13:44:44 +0100 Subject: [PATCH 19/57] docs: S23.22 give each platform its own docs folder and nav tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each platform becomes docs/platforms//, holding index.md (what the pack ships) and, where one exists today, setup.md (how to wire it). The three integrating-*.md guides move in as those setup pages. Paths only — no content is rewritten. The docs slug for the lwIP pack becomes lwipraw, matching its Platform/LwipRaw directory and its LwipRaw registry token. A future lwIP sockets pack would be a sibling, not a rename, so the slug has to say which lwIP API this one wraps. The nav label stays "lwIP (Raw API)". Platforms leaves API reference for a tab of its own: it answers whether the library runs on a given target, which is an adoption question rather than a reference one. The porting guide sits at the end of that tab as the other half of it — nothing shipped fits, so write one. Published URLs move for the three setup pages. Nothing external links them; link stability begins at 0.1.0, which is a reason to land this before the release rather than after. Part of #708 --- Bdd/Targets/FreeRtosLwip/README.md | 2 +- .../Interface/SolidSyslogLwipRawDnsResolver.h | 2 +- .../Interface/SolidSyslogLwipRawMarshal.h | 2 +- .../Interface/SolidSyslogLwipRawTcpStream.h | 2 +- .../Interface/SolidSyslogMbedTlsStream.h | 2 +- docs/README.md | 17 ++-- docs/build-integration.md | 14 +-- docs/iec62443.md | 2 +- docs/misra-deviations.md | 2 +- .../{atomics.md => atomics/index.md} | 6 +- docs/platforms/{fatfs.md => fatfs/index.md} | 4 +- docs/platforms/freertos.md | 19 ---- docs/platforms/freertos/index.md | 19 ++++ docs/platforms/index.md | 20 ++-- docs/platforms/{lwip.md => lwipraw/index.md} | 20 ++-- .../lwipraw/setup.md} | 3 +- docs/platforms/mbedtls.md | 25 ----- docs/platforms/mbedtls/index.md | 25 +++++ .../mbedtls/setup.md} | 18 ++-- docs/platforms/openssl.md | 20 ---- docs/platforms/openssl/index.md | 20 ++++ .../{plusfat.md => plusfat/index.md} | 6 +- .../plusfat/setup.md} | 20 ++-- .../{plustcp.md => plustcp/index.md} | 10 +- docs/platforms/posix.md | 32 ------ docs/platforms/posix/index.md | 32 ++++++ docs/platforms/windows.md | 30 ------ docs/platforms/windows/index.md | 30 ++++++ docs/porting.md | 4 +- docs/rfc-compliance.md | 2 +- docs/security/at-rest-cryptography.md | 2 +- hooks/page_descriptions.py | 97 ++++++++++--------- mkdocs.yml | 48 +++++---- 33 files changed, 285 insertions(+), 272 deletions(-) rename docs/platforms/{atomics.md => atomics/index.md} (53%) rename docs/platforms/{fatfs.md => fatfs/index.md} (64%) delete mode 100644 docs/platforms/freertos.md create mode 100644 docs/platforms/freertos/index.md rename docs/platforms/{lwip.md => lwipraw/index.md} (64%) rename docs/{integrating-lwip.md => platforms/lwipraw/setup.md} (99%) delete mode 100644 docs/platforms/mbedtls.md create mode 100644 docs/platforms/mbedtls/index.md rename docs/{integrating-mbedtls.md => platforms/mbedtls/setup.md} (91%) delete mode 100644 docs/platforms/openssl.md create mode 100644 docs/platforms/openssl/index.md rename docs/platforms/{plusfat.md => plusfat/index.md} (60%) rename docs/{integrating-plusfat.md => platforms/plusfat/setup.md} (87%) rename docs/platforms/{plustcp.md => plustcp/index.md} (50%) delete mode 100644 docs/platforms/posix.md create mode 100644 docs/platforms/posix/index.md delete mode 100644 docs/platforms/windows.md create mode 100644 docs/platforms/windows/index.md diff --git a/Bdd/Targets/FreeRtosLwip/README.md b/Bdd/Targets/FreeRtosLwip/README.md index c7ee560c..a40d26ca 100644 --- a/Bdd/Targets/FreeRtosLwip/README.md +++ b/Bdd/Targets/FreeRtosLwip/README.md @@ -64,7 +64,7 @@ so the resolve completes on-device without a DNS server. > timeout branches are unit-tested in > `Tests/Lwip/SolidSyslogLwipRawDnsResolverTest`, consistent with the project's > integration-over-BDD stance for paths the harness can't realistically drive. -> See [`docs/integrating-lwip.md`](../../../docs/integrating-lwip.md#dns). +> See [lwIP setup — DNS](../../../docs/platforms/lwipraw/setup.md#dns). ## Build diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h index 90107be9..4036bfd3 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h @@ -18,7 +18,7 @@ * - Any other immediate rejection, or the deadline elapsing, fails the Resolve * so the caller's unresolved-host error path runs. * - * The transport is ignored. Requires LWIP_DNS=1. See docs/integrating-lwip.md. */ + * The transport is ignored. Requires LWIP_DNS=1. See docs/platforms/lwipraw/setup.md. */ #ifndef SOLIDSYSLOGLWIPRAWDNSRESOLVER_H #define SOLIDSYSLOGLWIPRAWDNSRESOLVER_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h index 7ea714cc..3fad20aa 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h @@ -16,7 +16,7 @@ * The marshal MUST invoke its callback synchronously, before it returns: the * wrapper reads results the callback writes immediately after the hop, so an * asynchronous marshal is caller error. tcpip_callback_with_block(.., block=1) - * honours this; a bare tcpip_callback(..) does not. See docs/integrating-lwip.md. */ + * honours this; a bare tcpip_callback(..) does not. See docs/platforms/lwipraw/setup.md. */ #ifndef SOLIDSYSLOGLWIPRAWMARSHAL_H #define SOLIDSYSLOGLWIPRAWMARSHAL_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h index f59085bb..b02d8271 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h @@ -28,7 +28,7 @@ * callback nulls the pcb pointer, and Close only calls tcp_close when the * pointer is still live, so a released pcb is never closed twice. Accepted * pbufs are always freed on close regardless of pcb state. See - * docs/integrating-lwip.md for the full integrator guide. */ + * docs/platforms/lwipraw/setup.md for the full integrator guide. */ #ifndef SOLIDSYSLOGLWIPRAWTCPSTREAM_H #define SOLIDSYSLOGLWIPRAWTCPSTREAM_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index ca8ebff1..a59b0d49 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -27,7 +27,7 @@ * ssl_config / ssl_context state and never calls process-global mbedTLS APIs * (platform setup/teardown, psa_crypto_init, threading-alt, debug hooks), so it * drops into an integrator process that already uses Mbed TLS elsewhere. See - * docs/integrating-mbedtls.md. */ + * docs/platforms/mbedtls/setup.md. */ #ifndef SOLIDSYSLOGMBEDTLSSTREAM_H #define SOLIDSYSLOGMBEDTLSSTREAM_H diff --git a/docs/README.md b/docs/README.md index acee343e..d62a72d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,7 +14,7 @@ Pick a lane: - [Overview](#overview): what SolidSyslog is and how it helps with CRA and IEC 62443 compliance. - [Adopt it](#adopt): get a syslog stack compiling and sending in your product. -- [Port it to a new platform](#port-a-new-platform): fill a role for an OS, network stack, filesystem, or crypto library we don't ship yet. +- [Platforms](#platforms): what reaches your hardware — the shipped adapter packs, and how to write one for a target we don't cover. - [Compliance](#compliance): the CRA, IEC 62443, the RFCs, and the security posture. - [API reference](#api-reference): the public contracts, by audience. - [Maintaining the library](#maintaining): building, testing, and releasing SolidSyslog itself. @@ -34,18 +34,17 @@ Everything you need to consume SolidSyslog in your product. - [Adding it to your build](build-integration.md): the build detail. The capability matrix, the three ways to consume the library (CMake, Make, and a source manifest for an IDE project), and the compile-time tunables. - [Authoring custom structured data](structured-data.md): attaching RFC 5424 SD-ELEMENTs. - [Error-event severity policy](error-severity.md): installing an error handler and reading the event axes. -- Platform integration guides: - - [lwIP (Raw API)](integrating-lwip.md) - - [Mbed TLS](integrating-mbedtls.md) - - [FreeRTOS-Plus-FAT](integrating-plusfat.md) - Tunables: the compile-time limits, all `#ifndef`-guarded. See [Adding it to your build → Tunables](build-integration.md#tunables) and [`Core/Interface/SolidSyslogTunablesDefaults.h`](../Core/Interface/SolidSyslogTunablesDefaults.h). -## Port a new platform +## Platforms -Porting SolidSyslog is filling roles, not editing Core: omit an adapter and -Core's Null object stands in. +A platform is a set of adapters wrapping one upstream thing — a network stack, a +TLS library, a filesystem, an OS — behind the library's vtables. Each page says +what that pack ships and what wiring it needs; the pages above speak of platforms +in the general case. -- [Porting guide](porting.md): the role model, the anatomy of an adapter (instance shape, the no-`malloc` static pool, the error convention), the invariants every adapter must honour, and the twelve vtable role contracts, each with its Null fallback and shipped reference implementation (POSIX where available, otherwise a Core composition). +- [Platform × capability matrix](platforms/index.md): start here. Read across a row for what a platform gives you, down a column for who provides a capability. +- [Porting guide](porting.md): nothing shipped fits your target. The role model, the anatomy of an adapter (instance shape, the no-`malloc` static pool, the error convention), the invariants every adapter must honour, and the twelve vtable role contracts, each with its Null fallback and shipped reference implementation. - The contracts themselves: the `SolidSyslog*Definition.h` vtables under [`Core/Interface/`](../Core/Interface/). [`Platform/Posix/`](../Platform/Posix/) is the reference implementation to read alongside them. ## Compliance diff --git a/docs/build-integration.md b/docs/build-integration.md index 0e9bfa5d..72d41cba 100644 --- a/docs/build-integration.md +++ b/docs/build-integration.md @@ -257,9 +257,9 @@ See the worked target wiring in [`Bdd/Targets/FreeRtosLwip/`](../Bdd/Targets/FreeRtosLwip/) (both consume the umbrellas), and the platform-specific guides: -- [Integrating with lwIP (Raw API)](integrating-lwip.md) -- [Integrating with Mbed TLS](integrating-mbedtls.md) -- [Integrating with FreeRTOS-Plus-FAT](integrating-plusfat.md) +- [Integrating with lwIP (Raw API)](platforms/lwipraw/setup.md) +- [Integrating with Mbed TLS](platforms/mbedtls/setup.md) +- [Integrating with FreeRTOS-Plus-FAT](platforms/plusfat/setup.md) --- @@ -457,7 +457,7 @@ you. degrades to the Null counter (always 1). For the exact wiring of each adapter's `_Create` config struct, follow the -platform guides: [lwIP](integrating-lwip.md), [Mbed TLS](integrating-mbedtls.md). +platform guides: [lwIP](platforms/lwipraw/setup.md), [Mbed TLS](platforms/mbedtls/setup.md). --- @@ -512,9 +512,9 @@ SOLIDSYSLOG_USER_TUNABLES := -DSOLIDSYSLOG_USER_TUNABLES_FILE=\"$(CURDIR)/$(APP_ ## Where to go next - [Building up the protection you need](hardening-path.md): what to wire and why, stage by stage -- [Integrating with lwIP (Raw API)](integrating-lwip.md) -- [Integrating with Mbed TLS](integrating-mbedtls.md) -- [Integrating with FreeRTOS-Plus-FAT](integrating-plusfat.md) +- [Integrating with lwIP (Raw API)](platforms/lwipraw/setup.md) +- [Integrating with Mbed TLS](platforms/mbedtls/setup.md) +- [Integrating with FreeRTOS-Plus-FAT](platforms/plusfat/setup.md) - [Porting to a new platform](porting.md): writing an adapter for an OS, network stack, filesystem, or crypto library we don't ship - [Structured data](structured-data.md) - [Error handling and severity](error-severity.md) diff --git a/docs/iec62443.md b/docs/iec62443.md index c76cc508..51da2dc8 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -147,7 +147,7 @@ store) is unchanged. `mbedtls_ssl_close_notify` / `_ssl_free` / `_ssl_config_free` idempotently and never free integrator-owned handles. -Integrator guide: [`docs/integrating-mbedtls.md`](integrating-mbedtls.md). +Integrator guide: [Mbed TLS setup](platforms/mbedtls/setup.md). Reference wiring: the FreeRTOS QEMU mps2-an385 BDD target at [`Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c`](../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c). diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 21643645..d8877d49 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -595,7 +595,7 @@ Two distinct site categories trigger this rule: lwIP's `struct pbuf::payload` is declared `void*` (no `const` variant in the lwIP headers); `udp_sendto` only reads the payload — that is the `PBUF_REF` zero-copy contract, set out under - [Datagram — pbuf strategy](integrating-lwip.md#datagram--pbuf-strategy) + [Datagram — pbuf strategy](platforms/lwipraw/setup.md#datagram--pbuf-strategy) — but the field type does not encode that. Assigning our `const void*` parameter to lwIP's `void*` field strips the qualifier at the platform-API boundary, same shape as the diff --git a/docs/platforms/atomics.md b/docs/platforms/atomics/index.md similarity index 53% rename from docs/platforms/atomics.md rename to docs/platforms/atomics/index.md index 67d81be2..dcae5744 100644 --- a/docs/platforms/atomics.md +++ b/docs/platforms/atomics/index.md @@ -3,15 +3,15 @@ `Platform/Atomics/` is a portable AtomicCounter built on C11 `` — the sequenceId source on any target with a C11 compiler, no OS dependency. -Fills the [AtomicCounter](../api/structSolidSyslogAtomicCounter.md) role. +Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. ## What it ships | Class | Role | |---|---| -| [`SolidSyslogStdAtomicCounter`](../api/SolidSyslogStdAtomicCounter_8h.md) | atomic counter (`_Atomic uint32_t` CAS) | +| [`SolidSyslogStdAtomicCounter`](../../api/SolidSyslogStdAtomicCounter_8h.md) | atomic counter (`_Atomic uint32_t` CAS) | ## Requirements A C11 compiler with ``. Windows toolchains without it use -[`SolidSyslogWindowsAtomicCounter`](windows.md) instead. +[`SolidSyslogWindowsAtomicCounter`](../windows/index.md) instead. diff --git a/docs/platforms/fatfs.md b/docs/platforms/fatfs/index.md similarity index 64% rename from docs/platforms/fatfs.md rename to docs/platforms/fatfs/index.md index abf77c7a..14ddd158 100644 --- a/docs/platforms/fatfs.md +++ b/docs/platforms/fatfs/index.md @@ -4,14 +4,14 @@ ([FatFs documentation](http://elm-chan.org/fsw/ff/)). RTOS-agnostic — bare-metal, FreeRTOS, Zephyr, NuttX. -Fills the [File](../api/structSolidSyslogFile.md) role — the primitive beneath a +Fills the [File](../../api/structSolidSyslogFile.md) role — the primitive beneath a BlockDevice. ## What it ships | Class | Role | |---|---| -| [`SolidSyslogFatFsFile`](../api/SolidSyslogFatFsFile_8h.md) | file — `f_sync` after every write | +| [`SolidSyslogFatFsFile`](../../api/SolidSyslogFatFsFile_8h.md) | file — `f_sync` after every write | ## Requirements diff --git a/docs/platforms/freertos.md b/docs/platforms/freertos.md deleted file mode 100644 index d6a64c79..00000000 --- a/docs/platforms/freertos.md +++ /dev/null @@ -1,19 +0,0 @@ -# FreeRTOS - -`Platform/FreeRtos/` wraps FreeRTOS kernel primitives -([FreeRTOS documentation](https://www.freertos.org/Documentation/00-Overview)). -Networking is a separate backend — [FreeRTOS-Plus-TCP](plustcp.md) or -[lwIP](lwip.md). - -Fills the Mutex [role](../roles/index.md), plus a sysUpTime callback. - -## What it ships - -| Class | Role | -|---|---| -| [`SolidSyslogFreeRtosMutex`](../api/SolidSyslogFreeRtosMutex_8h.md) | mutex (`xSemaphoreCreateMutexStatic`) | -| [`SolidSyslogFreeRtosSysUpTime`](../api/SolidSyslogFreeRtosSysUpTime_8h.md) | uptime (`xTaskGetTickCount`) | - -## Requirements - -`configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md new file mode 100644 index 00000000..824a6358 --- /dev/null +++ b/docs/platforms/freertos/index.md @@ -0,0 +1,19 @@ +# FreeRTOS + +`Platform/FreeRtos/` wraps FreeRTOS kernel primitives +([FreeRTOS documentation](https://www.freertos.org/Documentation/00-Overview)). +Networking is a separate backend — [FreeRTOS-Plus-TCP](../plustcp/index.md) or +[lwIP](../lwipraw/index.md). + +Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. + +## What it ships + +| Class | Role | +|---|---| +| [`SolidSyslogFreeRtosMutex`](../../api/SolidSyslogFreeRtosMutex_8h.md) | mutex (`xSemaphoreCreateMutexStatic`) | +| [`SolidSyslogFreeRtosSysUpTime`](../../api/SolidSyslogFreeRtosSysUpTime_8h.md) | uptime (`xTaskGetTickCount`) | + +## Requirements + +`configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. diff --git a/docs/platforms/index.md b/docs/platforms/index.md index cfe35e05..eef38310 100644 --- a/docs/platforms/index.md +++ b/docs/platforms/index.md @@ -13,16 +13,16 @@ vtable. | Platform | Wraps | Network | TLS | At-rest crypto | Files | OS primitives | Time & host | |---|---|:-:|:-:|:-:|:-:|:-:|:-:| -| [Posix](posix.md) | POSIX / BSD sockets | ● | | | ● | ● | ● | -| [Windows](windows.md) | Win32 / Winsock | ● | | | ● | ● | ● | -| [FreeRTOS](freertos.md) | FreeRTOS kernel | | | | | ● | ● | -| [FreeRTOS-Plus-TCP](plustcp.md) | FreeRTOS-Plus-TCP | ● | | | | | | -| [lwIP (Raw API)](lwip.md) | lwIP Raw API | ● | | | | | | -| [OpenSSL](openssl.md) | OpenSSL ≥ 3.0 | | ● | ● | | | | -| [Mbed TLS](mbedtls.md) | Mbed TLS | | ● | ● | | | | -| [FatFs](fatfs.md) | ChaN FatFs | | | | ● | | | -| [FreeRTOS-Plus-FAT](plusfat.md) | FreeRTOS-Plus-FAT | | | | ● | | | -| [C11 atomics](atomics.md) | `` | | | | | ● | | +| [Posix](posix/index.md) | POSIX / BSD sockets | ● | | | ● | ● | ● | +| [Windows](windows/index.md) | Win32 / Winsock | ● | | | ● | ● | ● | +| [FreeRTOS](freertos/index.md) | FreeRTOS kernel | | | | | ● | ● | +| [FreeRTOS-Plus-TCP](plustcp/index.md) | FreeRTOS-Plus-TCP | ● | | | | | | +| [lwIP (Raw API)](lwipraw/index.md) | lwIP Raw API | ● | | | | | | +| [OpenSSL](openssl/index.md) | OpenSSL ≥ 3.0 | | ● | ● | | | | +| [Mbed TLS](mbedtls/index.md) | Mbed TLS | | ● | ● | | | | +| [FatFs](fatfs/index.md) | ChaN FatFs | | | | ● | | | +| [FreeRTOS-Plus-FAT](plusfat/index.md) | FreeRTOS-Plus-FAT | | | | ● | | | +| [C11 atomics](atomics/index.md) | `` | | | | | ● | | The at-rest-crypto column is the keyed policies (HMAC-SHA256, AES-256-GCM); the unkeyed CRC-16 policy is Core. Buffer, Store, and Structured Data are roles Core diff --git a/docs/platforms/lwip.md b/docs/platforms/lwipraw/index.md similarity index 64% rename from docs/platforms/lwip.md rename to docs/platforms/lwipraw/index.md index df81ef83..39e3c215 100644 --- a/docs/platforms/lwip.md +++ b/docs/platforms/lwipraw/index.md @@ -5,26 +5,26 @@ files compile against your `lwipopts.h`, so the adapter inherits your stack's configuration. -Fills the Resolver, Datagram and Stream [roles](../roles/index.md), plus the -address handle they share. Layer [Mbed TLS](mbedtls.md) over the TCP stream for +Fills the Resolver, Datagram and Stream [roles](../../roles/index.md), plus the +address handle they share. Layer [Mbed TLS](../mbedtls/index.md) over the TCP stream for TLS. ## What it ships | Class | Role | |---|---| -| [`SolidSyslogLwipRawAddress`](../api/SolidSyslogLwipRawAddress_8h.md) | destination handle | -| [`SolidSyslogLwipRawResolver`](../api/SolidSyslogLwipRawResolver_8h.md) | numeric IPv4 resolver | -| [`SolidSyslogLwipRawDnsResolver`](../api/SolidSyslogLwipRawDnsResolver_8h.md) | DNS resolver (`LWIP_DNS=1`) | -| [`SolidSyslogLwipRawDatagram`](../api/SolidSyslogLwipRawDatagram_8h.md) | UDP sender | -| [`SolidSyslogLwipRawTcpStream`](../api/SolidSyslogLwipRawTcpStream_8h.md) | TCP byte transport | +| [`SolidSyslogLwipRawAddress`](../../api/SolidSyslogLwipRawAddress_8h.md) | destination handle | +| [`SolidSyslogLwipRawResolver`](../../api/SolidSyslogLwipRawResolver_8h.md) | numeric IPv4 resolver | +| [`SolidSyslogLwipRawDnsResolver`](../../api/SolidSyslogLwipRawDnsResolver_8h.md) | DNS resolver (`LWIP_DNS=1`) | +| [`SolidSyslogLwipRawDatagram`](../../api/SolidSyslogLwipRawDatagram_8h.md) | UDP sender | +| [`SolidSyslogLwipRawTcpStream`](../../api/SolidSyslogLwipRawTcpStream_8h.md) | TCP byte transport | The source calls lwIP only — no direct OS calls. The TCP stream's synchronous Open needs a bounded sleep, injected as a `SolidSyslogSleepFunction`. ## The marshal -Not a role: [`SolidSyslogLwipRaw_SetMarshal`](../api/SolidSyslogLwipRawMarshal_8h.md) +Not a role: [`SolidSyslogLwipRaw_SetMarshal`](../../api/SolidSyslogLwipRawMarshal_8h.md) is a process-global seam, not a component you wire into the config. Every lwIP call the Datagram and TcpStream make is routed through one marshal hop. @@ -38,7 +38,7 @@ the Datagram and TcpStream make is routed through one marshal hop. The marshal must invoke its callback synchronously — the adapter reads results the moment the hop returns. `tcpip_callback_with_block(…, 1)` or a `LOCK_TCPIP_CORE` / `UNLOCK_TCPIP_CORE` pair satisfy that; a bare `tcpip_callback` does not. Worked -example: [`Bdd/Targets/FreeRtosLwip/main.c`](../../Bdd/Targets/FreeRtosLwip/main.c). +example: [`Bdd/Targets/FreeRtosLwip/main.c`](../../../Bdd/Targets/FreeRtosLwip/main.c). ## Requirements @@ -55,4 +55,4 @@ Also set `ARP_QUEUEING=1` (else the first datagram to an unresolved peer is dropped) and `LWIP_TCP_KEEPALIVE=1`, and size `PBUF_POOL_SIZE` / `MEMP_NUM_TCP_PCB` / `MEMP_NUM_UDP_PCB` to your instance counts. -Full setup — config, marshal, DNS — is [Integrating lwIP](../integrating-lwip.md). +Full setup — config, marshal, DNS — is [Integrating lwIP](setup.md). diff --git a/docs/integrating-lwip.md b/docs/platforms/lwipraw/setup.md similarity index 99% rename from docs/integrating-lwip.md rename to docs/platforms/lwipraw/setup.md index 52dcbbc6..07c13038 100644 --- a/docs/integrating-lwip.md +++ b/docs/platforms/lwipraw/setup.md @@ -40,8 +40,7 @@ configure time. mbedTLS layering is unchanged: `SolidSyslogMbedTlsStream` consumes `SolidSyslogLwipRawTcpStream` as its byte transport without -modification. See [`docs/integrating-mbedtls.md`](integrating-mbedtls.md) -for the TLS side. +modification. See [Mbed TLS setup](../mbedtls/setup.md) for the TLS side. --- diff --git a/docs/platforms/mbedtls.md b/docs/platforms/mbedtls.md deleted file mode 100644 index db62ddc9..00000000 --- a/docs/platforms/mbedtls.md +++ /dev/null @@ -1,25 +0,0 @@ -# Mbed TLS - -`Platform/MbedTls/` wraps Mbed TLS for TLS transport and keyed at-rest crypto on -embedded targets ([Mbed TLS documentation](https://mbed-tls.readthedocs.io/)). - -Fills the [Stream](../api/structSolidSyslogStream.md) role with TLS, and the -[SecurityPolicy](../api/structSolidSyslogSecurityPolicy.md) role for at-rest -integrity. - -## What it ships - -| Class | Role | -|---|---| -| [`SolidSyslogMbedTlsStream`](../api/SolidSyslogMbedTlsStream_8h.md) | TLS stream over an injected byte transport | -| [`SolidSyslogMbedTlsHmacSha256Policy`](../api/SolidSyslogMbedTlsHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | -| [`SolidSyslogMbedTlsAesGcmPolicy`](../api/SolidSyslogMbedTlsAesGcmPolicy_8h.md) | at-rest AES-256-GCM | - -## Requirements - -Your `mbedtls_config.h`. You pass caller-built handles (RNG, cert chain, key), not -file paths. `Platform/MbedTls/Source/` never calls process-global Mbed TLS APIs -(`mbedtls_platform_setup`, `psa_crypto_init`, …) — you own those, so SolidSyslog -coexists with your other Mbed TLS use. - -Full setup is [Integrating Mbed TLS](../integrating-mbedtls.md). diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md new file mode 100644 index 00000000..0124ed42 --- /dev/null +++ b/docs/platforms/mbedtls/index.md @@ -0,0 +1,25 @@ +# Mbed TLS + +`Platform/MbedTls/` wraps Mbed TLS for TLS transport and keyed at-rest crypto on +embedded targets ([Mbed TLS documentation](https://mbed-tls.readthedocs.io/)). + +Fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS, and the +[SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for at-rest +integrity. + +## What it ships + +| Class | Role | +|---|---| +| [`SolidSyslogMbedTlsStream`](../../api/SolidSyslogMbedTlsStream_8h.md) | TLS stream over an injected byte transport | +| [`SolidSyslogMbedTlsHmacSha256Policy`](../../api/SolidSyslogMbedTlsHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | +| [`SolidSyslogMbedTlsAesGcmPolicy`](../../api/SolidSyslogMbedTlsAesGcmPolicy_8h.md) | at-rest AES-256-GCM | + +## Requirements + +Your `mbedtls_config.h`. You pass caller-built handles (RNG, cert chain, key), not +file paths. `Platform/MbedTls/Source/` never calls process-global Mbed TLS APIs +(`mbedtls_platform_setup`, `psa_crypto_init`, …) — you own those, so SolidSyslog +coexists with your other Mbed TLS use. + +Full setup is [Integrating Mbed TLS](setup.md). diff --git a/docs/integrating-mbedtls.md b/docs/platforms/mbedtls/setup.md similarity index 91% rename from docs/integrating-mbedtls.md rename to docs/platforms/mbedtls/setup.md index 6aa9d447..d82aa030 100644 --- a/docs/integrating-mbedtls.md +++ b/docs/platforms/mbedtls/setup.md @@ -39,7 +39,7 @@ the per-context mbedTLS handles passed through | Item | Owner | Notes | |---|---|---| -| `Transport` | You | A `SolidSyslogStream*` carrying TCP. The library ships `SolidSyslogPosixTcpStream` (POSIX), `SolidSyslogWinsockTcpStream` (Windows), and `SolidSyslogPlusTcpTcpStream` (FreeRTOS-Plus-TCP). If your TCP/IP stack is different (LwIP, NicheStack, vendor BSP), write your own `SolidSyslogStream`; see [`Platform/Posix/Source/SolidSyslogPosixTcpStream.c`](../Platform/Posix/Source/SolidSyslogPosixTcpStream.c) as a reference. | +| `Transport` | You | A `SolidSyslogStream*` carrying TCP. The library ships `SolidSyslogPosixTcpStream` (POSIX), `SolidSyslogWinsockTcpStream` (Windows), and `SolidSyslogPlusTcpTcpStream` (FreeRTOS-Plus-TCP). If your TCP/IP stack is different (LwIP, NicheStack, vendor BSP), write your own `SolidSyslogStream`; see [`Platform/Posix/Source/SolidSyslogPosixTcpStream.c`](../../../Platform/Posix/Source/SolidSyslogPosixTcpStream.c) as a reference. | | `Sleep` | You | A `SolidSyslogSleepFunction`. Drives the bounded handshake retry between `WANT_READ` / `WANT_WRITE` polls. On FreeRTOS use a `vTaskDelay`-backed wrapper; on POSIX `SolidSyslogPosixSleep` is the natural fit. Required. | | `GetHandshakeTimeoutMs` / `HandshakeTimeoutContext` | You (optional) | Per-instance accessor pair for the bounded handshake budget. `NULL` falls back to the `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` compile-time tunable (default 5000 ms). Install when you need to runtime-tune the handshake deadline: slow peers on a constrained link, or per-tenant policy from your existing configuration store. The accessor is called on every `Open`. | | `Rng` | You | `mbedtls_ctr_drbg_context*` you seeded yourself. The adapter calls `mbedtls_ctr_drbg_random` against it. Required. | @@ -48,7 +48,7 @@ the per-context mbedTLS handles passed through | `ClientCertChain` / `ClientKey` | You | `mbedtls_x509_crt*` + `mbedtls_pk_context*` for mTLS. Both `NULL` = server-auth-only TLS. Both non-`NULL` = mTLS. Supplying only one is treated as "no client cert"; the adapter never half-configures. | The full struct shape lives in -[`Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h`](../Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h). +[`Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h`](../../../Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h). The adapter pins the minimum protocol version to TLS 1.2 on its own `ssl_config` rather than inheriting `MBEDTLS_SSL_PRESET_DEFAULT`, which, on a @@ -143,10 +143,10 @@ specifically for this adapter: exactly as in [Scenario A](#scenario-a-you-already-have-mbed-tls-in-your-image). A worked end-to-end example for all of the above lives at -[`Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c`](../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c) +[`Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c`](../../../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c) (FreeRTOS-Plus-TCP on QEMU mps2-an385). The matching Mbed TLS config overrides live at -[`Bdd/Targets/FreeRtos/mbedtls_user_config.h`](../Bdd/Targets/FreeRtos/mbedtls_user_config.h). +[`Bdd/Targets/FreeRtos/mbedtls_user_config.h`](../../../Bdd/Targets/FreeRtos/mbedtls_user_config.h). --- @@ -177,7 +177,7 @@ newlib, treat them as integrator-side checklist items: - Route mbedTLS allocations to the RTOS heap. Mbed TLS calls libc `calloc`, which on newlib targets typically hits a tiny `_sbrk`-backed syscall heap (4 KiB in the SolidSyslog BDD reference at - [`Bdd/Targets/FreeRtos/Common/Syscalls.c`](../Bdd/Targets/FreeRtos/Common/Syscalls.c)). + [`Bdd/Targets/FreeRtos/Common/Syscalls.c`](../../../Bdd/Targets/FreeRtos/Common/Syscalls.c)). A single `mbedtls_ssl_setup` wants ~10–16 KiB and will fail with `MBEDTLS_ERR_SSL_ALLOC_FAILED` (-0x7F00). Set `MBEDTLS_PLATFORM_MEMORY` in your config and call @@ -199,7 +199,7 @@ newlib, treat them as integrator-side checklist items: `PSA_ERROR_INSUFFICIENT_ENTROPY` (-148). The BDD target's -[mbedtls_user_config.h](../Bdd/Targets/FreeRtos/mbedtls_user_config.h) +[mbedtls_user_config.h](../../../Bdd/Targets/FreeRtos/mbedtls_user_config.h) shows the minimal config that satisfies the above for QEMU mps2-an385. --- @@ -208,9 +208,9 @@ shows the minimal config that satisfies the above for QEMU mps2-an385. | Target | Adapter source | Mbed TLS config | Notes | |---|---|---|---| -| FreeRTOS QEMU mps2-an385 + FreeRTOS-Plus-TCP | [BddTargetTlsSender_MbedTls_PlusTcpTcp.c](../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c) | [mbedtls_user_config.h](../Bdd/Targets/FreeRtos/mbedtls_user_config.h) | Demo-quality entropy and baked-in PEMs; loudly tagged not-for-production. | -| Linux host (host-TDD parity with the embedded path) | [Tests/MbedTlsIntegration/](../Tests/MbedTlsIntegration/) | — | In-process TLS server drives a real handshake against the wrapper. | -| POSIX (OpenSSL reference, for comparison) | [BddTargetTlsSender_OpenSsl_PosixTcp.c](../Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c) | — | Same composition shape using `SolidSyslogTlsStream` for the TLS layer. | +| FreeRTOS QEMU mps2-an385 + FreeRTOS-Plus-TCP | [BddTargetTlsSender_MbedTls_PlusTcpTcp.c](../../../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c) | [mbedtls_user_config.h](../../../Bdd/Targets/FreeRtos/mbedtls_user_config.h) | Demo-quality entropy and baked-in PEMs; loudly tagged not-for-production. | +| Linux host (host-TDD parity with the embedded path) | [Tests/MbedTlsIntegration/](../../../Tests/MbedTlsIntegration/) | — | In-process TLS server drives a real handshake against the wrapper. | +| POSIX (OpenSSL reference, for comparison) | [BddTargetTlsSender_OpenSsl_PosixTcp.c](../../../Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c) | — | Same composition shape using `SolidSyslogTlsStream` for the TLS layer. | --- diff --git a/docs/platforms/openssl.md b/docs/platforms/openssl.md deleted file mode 100644 index 52e9bf41..00000000 --- a/docs/platforms/openssl.md +++ /dev/null @@ -1,20 +0,0 @@ -# OpenSSL - -`Platform/OpenSsl/` wraps OpenSSL for TLS transport and keyed at-rest crypto -([OpenSSL documentation](https://docs.openssl.org/)). - -Fills the [Stream](../api/structSolidSyslogStream.md) role with TLS, and the -[SecurityPolicy](../api/structSolidSyslogSecurityPolicy.md) role for at-rest -integrity. - -## What it ships - -| Class | Role | -|---|---| -| [`SolidSyslogTlsStream`](../api/SolidSyslogTlsStream_8h.md) | TLS stream — server-cert + hostname verification, cipher pinning, optional mutual TLS | -| [`SolidSyslogOpenSslHmacSha256Policy`](../api/SolidSyslogOpenSslHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | -| [`SolidSyslogOpenSslAesGcmPolicy`](../api/SolidSyslogOpenSslAesGcmPolicy_8h.md) | at-rest AES-256-GCM | - -## Requirements - -OpenSSL 3.0 or later. diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md new file mode 100644 index 00000000..15f0fdbe --- /dev/null +++ b/docs/platforms/openssl/index.md @@ -0,0 +1,20 @@ +# OpenSSL + +`Platform/OpenSsl/` wraps OpenSSL for TLS transport and keyed at-rest crypto +([OpenSSL documentation](https://docs.openssl.org/)). + +Fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS, and the +[SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for at-rest +integrity. + +## What it ships + +| Class | Role | +|---|---| +| [`SolidSyslogTlsStream`](../../api/SolidSyslogTlsStream_8h.md) | TLS stream — server-cert + hostname verification, cipher pinning, optional mutual TLS | +| [`SolidSyslogOpenSslHmacSha256Policy`](../../api/SolidSyslogOpenSslHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | +| [`SolidSyslogOpenSslAesGcmPolicy`](../../api/SolidSyslogOpenSslAesGcmPolicy_8h.md) | at-rest AES-256-GCM | + +## Requirements + +OpenSSL 3.0 or later. diff --git a/docs/platforms/plusfat.md b/docs/platforms/plusfat/index.md similarity index 60% rename from docs/platforms/plusfat.md rename to docs/platforms/plusfat/index.md index 3c00c1aa..c340555f 100644 --- a/docs/platforms/plusfat.md +++ b/docs/platforms/plusfat/index.md @@ -3,18 +3,18 @@ `Platform/PlusFat/` wraps FreeRTOS-Plus-FAT as the File layer ([FreeRTOS-Plus-FAT documentation](https://www.freertos.org/Documentation/03-Libraries/05-FreeRTOS-labs/04-FreeRTOS-plus-FAT/01-FreeRTOS-plus-FAT)). -Fills the [File](../api/structSolidSyslogFile.md) role — the primitive beneath a +Fills the [File](../../api/structSolidSyslogFile.md) role — the primitive beneath a BlockDevice. ## What it ships | Class | Role | |---|---| -| [`SolidSyslogPlusFatFile`](../api/SolidSyslogPlusFatFile_8h.md) | file — `ff_fflush` after every write | +| [`SolidSyslogPlusFatFile`](../../api/SolidSyslogPlusFatFile_8h.md) | file — `ff_fflush` after every write | ## Requirements FreeRTOS — Plus-FAT is FreeRTOS-coupled. Supply an `FF_Disk_t` media driver and `FreeRTOSFATConfig.h`. -Full setup is [Integrating FreeRTOS-Plus-FAT](../integrating-plusfat.md). +Full setup is [Integrating FreeRTOS-Plus-FAT](setup.md). diff --git a/docs/integrating-plusfat.md b/docs/platforms/plusfat/setup.md similarity index 87% rename from docs/integrating-plusfat.md rename to docs/platforms/plusfat/setup.md index 5b0c1fdc..6cf05237 100644 --- a/docs/integrating-plusfat.md +++ b/docs/platforms/plusfat/setup.md @@ -3,14 +3,14 @@ `SolidSyslogPlusFatFile` is the `SolidSyslogFile` adapter backed by [FreeRTOS-Plus-FAT](https://www.freertos.org/Documentation/03-Libraries/05-FreeRTOS-labs/04-FreeRTOS-plus-FAT/01-FreeRTOS-plus-FAT) (the `ff_stdio` API). It is the FreeRTOS-Plus ecosystem counterpart to the -OS-agnostic [ChaN FatFs adapter](../Platform/FatFs/); pair it with +OS-agnostic [ChaN FatFs adapter](../../../Platform/FatFs/); pair it with FreeRTOS-Plus-TCP for a coherent all-FreeRTOS-Plus storage + transport stack. It gives the store-and-forward layer (`SolidSyslogBlockStore` over `SolidSyslogFileBlockDevice`) a real on-flash file backend. This guide covers what you must supply around the adapter. For the file seam -itself see [`SolidSyslogFile.h`](../Core/Interface/SolidSyslogFile.h); for the -store see [`SolidSyslogBlockStore.h`](../Core/Interface/SolidSyslogBlockStore.h). +itself see [`SolidSyslogFile.h`](../../../Core/Interface/SolidSyslogFile.h); for the +store see [`SolidSyslogBlockStore.h`](../../../Core/Interface/SolidSyslogBlockStore.h). ## The shape @@ -43,14 +43,14 @@ yours to provide; the library never reaches the block device directly. to register the volume in `ff_stdio`'s virtual file system. Plus-FAT ships reference drivers under `portable/` (`ff_ramdisk.c` is the clearest template). The library's BDD target ships a semihosting example, - [`Bdd/Targets/Common/FFSemihostingDisk.c`](../Bdd/Targets/Common/FFSemihostingDisk.c). + [`Bdd/Targets/Common/FFSemihostingDisk.c`](../../../Bdd/Targets/Common/FFSemihostingDisk.c). 3. A `FreeRTOSFATConfig.h` on your include path. `ff_headers.h` pulls it via `#include "FreeRTOSFATConfig.h"`; unlike ChaN FatFs's `ffconf.h`, it resolves off the `-I` path (no source-tree colocation needed). At minimum set `ffconfigBYTE_ORDER` and `ffconfigCWD_THREAD_LOCAL_INDEX`; `FreeRTOSFATConfigDefaults.h` fills the rest. See - [`Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h`](../Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h). + [`Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h`](../../../Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h). 4. Kernel configuration in `FreeRTOSConfig.h`: - `configUSE_RECURSIVE_MUTEXES = 1` (Plus-FAT's `ff_locking.c` enforces this). @@ -101,19 +101,19 @@ not. Size your heap for the IO-manager cache you request in `FF_CreateIOManager` ## Reference integration -[`Bdd/Targets/FreeRtos/`](../Bdd/Targets/FreeRtos/) is the worked example, the +[`Bdd/Targets/FreeRtos/`](../../../Bdd/Targets/FreeRtos/) is the worked example, the FreeRTOS-Plus-TCP + FreeRTOS-Plus-FAT QEMU BDD target. It wires: -- [`FFSemihostingDisk.c`](../Bdd/Targets/Common/FFSemihostingDisk.c): an +- [`FFSemihostingDisk.c`](../../../Bdd/Targets/Common/FFSemihostingDisk.c): an `FF_Disk_t` over an ARM-semihosting host-backed flat disk (8 MiB, FAT16), modelled on Plus-FAT's `ff_ramdisk.c` but persistent (mount-or-format-on-first- use, so a power cycle keeps its data). -- [`BddTargetPlusFatMount.c`](../Bdd/Targets/Common/BddTargetPlusFatMount.c): +- [`BddTargetPlusFatMount.c`](../../../Bdd/Targets/Common/BddTargetPlusFatMount.c): the mount/unmount + `SolidSyslogPlusFatFile` create/destroy wired into the shared FreeRTOS pipeline's FS-mount seam. -- [`FreeRTOSFATConfig.h`](../Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h) and the +- [`FreeRTOSFATConfig.h`](../../../Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h) and the `configNUM_THREAD_LOCAL_STORAGE_POINTERS` knob in - [`FreeRTOSConfig.h`](../Bdd/Targets/FreeRtos/FreeRTOSConfig.h). + [`FreeRTOSConfig.h`](../../../Bdd/Targets/FreeRtos/FreeRTOSConfig.h). The full store / capacity / power-cycle-replay BDD suite runs against this target on QEMU (`bdd-freertos-qemu-plustcp`). diff --git a/docs/platforms/plustcp.md b/docs/platforms/plustcp/index.md similarity index 50% rename from docs/platforms/plustcp.md rename to docs/platforms/plustcp/index.md index e5319d02..34b44576 100644 --- a/docs/platforms/plustcp.md +++ b/docs/platforms/plustcp/index.md @@ -3,17 +3,17 @@ `Platform/PlusTcp/` wraps FreeRTOS-Plus-TCP for networking on FreeRTOS targets ([FreeRTOS-Plus-TCP documentation](https://www.freertos.org/Documentation/03-Libraries/02-FreeRTOS-plus/02-FreeRTOS-plus-TCP/01-FreeRTOS-Plus-TCP)). -Fills the Resolver, Datagram and Stream [roles](../roles/index.md), plus the +Fills the Resolver, Datagram and Stream [roles](../../roles/index.md), plus the address handle they share. ## What it ships | Class | Role | |---|---| -| [`SolidSyslogPlusTcpResolver`](../api/SolidSyslogPlusTcpResolver_8h.md) | DNS resolver (`FreeRTOS_getaddrinfo`) | -| [`SolidSyslogPlusTcpAddress`](../api/SolidSyslogPlusTcpAddress_8h.md) | address (`freertos_sockaddr`) | -| [`SolidSyslogPlusTcpDatagram`](../api/SolidSyslogPlusTcpDatagram_8h.md) | UDP sender | -| [`SolidSyslogPlusTcpTcpStream`](../api/SolidSyslogPlusTcpTcpStream_8h.md) | TCP stream (bounded connect) | +| [`SolidSyslogPlusTcpResolver`](../../api/SolidSyslogPlusTcpResolver_8h.md) | DNS resolver (`FreeRTOS_getaddrinfo`) | +| [`SolidSyslogPlusTcpAddress`](../../api/SolidSyslogPlusTcpAddress_8h.md) | address (`freertos_sockaddr`) | +| [`SolidSyslogPlusTcpDatagram`](../../api/SolidSyslogPlusTcpDatagram_8h.md) | UDP sender | +| [`SolidSyslogPlusTcpTcpStream`](../../api/SolidSyslogPlusTcpTcpStream_8h.md) | TCP stream (bounded connect) | ## Requirements diff --git a/docs/platforms/posix.md b/docs/platforms/posix.md deleted file mode 100644 index e5bff43c..00000000 --- a/docs/platforms/posix.md +++ /dev/null @@ -1,32 +0,0 @@ -# Posix - -`Platform/Posix/` wraps the standard POSIX APIs — BSD sockets, pthreads, POSIX -message queues, `clock_gettime`, stdio -([POSIX.1-2017 specification](https://pubs.opengroup.org/onlinepubs/9699919799/)). -Linux is the reference target. - -Fills the Resolver, Datagram, Stream, Buffer, File and Mutex -[roles](../roles/index.md), plus the clock / hostname / process-id / sleep -callbacks. - -## What it ships - -| Class | Role | -|---|---| -| [`SolidSyslogGetAddrInfoResolver`](../api/SolidSyslogGetAddrInfoResolver_8h.md) | resolver (`getaddrinfo`) | -| [`SolidSyslogPosixAddress`](../api/SolidSyslogPosixAddress_8h.md) | address | -| [`SolidSyslogPosixDatagram`](../api/SolidSyslogPosixDatagram_8h.md) | UDP sender | -| [`SolidSyslogPosixTcpStream`](../api/SolidSyslogPosixTcpStream_8h.md) | TCP stream (non-blocking, bounded connect) | -| [`SolidSyslogPosixFile`](../api/SolidSyslogPosixFile_8h.md) | file | -| [`SolidSyslogPosixMessageQueueBuffer`](../api/SolidSyslogPosixMessageQueueBuffer_8h.md) | message-queue buffer | -| [`SolidSyslogPosixMutex`](../api/SolidSyslogPosixMutex_8h.md) | mutex | -| [`SolidSyslogPosixClock`](../api/SolidSyslogPosixClock_8h.md) | clock | -| [`SolidSyslogPosixHostname`](../api/SolidSyslogPosixHostname_8h.md) | hostname | -| [`SolidSyslogPosixProcessId`](../api/SolidSyslogPosixProcessId_8h.md) | process-id | -| [`SolidSyslogPosixSleep`](../api/SolidSyslogPosixSleep_8h.md) | sleep | -| [`SolidSyslogPosixSysUpTime`](../api/SolidSyslogPosixSysUpTime_8h.md) | uptime (`CLOCK_BOOTTIME`) | - -## Requirements - -A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs -POSIX message queues (link `-lrt` on glibc). diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md new file mode 100644 index 00000000..b97fa1d2 --- /dev/null +++ b/docs/platforms/posix/index.md @@ -0,0 +1,32 @@ +# Posix + +`Platform/Posix/` wraps the standard POSIX APIs — BSD sockets, pthreads, POSIX +message queues, `clock_gettime`, stdio +([POSIX.1-2017 specification](https://pubs.opengroup.org/onlinepubs/9699919799/)). +Linux is the reference target. + +Fills the Resolver, Datagram, Stream, Buffer, File and Mutex +[roles](../../roles/index.md), plus the clock / hostname / process-id / sleep +callbacks. + +## What it ships + +| Class | Role | +|---|---| +| [`SolidSyslogGetAddrInfoResolver`](../../api/SolidSyslogGetAddrInfoResolver_8h.md) | resolver (`getaddrinfo`) | +| [`SolidSyslogPosixAddress`](../../api/SolidSyslogPosixAddress_8h.md) | address | +| [`SolidSyslogPosixDatagram`](../../api/SolidSyslogPosixDatagram_8h.md) | UDP sender | +| [`SolidSyslogPosixTcpStream`](../../api/SolidSyslogPosixTcpStream_8h.md) | TCP stream (non-blocking, bounded connect) | +| [`SolidSyslogPosixFile`](../../api/SolidSyslogPosixFile_8h.md) | file | +| [`SolidSyslogPosixMessageQueueBuffer`](../../api/SolidSyslogPosixMessageQueueBuffer_8h.md) | message-queue buffer | +| [`SolidSyslogPosixMutex`](../../api/SolidSyslogPosixMutex_8h.md) | mutex | +| [`SolidSyslogPosixClock`](../../api/SolidSyslogPosixClock_8h.md) | clock | +| [`SolidSyslogPosixHostname`](../../api/SolidSyslogPosixHostname_8h.md) | hostname | +| [`SolidSyslogPosixProcessId`](../../api/SolidSyslogPosixProcessId_8h.md) | process-id | +| [`SolidSyslogPosixSleep`](../../api/SolidSyslogPosixSleep_8h.md) | sleep | +| [`SolidSyslogPosixSysUpTime`](../../api/SolidSyslogPosixSysUpTime_8h.md) | uptime (`CLOCK_BOOTTIME`) | + +## Requirements + +A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs +POSIX message queues (link `-lrt` on glibc). diff --git a/docs/platforms/windows.md b/docs/platforms/windows.md deleted file mode 100644 index b67d2adf..00000000 --- a/docs/platforms/windows.md +++ /dev/null @@ -1,30 +0,0 @@ -# Windows - -`Platform/Windows/` wraps the Win32 and Winsock APIs for MSVC targets -([Winsock documentation](https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2)). - -Fills the Resolver, Datagram, Stream, File, Mutex and AtomicCounter -[roles](../roles/index.md), plus the clock / hostname / process-id / sleep -callbacks. - -## What it ships - -| Class | Role | -|---|---| -| [`SolidSyslogWinsockResolver`](../api/SolidSyslogWinsockResolver_8h.md) | resolver | -| [`SolidSyslogWinsockAddress`](../api/SolidSyslogWinsockAddress_8h.md) | address | -| [`SolidSyslogWinsockDatagram`](../api/SolidSyslogWinsockDatagram_8h.md) | UDP sender | -| [`SolidSyslogWinsockTcpStream`](../api/SolidSyslogWinsockTcpStream_8h.md) | TCP stream (non-blocking + `select`, bounded connect) | -| [`SolidSyslogWindowsFile`](../api/SolidSyslogWindowsFile_8h.md) | file | -| [`SolidSyslogWindowsMutex`](../api/SolidSyslogWindowsMutex_8h.md) | mutex (`CRITICAL_SECTION`) | -| [`SolidSyslogWindowsAtomicCounter`](../api/SolidSyslogWindowsAtomicCounter_8h.md) | atomic counter (`Interlocked`) | -| [`SolidSyslogWindowsClock`](../api/SolidSyslogWindowsClock_8h.md) | clock | -| [`SolidSyslogWindowsHostname`](../api/SolidSyslogWindowsHostname_8h.md) | hostname | -| [`SolidSyslogWindowsProcessId`](../api/SolidSyslogWindowsProcessId_8h.md) | process-id | -| [`SolidSyslogWindowsSleep`](../api/SolidSyslogWindowsSleep_8h.md) | sleep | -| [`SolidSyslogWindowsSysUpTime`](../api/SolidSyslogWindowsSysUpTime_8h.md) | uptime (`GetTickCount64`) | - -## Requirements - -The MSVC toolchain and Winsock — call `WSAStartup` once at process init before -creating a sender. diff --git a/docs/platforms/windows/index.md b/docs/platforms/windows/index.md new file mode 100644 index 00000000..e4af0132 --- /dev/null +++ b/docs/platforms/windows/index.md @@ -0,0 +1,30 @@ +# Windows + +`Platform/Windows/` wraps the Win32 and Winsock APIs for MSVC targets +([Winsock documentation](https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2)). + +Fills the Resolver, Datagram, Stream, File, Mutex and AtomicCounter +[roles](../../roles/index.md), plus the clock / hostname / process-id / sleep +callbacks. + +## What it ships + +| Class | Role | +|---|---| +| [`SolidSyslogWinsockResolver`](../../api/SolidSyslogWinsockResolver_8h.md) | resolver | +| [`SolidSyslogWinsockAddress`](../../api/SolidSyslogWinsockAddress_8h.md) | address | +| [`SolidSyslogWinsockDatagram`](../../api/SolidSyslogWinsockDatagram_8h.md) | UDP sender | +| [`SolidSyslogWinsockTcpStream`](../../api/SolidSyslogWinsockTcpStream_8h.md) | TCP stream (non-blocking + `select`, bounded connect) | +| [`SolidSyslogWindowsFile`](../../api/SolidSyslogWindowsFile_8h.md) | file | +| [`SolidSyslogWindowsMutex`](../../api/SolidSyslogWindowsMutex_8h.md) | mutex (`CRITICAL_SECTION`) | +| [`SolidSyslogWindowsAtomicCounter`](../../api/SolidSyslogWindowsAtomicCounter_8h.md) | atomic counter (`Interlocked`) | +| [`SolidSyslogWindowsClock`](../../api/SolidSyslogWindowsClock_8h.md) | clock | +| [`SolidSyslogWindowsHostname`](../../api/SolidSyslogWindowsHostname_8h.md) | hostname | +| [`SolidSyslogWindowsProcessId`](../../api/SolidSyslogWindowsProcessId_8h.md) | process-id | +| [`SolidSyslogWindowsSleep`](../../api/SolidSyslogWindowsSleep_8h.md) | sleep | +| [`SolidSyslogWindowsSysUpTime`](../../api/SolidSyslogWindowsSysUpTime_8h.md) | uptime (`GetTickCount64`) | + +## Requirements + +The MSVC toolchain and Winsock — call `WSAStartup` once at process init before +creating a sender. diff --git a/docs/porting.md b/docs/porting.md index 521e1f0a..fcc0b906 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -146,7 +146,7 @@ This is the only synchronisation primitive the pools use for their own walks. - Never free injected handles. An adapter frees only what it created. Handles the integrator passed in (an `mbedtls_x509_crt*`, an RNG, a caller's socket) are borrowed; the owner frees them. The [Mbed TLS coexistence - contract](integrating-mbedtls.md#coexistence-contract) is the template: + contract](platforms/mbedtls/setup.md#coexistence-contract) is the template: `Platform/MbedTls/Source/` never touches process-global Mbed TLS state. - A Null must be safe to call. Whatever your role's Null returns (see each contract below), it must let Core's algorithm proceed sanely: drop-on-the-floor @@ -307,6 +307,6 @@ reference to read first. ## Where to go next - [Adding it to your build](build-integration.md): the capability matrix, tunables, and build wiring. -- [Integrating with lwIP (Raw API)](integrating-lwip.md), [Mbed TLS](integrating-mbedtls.md), [FreeRTOS-Plus-FAT](integrating-plusfat.md): worked ports of the networking, TLS, and file roles. +- [Integrating with lwIP (Raw API)](platforms/lwipraw/setup.md), [Mbed TLS](platforms/mbedtls/setup.md), [FreeRTOS-Plus-FAT](platforms/plusfat/setup.md): worked ports of the networking, TLS, and file roles. - [Naming conventions](NAMING.md) and [MISRA deviations](misra-deviations.md): the rules Tier 1/2 adapter code follows. - [Error-event severity policy](error-severity.md): choosing the severity for your adapter's reports. diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index 294c297b..372aa08c 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -61,7 +61,7 @@ Status key: ## RFC 5425 — TLS Transport Mapping for Syslog -The library ships two reference TLS adapters that satisfy this RFC: `SolidSyslogTlsStream` (OpenSSL, the POSIX / Windows reference) and `SolidSyslogMbedTlsStream` (Mbed TLS, the embedded / FreeRTOS reference). Both implement the same `SolidSyslogStream` vtable, so the section-by-section requirements below apply to whichever the integrator selects. mbedTLS-specific integration guidance lives in [`docs/integrating-mbedtls.md`](integrating-mbedtls.md). +The library ships two reference TLS adapters that satisfy this RFC: `SolidSyslogTlsStream` (OpenSSL, the POSIX / Windows reference) and `SolidSyslogMbedTlsStream` (Mbed TLS, the embedded / FreeRTOS reference). Both implement the same `SolidSyslogStream` vtable, so the section-by-section requirements below apply to whichever the integrator selects. mbedTLS-specific integration guidance lives in [Mbed TLS setup](platforms/mbedtls/setup.md). | Section | Requirement | Status | Notes | |---|---|---|---| diff --git a/docs/security/at-rest-cryptography.md b/docs/security/at-rest-cryptography.md index 86593a8b..5598de89 100644 --- a/docs/security/at-rest-cryptography.md +++ b/docs/security/at-rest-cryptography.md @@ -43,7 +43,7 @@ trailer is `nonce (12) ‖ tag (16)`. systematic reuse a reset counter would — provided each new DRBG instance is seeded from fresh entropy. On the Mbed TLS path that guarantee is yours: a CTR-DRBG re-seeded from a repeating source reproduces its output, and so reproduces nonces - under the same key. [Integrating Mbed TLS](../integrating-mbedtls.md) states the + under the same key. [Integrating Mbed TLS](../platforms/mbedtls/setup.md) states the entropy the adapter assumes and the silent failure mode when it is missing. Uniqueness stays probabilistic, bounded by the 2³² per-key envelope below. - Failure: `OpenRecord` returns a single `bool`. A tag mismatch (the diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index fc086aa8..595d1828 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -49,19 +49,64 @@ "Which severity a SolidSyslog error event carries — the urgency ladder " "each emit site picks from, and what each level asks of your handler." ), - "integrating-lwip.md": ( + # Platforms — each platform's overview page followed by its setup guide, + # in nav order, with porting last as the other half of the same question. + "platforms/index.md": ( + "The adapter packs that reach your hardware — POSIX, Windows, FreeRTOS, " + "lwIP, OpenSSL, Mbed TLS, FatFs and more — and the capabilities each fills." + ), + "platforms/posix/index.md": ( + "The POSIX adapter pack — sockets, pthreads, message queues, " + "clock_gettime and stdio — filling six of the library's roles on Linux." + ), + "platforms/windows/index.md": ( + "The Win32 and Winsock adapter pack for MSVC targets, filling the " + "Resolver, Datagram, Stream, File, Mutex and AtomicCounter roles." + ), + "platforms/freertos/index.md": ( + "The FreeRTOS adapter pack: kernel primitives filling the Mutex role and " + "the sysUpTime callback, with networking from Plus-TCP or lwIP." + ), + "platforms/plustcp/index.md": ( + "The FreeRTOS-Plus-TCP adapter pack, filling the Resolver, Datagram and " + "Stream roles for networking on FreeRTOS targets." + ), + "platforms/lwipraw/index.md": ( + "The lwIP Raw API adapter pack, filling the Resolver, Datagram and Stream " + "roles — compiled against your lwipopts.h, NO_SYS=1 builds included." + ), + "platforms/lwipraw/setup.md": ( "Wire SolidSyslog to the lwIP Raw API: what the adapter fills, what you " "supply, and how it works under NO_SYS=1 beside other lwIP subsystems." ), - "integrating-mbedtls.md": ( + "platforms/openssl/index.md": ( + "The OpenSSL adapter pack: TLS transport for the Stream role, and keyed " + "at-rest crypto for the SecurityPolicy role, on hosted targets." + ), + "platforms/mbedtls/index.md": ( + "The Mbed TLS adapter pack for embedded targets: TLS transport for the " + "Stream role, and keyed at-rest crypto for the SecurityPolicy role." + ), + "platforms/mbedtls/setup.md": ( "Deliver RFC 5425 syslog over TLS through Mbed TLS on embedded targets: " "the handles you pre-build and pass in, and how they are wired." ), - "integrating-plusfat.md": ( + "platforms/fatfs/index.md": ( + "The ChaN FatFs adapter pack, filling the File role beneath a BlockDevice " + "— RTOS-agnostic, for bare-metal, FreeRTOS, Zephyr and NuttX targets." + ), + "platforms/plusfat/index.md": ( + "The FreeRTOS-Plus-FAT adapter pack, filling the File role beneath a " + "BlockDevice for an all-FreeRTOS-Plus storage and transport stack." + ), + "platforms/plusfat/setup.md": ( "Back store-and-forward with FreeRTOS-Plus-FAT: what the ff_stdio File " "adapter needs from your build and media, and how the store sits above it." ), - # Port a new platform + "platforms/atomics/index.md": ( + "The portable C11 stdatomic.h AtomicCounter — the sequenceId source on " + "any target with a C11 compiler, with no OS dependency." + ), "porting.md": ( "Port SolidSyslog to a new RTOS, network stack, filesystem or crypto " "library by filling a vtable contract. Core never changes." @@ -109,54 +154,10 @@ "What Core provides: the facade you call, the pipeline that formats and " "drains a record, and the role implementations that need no platform." ), - "platforms/index.md": ( - "The adapter packs that reach your hardware — POSIX, Windows, FreeRTOS, " - "lwIP, OpenSSL, Mbed TLS, FatFs and more — and the capabilities each fills." - ), "roles/index.md": ( "The twelve vtable contracts SolidSyslog composes against, what fills " "each one, and the Null fallback that keeps an unfilled role safe." ), - "platforms/posix.md": ( - "The POSIX adapter pack — sockets, pthreads, message queues, " - "clock_gettime and stdio — filling six of the library's roles on Linux." - ), - "platforms/windows.md": ( - "The Win32 and Winsock adapter pack for MSVC targets, filling the " - "Resolver, Datagram, Stream, File, Mutex and AtomicCounter roles." - ), - "platforms/freertos.md": ( - "The FreeRTOS adapter pack: kernel primitives filling the Mutex role and " - "the sysUpTime callback, with networking from Plus-TCP or lwIP." - ), - "platforms/plustcp.md": ( - "The FreeRTOS-Plus-TCP adapter pack, filling the Resolver, Datagram and " - "Stream roles for networking on FreeRTOS targets." - ), - "platforms/lwip.md": ( - "The lwIP Raw API adapter pack, filling the Resolver, Datagram and Stream " - "roles — compiled against your lwipopts.h, NO_SYS=1 builds included." - ), - "platforms/openssl.md": ( - "The OpenSSL adapter pack: TLS transport for the Stream role, and keyed " - "at-rest crypto for the SecurityPolicy role, on hosted targets." - ), - "platforms/mbedtls.md": ( - "The Mbed TLS adapter pack for embedded targets: TLS transport for the " - "Stream role, and keyed at-rest crypto for the SecurityPolicy role." - ), - "platforms/fatfs.md": ( - "The ChaN FatFs adapter pack, filling the File role beneath a BlockDevice " - "— RTOS-agnostic, for bare-metal, FreeRTOS, Zephyr and NuttX targets." - ), - "platforms/plusfat.md": ( - "The FreeRTOS-Plus-FAT adapter pack, filling the File role beneath a " - "BlockDevice for an all-FreeRTOS-Plus storage and transport stack." - ), - "platforms/atomics.md": ( - "The portable C11 stdatomic.h AtomicCounter — the sequenceId source on " - "any target with a C11 compiler, with no OS dependency." - ), # Maintaining "builds.md": ( "The contributor build doc: the CMake preset catalogue for developing " diff --git a/mkdocs.yml b/mkdocs.yml index f9ab2fd1..5927555f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -146,11 +146,37 @@ nav: - Adding it to your build: build-integration.md - Structured data: structured-data.md - Error handling: error-severity.md - - Integrating lwIP: integrating-lwip.md - - Integrating Mbed TLS: integrating-mbedtls.md - - Integrating FreeRTOS-Plus-FAT: integrating-plusfat.md - - Port a new platform: - - Porting guide: porting.md + # Platforms answers "will this run on my target" — an adoption question, not a + # reference one, so it is a tab rather than a child of API reference. Porting + # sits at the end as the other half of the same question: nothing shipped fits, + # so write one. Each platform is a section whose index.md navigation.indexes + # promotes into the section link, and whose setup.md is its wiring guide. + - Platforms: + - platforms/index.md + - Posix: + - platforms/posix/index.md + - Windows: + - platforms/windows/index.md + - FreeRTOS: + - platforms/freertos/index.md + - FreeRTOS-Plus-TCP: + - platforms/plustcp/index.md + - lwIP (Raw API): + - platforms/lwipraw/index.md + - Setup: platforms/lwipraw/setup.md + - OpenSSL: + - platforms/openssl/index.md + - Mbed TLS: + - platforms/mbedtls/index.md + - Setup: platforms/mbedtls/setup.md + - FatFs: + - platforms/fatfs/index.md + - FreeRTOS-Plus-FAT: + - platforms/plusfat/index.md + - Setup: platforms/plusfat/setup.md + - C11 atomics: + - platforms/atomics/index.md + - Port a new platform: porting.md - Compliance: - CRA guide: cra.md - IEC 62443 guide: iec62443.md @@ -179,18 +205,6 @@ nav: # child — navigation.indexes promotes it into the section link. - Core: - core/index.md - - Platforms: - - platforms/index.md - - Posix: platforms/posix.md - - Windows: platforms/windows.md - - FreeRTOS: platforms/freertos.md - - FreeRTOS-Plus-TCP: platforms/plustcp.md - - lwIP (Raw API): platforms/lwip.md - - OpenSSL: platforms/openssl.md - - Mbed TLS: platforms/mbedtls.md - - FatFs: platforms/fatfs.md - - FreeRTOS-Plus-FAT: platforms/plusfat.md - - C11 atomics: platforms/atomics.md # A role *is* its vtable, so each entry links straight to the generated # contract page; detail belongs in the SolidSyslogDefinition.h # doc comment, not a hand-maintained copy here. From 626c462d2559c93fa4599f9281b766e05544f44c Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 13:53:34 +0100 Subject: [PATCH 20/57] docs: S23.22 give each platform a Doxygen group, both ways Traversal between the code and the hand-written docs had one direction only, and no stable anchor at all: api/modules.md read "No modules found", and the directory pages sit at content-hash URLs that change whenever a directory moves. Each pack now defines one group in SolidSyslogPlatform.dox at its root, and every public header in the pack carries @ingroup. The group page lists the pack's headers with their briefs and links back to the platform's page; the platform's page gains a Source section linking forward to the group. A reader arriving at either end reaches the other. The group definition lives in a .dox rather than a header so nothing suggests it can be included, and install() globs *.h so none of them ship. The cost is one visible .dox entry per pack in the Headers index. mkdoxy now sweeps Platform whole rather than listing the ten Interface directories, so a new pack's headers and its group are picked up by existing there. EXCLUDE_PATTERNS keeps the private Source/ headers out, which the explicit list had been doing implicitly. Every Platform MISRA suppression is anchored under Source/, so adding lines to Interface/ headers shifts none of them. Part of #708 --- .../Interface/SolidSyslogStdAtomicCounter.h | 4 +++- .../SolidSyslogStdAtomicCounterErrors.h | 4 +++- Platform/Atomics/SolidSyslogAtomicsPlatform.dox | 9 +++++++++ Platform/FatFs/Interface/SolidSyslogFatFsFile.h | 4 +++- .../Interface/SolidSyslogFatFsFileErrors.h | 4 +++- Platform/FatFs/SolidSyslogFatFsPlatform.dox | 8 ++++++++ .../Interface/SolidSyslogFreeRtosMutex.h | 4 +++- .../Interface/SolidSyslogFreeRtosMutexErrors.h | 4 +++- .../Interface/SolidSyslogFreeRtosSysUpTime.h | 4 +++- .../FreeRtos/SolidSyslogFreeRtosPlatform.dox | 9 +++++++++ .../Interface/SolidSyslogLwipRawAddress.h | 4 +++- .../Interface/SolidSyslogLwipRawAddressErrors.h | 4 +++- .../Interface/SolidSyslogLwipRawDatagram.h | 4 +++- .../SolidSyslogLwipRawDatagramErrors.h | 4 +++- .../Interface/SolidSyslogLwipRawDnsResolver.h | 4 +++- .../SolidSyslogLwipRawDnsResolverErrors.h | 4 +++- .../Interface/SolidSyslogLwipRawMarshal.h | 4 +++- .../Interface/SolidSyslogLwipRawResolver.h | 4 +++- .../SolidSyslogLwipRawResolverErrors.h | 4 +++- .../Interface/SolidSyslogLwipRawTcpStream.h | 4 +++- .../SolidSyslogLwipRawTcpStreamErrors.h | 4 +++- Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox | 9 +++++++++ .../Interface/SolidSyslogMbedTlsAesGcmPolicy.h | 4 +++- .../SolidSyslogMbedTlsAesGcmPolicyErrors.h | 4 +++- .../SolidSyslogMbedTlsHmacSha256Policy.h | 4 +++- .../SolidSyslogMbedTlsHmacSha256PolicyErrors.h | 4 +++- .../Interface/SolidSyslogMbedTlsStream.h | 4 +++- .../Interface/SolidSyslogMbedTlsStreamErrors.h | 4 +++- Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox | 9 +++++++++ .../Interface/SolidSyslogOpenSslAesGcmPolicy.h | 4 +++- .../SolidSyslogOpenSslAesGcmPolicyErrors.h | 4 +++- .../SolidSyslogOpenSslHmacSha256Policy.h | 4 +++- .../SolidSyslogOpenSslHmacSha256PolicyErrors.h | 4 +++- .../OpenSsl/Interface/SolidSyslogTlsStream.h | 4 +++- .../Interface/SolidSyslogTlsStreamErrors.h | 4 +++- Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox | 9 +++++++++ .../PlusFat/Interface/SolidSyslogPlusFatFile.h | 4 +++- .../Interface/SolidSyslogPlusFatFileErrors.h | 4 +++- Platform/PlusFat/SolidSyslogPlusFatPlatform.dox | 8 ++++++++ .../Interface/SolidSyslogPlusTcpAddress.h | 4 +++- .../Interface/SolidSyslogPlusTcpAddressErrors.h | 4 +++- .../Interface/SolidSyslogPlusTcpDatagram.h | 4 +++- .../SolidSyslogPlusTcpDatagramErrors.h | 4 +++- .../Interface/SolidSyslogPlusTcpResolver.h | 4 +++- .../SolidSyslogPlusTcpResolverErrors.h | 4 +++- .../Interface/SolidSyslogPlusTcpTcpStream.h | 4 +++- .../SolidSyslogPlusTcpTcpStreamErrors.h | 4 +++- Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox | 8 ++++++++ .../Interface/SolidSyslogGetAddrInfoResolver.h | 4 +++- .../SolidSyslogGetAddrInfoResolverErrors.h | 4 +++- .../Posix/Interface/SolidSyslogPosixAddress.h | 4 +++- .../Interface/SolidSyslogPosixAddressErrors.h | 4 +++- .../Posix/Interface/SolidSyslogPosixClock.h | 4 +++- .../Posix/Interface/SolidSyslogPosixDatagram.h | 4 +++- .../Interface/SolidSyslogPosixDatagramErrors.h | 4 +++- Platform/Posix/Interface/SolidSyslogPosixFile.h | 4 +++- .../Interface/SolidSyslogPosixFileErrors.h | 4 +++- .../Posix/Interface/SolidSyslogPosixHostname.h | 4 +++- .../SolidSyslogPosixMessageQueueBuffer.h | 4 +++- .../SolidSyslogPosixMessageQueueBufferErrors.h | 4 +++- .../Posix/Interface/SolidSyslogPosixMutex.h | 4 +++- .../Interface/SolidSyslogPosixMutexErrors.h | 4 +++- .../Posix/Interface/SolidSyslogPosixProcessId.h | 4 +++- .../Posix/Interface/SolidSyslogPosixSleep.h | 4 +++- .../Posix/Interface/SolidSyslogPosixSysUpTime.h | 4 +++- .../Posix/Interface/SolidSyslogPosixTcpStream.h | 4 +++- .../Interface/SolidSyslogPosixTcpStreamErrors.h | 4 +++- Platform/Posix/SolidSyslogPosixPlatform.dox | 9 +++++++++ .../Interface/SolidSyslogWindowsAtomicCounter.h | 4 +++- .../SolidSyslogWindowsAtomicCounterErrors.h | 4 +++- .../Windows/Interface/SolidSyslogWindowsClock.h | 4 +++- .../Windows/Interface/SolidSyslogWindowsFile.h | 4 +++- .../Interface/SolidSyslogWindowsFileErrors.h | 4 +++- .../Interface/SolidSyslogWindowsHostname.h | 4 +++- .../Windows/Interface/SolidSyslogWindowsMutex.h | 4 +++- .../Interface/SolidSyslogWindowsMutexErrors.h | 4 +++- .../Interface/SolidSyslogWindowsProcessId.h | 4 +++- .../Windows/Interface/SolidSyslogWindowsSleep.h | 4 +++- .../Interface/SolidSyslogWindowsSysUpTime.h | 4 +++- .../Interface/SolidSyslogWinsockAddress.h | 4 +++- .../Interface/SolidSyslogWinsockAddressErrors.h | 4 +++- .../Interface/SolidSyslogWinsockDatagram.h | 4 +++- .../SolidSyslogWinsockDatagramErrors.h | 4 +++- .../Interface/SolidSyslogWinsockResolver.h | 4 +++- .../SolidSyslogWinsockResolverErrors.h | 4 +++- .../Interface/SolidSyslogWinsockTcpStream.h | 4 +++- .../SolidSyslogWinsockTcpStreamErrors.h | 4 +++- Platform/Windows/SolidSyslogWindowsPlatform.dox | 9 +++++++++ docs/platforms/atomics/index.md | 6 ++++++ docs/platforms/fatfs/index.md | 6 ++++++ docs/platforms/freertos/index.md | 6 ++++++ docs/platforms/lwipraw/index.md | 6 ++++++ docs/platforms/mbedtls/index.md | 6 ++++++ docs/platforms/openssl/index.md | 6 ++++++ docs/platforms/plusfat/index.md | 6 ++++++ docs/platforms/plustcp/index.md | 6 ++++++ docs/platforms/posix/index.md | 6 ++++++ docs/platforms/windows/index.md | 6 ++++++ mkdocs.yml | 17 +++++++++++++++-- 99 files changed, 396 insertions(+), 80 deletions(-) create mode 100644 Platform/Atomics/SolidSyslogAtomicsPlatform.dox create mode 100644 Platform/FatFs/SolidSyslogFatFsPlatform.dox create mode 100644 Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox create mode 100644 Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox create mode 100644 Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox create mode 100644 Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox create mode 100644 Platform/PlusFat/SolidSyslogPlusFatPlatform.dox create mode 100644 Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox create mode 100644 Platform/Posix/SolidSyslogPosixPlatform.dox create mode 100644 Platform/Windows/SolidSyslogWindowsPlatform.dox diff --git a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h index afc0c157..553a30a2 100644 --- a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h +++ b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h @@ -2,7 +2,9 @@ * An AtomicCounter over C11 , backing the RFC 5424 sequenceId. * Increment runs an atomic_compare_exchange_strong_explicit CAS loop * on an _Atomic uint32_t; the sequence is wrap-aware in [1, 2^31 - 1] and - * skips zero on wrap, so a returned value is never 0. */ + * skips zero on wrap, so a returned value is never 0. + * + * @ingroup platform_atomics */ #ifndef SOLIDSYSLOGSTDATOMICCOUNTER_H #define SOLIDSYSLOGSTDATOMICCOUNTER_H diff --git a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h index 0f49be64..e42958f7 100644 --- a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h +++ b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the StdAtomicCounter adapter. */ + * Error codes and Source identity for the StdAtomicCounter adapter. + * + * @ingroup platform_atomics */ #ifndef SOLIDSYSLOGSTDATOMICCOUNTERERRORS_H #define SOLIDSYSLOGSTDATOMICCOUNTERERRORS_H diff --git a/Platform/Atomics/SolidSyslogAtomicsPlatform.dox b/Platform/Atomics/SolidSyslogAtomicsPlatform.dox new file mode 100644 index 00000000..9731425b --- /dev/null +++ b/Platform/Atomics/SolidSyslogAtomicsPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_atomics C11 atomics platform + * @brief The AtomicCounter role over — the sequenceId source on any + * target with a C11 compiler, with no OS dependency. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/atomics/ + */ diff --git a/Platform/FatFs/Interface/SolidSyslogFatFsFile.h b/Platform/FatFs/Interface/SolidSyslogFatFsFile.h index 3a3fafc2..5bd5e913 100644 --- a/Platform/FatFs/Interface/SolidSyslogFatFsFile.h +++ b/Platform/FatFs/Interface/SolidSyslogFatFsFile.h @@ -3,7 +3,9 @@ * SolidSyslogFile vtable, for a file-backed BlockDevice or Store. f_sync runs * after every successful write so a power loss never loses a record the * BlockStore already claimed it stored. The integrator supplies diskio.c (and - * ffsystem.c when FF_FS_REENTRANT=1). */ + * ffsystem.c when FF_FS_REENTRANT=1). + * + * @ingroup platform_fatfs */ #ifndef SOLIDSYSLOGFATFSFILE_H #define SOLIDSYSLOGFATFSFILE_H diff --git a/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h b/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h index 7342284f..c916f281 100644 --- a/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h +++ b/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the FatFsFile adapter. */ + * Error codes and Source identity for the FatFsFile adapter. + * + * @ingroup platform_fatfs */ #ifndef SOLIDSYSLOGFATFSFILEERRORS_H #define SOLIDSYSLOGFATFSFILEERRORS_H diff --git a/Platform/FatFs/SolidSyslogFatFsPlatform.dox b/Platform/FatFs/SolidSyslogFatFsPlatform.dox new file mode 100644 index 00000000..5fd349bb --- /dev/null +++ b/Platform/FatFs/SolidSyslogFatFsPlatform.dox @@ -0,0 +1,8 @@ +/** + * @defgroup platform_fatfs FatFs platform + * @brief The File role over ChaN FatFs, beneath a BlockDevice. RTOS-agnostic. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/fatfs/ + */ diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h index 2ab56c67..ea79b0d0 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h @@ -1,7 +1,9 @@ /** @file * A Mutex wrapping a statically-allocated FreeRTOS mutex semaphore, for * thread-safe buffers and pools on a FreeRTOS target. Requires - * configSUPPORT_STATIC_ALLOCATION=1. */ + * configSUPPORT_STATIC_ALLOCATION=1. + * + * @ingroup platform_freertos */ #ifndef SOLIDSYSLOGFREERTOSMUTEX_H #define SOLIDSYSLOGFREERTOSMUTEX_H diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h index 3111c157..42adf7ad 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the FreeRtosMutex adapter. */ + * Error codes and Source identity for the FreeRtosMutex adapter. + * + * @ingroup platform_freertos */ #ifndef SOLIDSYSLOGFREERTOSMUTEXERRORS_H #define SOLIDSYSLOGFREERTOSMUTEXERRORS_H diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h index 65d3c3a8..04111783 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h @@ -1,6 +1,8 @@ /** @file * The FreeRTOS SolidSyslogSysUpTimeFunction, for the MetaSd structured-data - * element. */ + * element. + * + * @ingroup platform_freertos */ #ifndef SOLIDSYSLOGFREERTOSSYSUPTIME_H #define SOLIDSYSLOGFREERTOSSYSUPTIME_H diff --git a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox new file mode 100644 index 00000000..00ffa520 --- /dev/null +++ b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_freertos FreeRTOS platform + * @brief Kernel primitives: the Mutex role and the sysUpTime callback. Networking + * comes from FreeRTOS-Plus-TCP or lwIP alongside. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/freertos/ + */ diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h index f15eb7ea..466dc5fc 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h @@ -3,7 +3,9 @@ * * A Resolver writes the resolved endpoint into it; a Datagram or TcpStream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. */ + * object. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWADDRESS_H #define SOLIDSYSLOGLWIPRAWADDRESS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h index 3806acc3..c0112797 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the LwipRawAddress adapter. */ + * Error codes and Source identity for the LwipRawAddress adapter. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWADDRESSERRORS_H #define SOLIDSYSLOGLWIPRAWADDRESSERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h index 14b46dbb..5aae31cd 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h @@ -11,7 +11,9 @@ * caller's buffer instead of copying it, safe because the buffer outlives the * synchronous hop. Reports SENT on udp_sendto success, else FAILED. Cache-miss * recovery is left to lwIP's ARP_QUEUEING. - * - MaxPayload returns the IPv6-safe default. */ + * - MaxPayload returns the IPv6-safe default. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWDATAGRAM_H #define SOLIDSYSLOGLWIPRAWDATAGRAM_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h index b2c665cc..3cfa97a5 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the LwipRawDatagram adapter. */ + * Error codes and Source identity for the LwipRawDatagram adapter. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWDATAGRAMERRORS_H #define SOLIDSYSLOGLWIPRAWDATAGRAMERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h index 4036bfd3..10a57663 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h @@ -18,7 +18,9 @@ * - Any other immediate rejection, or the deadline elapsing, fails the Resolve * so the caller's unresolved-host error path runs. * - * The transport is ignored. Requires LWIP_DNS=1. See docs/platforms/lwipraw/setup.md. */ + * The transport is ignored. Requires LWIP_DNS=1. See docs/platforms/lwipraw/setup.md. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWDNSRESOLVER_H #define SOLIDSYSLOGLWIPRAWDNSRESOLVER_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h index 57adbd8b..63c2dfd0 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the LwipRawDnsResolver adapter. */ + * Error codes and Source identity for the LwipRawDnsResolver adapter. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWDNSRESOLVERERRORS_H #define SOLIDSYSLOGLWIPRAWDNSRESOLVERERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h index 3fad20aa..e53ab3a8 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h @@ -16,7 +16,9 @@ * The marshal MUST invoke its callback synchronously, before it returns: the * wrapper reads results the callback writes immediately after the hop, so an * asynchronous marshal is caller error. tcpip_callback_with_block(.., block=1) - * honours this; a bare tcpip_callback(..) does not. See docs/platforms/lwipraw/setup.md. */ + * honours this; a bare tcpip_callback(..) does not. See docs/platforms/lwipraw/setup.md. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWMARSHAL_H #define SOLIDSYSLOGLWIPRAWMARSHAL_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h index a67defd1..d654d8c7 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h @@ -6,7 +6,9 @@ * whatever ipaddr_aton accepts is accepted, whatever it rejects (DNS names, * the empty string, and other non-address text) fails the Resolve, so the caller's * unresolved-host error path runs. The transport is ignored. The parse touches - * no lwIP core state, so unlike the DNS sibling it takes no marshal hop. */ + * no lwIP core state, so unlike the DNS sibling it takes no marshal hop. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWRESOLVER_H #define SOLIDSYSLOGLWIPRAWRESOLVER_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h index 1d066585..b577f0bb 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the LwipRawResolver adapter. */ + * Error codes and Source identity for the LwipRawResolver adapter. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWRESOLVERERRORS_H #define SOLIDSYSLOGLWIPRAWRESOLVERERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h index b02d8271..44164ff7 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h @@ -28,7 +28,9 @@ * callback nulls the pcb pointer, and Close only calls tcp_close when the * pointer is still live, so a released pcb is never closed twice. Accepted * pbufs are always freed on close regardless of pcb state. See - * docs/platforms/lwipraw/setup.md for the full integrator guide. */ + * docs/platforms/lwipraw/setup.md for the full integrator guide. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWTCPSTREAM_H #define SOLIDSYSLOGLWIPRAWTCPSTREAM_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h index 556adb41..b7adcdde 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the LwipRawTcpStream adapter. */ + * Error codes and Source identity for the LwipRawTcpStream adapter. + * + * @ingroup platform_lwipraw */ #ifndef SOLIDSYSLOGLWIPRAWTCPSTREAMERRORS_H #define SOLIDSYSLOGLWIPRAWTCPSTREAMERRORS_H diff --git a/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox b/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox new file mode 100644 index 00000000..6000f81c --- /dev/null +++ b/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_lwipraw lwIP (Raw API) platform + * @brief Resolver, Datagram and Stream over the lwIP Raw API, compiled against your + * lwipopts.h. Wraps the Raw API specifically, not netconn or sockets. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/lwipraw/ + */ diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h index 0f3a8f10..b6f3d883 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h @@ -14,7 +14,9 @@ * and returns false silently; only a genuine mbedTLS fault is reported. * * The key is fetched on demand via GetKey and wiped after every operation — it - * is never stored on the instance. */ + * is never stored on the instance. + * + * @ingroup platform_mbedtls */ #ifndef SOLIDSYSLOGMBEDTLSAESGCMPOLICY_H #define SOLIDSYSLOGMBEDTLSAESGCMPOLICY_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h index f6f8413f..9541e313 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the MbedTlsAesGcmPolicy. */ + * Error codes and Source identity for the MbedTlsAesGcmPolicy. + * + * @ingroup platform_mbedtls */ #ifndef SOLIDSYSLOGMBEDTLSAESGCMPOLICYERRORS_H #define SOLIDSYSLOGMBEDTLSAESGCMPOLICYERRORS_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h index 53785390..565d56b8 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h @@ -14,7 +14,9 @@ * expected tamper verdict — and is not reported. * * The key is fetched on demand via GetKey and wiped after every computation — it - * is never stored on the instance. */ + * is never stored on the instance. + * + * @ingroup platform_mbedtls */ #ifndef SOLIDSYSLOGMBEDTLSHMACSHA256POLICY_H #define SOLIDSYSLOGMBEDTLSHMACSHA256POLICY_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h index c8ae3010..f3ae9c3b 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the MbedTlsHmacSha256Policy. */ + * Error codes and Source identity for the MbedTlsHmacSha256Policy. + * + * @ingroup platform_mbedtls */ #ifndef SOLIDSYSLOGMBEDTLSHMACSHA256POLICYERRORS_H #define SOLIDSYSLOGMBEDTLSHMACSHA256POLICYERRORS_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index a59b0d49..18cbea50 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -27,7 +27,9 @@ * ssl_config / ssl_context state and never calls process-global mbedTLS APIs * (platform setup/teardown, psa_crypto_init, threading-alt, debug hooks), so it * drops into an integrator process that already uses Mbed TLS elsewhere. See - * docs/platforms/mbedtls/setup.md. */ + * docs/platforms/mbedtls/setup.md. + * + * @ingroup platform_mbedtls */ #ifndef SOLIDSYSLOGMBEDTLSSTREAM_H #define SOLIDSYSLOGMBEDTLSSTREAM_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index cdd68d70..a24f7edc 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the MbedTlsStream adapter. */ + * Error codes and Source identity for the MbedTlsStream adapter. + * + * @ingroup platform_mbedtls */ #ifndef SOLIDSYSLOGMBEDTLSSTREAMERRORS_H #define SOLIDSYSLOGMBEDTLSSTREAMERRORS_H diff --git a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox new file mode 100644 index 00000000..25ce9601 --- /dev/null +++ b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_mbedtls Mbed TLS platform + * @brief TLS transport and keyed at-rest crypto via Mbed TLS, for embedded targets + * where OpenSSL is too heavy. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/mbedtls/ + */ diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h index 1643d42a..66d49172 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h @@ -15,7 +15,9 @@ * - OpenRecord decrypts the body and verifies the tag over header + ciphertext. * A tag mismatch is the expected tamper-detected outcome and returns false * silently; only a genuine OpenSSL error is reported. The key is likewise - * fetched on demand and wiped. */ + * fetched on demand and wiped. + * + * @ingroup platform_openssl */ #ifndef SOLIDSYSLOGOPENSSLAESGCMPOLICY_H #define SOLIDSYSLOGOPENSSLAESGCMPOLICY_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h index bef34848..21e49257 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the OpenSslAesGcmPolicy adapter. */ + * Error codes and Source identity for the OpenSslAesGcmPolicy adapter. + * + * @ingroup platform_openssl */ #ifndef SOLIDSYSLOGOPENSSLAESGCMPOLICYERRORS_H #define SOLIDSYSLOGOPENSSLAESGCMPOLICYERRORS_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h index b7d40d0d..e992822d 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h @@ -14,7 +14,9 @@ * - It fails closed — returns false, so nothing is sealed / verified — if the * key is unavailable, shorter than 32 bytes (RFC 2104 / NIST SP 800-107: an * HMAC key should be at least the hash output length), or the HMAC computation - * fails. */ + * fails. + * + * @ingroup platform_openssl */ #ifndef SOLIDSYSLOGOPENSSLHMACSHA256POLICY_H #define SOLIDSYSLOGOPENSSLHMACSHA256POLICY_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h index 05caa880..b7b116bd 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the OpenSslHmacSha256Policy adapter. */ + * Error codes and Source identity for the OpenSslHmacSha256Policy adapter. + * + * @ingroup platform_openssl */ #ifndef SOLIDSYSLOGOPENSSLHMACSHA256POLICYERRORS_H #define SOLIDSYSLOGOPENSSLHMACSHA256POLICYERRORS_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h index aa282160..f4fe9f86 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h @@ -21,7 +21,9 @@ * - Read returns the bytes read, 0 for would-block (WANT_READ, connection kept), * or closes on anything else — including a mid-stream WANT_WRITE (renegotiation) * which fail-fast semantics treat as a transport failure; store-and-forward - * replays after the reopen. */ + * replays after the reopen. + * + * @ingroup platform_openssl */ #ifndef SOLIDSYSLOGTLSSTREAM_H #define SOLIDSYSLOGTLSSTREAM_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h index 5d1287ea..affada9a 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the TlsStream adapter. */ + * Error codes and Source identity for the TlsStream adapter. + * + * @ingroup platform_openssl */ #ifndef SOLIDSYSLOGTLSSTREAMERRORS_H #define SOLIDSYSLOGTLSSTREAMERRORS_H diff --git a/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox b/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox new file mode 100644 index 00000000..305d22ca --- /dev/null +++ b/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_openssl OpenSSL platform + * @brief TLS transport for the Stream role and keyed at-rest crypto for the + * SecurityPolicy role, on hosted targets. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/openssl/ + */ diff --git a/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h b/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h index cc6c4b52..f4443809 100644 --- a/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h +++ b/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h @@ -10,7 +10,9 @@ * so a power loss never loses a record the BlockStore was told had been * stored. Open-state is carried by the FF_FILE* sentinel — no separate flag. * Plus-FAT is FreeRTOS-coupled; the integrator supplies the FF_Disk_t media - * driver and FreeRTOSFATConfig.h. */ + * driver and FreeRTOSFATConfig.h. + * + * @ingroup platform_plusfat */ #ifndef SOLIDSYSLOGPLUSFATFILE_H #define SOLIDSYSLOGPLUSFATFILE_H diff --git a/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h b/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h index eac2b9ad..cc7340fa 100644 --- a/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h +++ b/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PlusFatFile adapter. */ + * Error codes and Source identity for the PlusFatFile adapter. + * + * @ingroup platform_plusfat */ #ifndef SOLIDSYSLOGPLUSFATFILEERRORS_H #define SOLIDSYSLOGPLUSFATFILEERRORS_H diff --git a/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox b/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox new file mode 100644 index 00000000..52c5344c --- /dev/null +++ b/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox @@ -0,0 +1,8 @@ +/** + * @defgroup platform_plusfat FreeRTOS-Plus-FAT platform + * @brief The File role over FreeRTOS-Plus-FAT's ff_stdio, beneath a BlockDevice. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/plusfat/ + */ diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h index 3c259289..758d441d 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h @@ -4,7 +4,9 @@ * * A Resolver writes the resolved IPv4 endpoint into it; a Datagram or Stream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. */ + * object. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPADDRESS_H #define SOLIDSYSLOGPLUSTCPADDRESS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h index 2ed03eb9..c19717a0 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PlusTcpAddress adapter. */ + * Error codes and Source identity for the PlusTcpAddress adapter. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPADDRESSERRORS_H #define SOLIDSYSLOGPLUSTCPADDRESSERRORS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h index 03ccac9a..fd939a03 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h @@ -8,7 +8,9 @@ * cold-start packet would otherwise be dropped at the IP layer; if the reply * is late the send is left to fail, since UDP is best-effort and retry belongs * in the store-and-forward layer above. MaxPayload is the fixed IPv6-safe - * default. */ + * default. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPDATAGRAM_H #define SOLIDSYSLOGPLUSTCPDATAGRAM_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h index 44fea6cc..ddbf68e3 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PlusTcpDatagram adapter. */ + * Error codes and Source identity for the PlusTcpDatagram adapter. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPDATAGRAMERRORS_H #define SOLIDSYSLOGPLUSTCPDATAGRAMERRORS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h index 129b9b12..b567d539 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h @@ -5,7 +5,9 @@ * FreeRTOS_getaddrinfo and writes it, with the per-call port, into the * destination SolidSyslogAddress; the requested transport selects the socktype * hint (stream for TCP, datagram otherwise). A failed lookup returns false, so - * the caller's unresolved-host error path runs. */ + * the caller's unresolved-host error path runs. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPRESOLVER_H #define SOLIDSYSLOGPLUSTCPRESOLVER_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h index fff35238..cf7b4656 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PlusTcpResolver adapter. */ + * Error codes and Source identity for the PlusTcpResolver adapter. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPRESOLVERERRORS_H #define SOLIDSYSLOGPLUSTCPRESOLVERERRORS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h index 169d4726..ff34a3ff 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h @@ -15,7 +15,9 @@ * or any error is taken as a dead connection, so the stream closes itself and * the sender reconnects on its next pass. * - Read returns the bytes read, 0 for would-block (RCVTIMEO=0, connection - * kept), or tears the connection down on error. */ + * kept), or tears the connection down on error. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPTCPSTREAM_H #define SOLIDSYSLOGPLUSTCPTCPSTREAM_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h index 16f0933f..4d258c6c 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PlusTcpTcpStream adapter. */ + * Error codes and Source identity for the PlusTcpTcpStream adapter. + * + * @ingroup platform_plustcp */ #ifndef SOLIDSYSLOGPLUSTCPTCPSTREAMERRORS_H #define SOLIDSYSLOGPLUSTCPTCPSTREAMERRORS_H diff --git a/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox b/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox new file mode 100644 index 00000000..b9e98648 --- /dev/null +++ b/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox @@ -0,0 +1,8 @@ +/** + * @defgroup platform_plustcp FreeRTOS-Plus-TCP platform + * @brief Resolver, Datagram and Stream over the FreeRTOS-Plus-TCP stack. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/plustcp/ + */ diff --git a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h index 824d60bd..352ebd43 100644 --- a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h +++ b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h @@ -5,7 +5,9 @@ * synchronous getaddrinfo call and writes it into the destination * SolidSyslogAddress; the requested transport selects the socktype hint. A * failed lookup returns false, so the caller's unresolved-host error path - * runs. */ + * runs. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGGETADDRINFORESOLVERH #define SOLIDSYSLOGGETADDRINFORESOLVERH diff --git a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h index 31a45333..4dfcccc7 100644 --- a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h +++ b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the GetAddrInfoResolver adapter. */ + * Error codes and Source identity for the GetAddrInfoResolver adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGGETADDRINFORESOLVERERRORS_H #define SOLIDSYSLOGGETADDRINFORESOLVERERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixAddress.h b/Platform/Posix/Interface/SolidSyslogPosixAddress.h index 4d03a6ca..d508e09b 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixAddress.h +++ b/Platform/Posix/Interface/SolidSyslogPosixAddress.h @@ -3,7 +3,9 @@ * * A Resolver writes the resolved IPv4 endpoint into it; a Datagram or Stream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. */ + * object. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXADDRESS_H #define SOLIDSYSLOGPOSIXADDRESS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h b/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h index 35be3051..c78ac880 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PosixAddress adapter. */ + * Error codes and Source identity for the PosixAddress adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXADDRESSERRORS_H #define SOLIDSYSLOGPOSIXADDRESSERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixClock.h b/Platform/Posix/Interface/SolidSyslogPosixClock.h index 3015a7db..4186ada6 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixClock.h +++ b/Platform/Posix/Interface/SolidSyslogPosixClock.h @@ -1,5 +1,7 @@ /** @file - * The POSIX SolidSyslogClockFunction, for SolidSyslogConfig.Clock. */ + * The POSIX SolidSyslogClockFunction, for SolidSyslogConfig.Clock. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXCLOCK_H #define SOLIDSYSLOGPOSIXCLOCK_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixDatagram.h b/Platform/Posix/Interface/SolidSyslogPosixDatagram.h index e198f3d4..df521cb5 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixDatagram.h +++ b/Platform/Posix/Interface/SolidSyslogPosixDatagram.h @@ -5,7 +5,9 @@ * UDP) and turns on path-MTU discovery (IP_PMTUDISC_DO). SendTo then reports * SENT, OVERSIZE (the datagram exceeds the path MTU, EMSGSIZE), or FAILED. * MaxPayload returns the IPv6-safe default until connected, then tracks the - * discovered path MTU. */ + * discovered path MTU. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXDATAGRAM_H #define SOLIDSYSLOGPOSIXDATAGRAM_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h b/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h index 36430901..1d357260 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PosixDatagram adapter. */ + * Error codes and Source identity for the PosixDatagram adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXDATAGRAMERRORS_H #define SOLIDSYSLOGPOSIXDATAGRAMERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixFile.h b/Platform/Posix/Interface/SolidSyslogPosixFile.h index 0c9fc51c..93fa9cf5 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixFile.h +++ b/Platform/Posix/Interface/SolidSyslogPosixFile.h @@ -1,6 +1,8 @@ /** @file * POSIX file I/O (open / read / write / lseek / ftruncate) behind the - * SolidSyslogFile vtable, for a file-backed BlockDevice or Store. */ + * SolidSyslogFile vtable, for a file-backed BlockDevice or Store. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXFILE_H #define SOLIDSYSLOGPOSIXFILE_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h b/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h index 7c3796db..36600868 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PosixFile adapter. */ + * Error codes and Source identity for the PosixFile adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXFILEERRORS_H #define SOLIDSYSLOGPOSIXFILEERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixHostname.h b/Platform/Posix/Interface/SolidSyslogPosixHostname.h index e2fa8f50..0f212ed8 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixHostname.h +++ b/Platform/Posix/Interface/SolidSyslogPosixHostname.h @@ -1,6 +1,8 @@ /** @file * The POSIX SolidSyslogHeaderFieldFunction for RFC 5424 HOSTNAME, for - * SolidSyslogConfig.GetHostname. */ + * SolidSyslogConfig.GetHostname. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXHOSTNAME_H #define SOLIDSYSLOGPOSIXHOSTNAME_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h index 926442b2..77e7464d 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h @@ -6,7 +6,9 @@ * error when the queue is full, so SolidSyslog_Log never blocks; Read * (mq_receive) is a non-blocking poll where an empty queue is silent and any * other failure is reported. Each pool slot owns a distinct queue - * /solidsyslog__, unlinked on Destroy. */ + * /solidsyslog__, unlinked on Destroy. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFER_H #define SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFER_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h index 4cd53601..9ea4fd13 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PosixMessageQueueBuffer adapter. */ + * Error codes and Source identity for the PosixMessageQueueBuffer adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERERRORS_H #define SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMutex.h b/Platform/Posix/Interface/SolidSyslogPosixMutex.h index 683cd4ca..1e02a2b4 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMutex.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMutex.h @@ -1,6 +1,8 @@ /** @file * A Mutex wrapping pthread_mutex_t, for thread-safe buffers and pools on a - * POSIX host. */ + * POSIX host. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXMUTEX_H #define SOLIDSYSLOGPOSIXMUTEX_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h b/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h index 8a4b03a6..91039450 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PosixMutex adapter. */ + * Error codes and Source identity for the PosixMutex adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXMUTEXERRORS_H #define SOLIDSYSLOGPOSIXMUTEXERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixProcessId.h b/Platform/Posix/Interface/SolidSyslogPosixProcessId.h index 9c58e74c..25ceda21 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixProcessId.h +++ b/Platform/Posix/Interface/SolidSyslogPosixProcessId.h @@ -1,6 +1,8 @@ /** @file * The POSIX SolidSyslogHeaderFieldFunction for RFC 5424 PROCID, for - * SolidSyslogConfig.GetProcessId. */ + * SolidSyslogConfig.GetProcessId. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXPROCESSID_H #define SOLIDSYSLOGPOSIXPROCESSID_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixSleep.h b/Platform/Posix/Interface/SolidSyslogPosixSleep.h index 3f50b3b0..68bcc86a 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixSleep.h +++ b/Platform/Posix/Interface/SolidSyslogPosixSleep.h @@ -1,5 +1,7 @@ /** @file - * The POSIX SolidSyslogSleepFunction. */ + * The POSIX SolidSyslogSleepFunction. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXSLEEP_H #define SOLIDSYSLOGPOSIXSLEEP_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h b/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h index 3d9bc7f9..8e960e42 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h +++ b/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h @@ -1,5 +1,7 @@ /** @file - * The POSIX SolidSyslogSysUpTimeFunction, for MetaSd. */ + * The POSIX SolidSyslogSysUpTimeFunction, for MetaSd. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXSYSUPTIME_H #define SOLIDSYSLOGPOSIXSYSUPTIME_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h index d443d2cf..8d37a12e 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h @@ -16,7 +16,9 @@ * * TCP_NODELAY is on, and kernel keepalive (idle ~45s, then 4 x 10s probes) plus * TCP_USER_TIMEOUT (30s) on unacked writes surface a wedged peer as a failed - * Send/Read rather than a hung service thread. */ + * Send/Read rather than a hung service thread. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXTCPSTREAM_H #define SOLIDSYSLOGPOSIXTCPSTREAM_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h index 06bf09f8..a55fc70b 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the PosixTcpStream adapter. */ + * Error codes and Source identity for the PosixTcpStream adapter. + * + * @ingroup platform_posix */ #ifndef SOLIDSYSLOGPOSIXTCPSTREAMERRORS_H #define SOLIDSYSLOGPOSIXTCPSTREAMERRORS_H diff --git a/Platform/Posix/SolidSyslogPosixPlatform.dox b/Platform/Posix/SolidSyslogPosixPlatform.dox new file mode 100644 index 00000000..b4057d99 --- /dev/null +++ b/Platform/Posix/SolidSyslogPosixPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_posix POSIX platform + * @brief Sockets, name resolution, files, mutexes, message queues and the clock on + * POSIX hosts. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/posix/ + */ diff --git a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h index 468dfecc..5f9b2e51 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h @@ -3,7 +3,9 @@ * sequenceId on Windows targets without C11 (legacy MSVC). * Increment runs a lock-free InterlockedCompareExchange CAS loop on a * volatile LONG; the sequence is wrap-aware in [1, 2^31 - 1] and skips zero on - * wrap, so a returned value is never 0. */ + * wrap, so a returned value is never 0. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSATOMICCOUNTER_H #define SOLIDSYSLOGWINDOWSATOMICCOUNTER_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h index 8851766e..e89c95a2 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WindowsAtomicCounter adapter. */ + * Error codes and Source identity for the WindowsAtomicCounter adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSATOMICCOUNTERERRORS_H #define SOLIDSYSLOGWINDOWSATOMICCOUNTERERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsClock.h b/Platform/Windows/Interface/SolidSyslogWindowsClock.h index 902e1229..9450fe96 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsClock.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsClock.h @@ -1,5 +1,7 @@ /** @file - * The Windows SolidSyslogClockFunction, for SolidSyslogConfig.Clock. */ + * The Windows SolidSyslogClockFunction, for SolidSyslogConfig.Clock. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSCLOCK_H #define SOLIDSYSLOGWINDOWSCLOCK_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsFile.h b/Platform/Windows/Interface/SolidSyslogWindowsFile.h index cdbdec47..8dde19fe 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsFile.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsFile.h @@ -4,7 +4,9 @@ * or Store. * * Files open in binary mode (_O_BINARY) so the CRT's CR/LF translation never - * corrupts arbitrary bytes — BlockStore frames round-trip unchanged. */ + * corrupts arbitrary bytes — BlockStore frames round-trip unchanged. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSFILE_H #define SOLIDSYSLOGWINDOWSFILE_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h b/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h index 8e6aa764..5d0c782e 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WindowsFile adapter. */ + * Error codes and Source identity for the WindowsFile adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSFILEERRORS_H #define SOLIDSYSLOGWINDOWSFILEERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsHostname.h b/Platform/Windows/Interface/SolidSyslogWindowsHostname.h index ec7fc883..ad29596e 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsHostname.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsHostname.h @@ -1,6 +1,8 @@ /** @file * The Windows SolidSyslogHeaderFieldFunction for RFC 5424 HOSTNAME, for - * SolidSyslogConfig.GetHostname. */ + * SolidSyslogConfig.GetHostname. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSHOSTNAME_H #define SOLIDSYSLOGWINDOWSHOSTNAME_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsMutex.h b/Platform/Windows/Interface/SolidSyslogWindowsMutex.h index 0c86d4fa..3510bc17 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsMutex.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsMutex.h @@ -1,6 +1,8 @@ /** @file * A Mutex wrapping a Windows CRITICAL_SECTION, for thread-safe buffers and - * pools on a Windows host. */ + * pools on a Windows host. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSMUTEX_H #define SOLIDSYSLOGWINDOWSMUTEX_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h b/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h index df1454fa..7b35a518 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WindowsMutex adapter. */ + * Error codes and Source identity for the WindowsMutex adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSMUTEXERRORS_H #define SOLIDSYSLOGWINDOWSMUTEXERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h b/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h index 75b00836..63ba5f2a 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h @@ -1,6 +1,8 @@ /** @file * The Windows SolidSyslogHeaderFieldFunction for RFC 5424 PROCID, for - * SolidSyslogConfig.GetProcessId. */ + * SolidSyslogConfig.GetProcessId. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSPROCESSID_H #define SOLIDSYSLOGWINDOWSPROCESSID_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsSleep.h b/Platform/Windows/Interface/SolidSyslogWindowsSleep.h index f888ecca..02ab7ac9 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsSleep.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsSleep.h @@ -1,5 +1,7 @@ /** @file - * The Windows SolidSyslogSleepFunction. */ + * The Windows SolidSyslogSleepFunction. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSSLEEP_H #define SOLIDSYSLOGWINDOWSSLEEP_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h b/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h index 72be2918..c254e96f 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h @@ -1,5 +1,7 @@ /** @file - * The Windows SolidSyslogSysUpTimeFunction, for MetaSd. */ + * The Windows SolidSyslogSysUpTimeFunction, for MetaSd. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINDOWSSYSUPTIME_H #define SOLIDSYSLOGWINDOWSSYSUPTIME_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockAddress.h b/Platform/Windows/Interface/SolidSyslogWinsockAddress.h index 9690dea7..dba4877a 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockAddress.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockAddress.h @@ -3,7 +3,9 @@ * * A Resolver writes the resolved IPv4 endpoint into it; a Datagram or Stream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. */ + * object. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKADDRESS_H #define SOLIDSYSLOGWINSOCKADDRESS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h index 59f9bd13..8a7f0fea 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WinsockAddress adapter. */ + * Error codes and Source identity for the WinsockAddress adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKADDRESSERRORS_H #define SOLIDSYSLOGWINSOCKADDRESSERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h b/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h index 5d3325e2..7af769a8 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h @@ -8,7 +8,9 @@ * connected, then tracks the discovered path MTU (IP_MTU). * * The caller must invoke WSAStartup before use and WSACleanup on shutdown; the - * library does not manage the Winsock lifecycle. */ + * library does not manage the Winsock lifecycle. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKDATAGRAM_H #define SOLIDSYSLOGWINSOCKDATAGRAM_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h index a44f5c9f..f21e314c 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WinsockDatagram adapter. */ + * Error codes and Source identity for the WinsockDatagram adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKDATAGRAMERRORS_H #define SOLIDSYSLOGWINSOCKDATAGRAMERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockResolver.h b/Platform/Windows/Interface/SolidSyslogWinsockResolver.h index d4f15d30..42d64b21 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockResolver.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockResolver.h @@ -7,7 +7,9 @@ * failed lookup returns false, so the caller's unresolved-host error path runs. * * The caller must invoke WSAStartup before use and WSACleanup on shutdown; the - * library does not manage the Winsock lifecycle. */ + * library does not manage the Winsock lifecycle. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKRESOLVERH #define SOLIDSYSLOGWINSOCKRESOLVERH diff --git a/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h index 864043b4..1112dc13 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WinsockResolver adapter. */ + * Error codes and Source identity for the WinsockResolver adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKRESOLVERERRORS_H #define SOLIDSYSLOGWINSOCKRESOLVERERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h index cbb8c08b..9af59048 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h @@ -23,7 +23,9 @@ * OS-default retransmit behaviour. * * The caller must invoke WSAStartup before use and WSACleanup on shutdown; the - * library does not manage the Winsock lifecycle. */ + * library does not manage the Winsock lifecycle. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKTCPSTREAM_H #define SOLIDSYSLOGWINSOCKTCPSTREAM_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h index 41929b99..3ac95753 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h @@ -1,5 +1,7 @@ /** @file - * Error codes and Source identity for the WinsockTcpStream adapter. */ + * Error codes and Source identity for the WinsockTcpStream adapter. + * + * @ingroup platform_windows */ #ifndef SOLIDSYSLOGWINSOCKTCPSTREAMERRORS_H #define SOLIDSYSLOGWINSOCKTCPSTREAMERRORS_H diff --git a/Platform/Windows/SolidSyslogWindowsPlatform.dox b/Platform/Windows/SolidSyslogWindowsPlatform.dox new file mode 100644 index 00000000..47b76646 --- /dev/null +++ b/Platform/Windows/SolidSyslogWindowsPlatform.dox @@ -0,0 +1,9 @@ +/** + * @defgroup platform_windows Windows platform + * @brief Winsock networking plus Win32 files, mutexes, atomics and the clock on + * MSVC targets. + * + * What the pack ships, what your build must provide, the security behaviour + * it guarantees and the obligations it leaves to you: + * https://docs.cososo.co.uk/solid-syslog/platforms/windows/ + */ diff --git a/docs/platforms/atomics/index.md b/docs/platforms/atomics/index.md index dcae5744..7302dc8a 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/atomics/index.md @@ -15,3 +15,9 @@ Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. A C11 compiler with ``. Windows toolchains without it use [`SolidSyslogWindowsAtomicCounter`](../windows/index.md) instead. + +## Source + +[Every class in this pack](../../api/group__platform__atomics.md), generated +from the headers. The code itself is +[`Platform/Atomics/`](../../../Platform/Atomics/). diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index 14ddd158..03273f7b 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -17,3 +17,9 @@ BlockDevice. Your `ffconf.h`, a `diskio.c` media driver, and — if `FF_FS_REENTRANT=1` — an `ffsystem.c`. + +## Source + +[Every class in this pack](../../api/group__platform__fatfs.md), generated +from the headers. The code itself is +[`Platform/FatFs/`](../../../Platform/FatFs/). diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md index 824a6358..a4a09dec 100644 --- a/docs/platforms/freertos/index.md +++ b/docs/platforms/freertos/index.md @@ -17,3 +17,9 @@ Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. ## Requirements `configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. + +## Source + +[Every class in this pack](../../api/group__platform__freertos.md), generated +from the headers. The code itself is +[`Platform/FreeRtos/`](../../../Platform/FreeRtos/). diff --git a/docs/platforms/lwipraw/index.md b/docs/platforms/lwipraw/index.md index 39e3c215..98501d94 100644 --- a/docs/platforms/lwipraw/index.md +++ b/docs/platforms/lwipraw/index.md @@ -56,3 +56,9 @@ dropped) and `LWIP_TCP_KEEPALIVE=1`, and size `PBUF_POOL_SIZE` / `MEMP_NUM_TCP_PCB` / `MEMP_NUM_UDP_PCB` to your instance counts. Full setup — config, marshal, DNS — is [Integrating lwIP](setup.md). + +## Source + +[Every class in this pack](../../api/group__platform__lwipraw.md), generated +from the headers. The code itself is +[`Platform/LwipRaw/`](../../../Platform/LwipRaw/). diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index 0124ed42..0bc6c72f 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -23,3 +23,9 @@ file paths. `Platform/MbedTls/Source/` never calls process-global Mbed TLS APIs coexists with your other Mbed TLS use. Full setup is [Integrating Mbed TLS](setup.md). + +## Source + +[Every class in this pack](../../api/group__platform__mbedtls.md), generated +from the headers. The code itself is +[`Platform/MbedTls/`](../../../Platform/MbedTls/). diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 15f0fdbe..28ec9de1 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -18,3 +18,9 @@ integrity. ## Requirements OpenSSL 3.0 or later. + +## Source + +[Every class in this pack](../../api/group__platform__openssl.md), generated +from the headers. The code itself is +[`Platform/OpenSsl/`](../../../Platform/OpenSsl/). diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index c340555f..e73ae9bd 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -18,3 +18,9 @@ FreeRTOS — Plus-FAT is FreeRTOS-coupled. Supply an `FF_Disk_t` media driver an `FreeRTOSFATConfig.h`. Full setup is [Integrating FreeRTOS-Plus-FAT](setup.md). + +## Source + +[Every class in this pack](../../api/group__platform__plusfat.md), generated +from the headers. The code itself is +[`Platform/PlusFat/`](../../../Platform/PlusFat/). diff --git a/docs/platforms/plustcp/index.md b/docs/platforms/plustcp/index.md index 34b44576..6ad155b8 100644 --- a/docs/platforms/plustcp/index.md +++ b/docs/platforms/plustcp/index.md @@ -20,3 +20,9 @@ address handle they share. FreeRTOS-Plus-TCP, selected at CMake time with naming `PlusTcp` in `SOLIDSYSLOG_PLATFORMS`. The resolver wraps `FreeRTOS_getaddrinfo`, so your `FreeRTOSIPConfig.h` needs `ipconfigUSE_DNS=1`. + +## Source + +[Every class in this pack](../../api/group__platform__plustcp.md), generated +from the headers. The code itself is +[`Platform/PlusTcp/`](../../../Platform/PlusTcp/). diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md index b97fa1d2..c901d84b 100644 --- a/docs/platforms/posix/index.md +++ b/docs/platforms/posix/index.md @@ -30,3 +30,9 @@ callbacks. A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs POSIX message queues (link `-lrt` on glibc). + +## Source + +[Every class in this pack](../../api/group__platform__posix.md), generated +from the headers. The code itself is +[`Platform/Posix/`](../../../Platform/Posix/). diff --git a/docs/platforms/windows/index.md b/docs/platforms/windows/index.md index e4af0132..27af1285 100644 --- a/docs/platforms/windows/index.md +++ b/docs/platforms/windows/index.md @@ -28,3 +28,9 @@ callbacks. The MSVC toolchain and Winsock — call `WSAStartup` once at process init before creating a sender. + +## Source + +[Every class in this pack](../../api/group__platform__windows.md), generated +from the headers. The code itself is +[`Platform/Windows/`](../../../Platform/Windows/). diff --git a/mkdocs.yml b/mkdocs.yml index 5927555f..bcf43fa1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -108,11 +108,20 @@ plugins: - mkdoxy: projects: api: - src-dirs: "Core/Interface Platform/Atomics/Interface Platform/FatFs/Interface Platform/FreeRtos/Interface Platform/LwipRaw/Interface Platform/MbedTls/Interface Platform/OpenSsl/Interface Platform/PlusFat/Interface Platform/PlusTcp/Interface Platform/Posix/Interface Platform/Windows/Interface" + # Platform is swept whole rather than listed pack by pack, so a new + # platform's headers and its group file are picked up by existing + # there. EXCLUDE_PATTERNS below keeps the private Source/ headers out. + src-dirs: "Core/Interface Platform" full-doc: true doxy-cfg: - FILE_PATTERNS: "*.h" + # *.dox carries the per-platform @defgroup blocks — see the + # SolidSyslogPlatform.dox file at each pack's root. They are + # documentation only: install() globs *.h, so none of them ship. + FILE_PATTERNS: "*.h *.dox" RECURSIVE: true + # Platform//Source/ holds the *Private.h / *Internal.h + # headers, which are implementation detail and not public API. + EXCLUDE_PATTERNS: "*/Source/*" EXTRACT_ALL: true OPTIMIZE_OUTPUT_FOR_C: true # First sentence of each /** */ block becomes the API-index brief. @@ -200,6 +209,10 @@ nav: - Data structures: api/annotated.md - Functions: api/functions.md - Macros: api/macros.md + # One group per platform pack, defined in the pack's .dox file. Each + # group page lists that pack's headers and links back to its + # hand-written page — the API-to-docs direction of the traversal. + - Platform groups: api/modules.md # A section (not a bare page) so Material gives it md-nav__item--section and # it reads with the same weight as Platforms / Roles. Its index is its only # child — navigation.indexes promotes it into the section link. From 1328b987c65a822e9ae28603afcd8faabf603dff Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 13:57:18 +0100 Subject: [PATCH 21/57] docs: S23.22 write the two TLS platform pages against the source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Establishes the platform page template — what it ships, requirements, security behaviour and obligations, source, setup — on the two packs that carry the real content. The obligations sections are what an integrator cannot derive without reading the adapter: that OpenSSL rejects a half-supplied mTLS credential at Open while Mbed TLS proceeds with server-authenticated TLS; that OpenSSL checks the key against the certificate locally and Mbed TLS does not; that ServerName has three distinct behaviours, one of them a silent opt-out; that neither adapter performs revocation; and that rotation is a file replacement on one and a recreate on the other. Corrects a claim carried in the IEC 62443 substrate prose: the Mbed TLS TLS 1.2 floor is pinned explicitly with mbedtls_ssl_conf_min_tls_version, not inherited from MBEDTLS_SSL_PRESET_DEFAULT. The code pins it precisely because the preset can negotiate down to TLS 1.0 or 1.1, so the old wording understated the adapter. OpenSSL gains the setup page the fixed folder shape calls for, wired from the BDD target that exercises both the server-authenticated and mutual-TLS paths. Part of #708 --- docs/platforms/mbedtls/index.md | 84 +++++++++++++++++++++++++---- docs/platforms/openssl/index.md | 71 +++++++++++++++++++++--- docs/platforms/openssl/setup.md | 96 +++++++++++++++++++++++++++++++++ hooks/page_descriptions.py | 4 ++ mkdocs.yml | 1 + 5 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 docs/platforms/openssl/setup.md diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index 0bc6c72f..d08e7bf9 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -1,11 +1,10 @@ # Mbed TLS -`Platform/MbedTls/` wraps Mbed TLS for TLS transport and keyed at-rest crypto on -embedded targets ([Mbed TLS documentation](https://mbed-tls.readthedocs.io/)). - -Fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS, and the -[SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for at-rest -integrity. +`Platform/MbedTls/` wraps [Mbed TLS](https://mbed-tls.readthedocs.io/) for TLS +transport and keyed at-rest crypto on embedded targets, where OpenSSL is too +heavy. It fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS +and the [SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for +at-rest integrity and confidentiality. ## What it ships @@ -17,15 +16,78 @@ integrity. ## Requirements -Your `mbedtls_config.h`. You pass caller-built handles (RNG, cert chain, key), not -file paths. `Platform/MbedTls/Source/` never calls process-global Mbed TLS APIs -(`mbedtls_platform_setup`, `psa_crypto_init`, …) — you own those, so SolidSyslog -coexists with your other Mbed TLS use. +Your own `mbedtls_config.h` — the adapter's sources compile in your target +against your configuration, so the features you enable are the features it gets. + +You pass **caller-built, caller-owned handles**, not file paths: a seeded +`mbedtls_ctr_drbg_context` for the handshake, an `mbedtls_x509_crt` trust chain, +and for mutual TLS an `mbedtls_x509_crt` / `mbedtls_pk_context` pair. Nothing in +the adapter touches a filesystem, which is what lets it work on targets built +without `MBEDTLS_FS_IO`. Every handle must outlive the stream. + +A `SolidSyslogSleepFunction` is required and has no fallback — it bridges the +handshake's `WANT_READ` / `WANT_WRITE` polls. + +## Security behaviour and obligations + +**What the adapter guarantees.** Peer verification is pinned to +`MBEDTLS_SSL_VERIFY_REQUIRED` and the protocol floor to TLS 1.2, both set +explicitly on the adapter's own `ssl_config`. The floor is pinned rather than +inherited from `MBEDTLS_SSL_PRESET_DEFAULT`, which can otherwise negotiate down +to TLS 1.0 or 1.1 on a permissive build. TLS 1.3 still negotiates when both +peers offer it. + +**Peer identity is yours to assert.** `ServerName` drives both SNI and the +peer-certificate check. Three cases, and only you know which you want: + +| `ServerName` | Behaviour | +|---|---| +| a name | verified against the peer certificate's SAN or CN | +| `""` | chain verified, endpoint identity **not** checked — the opt-out for a closed network or private CA, and silent by design | +| `NULL` | as `""`, but reports a WARNING: the peer is unverified, which is MITM-class | -Full setup is [Integrating Mbed TLS](setup.md). +**Mutual TLS is opt-in, and a half-supplied credential does not fail.** Both +`ClientCertChain` and `ClientKey` set means the client certificate is presented; +either one NULL means no client certificate is configured and `Open` proceeds +with server-authenticated TLS. It does not fail. The +[OpenSSL adapter](../openssl/index.md) rejects the same partial configuration at +`Open`, so the two do not behave alike here. If a half-supplied credential must +be an error on this adapter, check before calling `Open`. + +The adapter also performs no local check that `ClientKey` matches +`ClientCertChain` — the OpenSSL adapter does, via `SSL_CTX_check_private_key`. A +mismatched pair here surfaces as a handshake rejection from the peer rather than +as a setup error on the device. + +**Rotation is a restart, not a reload.** Because the adapter consumes pre-built +handles rather than paths, refreshing credentials means parsing a new +`mbedtls_x509_crt` and recreating the stream — or the parent +`SolidSyslogStreamSender`, so the next connect picks it up. There is no reload +callback. + +**Key custody is entirely yours.** The library holds no keys of its own and +reads whatever material you hand it. Where the private key lives, how it is +protected at rest, and whether it is backed by a secure element are properties +of your platform, not of this adapter. The same applies to the at-rest policies: +HMAC-SHA256 and AES-256-GCM are keyed, and the key is yours to store and rotate. + +**Revocation is not performed.** Neither CRL nor OCSP is checked. If your threat +model needs revocation, it has to come from your own configuration of Mbed TLS. + +**Coexistence is an auditable contract.** `Platform/MbedTls/Source/` never calls +a process-global Mbed TLS API — no `mbedtls_platform_setup` or `_teardown`, no +threading-alt hooks, no `psa_crypto_init`, no global RNG reset, no replacement of +your debug callback. TLS policy is set per-`ssl_config` so it cannot leak into +the ones you build elsewhere. A device that already wires Mbed TLS for OTA or a +vendor cloud SDK keeps that wiring intact. The claim is grep-auditable against +the directory. ## Source [Every class in this pack](../../api/group__platform__mbedtls.md), generated from the headers. The code itself is [`Platform/MbedTls/`](../../../Platform/MbedTls/). + +## Setup + +Wiring it up, handle by handle: [Mbed TLS setup](setup.md). diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 28ec9de1..b79322bf 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -1,17 +1,16 @@ # OpenSSL -`Platform/OpenSsl/` wraps OpenSSL for TLS transport and keyed at-rest crypto -([OpenSSL documentation](https://docs.openssl.org/)). - -Fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS, and the +`Platform/OpenSsl/` wraps [OpenSSL](https://docs.openssl.org/) for TLS transport +and keyed at-rest crypto on hosted targets. It fills the +[Stream](../../api/structSolidSyslogStream.md) role with TLS and the [SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for at-rest -integrity. +integrity and confidentiality. ## What it ships | Class | Role | |---|---| -| [`SolidSyslogTlsStream`](../../api/SolidSyslogTlsStream_8h.md) | TLS stream — server-cert + hostname verification, cipher pinning, optional mutual TLS | +| [`SolidSyslogTlsStream`](../../api/SolidSyslogTlsStream_8h.md) | TLS stream over an injected byte transport | | [`SolidSyslogOpenSslHmacSha256Policy`](../../api/SolidSyslogOpenSslHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | | [`SolidSyslogOpenSslAesGcmPolicy`](../../api/SolidSyslogOpenSslAesGcmPolicy_8h.md) | at-rest AES-256-GCM | @@ -19,8 +18,68 @@ integrity. OpenSSL 3.0 or later. +Credentials are **file paths**, read at `Open`: a PEM `CaBundlePath` for the +trust anchors, and for mutual TLS a PEM `ClientCertChainPath` and +`ClientKeyPath`. A `SolidSyslogSleepFunction` is required and has no fallback — +it bridges the handshake's `WANT_READ` / `WANT_WRITE` polls. + +## Security behaviour and obligations + +**What the adapter guarantees.** `SSL_VERIFY_PEER` is pinned on the context and +the protocol floor is set to TLS 1.2 with the return value checked, so `Open` +fails rather than proceeding if libssl refuses the floor. Every setup call in +the handshake path is return-checked, which is what stops a "handshake succeeded +without checking the name" bypass. + +**The trust bundle is mandatory.** `CaBundlePath` must load. If it does not, +`Open` fails — there is no fallback to the system trust store. + +**Peer identity is yours to assert.** `ServerName` drives both SNI and the +certificate check. Three cases, and only you know which you want: + +| `ServerName` | Behaviour | +|---|---| +| a name | verified against the peer certificate's SAN or CN | +| `""` | chain verified, endpoint identity **not** checked — the opt-out for a closed network or private CA, and silent by design | +| `NULL` | as `""`, but reports a WARNING: the peer is unverified, which is MITM-class | + +**Mutual TLS is opt-in and all-or-nothing.** Supply both +`ClientCertChainPath` and `ClientKeyPath` to present a client certificate, or +neither for server-authenticated TLS. Supplying one without the other is +rejected at `Open`, so this adapter cannot silently downgrade. The pairing is +also checked locally with `SSL_CTX_check_private_key` before any bytes reach the +wire. The [Mbed TLS adapter](../mbedtls/index.md) does neither — it proceeds +with server-authenticated TLS instead — so do not carry an assumption from one +to the other. + +**The cipher policy is yours.** `CipherList` is passed through; NULL takes the +OpenSSL default. The library ships no baked-in list, because the right one +depends on the libssl build on your target and on your own security profile. +`"ECDHE+AESGCM:ECDHE+CHACHA20"` — TLS 1.2 AEAD with forward secrecy — is a +reasonable starting point to tune from, not a recommendation to adopt unread. + +**Rotation is a file replacement plus a reconnect.** The `SSL_CTX` is rebuilt on +every `Open`, re-reading all three files, so replacing them on disk takes effect +on the next connect — either through natural churn (retry, outage recovery) or +by calling `SolidSyslogSender_Disconnect` to force one. No reload callback or +version-fingerprint API is needed. + +**Key custody is entirely yours.** The library holds no keys of its own and +reads whatever material you point it at. Filesystem permissions on the key, +whether it is backed by an HSM, and how it is rotated are properties of your +deployment. The same applies to the at-rest policies: HMAC-SHA256 and +AES-256-GCM are keyed, and the key is yours to store and rotate. + +**Revocation is not performed.** Neither CRL nor OCSP is checked by the adapter. +If your threat model needs revocation, it has to come from your own OpenSSL +configuration, and verifying that it is actually in force is yours to do. + ## Source [Every class in this pack](../../api/group__platform__openssl.md), generated from the headers. The code itself is [`Platform/OpenSsl/`](../../../Platform/OpenSsl/). + +## Setup + +Wiring it up, over any byte transport: [OpenSSL setup](setup.md). diff --git a/docs/platforms/openssl/setup.md b/docs/platforms/openssl/setup.md new file mode 100644 index 00000000..4f692bcb --- /dev/null +++ b/docs/platforms/openssl/setup.md @@ -0,0 +1,96 @@ +# OpenSSL setup + +Wiring `SolidSyslogTlsStream` over a plain TCP stream so a +`SolidSyslogStreamSender` delivers RFC 5425 syslog over TLS. Read +[OpenSSL](index.md) first for what the adapter guarantees and what it leaves to +you — this page is the mechanics. + +## What you need + +OpenSSL 3.0 or later on the include and link path. With CMake, selecting the +platform is enough: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "Posix;OpenSsl") +``` + +OpenSSL is a stable system API rather than a header-configured upstream, so the +adapter compiles straight into `libSolidSyslog.a` and there is no separate +target to link. [Adding it to your build](../../build-integration.md) covers the +Make and IDE routes. + +## The layering + +TLS is a Stream wrapped around another Stream. The TLS adapter carries the +records; the transport underneath carries the bytes, and it can be any Stream — +here the POSIX TCP one. + +```text +StreamSender → SolidSyslogTlsStream → SolidSyslogPosixTcpStream → socket +``` + +The TLS stream **borrows** its transport. It may close it, but it never destroys +it: the transport is yours to create and to destroy, and it must stay valid +until `SolidSyslogTlsStream_Destroy`. + +## Wiring it + +```c +struct SolidSyslogStream* transport = SolidSyslogPosixTcpStream_Create(NULL); + +static struct SolidSyslogTlsStreamConfig tlsConfig; +tlsConfig = (struct SolidSyslogTlsStreamConfig) {0}; +tlsConfig.Transport = transport; +tlsConfig.Sleep = SolidSyslogPosixSleep; /* required — no fallback */ +tlsConfig.CaBundlePath = "/etc/ssl/collector-ca.pem"; +tlsConfig.ServerName = "collector.example.net"; + +struct SolidSyslogStream* tls = SolidSyslogTlsStream_Create(&tlsConfig); +``` + +Zero-initialise the config before filling it. Every field you leave NULL is a +documented default, and a struct you have not cleared is not. + +For mutual TLS, add the client credential — both fields or neither, since +supplying one without the other is rejected at `Open`: + +```c +tlsConfig.ClientCertChainPath = "/etc/ssl/device-chain.pem"; +tlsConfig.ClientKeyPath = "/etc/ssl/device-key.pem"; +``` + +Then the sender, unchanged from the plain-TCP case — it sees a Stream and does +not know or care that it is a TLS one: + +```c +static struct SolidSyslogStreamSenderConfig senderConfig; +senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; +senderConfig.Resolver = resolver; +senderConfig.Stream = tls; +senderConfig.Address = SolidSyslogPosixAddress_Create(); +senderConfig.Endpoint = GetEndpoint; +struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig); +``` + +Tear down in reverse order: sender, address, TLS stream, then the transport you +created. + +## Timeouts + +The handshake is bounded. `GetHandshakeTimeoutMs` gives a per-attempt deadline +in milliseconds; leaving it NULL uses the `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` +tunable, which defaults to 5000. The `Sleep` callback is what the bounded retry +waits on between `WANT_READ` / `WANT_WRITE` polls, which is why it has no +default — the library will not pick a blocking primitive on your behalf. + +## Checking it works + +The BDD suite wires exactly this, against a real syslog-ng collector, in +[`Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c`](../../../Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c), +for both the server-authenticated and mutual-TLS cases. It is the reference to +read against your own wiring. + +Failures report through the error handler rather than silently — install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you. A `CRITICAL` at create time means the stream fell +back to the Null object and nothing will be delivered. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index 595d1828..cb7602c1 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -83,6 +83,10 @@ "The OpenSSL adapter pack: TLS transport for the Stream role, and keyed " "at-rest crypto for the SecurityPolicy role, on hosted targets." ), + "platforms/openssl/setup.md": ( + "Wire SolidSyslogTlsStream over a TCP stream for RFC 5425 syslog over " + "TLS: the layering, the config fields, mutual TLS and the handshake budget." + ), "platforms/mbedtls/index.md": ( "The Mbed TLS adapter pack for embedded targets: TLS transport for the " "Stream role, and keyed at-rest crypto for the SecurityPolicy role." diff --git a/mkdocs.yml b/mkdocs.yml index bcf43fa1..bc64440e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -175,6 +175,7 @@ nav: - Setup: platforms/lwipraw/setup.md - OpenSSL: - platforms/openssl/index.md + - Setup: platforms/openssl/setup.md - Mbed TLS: - platforms/mbedtls/index.md - Setup: platforms/mbedtls/setup.md From 935afa335b6b11c8b1931e5f911047353cf70d4e Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 16:53:35 +0100 Subject: [PATCH 22/57] docs: S23.22 iterate the platform nav, traversal and page template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nav. The Platforms sidebar was a list of expandable two-child sections, which read as clutter. It becomes the overview, then porting, then a flat alphabetical list of packs. Each pack's setup guide leaves the nav and is reached from the pack's own page instead — the two have different audiences. Adopt becomes Integrate. Traversal. The API reference could not answer "which platform is this and what does it need from my build". Every generated page for a header under Platform// now carries a banner linking to that pack's page. The pack list is read from SOLIDSYSLOG_PLATFORM_REGISTRY and the display name from the platform page's own heading, so registering a platform is all it takes and the name is written once. Source links. The platform pages no longer link into the source tree, and neither does the compliance guide — a guide that names adapter internals is coupled to every pack that has them. Doxygen's verbatim source listings are off, so the reference documents the contract rather than shipping a second copy of the headers. Template. Bold lead-ins become subheadings, the ServerName tables go — that detail belongs on the field, where it cannot drift — and the two TLS pages no longer reference each other. A page that describes another platform is a page that has to be revisited when that platform changes, which is the coupling this restructure exists to remove. The description check now reads the page tree rather than the nav, since setup pages are deliberately off-nav and a page a search engine can reach still needs a snippet. It immediately found one published page that had never had a description. Part of #708 --- docs/iec62443.md | 4 +- docs/platforms/atomics/index.md | 7 +- docs/platforms/fatfs/index.md | 7 +- docs/platforms/freertos/index.md | 7 +- docs/platforms/lwipraw/index.md | 10 +-- docs/platforms/mbedtls/index.md | 146 +++++++++++++++++-------------- docs/platforms/openssl/index.md | 121 +++++++++++++------------ docs/platforms/plusfat/index.md | 7 +- docs/platforms/plustcp/index.md | 7 +- docs/platforms/posix/index.md | 7 +- docs/platforms/windows/index.md | 7 +- hooks/page_descriptions.py | 29 +++--- hooks/platform_backlinks.py | 81 +++++++++++++++++ hooks/test_page_descriptions.py | 42 +++++---- mkdocs.yml | 61 ++++++------- 15 files changed, 330 insertions(+), 213 deletions(-) create mode 100644 hooks/platform_backlinks.py diff --git a/docs/iec62443.md b/docs/iec62443.md index 51da2dc8..912e5f50 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -47,7 +47,7 @@ what the library does not do. | **CR 2.11** — Timestamps | Caller-injected `SolidSyslogClockFunction` — `SolidSyslogPosixClock_GetTimestamp` (POSIX) or `SolidSyslogWindowsClock_GetTimestamp` (Windows). Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | | **CR 2.12** — Non-repudiation | At the wire: `SolidSyslogTlsStream` mutual TLS cryptographically identifies the TLS peer to the receiver. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: cryptographic integrity via `SolidSyslogOpenSslHmacSha256Policy` / `SolidSyslogMbedTlsHmacSha256Policy`, or authenticated encryption via `SolidSyslogOpenSslAesGcmPolicy` / `SolidSyslogMbedTlsAesGcmPolicy`; `SolidSyslogCrc16Policy` remains for accidental-corruption detection | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the receiver authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Cryptographic at-rest policies are keyed — key custody, rotation, and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | | **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed at-rest policy (`SolidSyslogOpenSslHmacSha256Policy` / `SolidSyslogMbedTlsHmacSha256Policy`) makes an unauthorised edit detectable. `SolidSyslogCrc16Policy` does not serve this control: it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit with `SolidSyslogTlsStream` and at rest with `SolidSyslogOpenSslAesGcmPolicy` / `SolidSyslogMbedTlsAesGcmPolicy`. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport `Send` synchronously on the caller's thread. The TCP socket is non-blocking from the moment it is opened (see [`SolidSyslogPosixTcpStream.c`](../Platform/Posix/Source/SolidSyslogPosixTcpStream.c) and [`SolidSyslogWinsockTcpStream.c`](../Platform/Windows/Source/SolidSyslogWinsockTcpStream.c)), so `Send` returns immediately on a wedged peer or a full kernel buffer; the bounded blocking surface is the initial `connect()`, sized by `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms) or runtime-overridable per Stream via `GetConnectTimeoutMs(ConnectTimeoutContext)`. On the TLS path the same applies to the handshake, via `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` (default 5000 ms) or `GetHandshakeTimeoutMs(HandshakeTimeoutContext)` for both OpenSSL and Mbed TLS adapters. Long-term silent peer death is detected out-of-band via kernel `TCP_KEEPALIVE` + `TCP_USER_TIMEOUT`. Suitable when the application has no real-time deadline or the transport latency is known to be sub-millisecond. **Buffered (`SolidSyslogCircularBuffer` with an injected `SolidSyslogMutex` — Posix / Windows / FreeRTOS / Null / caller-supplied RTOS primitive — or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` is non-blocking: it formats and enqueues, returning before any I/O. `SolidSyslog_Service`, called on the integrator's chosen thread (typically a dedicated service thread), performs the transport I/O against the same non-blocking socket; the bounded surface is the same connect / handshake budget as the single-task wiring. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport `Send` synchronously on the caller's thread. The TCP socket is non-blocking from the moment it is opened, so `Send` returns immediately on a wedged peer or a full kernel buffer; the bounded blocking surface is the initial `connect()`, sized by `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms) or runtime-overridable per Stream via `GetConnectTimeoutMs(ConnectTimeoutContext)`. On the TLS path the same applies to the handshake, via `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` (default 5000 ms) or `GetHandshakeTimeoutMs(HandshakeTimeoutContext)` for both OpenSSL and Mbed TLS adapters. Long-term silent peer death is detected out-of-band via kernel `TCP_KEEPALIVE` + `TCP_USER_TIMEOUT`. Suitable when the application has no real-time deadline or the transport latency is known to be sub-millisecond. **Buffered (`SolidSyslogCircularBuffer` with an injected `SolidSyslogMutex` — Posix / Windows / FreeRTOS / Null / caller-supplied RTOS primitive — or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` is non-blocking: it formats and enqueues, returning before any I/O. `SolidSyslog_Service`, called on the integrator's chosen thread (typically a dedicated service thread), performs the transport I/O against the same non-blocking socket; the bounded surface is the same connect / handshake budget as the single-task wiring. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | | **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the @@ -148,8 +148,6 @@ store) is unchanged. idempotently and never free integrator-owned handles. Integrator guide: [Mbed TLS setup](platforms/mbedtls/setup.md). -Reference wiring: the FreeRTOS QEMU mps2-an385 BDD target at -[`Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c`](../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c). ## Architecture for Security diff --git a/docs/platforms/atomics/index.md b/docs/platforms/atomics/index.md index 7302dc8a..406b5638 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/atomics/index.md @@ -16,8 +16,7 @@ Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. A C11 compiler with ``. Windows toolchains without it use [`SolidSyslogWindowsAtomicCounter`](../windows/index.md) instead. -## Source +## API reference -[Every class in this pack](../../api/group__platform__atomics.md), generated -from the headers. The code itself is -[`Platform/Atomics/`](../../../Platform/Atomics/). +[Every class in this pack](../../api/group__platform__atomics.md), generated from the +headers. diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index 03273f7b..e2f98f32 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -18,8 +18,7 @@ BlockDevice. Your `ffconf.h`, a `diskio.c` media driver, and — if `FF_FS_REENTRANT=1` — an `ffsystem.c`. -## Source +## API reference -[Every class in this pack](../../api/group__platform__fatfs.md), generated -from the headers. The code itself is -[`Platform/FatFs/`](../../../Platform/FatFs/). +[Every class in this pack](../../api/group__platform__fatfs.md), generated from the +headers. diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md index a4a09dec..6cbd8fb6 100644 --- a/docs/platforms/freertos/index.md +++ b/docs/platforms/freertos/index.md @@ -18,8 +18,7 @@ Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. `configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. -## Source +## API reference -[Every class in this pack](../../api/group__platform__freertos.md), generated -from the headers. The code itself is -[`Platform/FreeRtos/`](../../../Platform/FreeRtos/). +[Every class in this pack](../../api/group__platform__freertos.md), generated from the +headers. diff --git a/docs/platforms/lwipraw/index.md b/docs/platforms/lwipraw/index.md index 98501d94..646b1949 100644 --- a/docs/platforms/lwipraw/index.md +++ b/docs/platforms/lwipraw/index.md @@ -37,8 +37,7 @@ the Datagram and TcpStream make is routed through one marshal hop. The marshal must invoke its callback synchronously — the adapter reads results the moment the hop returns. `tcpip_callback_with_block(…, 1)` or a `LOCK_TCPIP_CORE` / -`UNLOCK_TCPIP_CORE` pair satisfy that; a bare `tcpip_callback` does not. Worked -example: [`Bdd/Targets/FreeRtosLwip/main.c`](../../../Bdd/Targets/FreeRtosLwip/main.c). +`UNLOCK_TCPIP_CORE` pair satisfy that; a bare `tcpip_callback` does not. ## Requirements @@ -57,8 +56,7 @@ dropped) and `LWIP_TCP_KEEPALIVE=1`, and size `PBUF_POOL_SIZE` / Full setup — config, marshal, DNS — is [Integrating lwIP](setup.md). -## Source +## API reference -[Every class in this pack](../../api/group__platform__lwipraw.md), generated -from the headers. The code itself is -[`Platform/LwipRaw/`](../../../Platform/LwipRaw/). +[Every class in this pack](../../api/group__platform__lwipraw.md), generated from the +headers. diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index d08e7bf9..401a664c 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -1,10 +1,10 @@ # Mbed TLS `Platform/MbedTls/` wraps [Mbed TLS](https://mbed-tls.readthedocs.io/) for TLS -transport and keyed at-rest crypto on embedded targets, where OpenSSL is too -heavy. It fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS -and the [SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for -at-rest integrity and confidentiality. +transport and keyed at-rest cryptography on embedded targets. It fills the +[Stream](../../api/structSolidSyslogStream.md) role with TLS and the +[SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for at-rest +integrity and confidentiality. ## What it ships @@ -16,77 +16,91 @@ at-rest integrity and confidentiality. ## Requirements -Your own `mbedtls_config.h` — the adapter's sources compile in your target -against your configuration, so the features you enable are the features it gets. +The pack compiles against your own `mbedtls_config.h`, in your target, so the +features you enable are the features it gets. -You pass **caller-built, caller-owned handles**, not file paths: a seeded -`mbedtls_ctr_drbg_context` for the handshake, an `mbedtls_x509_crt` trust chain, -and for mutual TLS an `mbedtls_x509_crt` / `mbedtls_pk_context` pair. Nothing in -the adapter touches a filesystem, which is what lets it work on targets built -without `MBEDTLS_FS_IO`. Every handle must outlive the stream. +Credentials are passed as caller-built, caller-owned handles rather than file +paths: a seeded `mbedtls_ctr_drbg_context` for the handshake, an +`mbedtls_x509_crt` trust chain, and for mutual TLS an `mbedtls_x509_crt` and +`mbedtls_pk_context` pair. No part of the adapter opens a file, which is what +allows it to run on targets built without `MBEDTLS_FS_IO`. Each handle must +remain valid for the lifetime of the stream. -A `SolidSyslogSleepFunction` is required and has no fallback — it bridges the -handshake's `WANT_READ` / `WANT_WRITE` polls. +A `SolidSyslogSleepFunction` is required and has no default. ## Security behaviour and obligations -**What the adapter guarantees.** Peer verification is pinned to -`MBEDTLS_SSL_VERIFY_REQUIRED` and the protocol floor to TLS 1.2, both set -explicitly on the adapter's own `ssl_config`. The floor is pinned rather than -inherited from `MBEDTLS_SSL_PRESET_DEFAULT`, which can otherwise negotiate down -to TLS 1.0 or 1.1 on a permissive build. TLS 1.3 still negotiates when both -peers offer it. +The per-field detail is in +[`SolidSyslogMbedTlsStream.h`](../../api/SolidSyslogMbedTlsStream_8h.md), +alongside the fields themselves. What follows is the behaviour of the adapter as +a whole, and the work it leaves to you. -**Peer identity is yours to assert.** `ServerName` drives both SNI and the -peer-certificate check. Three cases, and only you know which you want: +### Transport security is fixed by the adapter -| `ServerName` | Behaviour | -|---|---| -| a name | verified against the peer certificate's SAN or CN | -| `""` | chain verified, endpoint identity **not** checked — the opt-out for a closed network or private CA, and silent by design | -| `NULL` | as `""`, but reports a WARNING: the peer is unverified, which is MITM-class | - -**Mutual TLS is opt-in, and a half-supplied credential does not fail.** Both -`ClientCertChain` and `ClientKey` set means the client certificate is presented; -either one NULL means no client certificate is configured and `Open` proceeds -with server-authenticated TLS. It does not fail. The -[OpenSSL adapter](../openssl/index.md) rejects the same partial configuration at -`Open`, so the two do not behave alike here. If a half-supplied credential must -be an error on this adapter, check before calling `Open`. - -The adapter also performs no local check that `ClientKey` matches -`ClientCertChain` — the OpenSSL adapter does, via `SSL_CTX_check_private_key`. A -mismatched pair here surfaces as a handshake rejection from the peer rather than -as a setup error on the device. - -**Rotation is a restart, not a reload.** Because the adapter consumes pre-built -handles rather than paths, refreshing credentials means parsing a new -`mbedtls_x509_crt` and recreating the stream — or the parent -`SolidSyslogStreamSender`, so the next connect picks it up. There is no reload -callback. - -**Key custody is entirely yours.** The library holds no keys of its own and -reads whatever material you hand it. Where the private key lives, how it is -protected at rest, and whether it is backed by a secure element are properties -of your platform, not of this adapter. The same applies to the at-rest policies: -HMAC-SHA256 and AES-256-GCM are keyed, and the key is yours to store and rotate. - -**Revocation is not performed.** Neither CRL nor OCSP is checked. If your threat -model needs revocation, it has to come from your own configuration of Mbed TLS. - -**Coexistence is an auditable contract.** `Platform/MbedTls/Source/` never calls -a process-global Mbed TLS API — no `mbedtls_platform_setup` or `_teardown`, no -threading-alt hooks, no `psa_crypto_init`, no global RNG reset, no replacement of -your debug callback. TLS policy is set per-`ssl_config` so it cannot leak into -the ones you build elsewhere. A device that already wires Mbed TLS for OTA or a -vendor cloud SDK keeps that wiring intact. The claim is grep-auditable against -the directory. - -## Source +Peer certificate verification is pinned to `MBEDTLS_SSL_VERIFY_REQUIRED` and the +protocol floor to TLS 1.2, both set on the adapter's own `ssl_config`. The floor +is set explicitly rather than inherited from `MBEDTLS_SSL_PRESET_DEFAULT`, which +on a permissive build can negotiate down to TLS 1.0 or 1.1. TLS 1.3 is +negotiated when both peers support it. + +### Peer identity is yours to declare + +The `ServerName` field supplies both the Server Name Indication sent in the +handshake and the identity checked against the peer certificate. It has a +distinct meaning when set, when empty, and when NULL — including one value that +disables endpoint verification without reporting anything — and the three are +documented on the field. Choosing between them is a deployment decision the +adapter cannot make. + +### Mutual TLS is optional and is not validated locally + +A client certificate is presented only when both `ClientCertChain` and +`ClientKey` are supplied. If either is absent, no client certificate is +configured and `Open` proceeds with server-authenticated TLS rather than +failing. Where a half-supplied credential must be treated as an error, check for +it before calling `Open`. + +The adapter performs no local check that the key matches the certificate, and +does not report a failure to install the pair. A mismatch is therefore seen as a +handshake rejection from the collector rather than as a setup error on the +device. + +### Rotation requires a restart of the stream + +Because the adapter consumes pre-built handles, refreshing credentials means +parsing new ones and recreating the stream, or the parent +`SolidSyslogStreamSender` so that the next connection uses them. There is no +reload callback. + +### Key custody is outside the library + +The library holds no keys of its own and uses whatever material is passed to it. +Where a private key is stored, how it is protected at rest, and whether it is +held in a secure element are properties of your platform. The same applies to +the at-rest policies: HMAC-SHA256 and AES-256-GCM are keyed, and storing and +rotating that key is yours. + +### Revocation is not checked + +The adapter performs no revocation checking, by Certificate Revocation List or +by the Online Certificate Status Protocol. Where a deployment requires it, it +must come from your own configuration of Mbed TLS, and confirming that it is in +force is part of your assessment rather than something the adapter reports. + +### Coexistence is an auditable contract + +`Platform/MbedTls/Source/` calls no process-global Mbed TLS API. It does not +call `mbedtls_platform_setup` or `mbedtls_platform_teardown`, install +threading-alt hooks, call `psa_crypto_init`, reset the global random number +generator, or replace a debug callback. TLS policy is applied per `ssl_config`, +so it cannot affect the ones you build elsewhere. A device that already uses +Mbed TLS for firmware update or a vendor cloud SDK keeps that configuration +intact, and the claim can be checked against the directory. + +## API reference [Every class in this pack](../../api/group__platform__mbedtls.md), generated -from the headers. The code itself is -[`Platform/MbedTls/`](../../../Platform/MbedTls/). +from the headers. ## Setup diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index b79322bf..c28ac34e 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -1,7 +1,7 @@ # OpenSSL `Platform/OpenSsl/` wraps [OpenSSL](https://docs.openssl.org/) for TLS transport -and keyed at-rest crypto on hosted targets. It fills the +and keyed at-rest cryptography on hosted targets. It fills the [Stream](../../api/structSolidSyslogStream.md) role with TLS and the [SecurityPolicy](../../api/structSolidSyslogSecurityPolicy.md) role for at-rest integrity and confidentiality. @@ -18,67 +18,80 @@ integrity and confidentiality. OpenSSL 3.0 or later. -Credentials are **file paths**, read at `Open`: a PEM `CaBundlePath` for the -trust anchors, and for mutual TLS a PEM `ClientCertChainPath` and -`ClientKeyPath`. A `SolidSyslogSleepFunction` is required and has no fallback — -it bridges the handshake's `WANT_READ` / `WANT_WRITE` polls. +Credentials are file paths, read when the stream is opened: a PEM trust bundle, +and for mutual TLS a PEM client certificate chain and private key. A +`SolidSyslogSleepFunction` is required and has no default. ## Security behaviour and obligations -**What the adapter guarantees.** `SSL_VERIFY_PEER` is pinned on the context and -the protocol floor is set to TLS 1.2 with the return value checked, so `Open` -fails rather than proceeding if libssl refuses the floor. Every setup call in -the handshake path is return-checked, which is what stops a "handshake succeeded -without checking the name" bypass. +The per-field detail is in +[`SolidSyslogTlsStream.h`](../../api/SolidSyslogTlsStream_8h.md), alongside the +fields themselves. What follows is the behaviour of the adapter as a whole, and +the work it leaves to you. -**The trust bundle is mandatory.** `CaBundlePath` must load. If it does not, -`Open` fails — there is no fallback to the system trust store. +### Transport security is fixed by the adapter -**Peer identity is yours to assert.** `ServerName` drives both SNI and the -certificate check. Three cases, and only you know which you want: +`SSL_VERIFY_PEER` is pinned on the context and the protocol floor is set to +TLS 1.2. Both are return-checked, so the stream fails to open rather than +proceeding if the underlying libssl refuses the floor. Every setup call on the +handshake path is checked in the same way, which is what prevents a handshake +completing without the identity check having been applied. -| `ServerName` | Behaviour | -|---|---| -| a name | verified against the peer certificate's SAN or CN | -| `""` | chain verified, endpoint identity **not** checked — the opt-out for a closed network or private CA, and silent by design | -| `NULL` | as `""`, but reports a WARNING: the peer is unverified, which is MITM-class | - -**Mutual TLS is opt-in and all-or-nothing.** Supply both -`ClientCertChainPath` and `ClientKeyPath` to present a client certificate, or -neither for server-authenticated TLS. Supplying one without the other is -rejected at `Open`, so this adapter cannot silently downgrade. The pairing is -also checked locally with `SSL_CTX_check_private_key` before any bytes reach the -wire. The [Mbed TLS adapter](../mbedtls/index.md) does neither — it proceeds -with server-authenticated TLS instead — so do not carry an assumption from one -to the other. - -**The cipher policy is yours.** `CipherList` is passed through; NULL takes the -OpenSSL default. The library ships no baked-in list, because the right one -depends on the libssl build on your target and on your own security profile. -`"ECDHE+AESGCM:ECDHE+CHACHA20"` — TLS 1.2 AEAD with forward secrecy — is a -reasonable starting point to tune from, not a recommendation to adopt unread. - -**Rotation is a file replacement plus a reconnect.** The `SSL_CTX` is rebuilt on -every `Open`, re-reading all three files, so replacing them on disk takes effect -on the next connect — either through natural churn (retry, outage recovery) or -by calling `SolidSyslogSender_Disconnect` to force one. No reload callback or -version-fingerprint API is needed. - -**Key custody is entirely yours.** The library holds no keys of its own and -reads whatever material you point it at. Filesystem permissions on the key, -whether it is backed by an HSM, and how it is rotated are properties of your -deployment. The same applies to the at-rest policies: HMAC-SHA256 and -AES-256-GCM are keyed, and the key is yours to store and rotate. - -**Revocation is not performed.** Neither CRL nor OCSP is checked by the adapter. -If your threat model needs revocation, it has to come from your own OpenSSL -configuration, and verifying that it is actually in force is yours to do. - -## Source +### The trust bundle is mandatory + +The trust bundle must load. If it does not, the stream fails to open — there is +no fallback to a system trust store. + +### Peer identity is yours to declare + +The `ServerName` field supplies both the Server Name Indication sent in the +handshake and the identity checked against the peer certificate. It has a +distinct meaning when set, when empty, and when NULL — including one value that +disables endpoint verification without reporting anything — and the three are +documented on the field. Choosing between them is a deployment decision the +adapter cannot make. + +### Mutual TLS is optional and all-or-nothing + +A client certificate chain and its private key are supplied together or not at +all. Supplying one without the other is rejected when the stream is opened, so a +partially configured credential cannot result in a connection that silently +omits the client certificate. The key is also checked against the certificate +locally, before any bytes reach the network. + +### The cipher policy is yours + +A cipher list is passed through to OpenSSL unchanged, and omitting it takes the +OpenSSL default. The library pins no list of its own: the appropriate one +depends on the libssl build present on the target and on the profile the +deployment is held to. + +### Rotation is a file replacement and a reconnection + +The `SSL_CTX` is rebuilt each time the stream is opened, re-reading all three +files, so replacing them takes effect on the next connection — either through +ordinary reconnection after an outage, or by calling +`SolidSyslogSender_Disconnect` to force one. No reload callback is needed. + +### Key custody is outside the library + +The library holds no keys of its own and reads whatever material it is pointed +at. Filesystem permissions on the private key, whether it is held in a hardware +security module, and how it is rotated are properties of your deployment. The +same applies to the at-rest policies: HMAC-SHA256 and AES-256-GCM are keyed, and +storing and rotating that key is yours. + +### Revocation is not checked + +The adapter performs no revocation checking, by Certificate Revocation List or +by the Online Certificate Status Protocol. Where a deployment requires it, it +must come from your own configuration of OpenSSL, and confirming that it is in +force is part of your assessment rather than something the adapter reports. + +## API reference [Every class in this pack](../../api/group__platform__openssl.md), generated -from the headers. The code itself is -[`Platform/OpenSsl/`](../../../Platform/OpenSsl/). +from the headers. ## Setup diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index e73ae9bd..b9662d08 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -19,8 +19,7 @@ FreeRTOS — Plus-FAT is FreeRTOS-coupled. Supply an `FF_Disk_t` media driver an Full setup is [Integrating FreeRTOS-Plus-FAT](setup.md). -## Source +## API reference -[Every class in this pack](../../api/group__platform__plusfat.md), generated -from the headers. The code itself is -[`Platform/PlusFat/`](../../../Platform/PlusFat/). +[Every class in this pack](../../api/group__platform__plusfat.md), generated from the +headers. diff --git a/docs/platforms/plustcp/index.md b/docs/platforms/plustcp/index.md index 6ad155b8..836eb0d2 100644 --- a/docs/platforms/plustcp/index.md +++ b/docs/platforms/plustcp/index.md @@ -21,8 +21,7 @@ FreeRTOS-Plus-TCP, selected at CMake time with naming `PlusTcp` in `SOLIDSYSLOG_PLATFORMS`. The resolver wraps `FreeRTOS_getaddrinfo`, so your `FreeRTOSIPConfig.h` needs `ipconfigUSE_DNS=1`. -## Source +## API reference -[Every class in this pack](../../api/group__platform__plustcp.md), generated -from the headers. The code itself is -[`Platform/PlusTcp/`](../../../Platform/PlusTcp/). +[Every class in this pack](../../api/group__platform__plustcp.md), generated from the +headers. diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md index c901d84b..f574e4c6 100644 --- a/docs/platforms/posix/index.md +++ b/docs/platforms/posix/index.md @@ -31,8 +31,7 @@ callbacks. A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs POSIX message queues (link `-lrt` on glibc). -## Source +## API reference -[Every class in this pack](../../api/group__platform__posix.md), generated -from the headers. The code itself is -[`Platform/Posix/`](../../../Platform/Posix/). +[Every class in this pack](../../api/group__platform__posix.md), generated from the +headers. diff --git a/docs/platforms/windows/index.md b/docs/platforms/windows/index.md index 27af1285..18f2ba1e 100644 --- a/docs/platforms/windows/index.md +++ b/docs/platforms/windows/index.md @@ -29,8 +29,7 @@ callbacks. The MSVC toolchain and Winsock — call `WSAStartup` once at process init before creating a sender. -## Source +## API reference -[Every class in this pack](../../api/group__platform__windows.md), generated -from the headers. The code itself is -[`Platform/Windows/`](../../../Platform/Windows/). +[Every class in this pack](../../api/group__platform__windows.md), generated from the +headers. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index cb7602c1..8119feb9 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -9,9 +9,14 @@ above the content. Keeping them out of the pages costs proximity, so the map is checked against -the navigation on every build: a page in the nav with no description, or a -description whose key names no nav page, aborts the build. Generated ``api/`` +the documentation tree on every build: a page with no description, or a +description whose key names no page, aborts the build. Generated ``api/`` pages are exempt — their briefs come from the header doc comments. + +The check is against every hand-written page, not only the ones in the nav. +Each platform's ``setup.md`` is deliberately off-nav — it is reached from the +platform's own page, because the two have different audiences — and a page a +search engine can reach still needs its own snippet. """ import os @@ -162,6 +167,10 @@ "The twelve vtable contracts SolidSyslog composes against, what fills " "each one, and the Null fallback that keeps an unfilled role safe." ), + "assets/postit/README.md": ( + "The post-it diagram kit behind the SolidSyslog architecture pictures: " + "what each colour and arrow means, and how the diagrams are generated." + ), # Maintaining "builds.md": ( "The contributor build doc: the CMake preset catalogue for developing " @@ -202,23 +211,23 @@ def _src_uri(file): return getattr(file, "src_uri", None) or file.src_path.replace(os.sep, "/") -def _described_pages(nav): +def _hand_written_pages(files): return { - _src_uri(page.file) - for page in nav.pages - if not _src_uri(page.file).startswith(GENERATED_PREFIX) + _src_uri(file) + for file in files + if file.is_documentation_page() and not _src_uri(file).startswith(GENERATED_PREFIX) } def on_nav(nav, config, files, **kwargs): - navigated = _described_pages(nav) + written = _hand_written_pages(files) faults = [] - missing = sorted(navigated - set(DESCRIPTIONS)) + missing = sorted(written - set(DESCRIPTIONS)) if missing: faults.append("no description for " + ", ".join(missing)) - stale = sorted(set(DESCRIPTIONS) - navigated) + stale = sorted(set(DESCRIPTIONS) - written) if stale: - faults.append("described but not in the nav: " + ", ".join(stale)) + faults.append("described but no such page: " + ", ".join(stale)) if faults: raise PluginError("hooks/page_descriptions.py: " + "; ".join(faults)) return nav diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py new file mode 100644 index 00000000..8380c0ad --- /dev/null +++ b/hooks/platform_backlinks.py @@ -0,0 +1,81 @@ +"""MkDocs build hook: link every generated platform API page to its platform. + +The API reference answers "what does this header declare". It never answered +"which platform pack is this, and what does that pack need from my build" — +which is the question a reader arriving from a search result actually has. + +Every generated page for a header under ``Platform//`` gets one line at +the top linking to that pack's hand-written page. The platform's own page links +the other way, to the pack's Doxygen group, so the pair closes the loop. + +The platform list is read from ``SOLIDSYSLOG_PLATFORM_REGISTRY`` in the +top-level CMakeLists.txt — the same table ``scripts/check_manifest.py`` treats +as authoritative — so a new pack is picked up by being registered, with no edit +here. The slug is the registry token lowercased, which is the convention the +docs folder and the Doxygen group both follow. +""" + +import os +import re + +REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) +ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') + +# Generated page stems are the header path with non-alphanumerics escaped; +# the leaf name is enough to identify the header, since every public header in +# the tree is uniquely named (docs/NAMING.md). +GENERATED_PREFIX = "api/" + +_CACHE = {} + + +def _label(root, slug): + """The platform's own H1 — so the name is written once, on its page.""" + path = os.path.join(root, "docs", "platforms", slug, "index.md") + with open(path, encoding="utf-8") as page: + for line in page: + if line.startswith("# "): + return line[2:].strip() + return slug + + +def _packs(config): + """Return {header stem: (label, slug)} for every public platform header.""" + root = os.path.dirname(config["config_file_path"]) + if root not in _CACHE: + with open(os.path.join(root, "CMakeLists.txt"), encoding="utf-8") as cmake: + registry = REGISTRY.search(cmake.read()) + headers = {} + for token, directory in ROW.findall(registry.group(1)) if registry else []: + interface = os.path.join(root, directory, "Interface") + if not os.path.isdir(interface): + continue + pack = (_label(root, token.lower()), token.lower()) + for name in os.listdir(interface): + if name.endswith(".h"): + headers[name[: -len(".h")]] = pack + _CACHE[root] = headers + return _CACHE[root] + + +def _stem(src_uri): + """api/SolidSyslogMbedTlsStream_8h.md -> SolidSyslogMbedTlsStream.""" + leaf = src_uri[len(GENERATED_PREFIX) : -len(".md")] + return leaf[: -len("_8h")] if leaf.endswith("_8h") else None + + +def on_page_markdown(markdown, page, config, files, **kwargs): + src_uri = getattr(page.file, "src_uri", None) or page.file.src_path.replace(os.sep, "/") + if not src_uri.startswith(GENERATED_PREFIX): + return markdown + stem = _stem(src_uri) + pack = _packs(config).get(stem) if stem else None + if pack is None: + return markdown + label, slug = pack + banner = ( + f"!!! info \"Part of the [{label}](../platforms/{slug}/index.md) platform\"\n" + f" What the pack ships, what your build must provide, and the\n" + f" obligations it leaves to you.\n\n" + ) + return banner + markdown diff --git a/hooks/test_page_descriptions.py b/hooks/test_page_descriptions.py index 35174cd2..7e06f143 100644 --- a/hooks/test_page_descriptions.py +++ b/hooks/test_page_descriptions.py @@ -3,8 +3,8 @@ Run: python3 hooks/test_page_descriptions.py (or: python3 -m unittest discover -s hooks -p 'test_*.py') -The strict build already proves the happy path — every nav page carries a -description, or on_nav aborts. What needs its own test is the abort itself, and +The strict build already proves the happy path — every hand-written page carries +a description, or on_nav aborts. What needs its own test is the abort itself, and the description text, which no build can check for length or duplication. """ @@ -19,37 +19,47 @@ MAX_LENGTH = 200 -def nav_of(*src_uris): - pages = [types.SimpleNamespace(file=types.SimpleNamespace(src_uri=uri)) for uri in src_uris] - return types.SimpleNamespace(pages=pages) +NAV = types.SimpleNamespace(pages=[]) + + +def files_of(*src_uris, assets=()): + pages = [types.SimpleNamespace(src_uri=uri, is_documentation_page=lambda: True) for uri in src_uris] + other = [types.SimpleNamespace(src_uri=uri, is_documentation_page=lambda: False) for uri in assets] + return pages + other def page_of(src_uri, meta=None): return types.SimpleNamespace(file=types.SimpleNamespace(src_uri=src_uri), meta=meta or {}) -class NavValidation(unittest.TestCase): +class TreeValidation(unittest.TestCase): def setUp(self): self.described = sorted(h.DESCRIPTIONS) - def test_accepts_a_nav_of_described_pages(self): - nav = nav_of(*self.described) - self.assertIs(h.on_nav(nav, {}, None), nav) + def test_accepts_a_tree_of_described_pages(self): + self.assertIs(h.on_nav(NAV, {}, files_of(*self.described)), NAV) - def test_undescribed_nav_page_aborts_the_build_and_is_named(self): + def test_undescribed_page_aborts_the_build_and_is_named(self): with self.assertRaises(h.PluginError) as raised: - h.on_nav(nav_of(*self.described, "brand-new.md"), {}, None) + h.on_nav(NAV, {}, files_of(*self.described, "brand-new.md")) self.assertIn("no description for brand-new.md", str(raised.exception)) - def test_description_for_a_page_no_longer_in_the_nav_aborts_the_build(self): + def test_description_for_a_page_that_no_longer_exists_aborts_the_build(self): kept = [uri for uri in self.described if uri != "porting.md"] with self.assertRaises(h.PluginError) as raised: - h.on_nav(nav_of(*kept), {}, None) - self.assertIn("not in the nav: porting.md", str(raised.exception)) + h.on_nav(NAV, {}, files_of(*kept)) + self.assertIn("no such page: porting.md", str(raised.exception)) def test_generated_api_pages_need_no_description(self): - nav = nav_of(*self.described, "api/files.md") - self.assertIs(h.on_nav(nav, {}, None), nav) + files = files_of(*self.described, "api/files.md") + self.assertIs(h.on_nav(NAV, {}, files), NAV) + + # An off-nav page still needs a description, so the check reads the file + # tree rather than the nav — which means it must ignore everything in that + # tree that is not a page. + def test_assets_are_not_pages_and_need_no_description(self): + files = files_of(*self.described, assets=("assets/images/logo.svg",)) + self.assertIs(h.on_nav(NAV, {}, files), NAV) class PageMeta(unittest.TestCase): diff --git a/mkdocs.yml b/mkdocs.yml index bc64440e..a1c32196 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,6 +81,11 @@ hooks: # Hides the one-entry table of contents on the generated Functions / Macros # indexes, whose letter buckets collapse under the common SolidSyslog prefix. - hooks/api_index_toc.py + # Puts a "Part of the platform" banner on every generated API page + # for a header under Platform//, linking to that pack's own page. The + # pack list comes from SOLIDSYSLOG_PLATFORM_REGISTRY, so registering a new + # platform is all it takes. + - hooks/platform_backlinks.py # Gives each hand-written page its own , the snippet # a search result shows, instead of the site_description above. The map lives # in the hook rather than in front matter because the same Markdown is read on @@ -122,6 +127,13 @@ plugins: # Platform//Source/ holds the *Private.h / *Internal.h # headers, which are implementation detail and not public API. EXCLUDE_PATTERNS: "*/Source/*" + # No verbatim source listings in the reference. The docs describe the + # contract; the code is read in the repository, and the two example + # repositories are where working code is shown. Without this, every + # file page carries a "Go to the source code of this file" link to a + # generated copy of the header. + XML_PROGRAMLISTING: false + VERBATIM_HEADERS: false EXTRACT_ALL: true OPTIMIZE_OUTPUT_FOR_C: true # First sentence of each /** */ block becomes the API-index brief. @@ -150,43 +162,32 @@ nav: - Home: README.md - Overview: - Compliance in one page: overview.md - - Adopt: + - Integrate: - Building up the protection you need: hardening-path.md - Adding it to your build: build-integration.md - Structured data: structured-data.md - Error handling: error-severity.md - # Platforms answers "will this run on my target" — an adoption question, not a - # reference one, so it is a tab rather than a child of API reference. Porting - # sits at the end as the other half of the same question: nothing shipped fits, - # so write one. Each platform is a section whose index.md navigation.indexes - # promotes into the section link, and whose setup.md is its wiring guide. + # Platforms answers "will this run on my target" — an integration question, not + # a reference one, so it is a tab rather than a child of API reference. Porting + # sits directly beneath the overview as the other half of it: nothing shipped + # fits, so write one. The packs themselves are then a flat alphabetical list, + # which is how a sidebar gets used — to look one up. Each pack's setup guide is + # deliberately absent here and reached from the pack's own page instead: the + # two have different audiences, and a nested pair per pack made the sidebar + # unreadable. - Platforms: - platforms/index.md - - Posix: - - platforms/posix/index.md - - Windows: - - platforms/windows/index.md - - FreeRTOS: - - platforms/freertos/index.md - - FreeRTOS-Plus-TCP: - - platforms/plustcp/index.md - - lwIP (Raw API): - - platforms/lwipraw/index.md - - Setup: platforms/lwipraw/setup.md - - OpenSSL: - - platforms/openssl/index.md - - Setup: platforms/openssl/setup.md - - Mbed TLS: - - platforms/mbedtls/index.md - - Setup: platforms/mbedtls/setup.md - - FatFs: - - platforms/fatfs/index.md - - FreeRTOS-Plus-FAT: - - platforms/plusfat/index.md - - Setup: platforms/plusfat/setup.md - - C11 atomics: - - platforms/atomics/index.md - Port a new platform: porting.md + - C11 atomics: platforms/atomics/index.md + - FatFs: platforms/fatfs/index.md + - FreeRTOS: platforms/freertos/index.md + - FreeRTOS-Plus-FAT: platforms/plusfat/index.md + - FreeRTOS-Plus-TCP: platforms/plustcp/index.md + - lwIP (Raw API): platforms/lwipraw/index.md + - Mbed TLS: platforms/mbedtls/index.md + - OpenSSL: platforms/openssl/index.md + - Posix: platforms/posix/index.md + - Windows: platforms/windows/index.md - Compliance: - CRA guide: cra.md - IEC 62443 guide: iec62443.md From 8765e83a8903c53ff1eb27094584d44b5ffb3c78 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 17:34:10 +0100 Subject: [PATCH 23/57] docs: S23.22 generate the platform doorways as chips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Option D for the platform pages, C for the API pages, from the mock. API reference and Setup were short sections at the foot of each platform page, where a reader looking for either had to scroll past everything else to find out they existed. They become two chips under the title. The banner on generated API pages becomes a chip in the same visual family, replacing a full-width admonition that used the whole column to say one thing — it is a signpost, not a caution. Both use the UML component symbol and the adapter blue of the post-it kit, where blue already means "a backend that realises a role" — which is what a platform adapter is. That borrows a vocabulary the site has rather than inventing one. All three are injected rather than written. Hand-written raw HTML was the first attempt and was wrong twice over: MkDocs rewrites and validates Markdown links only, so the hrefs pointed at .md files that do not exist in the built site, and the strict build could not see it. Generating them keeps the links inside MkDocs' own resolution, keeps the markup out of the pages, and means adding a platform needs no markup at all — the Setup chip appears when a setup page does. "Pack" is gone from the platform pages. It was jargon standing in for "platform" and earned nothing; it survives in six other documents and in CLAUDE.md, which is a separate sweep. Adopt becomes Integrate in the docs index too, matching the nav. Tests cover what the strict build cannot: that a Core header is not labelled with a platform, that the label is the platform page's own heading rather than the registry token, and that every registered platform has a docs folder. Part of #708 --- docs/README.md | 10 +- docs/_mock/template-options.md | 173 ++++++++++++++++++++++++++++++ docs/assets/stylesheets/brand.css | 78 ++++++++++++++ docs/platforms/atomics/index.md | 5 - docs/platforms/fatfs/index.md | 5 - docs/platforms/freertos/index.md | 5 - docs/platforms/lwipraw/index.md | 7 -- docs/platforms/mbedtls/index.md | 13 +-- docs/platforms/openssl/index.md | 9 -- docs/platforms/plusfat/index.md | 7 -- docs/platforms/plustcp/index.md | 5 - docs/platforms/posix/index.md | 5 - docs/platforms/windows/index.md | 5 - hooks/page_descriptions.py | 5 + hooks/platform_backlinks.py | 84 +++++++++------ hooks/test_page_descriptions.py | 6 +- hooks/test_platform_backlinks.py | 95 ++++++++++++++++ 17 files changed, 413 insertions(+), 104 deletions(-) create mode 100644 docs/_mock/template-options.md create mode 100644 hooks/test_platform_backlinks.py diff --git a/docs/README.md b/docs/README.md index d62a72d1..f65089a2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,8 +13,8 @@ and [solid-syslog-example-make](https://github.com/cososo-ltd/solid-syslog-examp Pick a lane: - [Overview](#overview): what SolidSyslog is and how it helps with CRA and IEC 62443 compliance. -- [Adopt it](#adopt): get a syslog stack compiling and sending in your product. -- [Platforms](#platforms): what reaches your hardware — the shipped adapter packs, and how to write one for a target we don't cover. +- [Integrate it](#integrate): get a syslog stack compiling and sending in your product. +- [Platforms](#platforms): what reaches your hardware — the shipped adapters, and how to write one for a target we don't cover. - [Compliance](#compliance): the CRA, IEC 62443, the RFCs, and the security posture. - [API reference](#api-reference): the public contracts, by audience. - [Maintaining the library](#maintaining): building, testing, and releasing SolidSyslog itself. @@ -26,7 +26,7 @@ Pick a lane: - Why SolidSyslog, and capability at a glance: the [project README](../README.md). - [Compliance in one page](overview.md): the evaluator's one-screen orientation. What CRA and IEC 62443 ask of an audit-logging function, and how SolidSyslog helps. -## Adopt +## Integrate Everything you need to consume SolidSyslog in your product. @@ -40,7 +40,7 @@ Everything you need to consume SolidSyslog in your product. A platform is a set of adapters wrapping one upstream thing — a network stack, a TLS library, a filesystem, an OS — behind the library's vtables. Each page says -what that pack ships and what wiring it needs; the pages above speak of platforms +what that platform ships and what wiring it needs; the pages above speak of them in the general case. - [Platform × capability matrix](platforms/index.md): start here. Read across a row for what a platform gives you, down a column for who provides a capability. @@ -74,7 +74,7 @@ reference — [Headers](api/files.md), [Data structures](api/annotated.md), ## Maintaining For contributors and maintainers of SolidSyslog itself (not for consuming it, -which is [Adopt](#adopt) above). +which is [Integrate](#integrate) above). - [Building and testing](builds.md): the CMake preset catalogue. - [Pre-PR local checks](local-checks.md): the tiered pre-PR check budget. diff --git a/docs/_mock/template-options.md b/docs/_mock/template-options.md new file mode 100644 index 00000000..1250fd34 --- /dev/null +++ b/docs/_mock/template-options.md @@ -0,0 +1,173 @@ +# Template options — scratch + +Not part of the documentation. A scratch page for choosing between layout +options in the real theme, deleted before this branch is finished. + + + + + + + +## Part 1 — getting to API reference and Setup from a platform page + +Both are currently short sections at the bottom. Four ways to move them to the +top. Each sample shows the real first screen of the Mbed TLS page. + +### A — quiet link row beneath the title + +
+

Mbed TLS

+

API reference·Setup

+

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

+

## What it ships …

+
+ +Lightest touch. Reads as metadata about the page rather than as content, which +is what it is. Risk: quiet enough to be missed. + +### B — Material buttons + +
+

Mbed TLS

+

API reference Setup

+

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

+

## What it ships …

+
+ +Impossible to miss, and native to the theme — no new CSS. But it pushes the +first paragraph down a long way, and two buttons on ten pages is a lot of +furniture for what is really navigation. + +### C — title suffix, your idea 2 + +
+

Mbed TLS API · Setup

+

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

+

## What it ships …

+
+ +Costs no vertical space at all. Two problems: it lands in the table of contents +and the browser tab title unless worked around, and "API" alone is terse to the +point of cryptic for a first-time reader. + +### D — chips with glyphs + +
+

Mbed TLS

+

API referenceSetup

+

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

+

## What it ships …

+
+ +Between A and B in weight, and the component glyph is the same one Part 2 uses, +so the two places agree visually. New CSS to own, and the wrench is decorative +rather than meaningful. + +--- + +## Part 2 — the platform banner on a generated API page + +Same four, against the current one. These sit at the very top of +`SolidSyslogMbedTlsStream_8h`, above the relationship diagram. + +### Current — full-width admonition + +
+

Part of the Mbed TLS platform

+

What the pack ships, what your build must provide, and the obligations it leaves to you.

+

SolidSyslogMbedTlsStream.h — File Reference …

+
+ +### B — one quiet line, left justified + +
+

Part of the Mbed TLS platform

+

SolidSyslogMbedTlsStream.h — File Reference …

+
+ +### C — post-it chip, in the kit's adapter blue + +
+ Mbed TLS platform +

SolidSyslogMbedTlsStream.h — File Reference …

+
+ +Blue is already the diagram kit's colour for "a backend that realises a role", +which is exactly what a platform adapter is — so this borrows a vocabulary the +site has rather than inventing one. + +### D — eyebrow label + +
+

Platform Mbed TLS

+

SolidSyslogMbedTlsStream.h — File Reference …

+
+ +Smallest of the four. Reads as a category label, which is accurate. + + + +--- + +## Part 3 — two wording decisions, no mock needed + +**"Pack" goes.** I introduced it to avoid repeating "platform" and it reads as +jargon for no gain. The page is the platform, so: *"Every class in this +platform"*, or name it — *"Every class in Mbed TLS"*. The second is better +still, because it works when read out of context. + +**When is a class name a link?** Right now it is a link in the *What it ships* +table and plain code in prose, which is the inconsistency you spotted. The rule +I would propose: the table is the canonical linked index of what the platform +ships, and every mention in prose is plain code. One place to click, no +judgement call per sentence, and no page full of blue. + +The alternative — link the first mention anywhere — needs a decision on every +paragraph and drifts the moment anyone edits. diff --git a/docs/assets/stylesheets/brand.css b/docs/assets/stylesheets/brand.css index b6f79dfc..e16a34e6 100644 --- a/docs/assets/stylesheets/brand.css +++ b/docs/assets/stylesheets/brand.css @@ -233,3 +233,81 @@ .postit-note a:hover { color: #3e2199; } + +/* ── Platform doorways ────────────────────────────────────────────────────── + Two links every platform page carries, and the one every generated API page + for a platform header carries back. Both use the UML component symbol, and + the adapter blue of the post-it kit (docs/assets/postit/README.md), where + blue already means "a backend that realises a role" — which is what a + platform adapter is. The glyphs are data URIs so the Markdown stays a plain + link and still reads on GitHub, where none of this CSS applies. */ + +.md-typeset .ss-chip { + display: inline-flex; + align-items: center; + gap: 0.38rem; + padding: 0.2rem 0.6rem 0.2rem 0.45rem; + border: 1px solid #d5d9f0; + border-radius: 999px; + background: #eef0fa; + color: #2f2a55; + font-size: 0.76rem; + text-decoration: none; +} + +.md-typeset .ss-chip:hover { + background: #e2e6f7; + color: #2f2a55; +} + +.md-typeset .ss-chip + .ss-chip { + margin-left: 0.4rem; +} + +.md-typeset .ss-chip::before { + content: ""; + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: center; +} + +.md-typeset .ss-chip--api::before { + background-image: url('data:image/svg+xml;utf8,'); +} + +.md-typeset .ss-chip--setup::before { + background-image: url('data:image/svg+xml;utf8,'); +} + +/* The banner on a generated API page. A post-it chip rather than an + admonition: it is a signpost, not a caution, and an admonition took the full + column width to say one thing. */ +.md-typeset a.ss-platform-chip { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin: 0 0 1.1rem; + padding: 0.3rem 0.7rem 0.3rem 0.55rem; + border: 1px solid #9aa6e0; + border-radius: 3px; + background: #cdd4f5; + box-shadow: 1px 2px 3px rgb(0 0 0 / 16%); + color: #23264a; + font-size: 0.78rem; + text-decoration: none; +} + +.md-typeset a.ss-platform-chip:hover { + background: #bcc5f0; + color: #23264a; +} + +.md-typeset a.ss-platform-chip::before { + content: ""; + width: 16px; + height: 16px; + background-repeat: no-repeat; + background-position: center; + background-image: url('data:image/svg+xml;utf8,'); +} diff --git a/docs/platforms/atomics/index.md b/docs/platforms/atomics/index.md index 406b5638..dcae5744 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/atomics/index.md @@ -15,8 +15,3 @@ Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. A C11 compiler with ``. Windows toolchains without it use [`SolidSyslogWindowsAtomicCounter`](../windows/index.md) instead. - -## API reference - -[Every class in this pack](../../api/group__platform__atomics.md), generated from the -headers. diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index e2f98f32..14ddd158 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -17,8 +17,3 @@ BlockDevice. Your `ffconf.h`, a `diskio.c` media driver, and — if `FF_FS_REENTRANT=1` — an `ffsystem.c`. - -## API reference - -[Every class in this pack](../../api/group__platform__fatfs.md), generated from the -headers. diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md index 6cbd8fb6..824a6358 100644 --- a/docs/platforms/freertos/index.md +++ b/docs/platforms/freertos/index.md @@ -17,8 +17,3 @@ Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. ## Requirements `configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. - -## API reference - -[Every class in this pack](../../api/group__platform__freertos.md), generated from the -headers. diff --git a/docs/platforms/lwipraw/index.md b/docs/platforms/lwipraw/index.md index 646b1949..9e87fbf2 100644 --- a/docs/platforms/lwipraw/index.md +++ b/docs/platforms/lwipraw/index.md @@ -53,10 +53,3 @@ Your `lwipopts.h` must enable the features the adapter wraps: Also set `ARP_QUEUEING=1` (else the first datagram to an unresolved peer is dropped) and `LWIP_TCP_KEEPALIVE=1`, and size `PBUF_POOL_SIZE` / `MEMP_NUM_TCP_PCB` / `MEMP_NUM_UDP_PCB` to your instance counts. - -Full setup — config, marshal, DNS — is [Integrating lwIP](setup.md). - -## API reference - -[Every class in this pack](../../api/group__platform__lwipraw.md), generated from the -headers. diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index 401a664c..f7e3a088 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -16,8 +16,8 @@ integrity and confidentiality. ## Requirements -The pack compiles against your own `mbedtls_config.h`, in your target, so the -features you enable are the features it gets. +The adapter sources compile in your target against your own +`mbedtls_config.h`, so the features you enable are the features it gets. Credentials are passed as caller-built, caller-owned handles rather than file paths: a seeded `mbedtls_ctr_drbg_context` for the handshake, an @@ -96,12 +96,3 @@ generator, or replace a debug callback. TLS policy is applied per `ssl_config`, so it cannot affect the ones you build elsewhere. A device that already uses Mbed TLS for firmware update or a vendor cloud SDK keeps that configuration intact, and the claim can be checked against the directory. - -## API reference - -[Every class in this pack](../../api/group__platform__mbedtls.md), generated -from the headers. - -## Setup - -Wiring it up, handle by handle: [Mbed TLS setup](setup.md). diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index c28ac34e..e23608b7 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -87,12 +87,3 @@ The adapter performs no revocation checking, by Certificate Revocation List or by the Online Certificate Status Protocol. Where a deployment requires it, it must come from your own configuration of OpenSSL, and confirming that it is in force is part of your assessment rather than something the adapter reports. - -## API reference - -[Every class in this pack](../../api/group__platform__openssl.md), generated -from the headers. - -## Setup - -Wiring it up, over any byte transport: [OpenSSL setup](setup.md). diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index b9662d08..44770331 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -16,10 +16,3 @@ BlockDevice. FreeRTOS — Plus-FAT is FreeRTOS-coupled. Supply an `FF_Disk_t` media driver and `FreeRTOSFATConfig.h`. - -Full setup is [Integrating FreeRTOS-Plus-FAT](setup.md). - -## API reference - -[Every class in this pack](../../api/group__platform__plusfat.md), generated from the -headers. diff --git a/docs/platforms/plustcp/index.md b/docs/platforms/plustcp/index.md index 836eb0d2..34b44576 100644 --- a/docs/platforms/plustcp/index.md +++ b/docs/platforms/plustcp/index.md @@ -20,8 +20,3 @@ address handle they share. FreeRTOS-Plus-TCP, selected at CMake time with naming `PlusTcp` in `SOLIDSYSLOG_PLATFORMS`. The resolver wraps `FreeRTOS_getaddrinfo`, so your `FreeRTOSIPConfig.h` needs `ipconfigUSE_DNS=1`. - -## API reference - -[Every class in this pack](../../api/group__platform__plustcp.md), generated from the -headers. diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md index f574e4c6..b97fa1d2 100644 --- a/docs/platforms/posix/index.md +++ b/docs/platforms/posix/index.md @@ -30,8 +30,3 @@ callbacks. A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs POSIX message queues (link `-lrt` on glibc). - -## API reference - -[Every class in this pack](../../api/group__platform__posix.md), generated from the -headers. diff --git a/docs/platforms/windows/index.md b/docs/platforms/windows/index.md index 18f2ba1e..e4af0132 100644 --- a/docs/platforms/windows/index.md +++ b/docs/platforms/windows/index.md @@ -28,8 +28,3 @@ callbacks. The MSVC toolchain and Winsock — call `WSAStartup` once at process init before creating a sender. - -## API reference - -[Every class in this pack](../../api/group__platform__windows.md), generated from the -headers. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index 8119feb9..5183efb9 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -167,6 +167,11 @@ "The twelve vtable contracts SolidSyslog composes against, what fills " "each one, and the Null fallback that keeps an unfilled role safe." ), + # Scratch — deleted before this branch is finished. + "_mock/template-options.md": ( + "Scratch page for choosing platform-page layout options in the real " + "theme. Not part of the documentation." + ), "assets/postit/README.md": ( "The post-it diagram kit behind the SolidSyslog architecture pictures: " "what each colour and arrow means, and how the diagrams are generated." diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 8380c0ad..9b612301 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -1,18 +1,25 @@ -"""MkDocs build hook: link every generated platform API page to its platform. +"""MkDocs build hook: the doorways between a platform and its API reference. -The API reference answers "what does this header declare". It never answered -"which platform pack is this, and what does that pack need from my build" — -which is the question a reader arriving from a search result actually has. +Three links, none of them hand-written: -Every generated page for a header under ``Platform//`` gets one line at -the top linking to that pack's hand-written page. The platform's own page links -the other way, to the pack's Doxygen group, so the pair closes the loop. +* every generated API page for a header under ``Platform//`` gets a chip + naming the platform it belongs to — the question a reader arriving from a + search result has, and one the API reference could not previously answer; +* every platform page gets a chip to its Doxygen group, the generated reference + for everything that platform declares; +* and one to its setup guide, where the platform has one. + +They are injected rather than written because they are navigation furniture, not +content: generating them keeps the Markdown clean, keeps the links inside +MkDocs' own resolution and validation (raw HTML in a page is neither rewritten +nor checked), and means adding a platform needs no markup at all. The platform list is read from ``SOLIDSYSLOG_PLATFORM_REGISTRY`` in the top-level CMakeLists.txt — the same table ``scripts/check_manifest.py`` treats -as authoritative — so a new pack is picked up by being registered, with no edit -here. The slug is the registry token lowercased, which is the convention the -docs folder and the Doxygen group both follow. +as authoritative — so a new platform is picked up by being registered. Its slug +is the registry token lowercased, which is what the docs folder and the Doxygen +group are both named after, and its display name is the platform page's own +heading, so the name is written once. """ import os @@ -21,17 +28,15 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') -# Generated page stems are the header path with non-alphanumerics escaped; -# the leaf name is enough to identify the header, since every public header in -# the tree is uniquely named (docs/NAMING.md). GENERATED_PREFIX = "api/" +PLATFORM_PREFIX = "platforms/" _CACHE = {} def _label(root, slug): - """The platform's own H1 — so the name is written once, on its page.""" - path = os.path.join(root, "docs", "platforms", slug, "index.md") + """The platform's own H1 — so the display name is written once, on its page.""" + path = os.path.join(root, "docs", PLATFORM_PREFIX, slug, "index.md") with open(path, encoding="utf-8") as page: for line in page: if line.startswith("# "): @@ -39,22 +44,24 @@ def _label(root, slug): return slug -def _packs(config): - """Return {header stem: (label, slug)} for every public platform header.""" +def _index(config): + """Return (headers, slugs): header stem -> (label, slug), and the slug set.""" root = os.path.dirname(config["config_file_path"]) if root not in _CACHE: with open(os.path.join(root, "CMakeLists.txt"), encoding="utf-8") as cmake: registry = REGISTRY.search(cmake.read()) - headers = {} + headers, slugs = {}, {} for token, directory in ROW.findall(registry.group(1)) if registry else []: interface = os.path.join(root, directory, "Interface") if not os.path.isdir(interface): continue - pack = (_label(root, token.lower()), token.lower()) + slug = token.lower() + pack = (_label(root, slug), slug) + slugs[slug] = os.path.isfile(os.path.join(root, "docs", PLATFORM_PREFIX, slug, "setup.md")) for name in os.listdir(interface): if name.endswith(".h"): headers[name[: -len(".h")]] = pack - _CACHE[root] = headers + _CACHE[root] = (headers, slugs) return _CACHE[root] @@ -66,16 +73,27 @@ def _stem(src_uri): def on_page_markdown(markdown, page, config, files, **kwargs): src_uri = getattr(page.file, "src_uri", None) or page.file.src_path.replace(os.sep, "/") - if not src_uri.startswith(GENERATED_PREFIX): - return markdown - stem = _stem(src_uri) - pack = _packs(config).get(stem) if stem else None - if pack is None: - return markdown - label, slug = pack - banner = ( - f"!!! info \"Part of the [{label}](../platforms/{slug}/index.md) platform\"\n" - f" What the pack ships, what your build must provide, and the\n" - f" obligations it leaves to you.\n\n" - ) - return banner + markdown + headers, slugs = _index(config) + + if src_uri.startswith(GENERATED_PREFIX): + stem = _stem(src_uri) + pack = headers.get(stem) if stem else None + if pack is None: + return markdown + label, slug = pack + chip = f"[{label} platform](../{PLATFORM_PREFIX}{slug}/index.md){{ .ss-platform-chip }}" + return f"{chip}\n\n{markdown}" + + if src_uri.startswith(PLATFORM_PREFIX) and src_uri.endswith("/index.md"): + slug = src_uri[len(PLATFORM_PREFIX) : -len("/index.md")] + if slug not in slugs: + return markdown + chips = [f"[API reference](../../api/group__platform__{slug}.md){{ .ss-chip .ss-chip--api }}"] + if slugs[slug]: + chips.append("[Setup](setup.md){ .ss-chip .ss-chip--setup }") + # One line, so the inline-flex chips sit side by side; placed under the + # title rather than above it, so the page still opens with its name. + title, _, body = markdown.partition("\n") + return f"{title}\n\n{' '.join(chips)}\n\n{body.lstrip()}" + + return markdown diff --git a/hooks/test_page_descriptions.py b/hooks/test_page_descriptions.py index 7e06f143..4f97cc91 100644 --- a/hooks/test_page_descriptions.py +++ b/hooks/test_page_descriptions.py @@ -74,8 +74,10 @@ def test_front_matter_wins(self): self.assertEqual(page.meta["description"], "hand written") def test_unmapped_page_falls_back_to_site_description(self): - page = page_of("assets/postit/README.md") - h.on_page_markdown("# Post-its", page, {}, None) + # A page name deliberately not in the map — on_nav is what makes a real + # unmapped page fail the build, so this only covers the fallback itself. + page = page_of("no-such-page.md") + h.on_page_markdown("# Nothing", page, {}, None) self.assertNotIn("description", page.meta) def test_markdown_is_returned_unchanged(self): diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py new file mode 100644 index 00000000..7c2dce46 --- /dev/null +++ b/hooks/test_platform_backlinks.py @@ -0,0 +1,95 @@ +"""Regression tests for the platform doorway hook (hooks/platform_backlinks.py). + +Run: python3 hooks/test_platform_backlinks.py + (or: python3 -m unittest discover -s hooks -p 'test_*.py') + +The strict build proves the links resolve. What it cannot prove is that the +right pages get chips and the wrong ones do not — a Core header quietly +labelled with a platform, or a platform page silently missing its doorway, +builds perfectly well. +""" + +import os +import sys +import types +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import platform_backlinks as h # noqa: E402 + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +CONFIG = {"config_file_path": os.path.join(ROOT, "mkdocs.yml")} + + +def page_of(src_uri): + return types.SimpleNamespace(file=types.SimpleNamespace(src_uri=src_uri)) + + +def render(src_uri, markdown="# Title\n\nBody.\n"): + return h.on_page_markdown(markdown, page_of(src_uri), CONFIG, None) + + +class GeneratedApiPages(unittest.TestCase): + def test_a_platform_header_is_labelled_with_its_platform(self): + out = render("api/SolidSyslogMbedTlsStream_8h.md") + self.assertIn("[Mbed TLS platform](../platforms/mbedtls/index.md){ .ss-platform-chip }", out) + + def test_the_label_is_the_platform_pages_own_heading(self): + # Not the registry token, which is "LwipRaw". + self.assertIn("[lwIP (Raw API) platform]", render("api/SolidSyslogLwipRawTcpStream_8h.md")) + + def test_a_core_header_is_not_labelled(self): + markdown = "# Core\n\nBody.\n" + self.assertEqual(render("api/SolidSyslogConfig_8h.md", markdown), markdown) + + def test_a_generated_index_is_not_labelled(self): + markdown = "# Files\n" + self.assertEqual(render("api/files.md", markdown), markdown) + + +class PlatformPages(unittest.TestCase): + def test_chips_go_under_the_title_not_above_it(self): + out = render("platforms/mbedtls/index.md") + self.assertTrue(out.startswith("# Title\n\n["), out[:40]) + + def test_a_platform_with_a_setup_guide_gets_both_chips(self): + out = render("platforms/mbedtls/index.md") + self.assertIn("[API reference](../../api/group__platform__mbedtls.md){ .ss-chip .ss-chip--api }", out) + self.assertIn("[Setup](setup.md){ .ss-chip .ss-chip--setup }", out) + + def test_a_platform_without_one_gets_no_setup_chip(self): + # Phase 3 adds the missing setup pages; until then the chip must not + # appear, because the page it would point at does not exist. + out = render("platforms/posix/index.md") + self.assertIn(".ss-chip--api", out) + self.assertNotIn(".ss-chip--setup", out) + + def test_the_platforms_overview_is_not_a_platform(self): + markdown = "# Platforms\n\nBody.\n" + self.assertEqual(render("platforms/index.md", markdown), markdown) + + def test_a_setup_page_gets_no_chips(self): + markdown = "# Mbed TLS setup\n\nBody.\n" + self.assertEqual(render("platforms/mbedtls/setup.md", markdown), markdown) + + def test_an_ordinary_page_is_untouched(self): + markdown = "# Porting\n\nBody.\n" + self.assertEqual(render("porting.md", markdown), markdown) + + +class Registry(unittest.TestCase): + def test_every_registered_platform_has_a_docs_folder(self): + _, slugs = h._index(CONFIG) + for slug in slugs: + self.assertTrue( + os.path.isfile(os.path.join(ROOT, "docs", "platforms", slug, "index.md")), + f"registry names {slug} but docs/platforms/{slug}/index.md is missing", + ) + + def test_the_registry_yields_all_ten_platforms(self): + _, slugs = h._index(CONFIG) + self.assertEqual(len(slugs), 10, sorted(slugs)) + + +if __name__ == "__main__": + unittest.main() From 60ec1fdaa53eb37b00a619c7be9b7388f7c1792b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 17:41:51 +0100 Subject: [PATCH 24/57] docs: S23.22 roll the platform template across the remaining eight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform page now carries a Security behaviour and obligations section. The eight added here are shorter than the two TLS ones because there is less to say, not because less was looked for — what they state is what an integrator cannot infer from the class list. Some of it is uncomfortable and belongs on the page for that reason: the POSIX and Windows transports carry syslog in clear; leaving the FreeRTOS mutex unfilled is a silent data race on a multi-task target, undetectable by construction; an asynchronous lwIP marshal corrupts stack state rather than failing; FAT is not journalling, so f_sync and ff_fflush bound loss without guaranteeing the directory entry; and if the AtomicCounter pool is exhausted the Null counter returns 1 every time, so gap detection reports nothing wrong while proving nothing at all. Sibling-platform references are gone. Three pages named another platform to say where a capability comes from — which is the coupling that makes adding a platform expensive, since the eleventh would have to be added to each of them. They point at the capability matrix instead, which is the one place that already answers "who fills this role". The API-page chip drops the UML-class styling and becomes the same pill as the platform pages' own chips. One chip style across the site. Part of #708 --- docs/assets/stylesheets/brand.css | 38 ++++--------------------------- docs/platforms/atomics/index.md | 28 +++++++++++++++++++++-- docs/platforms/fatfs/index.md | 22 ++++++++++++++++++ docs/platforms/freertos/index.md | 26 +++++++++++++++++++-- docs/platforms/lwipraw/index.md | 33 +++++++++++++++++++++++++-- docs/platforms/plusfat/index.md | 22 ++++++++++++++++++ docs/platforms/plustcp/index.md | 22 ++++++++++++++++++ docs/platforms/posix/index.md | 30 ++++++++++++++++++++++++ docs/platforms/windows/index.md | 26 +++++++++++++++++++++ hooks/platform_backlinks.py | 2 +- hooks/test_platform_backlinks.py | 2 +- 11 files changed, 210 insertions(+), 41 deletions(-) diff --git a/docs/assets/stylesheets/brand.css b/docs/assets/stylesheets/brand.css index e16a34e6..b6fd3a26 100644 --- a/docs/assets/stylesheets/brand.css +++ b/docs/assets/stylesheets/brand.css @@ -272,42 +272,14 @@ background-position: center; } -.md-typeset .ss-chip--api::before { +/* The doorway on a generated API page back to its platform. Same pill as + the platform page's own chips — one chip style across the site, not two. + It sits in its own paragraph, so the spacing below it is the paragraph's. */ +.md-typeset .ss-chip--api::before, +.md-typeset .ss-chip--platform::before { background-image: url('data:image/svg+xml;utf8,'); } .md-typeset .ss-chip--setup::before { background-image: url('data:image/svg+xml;utf8,'); } - -/* The banner on a generated API page. A post-it chip rather than an - admonition: it is a signpost, not a caution, and an admonition took the full - column width to say one thing. */ -.md-typeset a.ss-platform-chip { - display: inline-flex; - align-items: center; - gap: 0.5rem; - margin: 0 0 1.1rem; - padding: 0.3rem 0.7rem 0.3rem 0.55rem; - border: 1px solid #9aa6e0; - border-radius: 3px; - background: #cdd4f5; - box-shadow: 1px 2px 3px rgb(0 0 0 / 16%); - color: #23264a; - font-size: 0.78rem; - text-decoration: none; -} - -.md-typeset a.ss-platform-chip:hover { - background: #bcc5f0; - color: #23264a; -} - -.md-typeset a.ss-platform-chip::before { - content: ""; - width: 16px; - height: 16px; - background-repeat: no-repeat; - background-position: center; - background-image: url('data:image/svg+xml;utf8,'); -} diff --git a/docs/platforms/atomics/index.md b/docs/platforms/atomics/index.md index dcae5744..78b05a09 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/atomics/index.md @@ -13,5 +13,29 @@ Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. ## Requirements -A C11 compiler with ``. Windows toolchains without it use -[`SolidSyslogWindowsAtomicCounter`](../windows/index.md) instead. +A C11 compiler with ``. Where a toolchain lacks it, the +[platform × capability matrix](../index.md) shows which other platforms fill +the AtomicCounter role. + +## Security behaviour and obligations + +### The sequence is what makes loss detectable + +`sequenceId` is assigned when a record is raised, so a gap seen by the collector +reflects loss anywhere in the pipeline. That signal is only as good as the +counter behind it: if the pool is exhausted, the role falls back to the Null +counter, whose increment returns 1 every time. Gap detection then reports +nothing wrong while delivering nothing useful. Size the pool for the instances +you create, and install an error handler so exhaustion is seen. + +### The sequence wraps, and a collector must expect it + +Values run in `[1, 2^31 - 1]` and skip zero on wrap. A long-lived device will +reuse numbers, so collector-side gap detection has to treat wrap as ordinary +rather than as a discontinuity. + +### It evidences loss, not origin + +`sequenceId` is a plain counter, not a cryptographic construction. It shows that +a record is missing; it does not bind a record to the device that raised it, and +it can be reproduced by anything that can write records. diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index 14ddd158..8940ab58 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -17,3 +17,25 @@ BlockDevice. Your `ffconf.h`, a `diskio.c` media driver, and — if `FF_FS_REENTRANT=1` — an `ffsystem.c`. + +## Security behaviour and obligations + +### The file layer offers no confidentiality or tamper evidence + +Records are written as given. Detecting modification of a stored record, or +keeping it unreadable, is the SecurityPolicy role's job, not this one — see +[at-rest cryptography](../../security/at-rest-cryptography.md). + +### Durability is bounded by the write, not guaranteed by it + +`f_sync` runs after every write, so at most the record in flight is lost on +power failure. Whether that reaches the medium, and what the FAT metadata looks +like afterwards, is a property of your `diskio.c` driver and the hardware under +it. FAT is not a journalling filesystem, and a partially written directory entry +is possible on a device that loses power mid-update. + +### The media driver is yours + +`diskio.c`, and `ffsystem.c` where `FF_FS_REENTRANT=1`, are supplied by you. So +is any wear levelling: the store rewrites the same blocks in rotation, which on +raw flash without wear levelling concentrates erase cycles. diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md index 824a6358..60dd779a 100644 --- a/docs/platforms/freertos/index.md +++ b/docs/platforms/freertos/index.md @@ -2,8 +2,8 @@ `Platform/FreeRtos/` wraps FreeRTOS kernel primitives ([FreeRTOS documentation](https://www.freertos.org/Documentation/00-Overview)). -Networking is a separate backend — [FreeRTOS-Plus-TCP](../plustcp/index.md) or -[lwIP](../lwipraw/index.md). +Networking comes from a separate platform; the +[platform × capability matrix](../index.md) shows which fill it. Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. @@ -17,3 +17,25 @@ Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. ## Requirements `configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. + +## Security behaviour and obligations + +### The mutex is what makes a shared buffer safe + +Where the application task calling `Log` and the task calling `Service` are +different, the buffer between them needs this mutex. Leaving the role unfilled +resolves to the Null mutex, which is a working no-op on a single-task target and +a silent data race on a multi-task one. Nothing reports the difference, because +nothing can detect it. + +### Static allocation is required, and is the point + +`configSUPPORT_STATIC_ALLOCATION=1` is not a convenience: the kernel object is +created from storage inside the library's own pool, so the adapter allocates +nothing at run time and cannot fail for want of heap. + +### Uptime is a tick count, not a clock + +The sysUpTime callback reports kernel ticks since boot. It is not wall-clock +time and carries no timezone or synchronisation quality — the clock callback is +a separate injection point. diff --git a/docs/platforms/lwipraw/index.md b/docs/platforms/lwipraw/index.md index 9e87fbf2..4f6ea56e 100644 --- a/docs/platforms/lwipraw/index.md +++ b/docs/platforms/lwipraw/index.md @@ -6,8 +6,8 @@ files compile against your `lwipopts.h`, so the adapter inherits your stack's configuration. Fills the Resolver, Datagram and Stream [roles](../../roles/index.md), plus the -address handle they share. Layer [Mbed TLS](../mbedtls/index.md) over the TCP stream for -TLS. +address handle they share. A TLS platform layers over the TCP stream; the +[platform × capability matrix](../index.md) shows which provide it. ## What it ships @@ -53,3 +53,32 @@ Your `lwipopts.h` must enable the features the adapter wraps: Also set `ARP_QUEUEING=1` (else the first datagram to an unresolved peer is dropped) and `LWIP_TCP_KEEPALIVE=1`, and size `PBUF_POOL_SIZE` / `MEMP_NUM_TCP_PCB` / `MEMP_NUM_UDP_PCB` to your instance counts. + +## Security behaviour and obligations + +### The transport carries syslog in clear + +Neither the datagram nor the TCP stream provides confidentiality, integrity or +peer authentication. TLS is a separate role filled by a different platform — the +[platform × capability matrix](../index.md) shows which — layered over this +stream rather than replacing it. + +### The marshal is a correctness requirement, not a tuning knob + +On a build with an lwIP thread (`NO_SYS=0`), every call this adapter makes must +reach the core-owning context, and it must do so synchronously because results +are read the moment the hop returns. An asynchronous marshal, or none at all, +corrupts lwIP's internal state rather than failing cleanly. Install it once at +boot, before any adapter is created. + +### Resolution is trusted as the stack returns it + +The DNS resolver forwards what lwIP answers. A deployment that cannot trust its +DNS should give the collector a numeric address, so that no resolution step +exists to be poisoned. + +### Pool sizing is yours, and exhaustion is silent at the stack + +`PBUF_POOL_SIZE`, `MEMP_NUM_TCP_PCB` and `MEMP_NUM_UDP_PCB` must cover the +instances you create alongside everything else using the stack. Under-sizing +shows as dropped records rather than as an error from lwIP. diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index 44770331..e12bfbbc 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -16,3 +16,25 @@ BlockDevice. FreeRTOS — Plus-FAT is FreeRTOS-coupled. Supply an `FF_Disk_t` media driver and `FreeRTOSFATConfig.h`. + +## Security behaviour and obligations + +### The file layer offers no confidentiality or tamper evidence + +Records are written as given. Detecting modification of a stored record, or +keeping it unreadable, is the SecurityPolicy role's job, not this one — see +[at-rest cryptography](../../security/at-rest-cryptography.md). + +### Durability is bounded by the write, not guaranteed by it + +`ff_fflush` runs after every write, so at most the record in flight is lost on +power failure. Whether that reaches the medium is a property of your `FF_Disk_t` +driver and the hardware under it. FAT is not a journalling filesystem, and a +partially written directory entry is possible on a device that loses power +mid-update. + +### The media driver is yours + +The `FF_Disk_t` implementation is supplied by you, and so is any wear levelling: +the store rewrites the same blocks in rotation, which on raw flash without wear +levelling concentrates erase cycles. diff --git a/docs/platforms/plustcp/index.md b/docs/platforms/plustcp/index.md index 34b44576..72faee77 100644 --- a/docs/platforms/plustcp/index.md +++ b/docs/platforms/plustcp/index.md @@ -20,3 +20,25 @@ address handle they share. FreeRTOS-Plus-TCP, selected at CMake time with naming `PlusTcp` in `SOLIDSYSLOG_PLATFORMS`. The resolver wraps `FreeRTOS_getaddrinfo`, so your `FreeRTOSIPConfig.h` needs `ipconfigUSE_DNS=1`. + +## Security behaviour and obligations + +### The transport carries syslog in clear + +Neither the datagram nor the TCP stream provides confidentiality, integrity or +peer authentication. TLS is a separate role filled by a different platform — the +[platform × capability matrix](../index.md) shows which — layered over this +stream rather than replacing it. + +### Resolution is trusted as the stack returns it + +The resolver forwards what FreeRTOS-Plus-TCP answers. A deployment that cannot +trust its DNS should give the collector a numeric address rather than a name, so +that no resolution step exists to be poisoned. + +### The stack's configuration is yours + +Buffer counts, socket limits and timer behaviour are set in your +`FreeRTOSIPConfig.h`. Sizing them for the number of adapter instances you create +is part of the integration, and exhaustion surfaces as a failure to send rather +than as a crash. diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md index b97fa1d2..94921a61 100644 --- a/docs/platforms/posix/index.md +++ b/docs/platforms/posix/index.md @@ -30,3 +30,33 @@ callbacks. A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs POSIX message queues (link `-lrt` on glibc). + +## Security behaviour and obligations + +### The transport carries syslog in clear + +Neither the datagram nor the TCP stream provides confidentiality, integrity or +peer authentication. Anything on the path can read and alter the records. TLS is +a separate role filled by a different platform — the +[platform × capability matrix](../index.md) shows which — layered over this +stream rather than replacing it. + +### The store file is owner-only; its directory is yours + +Files are created readable and writable by the owning user alone. That protects +the file, not the path to it: the ownership and permissions of the directory you +place the store in, and whether another process on the device runs as the same +user, are yours to set and to verify. + +### Host identity is only as good as the operating system's + +The hostname and process id are read from the OS and forwarded unmodified. They +identify the record's origin exactly as far as the OS can be trusted to report +it, and the library performs no independent check. + +### The blocking surface is bounded but not zero + +Sockets are non-blocking once open, so a send returns immediately against a +wedged peer. The initial connection is the bounded exception, and its budget is +a tunable. A deployment with a hard real-time deadline should drive delivery +from a service thread rather than the calling one. diff --git a/docs/platforms/windows/index.md b/docs/platforms/windows/index.md index e4af0132..94cafd11 100644 --- a/docs/platforms/windows/index.md +++ b/docs/platforms/windows/index.md @@ -28,3 +28,29 @@ callbacks. The MSVC toolchain and Winsock — call `WSAStartup` once at process init before creating a sender. + +## Security behaviour and obligations + +### The transport carries syslog in clear + +Neither the datagram nor the TCP stream provides confidentiality, integrity or +peer authentication. TLS is a separate role filled by a different platform — the +[platform × capability matrix](../index.md) shows which — layered over this +stream rather than replacing it. + +### Winsock initialisation is yours + +`WSAStartup` must be called once at process start, before any sender is created, +and the matching `WSACleanup` is yours to place. The adapter does not initialise +Winsock, because a process that already uses sockets has done it. + +### Protection of the store is a property of its directory + +Where the store is written, and which accounts can reach it, are decided by the +directory you choose and the access control on it. The library sets no policy of +its own and checks none. + +### Host identity is only as good as the operating system's + +The hostname and process id are read from Windows and forwarded unmodified. The +library performs no independent check of either. diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 9b612301..05fd3f5f 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -81,7 +81,7 @@ def on_page_markdown(markdown, page, config, files, **kwargs): if pack is None: return markdown label, slug = pack - chip = f"[{label} platform](../{PLATFORM_PREFIX}{slug}/index.md){{ .ss-platform-chip }}" + chip = f"[{label} platform](../{PLATFORM_PREFIX}{slug}/index.md){{ .ss-chip .ss-chip--platform }}" return f"{chip}\n\n{markdown}" if src_uri.startswith(PLATFORM_PREFIX) and src_uri.endswith("/index.md"): diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 7c2dce46..e11bdd84 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -32,7 +32,7 @@ def render(src_uri, markdown="# Title\n\nBody.\n"): class GeneratedApiPages(unittest.TestCase): def test_a_platform_header_is_labelled_with_its_platform(self): out = render("api/SolidSyslogMbedTlsStream_8h.md") - self.assertIn("[Mbed TLS platform](../platforms/mbedtls/index.md){ .ss-platform-chip }", out) + self.assertIn("[Mbed TLS platform](../platforms/mbedtls/index.md){ .ss-chip .ss-chip--platform }", out) def test_the_label_is_the_platform_pages_own_heading(self): # Not the registry token, which is "LwipRaw". From 750874f171364ad2b46d86431fb56f313a49e379 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 18:46:24 +0100 Subject: [PATCH 25/57] docs: S23.22 trim the two TLS setup pages to the wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sets the pattern for the other eight: a setup page carries only what neither the platform page nor the header already owns. On that test most of the Mbed TLS page was duplication — 1,519 words to 663. Cut because the platform page now says it: the TLS 1.2 floor, the coexistence contract, certificate rotation, key custody, and what the adapter does not own. Cut because the config struct says it, on the fields themselves where it cannot drift: the whole "what you need to provide" table, whose ServerName row ran to nine lines. Cut because the example repositories are where working code is shown: the reference integrations table and the links into BDD targets. Cut because a page that names another platform has to be revisited when that platform changes: the opening comparison with the OpenSSL adapter. Kept, because it lives nowhere else and all of it was learned the hard way: the layering, the wiring itself, and the four failures that are almost unreadable from their symptoms — entropy sources that are not tagged STRONG, psa_crypto_init called before the DRBG is seeded, the external RNG hook on a target with no platform entropy, and Mbed TLS reaching newlib's syscall heap instead of the RTOS one. porting.md linked the coexistence contract by anchor; it now points at the platform page, where that section moved. Part of #708 --- docs/platforms/mbedtls/setup.md | 324 ++++++++++---------------------- docs/platforms/openssl/setup.md | 32 +--- docs/porting.md | 2 +- hooks/page_descriptions.py | 6 +- 4 files changed, 116 insertions(+), 248 deletions(-) diff --git a/docs/platforms/mbedtls/setup.md b/docs/platforms/mbedtls/setup.md index d82aa030..fc76137a 100644 --- a/docs/platforms/mbedtls/setup.md +++ b/docs/platforms/mbedtls/setup.md @@ -1,228 +1,110 @@ -# Integrating SolidSyslog with mbedTLS +# Mbed TLS setup -`SolidSyslogMbedTlsStream` lets you deliver RFC 5425 (syslog over TLS) -records from SolidSyslog through Mbed TLS instead of OpenSSL. It is the -recommended adapter on embedded / FreeRTOS / bare-metal targets where -OpenSSL is too large or impractical. Hosted Linux / Windows deployments -should use `SolidSyslogTlsStream` (OpenSSL); both adapters expose the -same `SolidSyslogStream` vtable, so the rest of the wiring -(`SolidSyslogStreamSender`, your buffer, your store) is identical. +Wiring `SolidSyslogMbedTlsStream` so a `SolidSyslogStreamSender` delivers +RFC 5425 syslog over TLS. [Mbed TLS](index.md) covers what the adapter +guarantees and what it leaves to you; the config fields are documented on the +struct itself. This page is the wiring, and the things that bite. -This document covers what you, the integrator, plug in. It does not -re-teach mbedTLS. For that, see the -[upstream Mbed TLS documentation](https://mbed-tls.readthedocs.io/). +## The layering ---- - -## The shape +TLS is a Stream wrapped around another Stream. The TLS adapter carries the +records; the transport underneath carries the bytes. ```text -SolidSyslog_Log ─▶ Buffer ─▶ Sender ─▶ SolidSyslogStreamSender - │ - ▼ - SolidSyslogMbedTlsStream ◀── you build CA/cert/key/DRBG handles - │ - ▼ - SolidSyslogStream ◀── you pick / write the TCP backend - │ - ▼ - (your TCP/IP stack) +SolidSyslog_Log ─▶ Buffer ─▶ SolidSyslogStreamSender + │ + ▼ + SolidSyslogMbedTlsStream ◀── your CA / cert / key / DRBG handles + │ + ▼ + a byte-transport Stream ◀── your TCP/IP stack +``` + +You supply two things: the byte transport, and the Mbed TLS handles. Everything +above the TLS stream is unchanged from a plaintext wiring — `StreamSender` +applies RFC 6587 octet-counting framing on top either way. + +## Wiring it + +```c +struct SolidSyslogMbedTlsStreamConfig cfg = { + .Transport = myTcpStream, + .Sleep = MyVTaskDelayWrapper, /* required — no fallback */ + .Rng = &mySeededDrbg, + .CaChain = &myParsedCaChain, + .ServerName = "syslog.example.com", + .ClientCertChain = &myClientCert, /* both, or neither */ + .ClientKey = &myClientKey, +}; +struct SolidSyslogStream* tls = SolidSyslogMbedTlsStream_Create(&cfg); ``` -You supply two things directly: the byte-transport `SolidSyslogStream` and -the per-context mbedTLS handles passed through -`SolidSyslogMbedTlsStreamConfig`. - ---- - -## What you need to provide - -| Item | Owner | Notes | -|---|---|---| -| `Transport` | You | A `SolidSyslogStream*` carrying TCP. The library ships `SolidSyslogPosixTcpStream` (POSIX), `SolidSyslogWinsockTcpStream` (Windows), and `SolidSyslogPlusTcpTcpStream` (FreeRTOS-Plus-TCP). If your TCP/IP stack is different (LwIP, NicheStack, vendor BSP), write your own `SolidSyslogStream`; see [`Platform/Posix/Source/SolidSyslogPosixTcpStream.c`](../../../Platform/Posix/Source/SolidSyslogPosixTcpStream.c) as a reference. | -| `Sleep` | You | A `SolidSyslogSleepFunction`. Drives the bounded handshake retry between `WANT_READ` / `WANT_WRITE` polls. On FreeRTOS use a `vTaskDelay`-backed wrapper; on POSIX `SolidSyslogPosixSleep` is the natural fit. Required. | -| `GetHandshakeTimeoutMs` / `HandshakeTimeoutContext` | You (optional) | Per-instance accessor pair for the bounded handshake budget. `NULL` falls back to the `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` compile-time tunable (default 5000 ms). Install when you need to runtime-tune the handshake deadline: slow peers on a constrained link, or per-tenant policy from your existing configuration store. The accessor is called on every `Open`. | -| `Rng` | You | `mbedtls_ctr_drbg_context*` you seeded yourself. The adapter calls `mbedtls_ctr_drbg_random` against it. Required. | -| `CaChain` | You | `mbedtls_x509_crt*` you parsed yourself (from filesystem, baked-in PEM, HSM, whatever fits your build). Required. | -| `ServerName` | You | SNI + peer-identity check string. A non-empty name is verified against the server certificate (SAN/CN), rejecting any other CA-issued cert. `NULL` connects but the peer is unverified (any cert chaining to a trusted CA is accepted, MITM-class), so the library emits a `WARNING` (`BAD_CONFIG` / `MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET`). Note that having no DNS does not force this: an IP-pinned target can still set `ServerName` to the name (or IP SAN) on its certificate and get full identity verification. Use `""` only as a deliberate opt-out for closed networks / private CAs where there is genuinely no name to verify; it connects chain-only with no diagnostic. | -| `ClientCertChain` / `ClientKey` | You | `mbedtls_x509_crt*` + `mbedtls_pk_context*` for mTLS. Both `NULL` = server-auth-only TLS. Both non-`NULL` = mTLS. Supplying only one is treated as "no client cert"; the adapter never half-configures. | - -The full struct shape lives in -[`Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h`](../../../Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h). - -The adapter pins the minimum protocol version to TLS 1.2 on its own -`ssl_config` rather than inheriting `MBEDTLS_SSL_PRESET_DEFAULT`, which, on a -permissive mbedTLS build (2.x, or 3.x with `MBEDTLS_SSL_PROTO_TLS1_0/1_1` -enabled), can otherwise negotiate down to TLS 1.0/1.1. This matches the OpenSSL -reference adapter's explicit floor, so the two are equivalent in downgrade -resistance; it is not something you configure. - ---- - -## Scenario A: you already have Mbed TLS in your image - -If your firmware already wires Mbed TLS for another subsystem (a cloud -client, an OTA updater, a vendor security framework), you keep that wiring -intact. The adapter consumes the handles you've already built; it never -calls `mbedtls_platform_setup` / `_teardown`, never installs -threading-alt hooks, never resets the global RNG, never replaces your -debug callback. See the [coexistence contract](#coexistence-contract) -below for the auditable list. - -Concretely, on top of your existing setup: - -1. Pick a `SolidSyslogStream` for the byte transport. Use one of the - shipped adapters that matches your TCP/IP stack - (`SolidSyslogPlusTcpTcpStream`, `SolidSyslogPosixTcpStream`, - `SolidSyslogWinsockTcpStream`) or write your own backing the same - `SolidSyslogStream` vtable. If you wrote your own, the existing - shipped adapters are the worked examples. -2. Fill in `SolidSyslogMbedTlsStreamConfig` with the handles you - already have: - - ```c - struct SolidSyslogMbedTlsStreamConfig cfg = { - .Transport = myTcpStream, /* from step 1 */ - .Sleep = MyVTaskDelayWrapper, /* or PosixSleep / similar */ - .GetHandshakeTimeoutMs = NULL, /* defaults to SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS (5000 ms) */ - .HandshakeTimeoutContext = NULL, - .Rng = &myAlreadySeededDrbg, - .CaChain = &myAlreadyParsedCaChain, - .ServerName = "syslog.example.com", - .ClientCertChain = &myClientCert, /* NULL for server-auth-only */ - .ClientKey = &myClientKey, /* paired with ClientCertChain */ - }; - struct SolidSyslogStream* tlsStream = SolidSyslogMbedTlsStream_Create(&cfg); - ``` - -3. Wire `tlsStream` into a `SolidSyslogStreamSender` as the `Stream` - field, the same way you'd wire a plain TCP stream. RFC 6587 - octet-counting framing is applied by `StreamSender` on top of the - adapter. - -That's the whole integration on the SolidSyslog side. There are no -process-wide hooks to install and nothing to teardown beyond the matching -`SolidSyslogMbedTlsStream_Destroy` when you tear the sender down. - ---- - -## Scenario B: you do not have Mbed TLS yet - -If you're bringing Mbed TLS in fresh for SolidSyslog, do that work first -following the upstream -[Mbed TLS porting guide](https://mbed-tls.readthedocs.io/en/latest/kb/how-to/how-do-i-port-mbed-tls-to-a-new-environment-OS/). -Once Mbed TLS itself is building on your target, you need the following -specifically for this adapter: - -- A seeded `mbedtls_ctr_drbg_context`. `mbedtls_entropy_init` + - `mbedtls_entropy_add_source` for at least one source registered as - `MBEDTLS_ENTROPY_SOURCE_STRONG` (without a STRONG-tagged source, - `mbedtls_entropy_func` never satisfies its internal threshold and - every `mbedtls_ctr_drbg_seed` call returns - `MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED`, silent on the wire, - loud in your tests), then `mbedtls_ctr_drbg_init` + - `mbedtls_ctr_drbg_seed`. Production-quality entropy is a hardware - question: TRNG, vendor HSM, or a board-specific source. -- `psa_crypto_init()` called *after* the DRBG is seeded. Mbed TLS - 3.6's TLS 1.3 code path routes through PSA. If PSA isn't initialised, - the first handshake state transition returns - `MBEDTLS_ERR_ERROR_GENERIC_ERROR` (-0x0001) before any TLS bytes leave - the socket. If your target has no platform entropy source (a common - embedded case), `#define MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` in your - mbedTLS config and provide - `mbedtls_psa_external_get_random` that wraps the DRBG you just seeded; - this keeps PSA and the classic mbedTLS API on the same entropy chain. -- A parsed CA chain. `mbedtls_x509_crt_init` + - `mbedtls_x509_crt_parse` against whatever delivery mechanism fits your - build (filesystem on POSIX, baked-in array via `xxd -i` on bare-metal, - HSM-pulled blob, etc.). PEM input must be NUL-terminated. -- (mTLS only) a parsed client cert chain and private key. Same - pattern as the CA chain plus `mbedtls_pk_init` / - `mbedtls_pk_parse_key`. -- A byte-transport `SolidSyslogStream` matching your TCP/IP stack, - exactly as in [Scenario A](#scenario-a-you-already-have-mbed-tls-in-your-image). - -A worked end-to-end example for all of the above lives at -[`Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c`](../../../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c) -(FreeRTOS-Plus-TCP on QEMU mps2-an385). The matching Mbed TLS config -overrides live at -[`Bdd/Targets/FreeRtos/mbedtls_user_config.h`](../../../Bdd/Targets/FreeRtos/mbedtls_user_config.h). - ---- - -## Coexistence contract - -`Platform/MbedTls/Source/` is auditably free of process-global Mbed TLS -calls. The adapter never: - -- calls `mbedtls_platform_setup` / `_teardown` -- calls `mbedtls_threading_set_alt` -- calls `psa_crypto_init` (you do) -- calls `mbedtls_platform_set_calloc_free` (you do, if you need it) -- calls `mbedtls_debug_set_threshold` / `mbedtls_ssl_conf_dbg` -- frees any handle you passed in via the config struct - -Everything in that list is global state your existing integration may -already own. Auditors verify the contract by grepping -`Platform/MbedTls/Source/`; any future change that introduces a global -call must be flagged in review. - ---- - -## FreeRTOS-specific gotchas - -These bit us during the BDD-target bring-up. If you're on FreeRTOS with -newlib, treat them as integrator-side checklist items: - -- Route mbedTLS allocations to the RTOS heap. Mbed TLS calls libc - `calloc`, which on newlib targets typically hits a tiny `_sbrk`-backed - syscall heap (4 KiB in the SolidSyslog BDD reference at - [`Bdd/Targets/FreeRtos/Common/Syscalls.c`](../../../Bdd/Targets/FreeRtos/Common/Syscalls.c)). - A single `mbedtls_ssl_setup` wants ~10–16 KiB and will fail with - `MBEDTLS_ERR_SSL_ALLOC_FAILED` (-0x7F00). Set - `MBEDTLS_PLATFORM_MEMORY` in your config and call - `mbedtls_platform_set_calloc_free(yourCalloc, yourFree)` (pvPortMalloc - / vPortFree) before any `mbedtls_*_init`. -- Shrink the TLS record buffers from the 16 KiB default. Set - `MBEDTLS_SSL_IN_CONTENT_LEN` to the largest TLS record your peer will - send (server cert + chain is typically 2–4 KiB), and - `MBEDTLS_SSL_OUT_CONTENT_LEN` to your largest application message. - The defaults cost ~32 KiB of FreeRTOS heap per TLS context. -- `mbedtls_ssl_setup` allocates roughly `IN + OUT + ~3 KiB` of - handshake state. Size your FreeRTOS heap - (`configTOTAL_HEAP_SIZE`) accordingly across all concurrent TLS - contexts. -- `MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` + the external RNG hook are - effectively mandatory if you've defined - `MBEDTLS_NO_PLATFORM_ENTROPY` (which you typically have on - embedded). Without it, `psa_crypto_init` returns - `PSA_ERROR_INSUFFICIENT_ENTROPY` (-148). - -The BDD target's -[mbedtls_user_config.h](../../../Bdd/Targets/FreeRtos/mbedtls_user_config.h) -shows the minimal config that satisfies the above for QEMU mps2-an385. - ---- - -## Reference integrations - -| Target | Adapter source | Mbed TLS config | Notes | -|---|---|---|---| -| FreeRTOS QEMU mps2-an385 + FreeRTOS-Plus-TCP | [BddTargetTlsSender_MbedTls_PlusTcpTcp.c](../../../Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c) | [mbedtls_user_config.h](../../../Bdd/Targets/FreeRtos/mbedtls_user_config.h) | Demo-quality entropy and baked-in PEMs; loudly tagged not-for-production. | -| Linux host (host-TDD parity with the embedded path) | [Tests/MbedTlsIntegration/](../../../Tests/MbedTlsIntegration/) | — | In-process TLS server drives a real handshake against the wrapper. | -| POSIX (OpenSSL reference, for comparison) | [BddTargetTlsSender_OpenSsl_PosixTcp.c](../../../Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c) | — | Same composition shape using `SolidSyslogTlsStream` for the TLS layer. | - ---- - -## What this adapter does not own - -- PEM-to-handle conversion: you parse, in whatever way fits your - build. -- Certificate rotation: re-parse and rebuild the adapter, or destroy - / re-create the `SolidSyslogStreamSender` so the next Connect picks up - the new chain. -- HSM / TRNG integration: your entropy source feeds CTR_DRBG; the - adapter consumes the seeded DRBG. -- Per-connection TLS configuration: one adapter instance, one - `ssl_config`. If you need per-peer cipher / version pinning, build - multiple adapters. +Wire `tls` into a `SolidSyslogStreamSender` as its `Stream`, exactly as you +would a plain TCP stream, and call `SolidSyslogMbedTlsStream_Destroy` when the +sender is torn down. There is nothing process-wide to install. + +If your firmware already uses Mbed TLS for something else — a cloud client, an +OTA updater, a vendor framework — that is the whole integration: the adapter +consumes handles you have already built and touches no global state. + +## Bringing Mbed TLS up, if it is new to the target + +Port Mbed TLS itself first, following the +[upstream porting guide](https://mbed-tls.readthedocs.io/en/latest/kb/how-to/how-do-i-port-mbed-tls-to-a-new-environment-OS/). +Four things then matter specifically for this adapter, and three of them fail in +ways that are hard to read. + +**Seed the DRBG from a source registered as `MBEDTLS_ENTROPY_SOURCE_STRONG`.** +Without a strong-tagged source, `mbedtls_entropy_func` never reaches its +internal threshold and every `mbedtls_ctr_drbg_seed` returns +`MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED`. Production entropy is a hardware +question — a true random number generator, a vendor security element, or a +board-specific source. + +**Call `psa_crypto_init()` after the DRBG is seeded, not before.** Mbed TLS +3.6 routes TLS 1.3 through PSA, and if PSA is uninitialised the first handshake +state transition returns `MBEDTLS_ERR_ERROR_GENERIC_ERROR` before any byte +reaches the socket. + +**On a target with no platform entropy, define +`MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG`** and provide +`mbedtls_psa_external_get_random` wrapping the DRBG you just seeded. This keeps +PSA and the classic API on one entropy chain. If you have defined +`MBEDTLS_NO_PLATFORM_ENTROPY` — usual on embedded — this is effectively +mandatory, and omitting it makes `psa_crypto_init` return +`PSA_ERROR_INSUFFICIENT_ENTROPY`. + +**Parse the CA chain, and the client credential if you are using mutual TLS**, +by whatever route suits the build: a filesystem, a baked-in array, a blob pulled +from a security element. PEM input must be NUL-terminated. + +## FreeRTOS with newlib + +Four sizing traps, all found during bring-up of the FreeRTOS test target. + +**Route Mbed TLS allocations to the RTOS heap.** Mbed TLS calls libc `calloc`, +which on newlib typically reaches a small `_sbrk`-backed syscall heap. A single +`mbedtls_ssl_setup` wants roughly 10–16 KiB and fails with +`MBEDTLS_ERR_SSL_ALLOC_FAILED`. Set `MBEDTLS_PLATFORM_MEMORY` and call +`mbedtls_platform_set_calloc_free(pvPortMalloc, vPortFree)` before any +`mbedtls_*_init`. + +**Shrink the record buffers from their 16 KiB default.** Set +`MBEDTLS_SSL_IN_CONTENT_LEN` to the largest record the collector will send — a +server certificate and chain is typically 2–4 KiB — and +`MBEDTLS_SSL_OUT_CONTENT_LEN` to your largest message. The defaults cost around +32 KiB of heap per TLS context. + +**Budget the handshake state.** `mbedtls_ssl_setup` allocates roughly +`IN + OUT + 3 KiB`. Size `configTOTAL_HEAP_SIZE` across every concurrent TLS +context, not just one. + +**Give the mutex role a real implementation** if `Log` and `Service` run on +different tasks. The library will not detect that you have not. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the stream fell +back to the Null object, and nothing will be delivered. diff --git a/docs/platforms/openssl/setup.md b/docs/platforms/openssl/setup.md index 4f692bcb..992dc06d 100644 --- a/docs/platforms/openssl/setup.md +++ b/docs/platforms/openssl/setup.md @@ -1,9 +1,9 @@ # OpenSSL setup -Wiring `SolidSyslogTlsStream` over a plain TCP stream so a -`SolidSyslogStreamSender` delivers RFC 5425 syslog over TLS. Read -[OpenSSL](index.md) first for what the adapter guarantees and what it leaves to -you — this page is the mechanics. +Wiring `SolidSyslogTlsStream` so a `SolidSyslogStreamSender` delivers RFC 5425 +syslog over TLS. [OpenSSL](index.md) covers what the adapter guarantees and what +it leaves to you; the config fields are documented on the struct itself. This +page is the wiring. ## What you need @@ -48,8 +48,7 @@ tlsConfig.ServerName = "collector.example.net"; struct SolidSyslogStream* tls = SolidSyslogTlsStream_Create(&tlsConfig); ``` -Zero-initialise the config before filling it. Every field you leave NULL is a -documented default, and a struct you have not cleared is not. +Zero-initialise the config before filling it. For mutual TLS, add the client credential — both fields or neither, since supplying one without the other is rejected at `Open`: @@ -75,22 +74,9 @@ struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig) Tear down in reverse order: sender, address, TLS stream, then the transport you created. -## Timeouts +## When it does not work -The handshake is bounded. `GetHandshakeTimeoutMs` gives a per-attempt deadline -in milliseconds; leaving it NULL uses the `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` -tunable, which defaults to 5000. The `Sleep` callback is what the bounded retry -waits on between `WANT_READ` / `WANT_WRITE` polls, which is why it has no -default — the library will not pick a blocking primitive on your behalf. - -## Checking it works - -The BDD suite wires exactly this, against a real syslog-ng collector, in -[`Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c`](../../../Bdd/Targets/Common/BddTargetTlsSender_OpenSsl_PosixTcp.c), -for both the server-authenticated and mutual-TLS cases. It is the reference to -read against your own wiring. - -Failures report through the error handler rather than silently — install one +Failures report through the error handler rather than silently. Install one before you start, and read [error severity](../../error-severity.md) for what -each level is telling you. A `CRITICAL` at create time means the stream fell -back to the Null object and nothing will be delivered. +each level is telling you — a `CRITICAL` at create time means the stream fell +back to the Null object, and nothing will be delivered. diff --git a/docs/porting.md b/docs/porting.md index fcc0b906..2a8be0c9 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -146,7 +146,7 @@ This is the only synchronisation primitive the pools use for their own walks. - Never free injected handles. An adapter frees only what it created. Handles the integrator passed in (an `mbedtls_x509_crt*`, an RNG, a caller's socket) are borrowed; the owner frees them. The [Mbed TLS coexistence - contract](platforms/mbedtls/setup.md#coexistence-contract) is the template: + contract](platforms/mbedtls/index.md#coexistence-is-an-auditable-contract) is the template: `Platform/MbedTls/Source/` never touches process-global Mbed TLS state. - A Null must be safe to call. Whatever your role's Null returns (see each contract below), it must let Core's algorithm proceed sanely: drop-on-the-floor diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index 5183efb9..15462d35 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -90,15 +90,15 @@ ), "platforms/openssl/setup.md": ( "Wire SolidSyslogTlsStream over a TCP stream for RFC 5425 syslog over " - "TLS: the layering, the config fields, mutual TLS and the handshake budget." + "TLS: what to link, the layering, the config, and mutual TLS." ), "platforms/mbedtls/index.md": ( "The Mbed TLS adapter pack for embedded targets: TLS transport for the " "Stream role, and keyed at-rest crypto for the SecurityPolicy role." ), "platforms/mbedtls/setup.md": ( - "Deliver RFC 5425 syslog over TLS through Mbed TLS on embedded targets: " - "the handles you pre-build and pass in, and how they are wired." + "Wire Mbed TLS for RFC 5425 syslog over TLS on an embedded target: the " + "layering, bringing the library up, and the FreeRTOS sizing traps." ), "platforms/fatfs/index.md": ( "The ChaN FatFs adapter pack, filling the File role beneath a BlockDevice " From d0b8531c2bd78926d1caca9ebb470b46ac080205 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 18:56:57 +0100 Subject: [PATCH 26/57] docs: S23.22 name every platform the same way, in the reader's words MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The C11 atomics platform appeared under four names: "C11 atomics" in the sidebar and on its own page, "C11 atomics platform" on the chip, and "Group platform_atomics" on its generated reference page, beneath a breadcrumb reading "Modules". Group and Module are Doxygen's vocabulary. They appear nowhere else in this documentation and mean nothing to a reader who has not used Doxygen, so they are rewritten out: a group page is now titled after the platform, and its breadcrumb points at the Platforms overview. mkdoxy ignores the title the @defgroup declares and prints the group's name instead, so the rewrite takes the name from the platform page's own heading — the same source the chips already use. One place names a platform. The modules index leaves the navigation. It is Doxygen's own listing of groups and duplicates the Platforms tab, under a heading no reader asked for. The Mbed TLS setup page loses its FreeRTOS section. Routing allocations to an RTOS heap and sizing newlib's syscall heap is neither this project's expertise to claim nor Mbed TLS's page to hold it on — and a page that explains another platform is a page that has to be revisited when that platform changes. What remains is the part that is ours: the adapter allocates nothing itself, so a TLS session costs what your mbedtls_config.h says it costs, and the sizing guidance belongs upstream. Part of #708 --- Platform/Posix/SolidSyslogPosixPlatform.dox | 2 +- docs/platforms/mbedtls/setup.md | 34 +++++++----------- hooks/platform_backlinks.py | 40 ++++++++++++++++++--- hooks/test_platform_backlinks.py | 27 ++++++++++++-- mkdocs.yml | 4 --- 5 files changed, 73 insertions(+), 34 deletions(-) diff --git a/Platform/Posix/SolidSyslogPosixPlatform.dox b/Platform/Posix/SolidSyslogPosixPlatform.dox index b4057d99..0f841b78 100644 --- a/Platform/Posix/SolidSyslogPosixPlatform.dox +++ b/Platform/Posix/SolidSyslogPosixPlatform.dox @@ -1,5 +1,5 @@ /** - * @defgroup platform_posix POSIX platform + * @defgroup platform_posix Posix platform * @brief Sockets, name resolution, files, mutexes, message queues and the clock on * POSIX hosts. * diff --git a/docs/platforms/mbedtls/setup.md b/docs/platforms/mbedtls/setup.md index fc76137a..115bfc27 100644 --- a/docs/platforms/mbedtls/setup.md +++ b/docs/platforms/mbedtls/setup.md @@ -29,7 +29,7 @@ applies RFC 6587 octet-counting framing on top either way. ```c struct SolidSyslogMbedTlsStreamConfig cfg = { .Transport = myTcpStream, - .Sleep = MyVTaskDelayWrapper, /* required — no fallback */ + .Sleep = MySleep, /* required — no fallback */ .Rng = &mySeededDrbg, .CaChain = &myParsedCaChain, .ServerName = "syslog.example.com", @@ -78,29 +78,19 @@ mandatory, and omitting it makes `psa_crypto_init` return by whatever route suits the build: a filesystem, a baked-in array, a blob pulled from a security element. PEM input must be NUL-terminated. -## FreeRTOS with newlib +## Memory -Four sizing traps, all found during bring-up of the FreeRTOS test target. +The adapter allocates nothing itself. Everything a TLS session costs is Mbed +TLS's own allocation, governed by your `mbedtls_config.h` — the record buffer +sizes dominate it, and their defaults are sized for a general-purpose host +rather than a constrained target. Budget for every TLS session you intend to +run concurrently, not one, and take the sizing guidance from the +[upstream documentation](https://mbed-tls.readthedocs.io/) rather than from +here. -**Route Mbed TLS allocations to the RTOS heap.** Mbed TLS calls libc `calloc`, -which on newlib typically reaches a small `_sbrk`-backed syscall heap. A single -`mbedtls_ssl_setup` wants roughly 10–16 KiB and fails with -`MBEDTLS_ERR_SSL_ALLOC_FAILED`. Set `MBEDTLS_PLATFORM_MEMORY` and call -`mbedtls_platform_set_calloc_free(pvPortMalloc, vPortFree)` before any -`mbedtls_*_init`. - -**Shrink the record buffers from their 16 KiB default.** Set -`MBEDTLS_SSL_IN_CONTENT_LEN` to the largest record the collector will send — a -server certificate and chain is typically 2–4 KiB — and -`MBEDTLS_SSL_OUT_CONTENT_LEN` to your largest message. The defaults cost around -32 KiB of heap per TLS context. - -**Budget the handshake state.** `mbedtls_ssl_setup` allocates roughly -`IN + OUT + 3 KiB`. Size `configTOTAL_HEAP_SIZE` across every concurrent TLS -context, not just one. - -**Give the mutex role a real implementation** if `Log` and `Service` run on -different tasks. The library will not detect that you have not. +Where Mbed TLS takes its memory from is also your configuration. On a target +whose libc heap is not the one you intend it to use, `mbedtls_ssl_setup` is +where that shows up. ## When it does not work diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 05fd3f5f..66111e03 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -27,6 +27,7 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') +MODULES_CRUMB = re.compile(r"\[\*\*Modules\*\*\]\([^)]*\)") GENERATED_PREFIX = "api/" PLATFORM_PREFIX = "platforms/" @@ -44,24 +45,32 @@ def _label(root, slug): return slug +def _group_slug(src_uri): + """api/group__platform__atomics.md -> atomics, else None.""" + leaf = src_uri[len(GENERATED_PREFIX) : -len(".md")] + prefix = "group__platform__" + return leaf[len(prefix) :] if leaf.startswith(prefix) else None + + def _index(config): - """Return (headers, slugs): header stem -> (label, slug), and the slug set.""" + """Return (headers, slugs, labels) built from the platform registry.""" root = os.path.dirname(config["config_file_path"]) if root not in _CACHE: with open(os.path.join(root, "CMakeLists.txt"), encoding="utf-8") as cmake: registry = REGISTRY.search(cmake.read()) - headers, slugs = {}, {} + headers, slugs, labels = {}, {}, {} for token, directory in ROW.findall(registry.group(1)) if registry else []: interface = os.path.join(root, directory, "Interface") if not os.path.isdir(interface): continue slug = token.lower() - pack = (_label(root, slug), slug) + labels[slug] = _label(root, slug) + pack = (labels[slug], slug) slugs[slug] = os.path.isfile(os.path.join(root, "docs", PLATFORM_PREFIX, slug, "setup.md")) for name in os.listdir(interface): if name.endswith(".h"): headers[name[: -len(".h")]] = pack - _CACHE[root] = (headers, slugs) + _CACHE[root] = (headers, slugs, labels) return _CACHE[root] @@ -73,9 +82,30 @@ def _stem(src_uri): def on_page_markdown(markdown, page, config, files, **kwargs): src_uri = getattr(page.file, "src_uri", None) or page.file.src_path.replace(os.sep, "/") - headers, slugs = _index(config) + headers, slugs, labels = _index(config) if src_uri.startswith(GENERATED_PREFIX): + # A group page arrives titled in Doxygen's vocabulary — "Group + # platform_atomics", under a breadcrumb reading "Modules" — where the + # reader has been shown "C11 atomics" everywhere else. Group and Module + # are terms this documentation does not otherwise use, and the group's + # own @defgroup title is ignored by the template, so rewrite all three + # to the platform page's heading. The doubled underscores of the refid + # (group__platform__atomics) do not contain the single-underscore group + # name, so the bare-name replacement cannot damage a link target. + group = _group_slug(src_uri) + if group is not None and group in slugs: + title = f"{labels[group]} platform" + # mkdoxy backslash-escapes the underscore so Markdown does not read + # it as emphasis, so the name appears as platform\_atomics. + name = r"platform\\?_" + re.escape(group) + markdown = re.sub(rf"#\s+Group\s+{name}", f"# {title}", markdown, count=1) + markdown = MODULES_CRUMB.sub(f"[**Platforms**](../{PLATFORM_PREFIX}index.md)", markdown, count=1) + return re.sub(name, title, markdown) + + if src_uri == f"{GENERATED_PREFIX}modules.md": + return markdown.replace("# Modules", "# Platform API reference", 1) + stem = _stem(src_uri) pack = headers.get(stem) if stem else None if pack is None: diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index e11bdd84..bee2569a 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -77,9 +77,32 @@ def test_an_ordinary_page_is_untouched(self): self.assertEqual(render("porting.md", markdown), markdown) +class GroupPages(unittest.TestCase): + # mkdoxy titles these in Doxygen's vocabulary and escapes the underscore. + RAW = "# Group platform\\_atomics\n\n[**Modules**](index_groups.md) **>** [**platform\\_atomics**](x.md)\n" + + def test_the_doxygen_title_becomes_the_platform_name(self): + out = render("api/group__platform__atomics.md", self.RAW) + self.assertTrue(out.startswith("# C11 atomics platform"), out[:50]) + + def test_group_and_modules_do_not_reach_the_reader(self): + out = render("api/group__platform__atomics.md", self.RAW) + self.assertNotIn("Group", out) + self.assertNotIn("Modules", out) + self.assertNotIn("platform_atomics", out.replace("\\", "")) + + def test_the_breadcrumb_points_at_the_platforms_overview(self): + out = render("api/group__platform__atomics.md", self.RAW) + self.assertIn("[**Platforms**](../platforms/index.md)", out) + + def test_a_group_page_gets_no_platform_chip(self): + out = render("api/group__platform__atomics.md", self.RAW) + self.assertNotIn("ss-chip", out) + + class Registry(unittest.TestCase): def test_every_registered_platform_has_a_docs_folder(self): - _, slugs = h._index(CONFIG) + _, slugs, _labels = h._index(CONFIG) for slug in slugs: self.assertTrue( os.path.isfile(os.path.join(ROOT, "docs", "platforms", slug, "index.md")), @@ -87,7 +110,7 @@ def test_every_registered_platform_has_a_docs_folder(self): ) def test_the_registry_yields_all_ten_platforms(self): - _, slugs = h._index(CONFIG) + _, slugs, _labels = h._index(CONFIG) self.assertEqual(len(slugs), 10, sorted(slugs)) diff --git a/mkdocs.yml b/mkdocs.yml index a1c32196..f97fcfe3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -211,10 +211,6 @@ nav: - Data structures: api/annotated.md - Functions: api/functions.md - Macros: api/macros.md - # One group per platform pack, defined in the pack's .dox file. Each - # group page lists that pack's headers and links back to its - # hand-written page — the API-to-docs direction of the traversal. - - Platform groups: api/modules.md # A section (not a bare page) so Material gives it md-nav__item--section and # it reads with the same weight as Platforms / Roles. Its index is its only # child — navigation.indexes promotes it into the section link. From f9472267384b32cdc79ed1e053124120b2cc015f Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 21:47:24 +0100 Subject: [PATCH 27/57] docs: S23.22 complete the setup pages, one shape for all ten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trims lwIP (3,059 words to 718) and FreeRTOS-Plus-FAT (802 to 551) to the same test the TLS pages set, and writes the six that were missing. Every platform now has both an overview and a setup page, so the folder shape is uniform and both chips appear on all ten. The lwIP page kept its marshal — the one thing that is genuinely hard to get right, and the reason an asynchronous hop corrupts stack state rather than failing — and the bare-metal trap where a Sleep that does not drive the stack leaves a connection unable to complete. It loses its class table, its tunables table, its adapter internals, a paragraph on layering TLS, and a note recording that earlier advice in this guide had been wrong, which is history rather than documentation. Corrects a claim on the FreeRTOS-Plus-FAT page. It said the adapter calls ff_fflush after every write. The library declares ff_fflush and never defines it, so the adapter flushes the IO manager's cache instead. The claim came from the page's original stub and I repeated it in the obligations section, where it mattered more. The six new pages are short because there is little to say, which is the cost of the fixed folder shape rather than a gap. Each says what to link, how to wire it, and the one thing that will catch you: Winsock needing initialisation that the adapter deliberately does not do, FreeRTOS needing static allocation, and the atomic counter's Null fallback returning the same value forever, so an unfilled role leaves gap detection proving nothing. Part of #708 --- docs/platforms/atomics/setup.md | 45 +++ docs/platforms/fatfs/setup.md | 64 ++++ docs/platforms/freertos/setup.md | 58 ++++ docs/platforms/lwipraw/setup.md | 512 +++++-------------------------- docs/platforms/plusfat/index.md | 10 +- docs/platforms/plusfat/setup.md | 192 +++++------- docs/platforms/plustcp/setup.md | 56 ++++ docs/platforms/posix/setup.md | 63 ++++ docs/platforms/windows/setup.md | 66 ++++ hooks/page_descriptions.py | 24 ++ hooks/test_platform_backlinks.py | 21 +- 11 files changed, 549 insertions(+), 562 deletions(-) create mode 100644 docs/platforms/atomics/setup.md create mode 100644 docs/platforms/fatfs/setup.md create mode 100644 docs/platforms/freertos/setup.md create mode 100644 docs/platforms/plustcp/setup.md create mode 100644 docs/platforms/posix/setup.md create mode 100644 docs/platforms/windows/setup.md diff --git a/docs/platforms/atomics/setup.md b/docs/platforms/atomics/setup.md new file mode 100644 index 00000000..2a02e54a --- /dev/null +++ b/docs/platforms/atomics/setup.md @@ -0,0 +1,45 @@ +# C11 atomics setup + +Wiring the sequence-number source. [C11 atomics](index.md) covers what it fills +and what it leaves to you. + +## What to link + +`` is part of the language rather than a configured upstream, so +the adapter compiles straight into the static library: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "Atomics") +``` + +The compiler must support C11 atomics. Where a toolchain does not, the +[platform × capability matrix](../index.md) shows which other platforms fill +the same role. + +## Wiring it + +```c +struct SolidSyslogAtomicCounter* counter = SolidSyslogStdAtomicCounter_Create(); +``` + +Hand the counter to the structured-data element that carries the sequence +number. Create takes no configuration; destroy it when the logger is torn down. + +## Why it is worth wiring + +The sequence number is what lets a collector notice that records are missing. +It is assigned when a record is raised rather than when it is sent, so a gap +reflects loss anywhere in the pipeline — the buffer, the store, or the +transport — not only on the wire. + +Leave the role unfilled and the Null counter stands in, returning the same +value every time. Nothing then reports a problem, and gap detection quietly +proves nothing. Install an error handler so that pool exhaustion is visible +rather than silent. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the counter fell +back to the Null object. diff --git a/docs/platforms/fatfs/setup.md b/docs/platforms/fatfs/setup.md new file mode 100644 index 00000000..6ae6504a --- /dev/null +++ b/docs/platforms/fatfs/setup.md @@ -0,0 +1,64 @@ +# FatFs setup + +Giving store-and-forward a file backend. [FatFs](index.md) covers what the +adapter fills and what it leaves to you. + +## The shape + +```text +SolidSyslogBlockStore + │ +SolidSyslogFileBlockDevice ◀── sequence-numbered .log files + │ +SolidSyslogFatFsFile ◀── this adapter + │ +FatFs core ◀── vendor sources, you compile them + │ +your disk I/O driver ◀── you write this +``` + +The adapter owns the middle box only. It never reaches the storage medium. + +## What to link + +FatFs is configured by a header you own, so the adapter compiles inside your +target against your configuration: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "FatFs") +target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FatFs) +``` + +## What you must provide + +**The FatFs sources**, compiled into your image, and a `ffconf.h` where the +library expects to find it — beside the sources rather than on the include +path, which differs from most configuration headers. + +**A disk I/O driver** for your storage hardware, implementing the read, write +and control entry points FatFs calls. This is the part that knows about your +medium, and nothing in this library reaches past it. + +**A system layer** if you have enabled re-entrancy, providing the +synchronisation objects FatFs expects. + +**Mounting** is yours. The adapter opens, reads and writes files; bringing the +volume up belongs to your start-up code. + +## Durability + +The adapter flushes after every complete write, so a power loss never discards +a record the store was told had been written. Whether that flush reaches the +medium is a property of your disk I/O driver. + +FAT is not a journalling file system. A device that loses power partway through +a directory update can leave that entry inconsistent, which is a property of +the format rather than of this adapter. Where records must survive a hard power +cut, size them and choose the store's discard policy with that in mind. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the file fell +back to the Null object, and nothing will be stored. diff --git a/docs/platforms/freertos/setup.md b/docs/platforms/freertos/setup.md new file mode 100644 index 00000000..c1c6598d --- /dev/null +++ b/docs/platforms/freertos/setup.md @@ -0,0 +1,58 @@ +# FreeRTOS setup + +Wiring the kernel primitives. [FreeRTOS](index.md) covers what they fill and +what they leave to you. + +## What to link + +FreeRTOS is configured by a header you own, so the adapter cannot be +precompiled: its sources compile inside your target, against your +`FreeRTOSConfig.h`. Select it and link the target it exports: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "FreeRtos") +target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FreeRtos) +``` + +Your target supplies the kernel's include path, because it is your kernel and +your configuration. + +`configSUPPORT_STATIC_ALLOCATION` must be 1. The mutex is created from storage +inside the library's own pool, so nothing is allocated at run time and creation +cannot fail for want of heap. + +## Wiring the mutex + +The mutex exists to make a buffer safe when the task calling `SolidSyslog_Log` +is not the task calling `SolidSyslog_Service`: + +```c +struct SolidSyslogMutex* mutex = SolidSyslogFreeRtosMutex_Create(); + +struct SolidSyslogBuffer* buffer = + SolidSyslogCircularBuffer_Create(&(struct SolidSyslogCircularBufferConfig) { + .Sender = sender, + .Mutex = mutex, + /* ring storage sized with SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES */ + }); +``` + +The ring memory and the mutex must both outlive the buffer. + +If both calls happen on one task, you do not need a mutex and can leave the +role unfilled. Be certain of that: nothing detects the difference at run time. + +## Uptime + +`SolidSyslogFreeRtosSysUpTime` reports kernel ticks since boot. It is not +wall-clock time — the clock callback in `SolidSyslogConfig` is a separate +injection point, and on a target with no real-time clock a timestamp the +library cannot establish is emitted as absent rather than as a plausible wrong +value. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the component +fell back to its Null object, and nothing will be delivered. diff --git a/docs/platforms/lwipraw/setup.md b/docs/platforms/lwipraw/setup.md index 07c13038..3e9de1c6 100644 --- a/docs/platforms/lwipraw/setup.md +++ b/docs/platforms/lwipraw/setup.md @@ -1,102 +1,37 @@ -# Integrating SolidSyslog with lwIP (Raw API) +# lwIP (Raw API) setup -`Platform/LwipRaw/` wraps lwIP's Raw API to provide the same -`SolidSyslogDatagram` / `SolidSyslogStream` / `SolidSyslogAddress` / -`SolidSyslogResolver` vtables the rest of the library composes against. -It is the right choice when: +Wiring the lwIP adapters so a sender delivers over UDP or TCP. +[lwIP (Raw API)](index.md) covers what the adapters fill and what your +`lwipopts.h` must enable; the config fields are documented on the structs +themselves. This page is the wiring, and the one thing that is easy to get +wrong. -- Your target runs lwIP (bare-metal, FreeRTOS, Zephyr, ThreadX, NuttX), - including `NO_SYS=1` deployments where sockets/NETCONN aren't - available. -- You want to share TCP/IP between SolidSyslog and other lwIP-using - subsystems (HTTP server, MQTT client, OTA updater) without - duplicating the stack. -- You're on FreeRTOS but prefer lwIP to FreeRTOS-Plus-TCP for licence - or sizing reasons. Both backends ship in the same library; pick at - CMake time by naming `LwipRaw` rather than `PlusTcp` in `SOLIDSYSLOG_PLATFORMS`. +## The marshal -This document covers what you, the integrator, plug in. It does not -re-teach lwIP; for that, see the -[upstream lwIP documentation](https://www.nongnu.org/lwip/2_1_x/index.html). - ---- - -## What ships in `Platform/LwipRaw/` - -| Class | Wraps | Purpose | -|---|---|---| -| `SolidSyslogLwipRawAddress` | `ip_addr_t` + `u16_t` port | Destination handle the Resolver writes into and the Datagram/TcpStream read from. | -| `SolidSyslogLwipRawResolver` | `ipaddr_aton` | Synchronous numeric IPv4 parsing. Rejects DNS names — use `SolidSyslogLwipRawDnsResolver` for those. Needs no `LWIP_DNS`. | -| `SolidSyslogLwipRawDnsResolver` | `dns_gethostbyname` | DNS name resolution (superset — numeric literals, the DNS cache, and the local hostlist all resolve too). Bridges lwIP's async DNS to the synchronous `Resolve()` contract via a bounded spin (see [DNS](#dns) below). Requires `LWIP_DNS=1`. | -| `SolidSyslogLwipRawDatagram` | `udp_new` / `udp_sendto` / `udp_remove` | UDP sender. Zero-copy `PBUF_REF` send. | -| `SolidSyslogLwipRawTcpStream` | `tcp_new` / `tcp_connect` / `tcp_write` / `tcp_output` / `tcp_recv` / `tcp_recved` / `tcp_close` / `tcp_abort` | TCP byte transport. Bounded synchronous Open. Bounded RX pbuf queue. | - -`Platform/LwipRaw/Source/` is OS-agnostic: it wraps lwIP only and -contains zero direct calls to FreeRTOS, POSIX, Win32, Zephyr, or any -other host primitive. The one host primitive the TcpStream needs (a -bounded sleep for the synchronous-Open spin loop) is abstracted behind -the `SolidSyslogSleepFunction` typedef and supplied by you at -configure time. - -mbedTLS layering is unchanged: `SolidSyslogMbedTlsStream` consumes -`SolidSyslogLwipRawTcpStream` as its byte transport without -modification. See [Mbed TLS setup](../mbedtls/setup.md) for the TLS side. - ---- - -## `NO_SYS=1` vs `NO_SYS=0` - -lwIP supports both threading models. SolidSyslog supports both; the -adapter code is the same. The difference is entirely in how you -drive lwIP forward, and which marshal you install. - -### The marshal seam - -Every Raw API call the adapters make (`udp_sendto`, `tcp_write`, -`pbuf_alloc`, `tcp_close`, …) is routed through a single hop: +Every Raw API call the adapters make is routed through a single hop, installed +once at boot: ```c #include "SolidSyslogLwipRawMarshal.h" -SolidSyslogLwipRaw_SetMarshal(MyMarshal); /* once, at boot */ +SolidSyslogLwipRaw_SetMarshal(MyMarshal); ``` -The default (nothing installed, or `SolidSyslogLwipRaw_SetMarshal(NULL)`) -is a direct call: the adapter calls lwIP on the calling thread. That -is exactly right for `NO_SYS=1`. For `NO_SYS=0` you install a marshal that -hops onto the thread owning the lwIP core. - -> Earlier guidance was wrong. Prior versions of this guide told you to -> "marshal at the SolidSyslog API boundary", wrap `SolidSyslog_Service()` -> in `tcpip_callback()`. Don't. That puts file I/O, mbedTLS crypto, the -> CircularBuffer mutex, the StreamSender's formatter work, and the Resolver -> parse (none of which touch lwIP) on the tcpip thread, starving lwIP's -> timer / RX path under load. The correct boundary is the individual lwIP -> Raw API call, which is what the marshal seam gives you. +**The marshal must run its callback synchronously, before it returns.** The +adapter reads results the moment the hop returns. One global slot serves the +process, because there is one lwIP instance to protect. -**Contract.** The marshal MUST invoke its callback synchronously, before -the marshal function returns. The adapter reads results the callback wrote -immediately after the hop returns. `tcpip_callback_with_block(.., block=1)` -honours this; a bare `tcpip_callback(..)` does not (it queues and returns). +Which marshal you install depends only on how lwIP is built. -One global slot serves the whole process: there is one lwIP instance and -one tcpip thread, so per-instance marshals would be flexibility without use. +### `NO_SYS=1` — bare metal -### `NO_SYS=1` (bare-metal main-loop) +Install nothing. There is one execution context and no core to protect, so the +default direct call is correct. -Install nothing: the default direct-call marshal is correct. There is one -execution context and no core to protect. - -Your `main()` is a forever-loop that, on each pass, calls -`sys_check_timeouts()` and drives the RX path -(`netif->input()` / `ethernetif_input()` / whichever your BSP wires). -Every lwIP Raw API call must happen on that same thread. - -SolidSyslog's `Service` loop fits this naturally: call it from your -main loop alongside `sys_check_timeouts()`. The TcpStream's bounded -synchronous-Open spin loop calls your injected `Sleep` callback -between polls; under `NO_SYS=1` your Sleep implementation should -tick the lwIP machinery while it waits: +The trap is the `Sleep` callback. The TCP stream's `Open` is synchronous over an +asynchronous `tcp_connect`, so it spins and sleeps while waiting for the +connection callback — and on bare metal nothing else is driving lwIP during that +sleep. Your `Sleep` must keep the stack running: ```c void MyLwipSleep(int milliseconds) @@ -105,398 +40,101 @@ void MyLwipSleep(int milliseconds) while (MyTimebase_NowMs() < deadline) { sys_check_timeouts(); - MyNetif_DrivePolledRx(); /* your BSP's RX pump */ + MyNetif_DrivePolledRx(); /* your board's receive pump */ } } ``` -Without this, `tcp_connect`'s `connected_cb` never fires (lwIP can't -advance its state machine while you sleep), and Open times out. +A `Sleep` that merely busy-waits leaves lwIP unable to advance its state +machine, so the connection callback never fires and `Open` times out with +nothing visibly wrong. -### `NO_SYS=0` (tcpip thread) — option A: `tcpip_callback` +Call `SolidSyslog_Service` from the same loop that calls `sys_check_timeouts`. -lwIP runs a dedicated `tcpip` thread that owns its state machine. Install a -marshal that posts each adapter callback to it and blocks until it runs: +### `NO_SYS=0` — an lwIP thread -```c -#include "lwip/tcpip.h" -#include "SolidSyslogLwipRawMarshal.h" - -struct MarshalHop -{ - SolidSyslogLwipRawCallback callback; - void* context; -}; - -static void RunHop(void* ctx) -{ - struct MarshalHop* hop = (struct MarshalHop*) ctx; - hop->callback(hop->context); -} - -void MyTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) -{ - /* Re-entry guard: if we are ALREADY on the tcpip thread (e.g. the adapter - * was called from inside a SolidSyslog callback that lwIP itself invoked), - * posting-and-blocking would deadlock. Run directly instead. */ - if (sys_current_task_is_tcpip_thread()) /* your port's predicate */ - { - callback(context); - return; - } - - struct MarshalHop hop = {callback, context}; - /* block=1 makes this synchronous — required by the marshal contract. */ - (void) tcpip_callback_with_block(RunHop, &hop, 1); -} - -/* ... at boot ... */ -SolidSyslogLwipRaw_SetMarshal(MyTcpipMarshal); -``` - -`tcpip_callback_with_block` needs an OS mailbox sized for the blocking post; -ensure `TCPIP_MBOX_SIZE` is adequate. lwIP does not expose a portable -"am I on the tcpip thread?" predicate; most ports compare the current task -handle against the one passed to `tcpip_init`. - -### `NO_SYS=0` (tcpip thread) — option B: core locking - -If you compiled lwIP with `LWIP_TCPIP_CORE_LOCKING=1`, take the core lock -around the hop instead of posting to the mailbox (lower latency, no context -switch): +A dedicated thread owns lwIP's state, so every call has to reach it. With core +locking compiled in, take the lock around the hop: ```c -#include "lwip/tcpip.h" -#include "SolidSyslogLwipRawMarshal.h" - void MyCoreLockMarshal(SolidSyslogLwipRawCallback callback, void* context) { LOCK_TCPIP_CORE(); callback(context); UNLOCK_TCPIP_CORE(); } - -/* ... at boot ... */ -SolidSyslogLwipRaw_SetMarshal(MyCoreLockMarshal); ``` -`LOCK_TCPIP_CORE` is a recursive lock on most ports, so this is safe even if -the adapter is reached from a context that already holds it. - -### The `Sleep` callback under `NO_SYS=0` - -TcpStream's bounded-Open spin runs on your thread (never the tcpip thread), -so its `Sleep` is just a yield, typically `vTaskDelay` on FreeRTOS: - -```c -void MyLwipSleep(int milliseconds) -{ - vTaskDelay(pdMS_TO_TICKS((uint32_t) milliseconds)); -} -``` - -The tcpip thread runs concurrently and processes the SYN/SYN-ACK exchange -while you yield. A worked `NO_SYS=0` + `tcpip_callback` integration ships as -the FreeRtosLwip BDD target. - ---- - -## `lwipopts.h` expectations - -The adapter wraps a specific subset of lwIP; your `lwipopts.h` needs -those features compiled in. Defaults that already cover us are noted; -features you must enable are flagged. - -| Setting | Required | Notes | -|---|---|---| -| `LWIP_RAW=1` | **Yes** | The whole point — Raw API. | -| `LWIP_UDP=1` | **Yes (Datagram)** | Wraps `udp_*`. | -| `LWIP_TCP=1` | **Yes (TcpStream)** | Wraps `tcp_*`. | -| `LWIP_DNS` | DNS-dependent | Required for `SolidSyslogLwipRawDnsResolver`. The numeric `SolidSyslogLwipRawResolver` needs nothing. See [DNS](#dns). | -| `DNS_LOCAL_HOSTLIST` | Optional | A static name→address map consulted before any DNS server, returned synchronously. Pins names without a server (handy for fixed deployments and for test topologies a real resolver can't reach). See [DNS](#dns). | -| `LWIP_TCPIP_CORE_LOCKING` | Marshal-dependent | Only needed if you install the core-locking marshal (option B above). The default `tcpip_callback` marshal (option A) does not require it. `NO_SYS=1` never needs it. | -| `ARP_QUEUEING=1` | **Recommended** | lwIP default. With it, the first datagram to an unresolved peer is `pbuf_clone`d into PBUF_RAM and queued behind the ARP request — when the reply lands, the packet ships. With `ARP_QUEUEING=0` the first datagram is silently dropped at the IP layer; cold-start logging loses messages. | -| `LWIP_TCP_KEEPALIVE=1` | **Recommended** | Without this, the `SOF_KEEPALIVE` bit the adapter sets on every pcb is a no-op. Tune `TCP_KEEPIDLE_DEFAULT` / `TCP_KEEPINTVL_DEFAULT` / `TCP_KEEPCNT_DEFAULT` for your deadline budget. | -| `TCP_MSS` | Per-platform | Default `536` (RFC-conservative). Bump to `1460` on Ethernet links if your MTU is 1500 and you want fewer segments per syslog record. | -| `PBUF_POOL_SIZE` | Per-traffic | Size the pool generously. The Datagram path borrows one `MEMP_PBUF` header per send; the TcpStream RX path borrows one pbuf per segment until `Stream_Read` drains it. | -| `MEMP_NUM_TCP_PCB` | Per-deployment | At least the number of concurrent `SolidSyslogLwipRawTcpStream` instances (default pool = 2 for the TLS-over-plain-TCP pair). | -| `MEMP_NUM_UDP_PCB` | Per-deployment | At least the number of concurrent `SolidSyslogLwipRawDatagram` instances (default pool = 1). | - ---- - -## Wiring example — bare-metal `NO_SYS=1` +Otherwise post to lwIP's mailbox and block until it runs — `block = 1` is what +satisfies the synchronous contract, and a bare `tcpip_callback` does not: ```c -#include "SolidSyslog.h" -#include "SolidSyslogConfig.h" -#include "SolidSyslogLwipRawAddress.h" -#include "SolidSyslogLwipRawDatagram.h" -#include "SolidSyslogLwipRawResolver.h" -#include "SolidSyslogLwipRawTcpStream.h" -#include "SolidSyslogPassthroughBuffer.h" -#include "SolidSyslogStreamSender.h" -#include "SolidSyslogUdpSender.h" - -extern void MyLwipSleep(int milliseconds); /* sys_check_timeouts + RX pump */ - -static struct SolidSyslog* g_syslog; -static struct SolidSyslogBuffer* g_buffer; - -void LogPipelineInit(void) +static void RunHop(void* ctx) { - struct SolidSyslogResolver* resolver = SolidSyslogLwipRawResolver_Create(); - struct SolidSyslogAddress* udpAddr = SolidSyslogLwipRawAddress_Create(); - struct SolidSyslogDatagram* datagram = SolidSyslogLwipRawDatagram_Create(); - - struct SolidSyslogUdpSenderConfig udpCfg = { - .Resolver = resolver, - .Datagram = datagram, - .Address = udpAddr, - .Endpoint = MyEndpoint, /* your SolidSyslogEndpointFunction */ - .EndpointVersion = MyEndpointVersion, - }; - struct SolidSyslogSender* sender = SolidSyslogUdpSender_Create(&udpCfg); - - g_buffer = SolidSyslogPassthroughBuffer_Create(sender); - - struct SolidSyslogConfig syslogCfg = { - .Hostname = MyHostname, - .AppName = MyAppName, - .ProcessId = MyProcessId, - .Clock = MyClock, - .Buffer = g_buffer, - }; - g_syslog = SolidSyslog_Create(&syslogCfg); + struct MarshalHop* hop = ctx; + hop->callback(hop->context); } -void MainLoop(void) +void MyTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) { - for (;;) + if (MyPort_CurrentTaskIsTcpipThread()) /* else this deadlocks */ { - sys_check_timeouts(); - MyNetif_DrivePolledRx(); - SolidSyslog_Service(g_syslog); - /* … rest of your application … */ + callback(context); + return; } + struct MarshalHop hop = {callback, context}; + (void) tcpip_callback_with_block(RunHop, &hop, 1); } ``` -For TCP, swap the UDP sender for a `SolidSyslogStreamSender` whose -`Stream` is a `SolidSyslogLwipRawTcpStream` built with your `Sleep`: - -```c -struct SolidSyslogLwipRawTcpStreamConfig streamCfg = { - .GetConnectTimeoutMs = NULL, /* falls back to SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS */ - .ConnectTimeoutContext = NULL, - .Sleep = MyLwipSleep, /* required */ -}; -struct SolidSyslogStream* tcpStream = SolidSyslogLwipRawTcpStream_Create(&streamCfg); -``` - ---- - -## Adapter-specific notes - -### Datagram — pbuf strategy - -`SolidSyslogLwipRawDatagram` uses `PBUF_REF`: a single pbuf header is -allocated per `SendTo`, its `payload` is pointed at the caller's -buffer, `udp_sendto` is called, and the header is `pbuf_free`d before -return. Zero copy on the hot path. - -This is safe across ARP queueing because lwIP's `etharp_query` does -`pbuf_clone(…, PBUF_RAM, q)`: it copies the referenced payload into -a private RAM pbuf before queueing, so the caller's buffer only needs -to live for the `udp_sendto` call itself (which is the synchronous -guarantee `SolidSyslogDatagram_SendTo` already provides). - -### TcpStream — `tcp_write` strategy - -`SolidSyslogLwipRawTcpStream` uses `TCP_WRITE_FLAG_COPY`: lwIP copies -your bytes into its own pbufs before `tcp_write` returns. This costs -one `memcpy` per send but honours the synchronous `Stream_Send(buf, -len)` lifetime contract: caller buffers are free at return, -regardless of when the peer ACKs. +Two things to check in your port: the mailbox must be sized for a blocking +post, and lwIP exposes no portable "am I on the lwIP thread?" predicate, so +most ports compare the current task handle against the one given to +`tcpip_init`. Without that guard, an adapter call made from inside a callback +lwIP itself invoked will deadlock. -`tcp_output` is called after every successful `tcp_write` to nudge -transmission. If `tcp_output` returns `ERR_MEM`, the data is already -in `pcb->snd_buf`; lwIP will retry on the next `tcp_tmr` tick and -the wrapper reports Send-success (lwIP owns the bytes, exactly -matching POSIX's "kernel accepted the data into the send buffer" -semantics). +Here `Sleep` is only a yield — the lwIP thread is running concurrently — so +whatever your RTOS offers is right. -### TcpStream — synchronous Open via spin-with-sleep +Marshal at the individual lwIP call, which is what this seam gives you. Wrapping +`SolidSyslog_Service` instead puts file I/O, crypto, buffer locking and record +formatting on the lwIP thread, none of which touch lwIP, and starves its timer +and receive path under load. -`tcp_connect` is asynchronous: it returns immediately and lwIP fires -the registered `connected_cb` when the SYN/SYN-ACK exchange -completes. `SolidSyslogStream_Open` is synchronous. The wrapper -bridges by spinning on a `Connected` flag set by its `connected_cb`, -sleeping `SOLIDSYSLOG_LWIP_RAW_TCP_CONNECT_POLL_MS` (default 10 ms) -between checks via your injected `Sleep`, bounded by the -`GetConnectTimeoutMs` getter (default `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` -= 200 ms; install a runtime getter if you need to vary it). +## Resolving by name -Timeout → `tcp_abort` on the pcb, Open returns `false`. Errored -callback → `tcp_abort`, Open returns `false`. Immediate non-`ERR_OK` -from `tcp_connect` → `tcp_abort`, Open returns `false`. +The DNS resolver bridges lwIP's asynchronous `dns_gethostbyname` to the +synchronous resolve contract the same way the TCP stream bridges connect: a +cache or hostlist hit returns immediately, and anything else spins on your +thread — never lwIP's — sleeping via your `Sleep` until the answer arrives or +the deadline passes. It needs `LWIP_DNS=1` and a `Sleep`; without one it falls +back to the Null resolver. -### TcpStream — RX queue - -lwIP's `tcp_recv` callback fires when bytes arrive. The wrapper owns -a bounded ring of pbuf pointers sized by `SOLIDSYSLOG_LWIP_RAW_TCP_RX_QUEUE_SIZE` -(default 8). Each `Stream_Read` drains bytes from the head pbuf, -calls `tcp_recved(pcb, n)` to ACK back to lwIP's receive window, and -`pbuf_free`s the head when fully drained. Queue full → the -callback returns non-`ERR_OK` so lwIP retains the pbuf and replays -the callback later (lwIP's flow-control hook). - -> Chained pbufs. lwIP hands a single received segment as a pbuf -> chain (`p->tot_len > p->len`, `p->next != NULL`) whenever the -> payload spans more than one pool pbuf: normal once a segment exceeds -> one `PBUF_POOL_BUFSIZE`, and influenced by the peer / on-path -> segmentation. The wrapper drains the whole chain keyed off -> `tot_len` (via `pbuf_copy_partial`, walking `p->next`) across one or -> more `Stream_Read` calls, and `pbuf_free`s the head (which frees -> every link) only once the chain is fully consumed. Integrators -> supplying their own `SolidSyslogStream` byte transport must honour the -> same contract: never read only `head->len` and then free the chain, or -> the tail bytes are lost (this corrupts a stacked TLS record stream). - -The default-8 queue size is sized for the typical mTLS handshake -flight (ServerHello + Certificate + ServerKeyExchange + -ServerHelloDone is 2–4 segments). Bump it for streaming server -responses; lower it if your `MEMP_NUM_PBUF` is constrained and you -need lwIP to backpressure sooner. - -### TcpStream — lifecycle ownership - -The wrapper owns its `tcp_pcb` end-to-end. Three things to know: - -1. `tcp_err` releases the pcb upstream. When lwIP fires - `tcp_err` for a fatal event (RST, OOM, ABRT), the pcb is gone - from lwIP's side before the callback runs. The wrapper's - `tcp_err` handler nulls its internal `Pcb` field and sets an - `Errored` flag. The next `Stream_Send` returns `false`; the next - `Stream_Read` returns `-1`. Crucially, calling `tcp_close` on a - pcb that was already released by `tcp_err` is a use-after-free - in lwIP, so the wrapper guards against this with a `Pcb != NULL` - check before `tcp_close`. You never see this rule unless you - bypass the abstraction and poke at lwIP pcbs directly through - your own code, so don't. - -2. Peer FIN (`tcp_recv` with `p == NULL`) drains before EOF. - The half-close sets `Errored`; the next `Stream_Read` that finds - the queue empty returns `-1` and internally `tcp_close`s the - pcb. Already-queued bytes drain first. - -3. `Close` is idempotent. Second `Close` is a no-op. `Destroy` - internally calls `Close` (which drains the RX queue's pbufs and - then `tcp_close`s if the pcb is still around), then overwrites - the abstract base with `SolidSyslogNullStream` so use-after- - destroy is a safe no-op rather than a NULL-fn-pointer crash. - ---- - -## Tunables - -All tunables live in `Core/Interface/SolidSyslogTunablesDefaults.h`. -Override by `#define`ing them in a user-tunables header passed via -the `SOLIDSYSLOG_USER_TUNABLES_FILE` CMake variable. - -| Tunable | Default | Adjust when | -|---|---|---| -| `SOLIDSYSLOG_RESOLVER_POOL_SIZE` | `1U` | You wire more than one resolver. Role pool shared by the numeric and DNS resolvers — counts instances, not implementations, so wiring **both** the numeric and DNS resolver in one build means setting this to `2U`. | -| `SOLIDSYSLOG_DNS_RESOLVE_TIMEOUT_MS` | `5000U` | Default suits a healthy recursive resolver. Raise for a slow / distant DNS server. | -| `SOLIDSYSLOG_LWIP_RAW_DNS_RESOLVE_POLL_MS` | `10U` | Default gives 500 polls inside the 5 s resolve deadline. Lower to notice completion sooner; raise to reduce spin overhead on a constrained MCU. | -| `SOLIDSYSLOG_DATAGRAM_POOL_SIZE` | `1U` | You wire more than one UDP sender. | -| `SOLIDSYSLOG_TCP_STREAM_POOL_SIZE` | `2U` | You wire more than the canonical plain-TCP + TLS-underlying-TCP pair. | -| `SOLIDSYSLOG_ADDRESS_POOL_SIZE` | `3U` | Shared with PlusTcp / Posix / Winsock — bump if you need >3 concurrent destinations. | -| `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` | `200U` | Default suits loopback / LAN. Raise for WAN deployments behind a high-RTT link; or install a runtime `GetConnectTimeoutMs` getter for per-instance tuning. | -| `SOLIDSYSLOG_LWIP_RAW_TCP_CONNECT_POLL_MS` | `10U` | Default gives 20 polls inside the 200 ms connect deadline. Lower it to notice a fast connect sooner; raise it to reduce spin overhead on a constrained MCU. | -| `SOLIDSYSLOG_LWIP_RAW_TCP_RX_QUEUE_SIZE` | `8U` | Sized for the typical mTLS handshake flight. Bump for streaming server responses; lower if `MEMP_NUM_PBUF` is tight. | - ---- - -## DNS - -`SolidSyslogLwipRawDnsResolver` resolves the destination by name via -lwIP's `dns_gethostbyname`. It is a superset of the numeric -`SolidSyslogLwipRawResolver` (numeric literals, DNS-cache hits, and -local-hostlist entries all resolve through it too), so you only need one -resolver, picked by whether you compile DNS in. - -### The async bridge - -`dns_gethostbyname` is asynchronous: a cold lookup returns `ERR_INPROGRESS` -and lwIP invokes a `dns_found_callback` later, on the tcpip thread, when the -answer arrives. The library's `SolidSyslogResolver_Resolve` contract is -synchronous, so the adapter bridges the two exactly as `SolidSyslogLwipRawTcpStream` -bridges `tcp_connect`: - -1. The `dns_gethostbyname` call is made under the marshal hop - (`SolidSyslogLwipRaw_SetMarshal`): it touches lwIP core state, unlike the - numeric resolver's pure `ipaddr_aton` parse, which is why only this resolver - marshals. -2. `ERR_OK` (synchronous hit: numeric, cache, or local hostlist) → the address - is ready immediately; no spin. -3. `ERR_INPROGRESS` → the adapter spins on the caller's thread, sleeping via - your injected `Sleep` between polls of a flag the callback sets, until the - answer arrives or the deadline elapses. The spin never sleeps the tcpip - thread (that would starve the DNS retransmit timer and RX). -4. A delivered address → success; a delivered `NULL` (no such host) → failure; - deadline exceeded → failure plus a `SolidSyslog_Error` report - (`LWIPRAWDNSRESOLVER_ERROR_RESOLVE_TIMEOUT`). - -`Sleep` is required (a `NULL` config falls back to `NullResolver`). The -deadline and poll period come from `SOLIDSYSLOG_DNS_RESOLVE_TIMEOUT_MS` (5 s) -and `SOLIDSYSLOG_LWIP_RAW_DNS_RESOLVE_POLL_MS` (10 ms), build-time only; DNS -timeout rarely needs runtime tuning, so there is no per-instance getter. - -### Local hostlist (no DNS server) - -For fixed deployments (or test topologies where a real resolver can't return a -reachable address), `DNS_LOCAL_HOSTLIST` maps names statically. A hostlist hit -returns `ERR_OK` before any server is consulted, so the resolve completes -synchronously and entirely on-device: +Where there is no DNS server, `DNS_LOCAL_HOSTLIST` maps names statically and +resolves entirely on-device: ```c -/* lwipopts.h */ #define LWIP_DNS 1 #define DNS_LOCAL_HOSTLIST 1 #define DNS_LOCAL_HOSTLIST_INIT \ - { DNS_LOCAL_HOSTLIST_ELEM("syslog-ng", IPADDR4_INIT_BYTES(10, 0, 2, 2)) } + { DNS_LOCAL_HOSTLIST_ELEM("collector", IPADDR4_INIT_BYTES(10, 0, 2, 2)) } ``` -### Wiring - -```c -#include "SolidSyslogLwipRawDnsResolver.h" - -extern void MyLwipSleep(int milliseconds); /* same Sleep the TcpStream uses */ - -struct SolidSyslogLwipRawDnsResolverConfig dnsCfg = { .Sleep = MyLwipSleep }; -struct SolidSyslogResolver* resolver = SolidSyslogLwipRawDnsResolver_Create(&dnsCfg); -/* hand `resolver` to the UdpSender / StreamSender config exactly as the - numeric resolver — the destination host is now a name, e.g. "logs.example.com". */ -``` +Both resolvers draw from one pool, so wiring the numeric and the DNS resolver +together needs `SOLIDSYSLOG_RESOLVER_POOL_SIZE` raised to 2. The pool sizes and +timeouts are in +[Adding it to your build](../../build-integration.md#tunables). -> Known limitation: over-the-wire DNS is not BDD-covered. The -> FreeRTOS-on-lwIP BDD target resolves the oracle by name, but only through the -> synchronous local-hostlist branch: the QEMU slirp + docker topology cannot -> hand the guest a reachable address for the `syslog-ng` alias over real DNS -> (slirp's forwarder resolves it to a docker-bridge IP the guest has no route -> to; only `10.0.2.2` reaches the oracle). The async / over-the-wire / timeout -> branches are unit-tested instead -> (`Tests/Lwip/SolidSyslogLwipRawDnsResolverTest`), consistent with the -> project's integration-over-BDD stance for paths the harness can't realistically -> drive. +## Limits ---- +The address and resolver are IPv4 only. Neither the datagram nor the TCP stream +selects an output interface — lwIP's routing table decides — and the datagram +reports a fixed conservative maximum payload rather than discovering the path +maximum transmission unit. -## What this guide does not cover +## When it does not work -- IPv6: the current Address / Resolver are IPv4-only. -- Multi-`netif` routing: neither Datagram nor TcpStream selects - an output interface; lwIP's routing table decides. -- Jumbo-frame MTU discovery: `Datagram_MaxPayload` returns - `SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD` (1232 bytes) unconditionally. +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the component +fell back to its Null object, and nothing will be delivered. diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index e12bfbbc..d58b302c 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -10,7 +10,7 @@ BlockDevice. | Class | Role | |---|---| -| [`SolidSyslogPlusFatFile`](../../api/SolidSyslogPlusFatFile_8h.md) | file — `ff_fflush` after every write | +| [`SolidSyslogPlusFatFile`](../../api/SolidSyslogPlusFatFile_8h.md) | file — cache flush after every write | ## Requirements @@ -27,9 +27,11 @@ keeping it unreadable, is the SecurityPolicy role's job, not this one — see ### Durability is bounded by the write, not guaranteed by it -`ff_fflush` runs after every write, so at most the record in flight is lost on -power failure. Whether that reaches the medium is a property of your `FF_Disk_t` -driver and the hardware under it. FAT is not a journalling filesystem, and a +The adapter flushes after every write, so at most the record in flight is lost +on power failure. It flushes the IO manager's cache rather than the file: +`ff_stdio.h` declares `ff_fflush` but the library never defines it, so +`FF_FlushCache` is the real durability primitive. Whether that reaches the +medium is a property of your `FF_Disk_t` driver and the hardware under it. FAT is not a journalling filesystem, and a partially written directory entry is possible on a device that loses power mid-update. diff --git a/docs/platforms/plusfat/setup.md b/docs/platforms/plusfat/setup.md index 6cf05237..637d1eda 100644 --- a/docs/platforms/plusfat/setup.md +++ b/docs/platforms/plusfat/setup.md @@ -1,128 +1,86 @@ -# Integrating SolidSyslog with FreeRTOS-Plus-FAT +# FreeRTOS-Plus-FAT setup -`SolidSyslogPlusFatFile` is the `SolidSyslogFile` adapter backed by -[FreeRTOS-Plus-FAT](https://www.freertos.org/Documentation/03-Libraries/05-FreeRTOS-labs/04-FreeRTOS-plus-FAT/01-FreeRTOS-plus-FAT) -(the `ff_stdio` API). It is the FreeRTOS-Plus ecosystem counterpart to the -OS-agnostic [ChaN FatFs adapter](../../../Platform/FatFs/); pair it with -FreeRTOS-Plus-TCP for a coherent all-FreeRTOS-Plus storage + transport stack. It -gives the store-and-forward layer (`SolidSyslogBlockStore` over -`SolidSyslogFileBlockDevice`) a real on-flash file backend. - -This guide covers what you must supply around the adapter. For the file seam -itself see [`SolidSyslogFile.h`](../../../Core/Interface/SolidSyslogFile.h); for the -store see [`SolidSyslogBlockStore.h`](../../../Core/Interface/SolidSyslogBlockStore.h). +Giving store-and-forward a real file backend. +[FreeRTOS-Plus-FAT](index.md) covers what the adapter fills and what it leaves +to you. This page is what you must supply around it. ## The shape ```text SolidSyslogBlockStore - │ (SolidSyslogStore vtable) -SolidSyslogFileBlockDevice <-- sequence-numbered .log files - │ (SolidSyslogFile vtable) -SolidSyslogPlusFatFile <-- this adapter: ff_fopen / ff_fread / ff_fwrite / … - │ (ff_stdio API) -FreeRTOS-Plus-FAT core (ff_*.c) <-- vendor library, you compile it - │ (FF_Disk_t block callbacks) -your FF_Disk_t media driver <-- YOU write this: SD / eMMC / QSPI-flash / RAM + │ +SolidSyslogFileBlockDevice ◀── sequence-numbered .log files + │ +SolidSyslogPlusFatFile ◀── this adapter, over the ff_stdio API + │ +FreeRTOS-Plus-FAT core ◀── vendor sources, you compile them + │ +your FF_Disk_t media driver ◀── you write this ``` -The adapter owns only the middle box. The vendor core and the media driver are -yours to provide; the library never reaches the block device directly. +The adapter owns the middle box only. It speaks `ff_stdio` and never reaches +the block device. ## What you must provide -1. The FreeRTOS-Plus-FAT sources, compiled into your image (`ff_crc.c`, - `ff_dir.c`, `ff_error.c`, `ff_fat.c`, `ff_file.c`, `ff_format.c`, `ff_ioman.c`, - `ff_locking.c`, `ff_memory.c`, `ff_stdio.c`, `ff_string.c`, `ff_sys.c`, - `ff_time.c`). These are not -Wsign-conversion / -Wconversion clean; compile - them under a relaxed warning set, as you would the FreeRTOS kernel. - -2. An `FF_Disk_t` media driver for your storage hardware: `FF_CreateIOManager` - with read/write block callbacks, `FF_Mount` (format-on-first-use via - `FF_Partition` + `FF_Format` when no FAT is present), and `FF_FS_Add("/", disk)` - to register the volume in `ff_stdio`'s virtual file system. Plus-FAT ships - reference drivers under `portable/` (`ff_ramdisk.c` is the clearest template). - The library's BDD target ships a semihosting example, - [`Bdd/Targets/Common/FFSemihostingDisk.c`](../../../Bdd/Targets/Common/FFSemihostingDisk.c). - -3. A `FreeRTOSFATConfig.h` on your include path. `ff_headers.h` pulls it via - `#include "FreeRTOSFATConfig.h"`; unlike ChaN FatFs's `ffconf.h`, it resolves - off the `-I` path (no source-tree colocation needed). At minimum set - `ffconfigBYTE_ORDER` and `ffconfigCWD_THREAD_LOCAL_INDEX`; - `FreeRTOSFATConfigDefaults.h` fills the rest. See - [`Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h`](../../../Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h). - -4. Kernel configuration in `FreeRTOSConfig.h`: - - `configUSE_RECURSIVE_MUTEXES = 1` (Plus-FAT's `ff_locking.c` enforces this). - - Event groups compiled in (`event_groups.c`); the IO manager uses them. - - `configNUM_THREAD_LOCAL_STORAGE_POINTERS >= ffconfigCWD_THREAD_LOCAL_INDEX + 3`. - `ff_stdio` stores its `errno`, CWD, and `FF_Error` in per-task thread-local - storage at offsets `ffconfigCWD_THREAD_LOCAL_INDEX + {0,1,2}`. With the index - at 0 that means at least 3 slots; `ff_stdio.h` enforces this at compile - time. - - `configSUPPORT_STATIC_ALLOCATION = 1` if your media driver creates its IO - manager mutex statically (the example does); dynamic allocation otherwise. - -## Path convention — absolute paths - -With `ffconfigHAS_CWD = 0` (the default), Plus-FAT's `ff_stdio` accepts only -absolute paths; its relative-path resolver (`prvABSPath`) is a pass-through. -Configure `SolidSyslogFileBlockDevice` with an absolute path prefix, e.g. -`/STORE`, so the store files land at the volume root as `/STORE00.log`, -`/STORE01.log`, … (A leading `/` is equally valid for the ChaN FatFs adapter, so -the same prefix works for either backend.) The default 8.3 short-filename mode -(`ffconfigLFN_SUPPORT = 0`) is sufficient for that naming. - -## Durability contract - -`SolidSyslogPlusFatFile_Write` flushes after every complete write so a power -loss never loses a record the `BlockStore` was told had been stored. Two notes -specific to Plus-FAT: - -- There is no per-file flush. `ff_stdio.h` *declares* `ff_fflush`, but the - library (as of SHA `8d38036`) never *defines* it. The adapter instead calls - `FF_FlushCache(file->pxIOManager)`, the IO-manager cache flush, which is the - real durability primitive. -- The directory entry (file size) is committed on `Close`, not on each - flush. `FF_FlushCache` persists the file's data sectors; the dirent's size - field is written by `ff_fclose`. A graceful shutdown (the library's - `SolidSyslog` teardown closes the store file) therefore leaves both data and - metadata consistent. If your platform must survive a hard power cut - mid-record, size the records and the discard policy with that in mind. - -## Heap usage - -The adapter struct itself is pool-allocated (`SOLIDSYSLOG_FILE_POOL_SIZE`) and -never calls `malloc`. FreeRTOS-Plus-FAT, however, does allocate: the IO -manager sector cache and internal buffers come from the FreeRTOS heap (`heap_4` -or your `pvPortMalloc`). This is integrator-scoped and expected, exactly the -mbedTLS precedent: the vendor library allocates; SolidSyslog's own structures do -not. Size your heap for the IO-manager cache you request in `FF_CreateIOManager`. - -## Reference integration - -[`Bdd/Targets/FreeRtos/`](../../../Bdd/Targets/FreeRtos/) is the worked example, the -FreeRTOS-Plus-TCP + FreeRTOS-Plus-FAT QEMU BDD target. It wires: - -- [`FFSemihostingDisk.c`](../../../Bdd/Targets/Common/FFSemihostingDisk.c): an - `FF_Disk_t` over an ARM-semihosting host-backed flat disk (8 MiB, FAT16), - modelled on Plus-FAT's `ff_ramdisk.c` but persistent (mount-or-format-on-first- - use, so a power cycle keeps its data). -- [`BddTargetPlusFatMount.c`](../../../Bdd/Targets/Common/BddTargetPlusFatMount.c): - the mount/unmount + `SolidSyslogPlusFatFile` create/destroy wired into the - shared FreeRTOS pipeline's FS-mount seam. -- [`FreeRTOSFATConfig.h`](../../../Bdd/Targets/FreeRtos/FreeRTOSFATConfig.h) and the - `configNUM_THREAD_LOCAL_STORAGE_POINTERS` knob in - [`FreeRTOSConfig.h`](../../../Bdd/Targets/FreeRtos/FreeRTOSConfig.h). - -The full store / capacity / power-cycle-replay BDD suite runs against this target -on QEMU (`bdd-freertos-qemu-plustcp`). - -## What this adapter does not own - -- The media driver. SD/eMMC/flash/RAM block I/O is yours (or a Plus-FAT - `portable/` driver). The adapter speaks only `ff_stdio`. -- Mounting and formatting. The adapter opens/reads/writes files; bringing the - volume up (`FF_Mount` / `FF_Format` / `FF_FS_Add`) is the media driver's job. -- The FreeRTOS-Plus-FAT sources and their licence. You vendor and compile - them. +**The FreeRTOS-Plus-FAT sources**, compiled into your image. They are not clean +under the conversion warnings this project builds with, so compile them under a +relaxed warning set as you would the kernel itself. + +**An `FF_Disk_t` media driver** for your storage hardware — the IO manager with +its read and write block callbacks, the mount, format-on-first-use where no +file system is present, and registration of the volume in the virtual file +system. Plus-FAT ships reference drivers; its RAM disk is the clearest +template. + +**A `FreeRTOSFATConfig.h`** on your include path. Unlike some file systems this +resolves off `-I` rather than needing to sit beside the sources. Set at least +the byte order and the thread-local storage index; the defaults header fills +the rest. + +**Kernel configuration**, because Plus-FAT requires it: + +- recursive mutexes, which its locking enforces +- event groups, used by the IO manager +- enough thread-local storage slots — `ff_stdio` keeps its error state, working + directory and error code in three consecutive slots from the index you + configure, and its header enforces this at compile time +- static allocation, if your media driver creates its IO manager mutex + statically + +## Paths must be absolute + +Without the working-directory option compiled in, `ff_stdio` accepts absolute +paths only — its relative-path resolver is a pass-through. Give +`SolidSyslogFileBlockDevice` an absolute prefix such as `/STORE`, so files land +at the volume root as `/STORE00.log`, `/STORE01.log`. The default short +filename mode is sufficient for that naming. + +## Durability + +The adapter flushes after every complete write, so a power loss never discards +a record the store was told had been written. + +Two things are specific to Plus-FAT and worth knowing. There is **no per-file +flush**: `ff_stdio.h` declares `ff_fflush` but the library never defines it, so +the adapter flushes the IO manager's cache instead, which is the real +durability primitive. And the **directory entry is committed on close, not on +each flush** — the cache flush persists the file's data sectors, while the +recorded file size is written when the file is closed. A graceful shutdown +therefore leaves data and metadata consistent. If the device must survive a +hard power cut mid-record, size your records and choose your discard policy +with that in mind. + +## Memory + +The adapter allocates nothing; its instance comes from a static pool. Plus-FAT +does allocate — the IO manager's sector cache and its internal buffers come +from the RTOS heap — so size that heap for the cache you ask the IO manager +for. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the file fell +back to the Null object, and nothing will be stored. diff --git a/docs/platforms/plustcp/setup.md b/docs/platforms/plustcp/setup.md new file mode 100644 index 00000000..bfd6bf89 --- /dev/null +++ b/docs/platforms/plustcp/setup.md @@ -0,0 +1,56 @@ +# FreeRTOS-Plus-TCP setup + +Wiring the networking adapters. [FreeRTOS-Plus-TCP](index.md) covers what they +fill and what they leave to you; the config fields are documented on the +structs themselves. + +## What to link + +The stack is configured by a header you own, so the adapters compile inside +your target against your `FreeRTOSIPConfig.h`: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "FreeRtos;PlusTcp") +target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::PlusTcp) +``` + +Bring the stack up and let it acquire an address before creating any sender. + +Resolving the collector by name needs DNS compiled into the stack. If you give +the collector a numeric address instead, you need neither DNS nor the resolver +it backs — which is also the more predictable choice where the network cannot +be trusted. + +## Wiring a sender + +```c +struct SolidSyslogResolver* resolver = SolidSyslogPlusTcpResolver_Create(); +struct SolidSyslogAddress* address = SolidSyslogPlusTcpAddress_Create(); +struct SolidSyslogStream* stream = SolidSyslogPlusTcpTcpStream_Create(NULL); + +static struct SolidSyslogStreamSenderConfig senderConfig; +senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; +senderConfig.Resolver = resolver; +senderConfig.Stream = stream; +senderConfig.Address = address; +senderConfig.Endpoint = MyEndpoint; +struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig); +``` + +For UDP, build a `SolidSyslogPlusTcpDatagram` and a `SolidSyslogUdpSender` +instead. Passing `NULL` to the TCP stream takes the default connect budget. + +## Sizing + +The stack's socket and buffer limits are yours to set, and they have to cover +what this library creates alongside everything else using the network. +Under-sizing shows up as a failure to send rather than as a crash, so it is +worth counting the instances you create rather than discovering the limit in +the field. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the component +fell back to its Null object, and nothing will be delivered. diff --git a/docs/platforms/posix/setup.md b/docs/platforms/posix/setup.md new file mode 100644 index 00000000..379a2d0d --- /dev/null +++ b/docs/platforms/posix/setup.md @@ -0,0 +1,63 @@ +# Posix setup + +Wiring the POSIX adapters. [Posix](index.md) covers what they fill and what +they leave to you; the config fields are documented on the structs themselves. + +## What to link + +POSIX is a stable system interface rather than a header-configured upstream, so +the adapters compile straight into `libSolidSyslog.a`. There is nothing extra to +link: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "Posix") +``` + +The one exception is the message-queue buffer, which needs POSIX message queues +— on glibc that means linking `rt`. If you use the circular buffer instead, you +do not need it. + +## Wiring a sender + +```c +struct SolidSyslogResolver* resolver = SolidSyslogGetAddrInfoResolver_Create(); +struct SolidSyslogAddress* address = SolidSyslogPosixAddress_Create(); +struct SolidSyslogStream* stream = SolidSyslogPosixTcpStream_Create(NULL); + +static struct SolidSyslogStreamSenderConfig senderConfig; +senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; +senderConfig.Resolver = resolver; +senderConfig.Stream = stream; +senderConfig.Address = address; +senderConfig.Endpoint = MyEndpoint; +struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig); +``` + +For UDP, build a `SolidSyslogPosixDatagram` and a `SolidSyslogUdpSender` +instead; the resolver and address are the same. Passing `NULL` to the TCP +stream takes the default connect budget — supply a config to override it per +instance. + +Tear down in reverse order, and destroy everything you created. + +## The callbacks + +`SolidSyslogConfig` takes the clock, hostname and process id as callbacks +rather than components, and this platform supplies one of each ready to use: +`SolidSyslogPosixClock_GetTimestamp`, `SolidSyslogPosixHostname` and +`SolidSyslogPosixProcessId`. Use them directly, or wrap your own if the values +should come from somewhere other than the operating system. + +## Threading + +If your application logs from one thread and drains from another, put a +circular buffer between them with `SolidSyslogPosixMutex` filling the mutex +role. Leaving the mutex unfilled is safe only while `Log` and `Service` run on +the same thread. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the component +fell back to its Null object, and nothing will be delivered. diff --git a/docs/platforms/windows/setup.md b/docs/platforms/windows/setup.md new file mode 100644 index 00000000..84d2173b --- /dev/null +++ b/docs/platforms/windows/setup.md @@ -0,0 +1,66 @@ +# Windows setup + +Wiring the Win32 and Winsock adapters. [Windows](index.md) covers what they +fill and what they leave to you; the config fields are documented on the +structs themselves. + +## What to link + +Win32 and Winsock are stable system interfaces, so the adapters compile +straight into the static library and there is nothing extra to link: + +```cmake +set(SOLIDSYSLOG_PLATFORMS "Windows") +``` + +## Initialise Winsock first + +Winsock must be started before any sender is created, and stopped when you are +finished with it. The adapter does not do this for you, because a process that +already uses sockets has done it once and must not have it done again: + +```c +WSADATA wsaData; +(void) WSAStartup(MAKEWORD(2, 2), &wsaData); +/* ... create, use and destroy your senders ... */ +(void) WSACleanup(); +``` + +## Wiring a sender + +```c +struct SolidSyslogResolver* resolver = SolidSyslogWinsockResolver_Create(); +struct SolidSyslogAddress* address = SolidSyslogWinsockAddress_Create(); +struct SolidSyslogStream* stream = SolidSyslogWinsockTcpStream_Create(NULL); + +static struct SolidSyslogStreamSenderConfig senderConfig; +senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; +senderConfig.Resolver = resolver; +senderConfig.Stream = stream; +senderConfig.Address = address; +senderConfig.Endpoint = MyEndpoint; +struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig); +``` + +For UDP, build a `SolidSyslogWinsockDatagram` and a `SolidSyslogUdpSender` +instead. Tear down in reverse order, before `WSACleanup`. + +## The callbacks + +`SolidSyslogConfig` takes the clock, hostname and process id as callbacks, and +this platform supplies one of each: `SolidSyslogWindowsClock_GetTimestamp`, +`SolidSyslogWindowsHostname` and `SolidSyslogWindowsProcessId`. + +## Threading + +If your application logs from one thread and drains from another, put a +circular buffer between them with `SolidSyslogWindowsMutex` filling the mutex +role. This platform also fills the atomic counter role, so the sequence number +behind gap detection needs no separate component. + +## When it does not work + +Failures report through the error handler rather than silently. Install one +before you start, and read [error severity](../../error-severity.md) for what +each level is telling you — a `CRITICAL` at create time means the component +fell back to its Null object, and nothing will be delivered. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index 15462d35..101558be 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -64,18 +64,34 @@ "The POSIX adapter pack — sockets, pthreads, message queues, " "clock_gettime and stdio — filling six of the library's roles on Linux." ), + "platforms/posix/setup.md": ( + "Wire the POSIX adapters: what to link, building a sender, " + "the clock and hostname callbacks, and what threading needs from you." + ), "platforms/windows/index.md": ( "The Win32 and Winsock adapter pack for MSVC targets, filling the " "Resolver, Datagram, Stream, File, Mutex and AtomicCounter roles." ), + "platforms/windows/setup.md": ( + "Wire the Win32 and Winsock adapters: starting Winsock, building " + "a sender, the callbacks, and what threading needs from you." + ), "platforms/freertos/index.md": ( "The FreeRTOS adapter pack: kernel primitives filling the Mutex role and " "the sysUpTime callback, with networking from Plus-TCP or lwIP." ), + "platforms/freertos/setup.md": ( + "Wire the FreeRTOS kernel primitives: what to link, static allocation, " + "and putting the mutex under a buffer shared across tasks." + ), "platforms/plustcp/index.md": ( "The FreeRTOS-Plus-TCP adapter pack, filling the Resolver, Datagram and " "Stream roles for networking on FreeRTOS targets." ), + "platforms/plustcp/setup.md": ( + "Wire the FreeRTOS-Plus-TCP adapters: what to link, resolving the " + "collector, building a sender, and sizing the stack for it." + ), "platforms/lwipraw/index.md": ( "The lwIP Raw API adapter pack, filling the Resolver, Datagram and Stream " "roles — compiled against your lwipopts.h, NO_SYS=1 builds included." @@ -104,6 +120,10 @@ "The ChaN FatFs adapter pack, filling the File role beneath a BlockDevice " "— RTOS-agnostic, for bare-metal, FreeRTOS, Zephyr and NuttX targets." ), + "platforms/fatfs/setup.md": ( + "Give store-and-forward a FatFs file backend: what to link, the disk " + "I/O driver and configuration you supply, and what durability you get." + ), "platforms/plusfat/index.md": ( "The FreeRTOS-Plus-FAT adapter pack, filling the File role beneath a " "BlockDevice for an all-FreeRTOS-Plus storage and transport stack." @@ -116,6 +136,10 @@ "The portable C11 stdatomic.h AtomicCounter — the sequenceId source on " "any target with a C11 compiler, with no OS dependency." ), + "platforms/atomics/setup.md": ( + "Wire the C11 atomics sequence-number source: what to link, creating " + "the counter, and why an unfilled role silently disables gap detection." + ), "porting.md": ( "Port SolidSyslog to a new RTOS, network stack, filesystem or crypto " "library by filling a vtable contract. Core never changes." diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index bee2569a..2fe81bd7 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -57,10 +57,23 @@ def test_a_platform_with_a_setup_guide_gets_both_chips(self): self.assertIn("[API reference](../../api/group__platform__mbedtls.md){ .ss-chip .ss-chip--api }", out) self.assertIn("[Setup](setup.md){ .ss-chip .ss-chip--setup }", out) - def test_a_platform_without_one_gets_no_setup_chip(self): - # Phase 3 adds the missing setup pages; until then the chip must not - # appear, because the page it would point at does not exist. - out = render("platforms/posix/index.md") + def test_every_platform_has_both_chips_today(self): + _, slugs, _labels = h._index(CONFIG) + for slug in slugs: + out = render(f"platforms/{slug}/index.md") + self.assertIn(".ss-chip--api", out, slug) + self.assertIn(".ss-chip--setup", out, slug) + + def test_the_setup_chip_appears_only_when_the_page_does(self): + # Every platform has a setup page now, so the absent case has to be + # constructed. The chip must never point at a page that is not there. + root = os.path.dirname(CONFIG["config_file_path"]) + headers, slugs, labels = h._index(CONFIG) + h._CACHE[root] = (headers, {**slugs, "posix": False}, labels) + try: + out = render("platforms/posix/index.md") + finally: + h._CACHE.pop(root, None) self.assertIn(".ss-chip--api", out) self.assertNotIn(".ss-chip--setup", out) From dc86a3cc4999185533e1abdc6956e2fbd41ddaed Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 22:14:44 +0100 Subject: [PATCH 28/57] docs: S23.22 keep the generated back-link inside the build it is in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each platform's .dox block carries the canonical docs URL so that someone reading the source tree can find the page. Doxygen auto-links it, so the group page's Detailed Description sent readers to the published site — from a local preview, or from a pull request's build artefact, which is exactly where the difference matters. The hook now rewrites that URL to a relative link, labelled with the platform rather than repeated as a bare address. The URL stays in the source, where it is the only pointer a source reader has. "Pack" leaves the .dox blocks too, so the group pages read the same way as the platform pages. Part of #708 --- Platform/Atomics/SolidSyslogAtomicsPlatform.dox | 4 ++-- Platform/FatFs/SolidSyslogFatFsPlatform.dox | 4 ++-- Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox | 4 ++-- Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox | 4 ++-- Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox | 4 ++-- Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox | 4 ++-- Platform/PlusFat/SolidSyslogPlusFatPlatform.dox | 4 ++-- Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox | 4 ++-- Platform/Posix/SolidSyslogPosixPlatform.dox | 4 ++-- Platform/Windows/SolidSyslogWindowsPlatform.dox | 4 ++-- hooks/platform_backlinks.py | 15 +++++++++++++++ hooks/test_platform_backlinks.py | 9 +++++++++ 12 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Platform/Atomics/SolidSyslogAtomicsPlatform.dox b/Platform/Atomics/SolidSyslogAtomicsPlatform.dox index 9731425b..d3fd7ed7 100644 --- a/Platform/Atomics/SolidSyslogAtomicsPlatform.dox +++ b/Platform/Atomics/SolidSyslogAtomicsPlatform.dox @@ -3,7 +3,7 @@ * @brief The AtomicCounter role over — the sequenceId source on any * target with a C11 compiler, with no OS dependency. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/atomics/ */ diff --git a/Platform/FatFs/SolidSyslogFatFsPlatform.dox b/Platform/FatFs/SolidSyslogFatFsPlatform.dox index 5fd349bb..fb643074 100644 --- a/Platform/FatFs/SolidSyslogFatFsPlatform.dox +++ b/Platform/FatFs/SolidSyslogFatFsPlatform.dox @@ -2,7 +2,7 @@ * @defgroup platform_fatfs FatFs platform * @brief The File role over ChaN FatFs, beneath a BlockDevice. RTOS-agnostic. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/fatfs/ */ diff --git a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox index 00ffa520..0c259a8b 100644 --- a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox +++ b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox @@ -3,7 +3,7 @@ * @brief Kernel primitives: the Mutex role and the sysUpTime callback. Networking * comes from FreeRTOS-Plus-TCP or lwIP alongside. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/freertos/ */ diff --git a/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox b/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox index 6000f81c..8f5dd280 100644 --- a/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox +++ b/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox @@ -3,7 +3,7 @@ * @brief Resolver, Datagram and Stream over the lwIP Raw API, compiled against your * lwipopts.h. Wraps the Raw API specifically, not netconn or sockets. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/lwipraw/ */ diff --git a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox index 25ce9601..db903048 100644 --- a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox +++ b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox @@ -3,7 +3,7 @@ * @brief TLS transport and keyed at-rest crypto via Mbed TLS, for embedded targets * where OpenSSL is too heavy. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/mbedtls/ */ diff --git a/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox b/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox index 305d22ca..4eb60e23 100644 --- a/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox +++ b/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox @@ -3,7 +3,7 @@ * @brief TLS transport for the Stream role and keyed at-rest crypto for the * SecurityPolicy role, on hosted targets. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/openssl/ */ diff --git a/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox b/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox index 52c5344c..7d3270c4 100644 --- a/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox +++ b/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox @@ -2,7 +2,7 @@ * @defgroup platform_plusfat FreeRTOS-Plus-FAT platform * @brief The File role over FreeRTOS-Plus-FAT's ff_stdio, beneath a BlockDevice. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/plusfat/ */ diff --git a/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox b/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox index b9e98648..27383212 100644 --- a/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox +++ b/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox @@ -2,7 +2,7 @@ * @defgroup platform_plustcp FreeRTOS-Plus-TCP platform * @brief Resolver, Datagram and Stream over the FreeRTOS-Plus-TCP stack. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/plustcp/ */ diff --git a/Platform/Posix/SolidSyslogPosixPlatform.dox b/Platform/Posix/SolidSyslogPosixPlatform.dox index 0f841b78..8dc020ea 100644 --- a/Platform/Posix/SolidSyslogPosixPlatform.dox +++ b/Platform/Posix/SolidSyslogPosixPlatform.dox @@ -3,7 +3,7 @@ * @brief Sockets, name resolution, files, mutexes, message queues and the clock on * POSIX hosts. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/posix/ */ diff --git a/Platform/Windows/SolidSyslogWindowsPlatform.dox b/Platform/Windows/SolidSyslogWindowsPlatform.dox index 47b76646..3da8b040 100644 --- a/Platform/Windows/SolidSyslogWindowsPlatform.dox +++ b/Platform/Windows/SolidSyslogWindowsPlatform.dox @@ -3,7 +3,7 @@ * @brief Winsock networking plus Win32 files, mutexes, atomics and the clock on * MSVC targets. * - * What the pack ships, what your build must provide, the security behaviour - * it guarantees and the obligations it leaves to you: + * What this platform ships, what your build must provide, the security + * behaviour it guarantees and the obligations it leaves to you: * https://docs.cososo.co.uk/solid-syslog/platforms/windows/ */ diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 66111e03..e09912c9 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -28,6 +28,7 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') MODULES_CRUMB = re.compile(r"\[\*\*Modules\*\*\]\([^)]*\)") +CANONICAL = "https://docs.cososo.co.uk/solid-syslog/" GENERATED_PREFIX = "api/" PLATFORM_PREFIX = "platforms/" @@ -74,6 +75,19 @@ def _index(config): return _CACHE[root] +def _relativise(markdown, slug, label): + """Point the group's own back-link at this build, not at the live site. + + The .dox block carries the canonical docs URL so that someone reading the + source tree can find the page. Doxygen auto-links it, which on any build + that is not production — a local preview, a pull-request artefact — sends + the reader to the published site instead of the one in front of them. + """ + url = re.escape(f"{CANONICAL}{PLATFORM_PREFIX}{slug}/") + linked = re.compile(rf"\[{url}\]\({url}\)|{url}") + return linked.sub(f"[{label}](../{PLATFORM_PREFIX}{slug}/index.md)", markdown) + + def _stem(src_uri): """api/SolidSyslogMbedTlsStream_8h.md -> SolidSyslogMbedTlsStream.""" leaf = src_uri[len(GENERATED_PREFIX) : -len(".md")] @@ -101,6 +115,7 @@ def on_page_markdown(markdown, page, config, files, **kwargs): name = r"platform\\?_" + re.escape(group) markdown = re.sub(rf"#\s+Group\s+{name}", f"# {title}", markdown, count=1) markdown = MODULES_CRUMB.sub(f"[**Platforms**](../{PLATFORM_PREFIX}index.md)", markdown, count=1) + markdown = _relativise(markdown, group, labels[group]) return re.sub(name, title, markdown) if src_uri == f"{GENERATED_PREFIX}modules.md": diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 2fe81bd7..16ac173f 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -108,6 +108,15 @@ def test_the_breadcrumb_points_at_the_platforms_overview(self): out = render("api/group__platform__atomics.md", self.RAW) self.assertIn("[**Platforms**](../platforms/index.md)", out) + def test_the_back_link_stays_inside_this_build(self): + raw = self.RAW + ( + "Obligations: [https://docs.cososo.co.uk/solid-syslog/platforms/atomics/]" + "(https://docs.cososo.co.uk/solid-syslog/platforms/atomics/)\n" + ) + out = render("api/group__platform__atomics.md", raw) + self.assertIn("[C11 atomics](../platforms/atomics/index.md)", out) + self.assertNotIn("docs.cososo.co.uk", out) + def test_a_group_page_gets_no_platform_chip(self): out = render("api/group__platform__atomics.md", self.RAW) self.assertNotIn("ss-chip", out) From b4a3188fdb8a3e9fbecbc029b4cd7304e9674184 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 22:25:01 +0100 Subject: [PATCH 29/57] docs: S23.22 make the compliance guide describe Core, not adapters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IEC 62443 guide had absorbed platform detail to the point where the table meant to describe the product named OpenSSL functions. CR 1.5 cited SSL_CTX_check_private_key, CR 1.8 SSL_CTX_load_verify_locations and SSL_VERIFY_PEER, CR 2.11 the POSIX and Windows clock functions and not FreeRTOS, CR 2.9 four filesystem adapters by name. Mbed TLS does the same jobs by other means and appeared in none of them. A guide written that way has to be revisited every time a platform is added, and misreads as though the library only supports what it happens to name. Every row now states what Core does, which role carries the rest, and what that leaves to the platform. No adapter API and no platform name appears anywhere in the document. The two substrate sections go, their content having moved to the platform pages where it belongs. Minimal footprint and RTOS integration go too: build and platform material that had drifted into a compliance guide. What replaces them is the section that makes the document worth reading — what your platform must provide, keyed to the six roles a control can rest on, each with the failure mode if it is not filled. Two of those failures are silent, and silence is the reason to write them down: an unfilled AtomicCounter still emits sequence numbers, so gap detection reports no gaps while proving nothing; an unfilled Mutex is a working no-op on one task and a corrupted audit trail on two. build-integration.md loses its capability matrix, the third copy of a table that already exists on the platforms page and in the registry, with a file list that duplicated the generated manifests CI checks. What stays is what is genuinely build-specific. Part of #708 --- docs/build-integration.md | 96 +++++++------------- docs/iec62443.md | 180 ++++++++++++-------------------------- 2 files changed, 86 insertions(+), 190 deletions(-) diff --git a/docs/build-integration.md b/docs/build-integration.md index 72d41cba..26f5ff76 100644 --- a/docs/build-integration.md +++ b/docs/build-integration.md @@ -44,66 +44,31 @@ Two facts decide everything below: --- -## Pick your stack — capability matrix - -Choose one provider per role you need. Roles you don't need: leave the adapter -out and the Core Null object stands in. - -*Files* lists the file group (`Solid…` prefix elided). Most adapters are -`Adapter.c` + `AdapterStatic.c` (the `Static` file is the instance pool) plus an -`AdapterPrivate.h`; compile the `.c` files, keep the directory on the include -path. *Pool tunable* is the `SOLIDSYSLOG_` slot count (see -[Tunables](#tunables)). - -### Networking - -| Role | Provider | Files (`Solid…`) | Upstream / config | Pool tunable | -|---|---|---|---|---| -| Resolver | GetAddrInfo | `GetAddrInfoResolver*` | POSIX | `RESOLVER_POOL_SIZE` | -| | Winsock | `WinsockResolver*` | Win32 | ″ | -| | PlusTcp (DNS) | `PlusTcpResolver*` | FreeRTOS-Plus-TCP + `ipconfigUSE_DNS=1` | ″ | -| | LwipRaw numeric | `LwipRawResolver*` | lwIP `ipaddr_aton` | ″ | -| | LwipRaw DNS | `LwipRawDnsResolver*` | lwIP `LWIP_DNS=1` + Sleep cb | ″ | -| Datagram (UDP) | Posix / Winsock / PlusTcp / LwipRaw | `{Posix,Winsock,PlusTcp,LwipRaw}Datagram*` | resp. stacks | `DATAGRAM_POOL_SIZE` | -| Stream (TCP) | Posix / Winsock / PlusTcp / LwipRaw | `{…}TcpStream*` | resp. stacks (lwIP also needs a Sleep cb) | `TCP_STREAM_POOL_SIZE` | -| Address | Posix / Winsock / PlusTcp / LwipRaw | `{…}Address*` | resp. `sockaddr` | `ADDRESS_POOL_SIZE` | -| Marshal (lwIP) | LwipRawMarshal | `LwipRawMarshal` | lwIP, for `NO_SYS=0` | — | - -### Transport security (TLS) and at-rest integrity - -| Role | Provider | Files (`Solid…`) | Upstream / config | Pool tunable | -|---|---|---|---|---| -| TLS Stream | OpenSSL | `TlsStream*` | OpenSSL ≥ 3.0 | `TLS_STREAM_POOL_SIZE` | -| | Mbed TLS | `MbedTlsStream*` | `mbedtls_config.h` | ″ | -| SecurityPolicy | CRC-16 | `Crc16Policy*` + `Crc16` | — | — | -| | HMAC-SHA256 | `{OpenSsl,MbedTls}HmacSha256Policy*` | OpenSSL or Mbed TLS | `HMAC_SHA256_POLICY_POOL_SIZE` | -| | AES-GCM | `{OpenSsl,MbedTls}AesGcmPolicy*` | OpenSSL or Mbed TLS | `AES_GCM_POLICY_POOL_SIZE` | - -### OS primitives, storage, structured data - -| Role | Provider | Files (`Solid…`) | Upstream / config | Pool tunable | -|---|---|---|---|---| -| Mutex | Posix / Windows / FreeRtos | `{…}Mutex*` | FreeRtos needs `configSUPPORT_STATIC_ALLOCATION=1` | `MUTEX_POOL_SIZE` | -| AtomicCounter | C11 std / Windows | `StdAtomicCounter*` / `WindowsAtomicCounter*` | C11 `` / Win32 | `ATOMIC_COUNTER_POOL_SIZE` | -| Buffer | Passthrough / Circular / Posix mq | `PassthroughBuffer*` / `CircularBuffer*` / `PosixMessageQueueBuffer*` | — / — / POSIX | resp. pool sizes | -| Store | BlockStore | `BlockStore*`, `RecordStore*`, `BlockSequence*` | — | `BLOCK_STORE_POOL_SIZE` | -| BlockDevice | FileBlockDevice | `FileBlockDevice*` | — | `FILE_BLOCK_DEVICE_POOL_SIZE` | -| File | Posix / Windows / FatFs / PlusFat | `{Posix,Windows}File*` / `FatFsFile*` / `PlusFatFile*` | FatFs: `ffconf.h` + `diskio.c`; PlusFat: `FreeRTOSFATConfig.h` + `FF_Disk_t` | `FILE_POOL_SIZE` | -| Structured Data | Meta / TimeQuality / Origin | `{Meta,TimeQuality,Origin}Sd*` | — | resp. pool sizes | - -### Bring-your-own callbacks - -A small tier is just function pointers. Host platforms ship a provider; embedded -targets supply a one-line callback. (Provider authoring for these is tracked in a -later epic, but the seams exist today.) - -| Role | Host provider | Embedded | -|---|---|---| -| Clock | `{Posix,Windows}Clock` | BYO `SolidSyslogClockFunction` | -| SysUpTime | `{Posix,Windows,FreeRtos}SysUpTime` | FreeRtos provided; else BYO | -| Sleep | `{Posix,Windows}Sleep` | BYO (needed by TLS + lwIP) — e.g. a `vTaskDelay` wrapper | -| Hostname / ProcessId | `{Posix,Windows}…` | BYO header-field callbacks | -| AtomicCounter | C11 / Win32 (above) | BYO, else sequence-id degrades to Null (always 1) | +## Pick your stack + +Choose one provider per role you need; leave the rest out and Core's Null +object stands in. Which platform fills which role is the +[platform × capability matrix](platforms/index.md), and each platform's own +page states what its adapters need from your build. + +Two things are build-specific rather than platform-specific, and belong here. + +**The file list is generated, not written.** Every platform publishes a +manifest of the exact `.c` files to compile and the include directories they +need — see [Worked manifest](#worked-manifest--the-beta-stack) below. CI +regenerates these and fails on any difference, so they cannot drift from the +tree the way a hand-maintained table would. + +**Some roles are callbacks rather than components.** The clock, the host name, +the process id and a bounded sleep are function pointers on +`SolidSyslogConfig`. Hosted platforms ship one of each ready to use; on a bare +target you write them, and they are usually a line apiece. A sleep is required +by the TLS adapters and by the lwIP TCP stream, which is the one that catches +people, because it has no default. + +Leaving the atomic counter unfilled is the exception worth calling out: it does +not fail, it degrades to a counter that returns the same value forever, and the +sequence numbers a collector uses to detect loss stop meaning anything. --- @@ -452,12 +417,13 @@ you. - Sleep: required by Mbed TLS (handshake retry) and the lwIP TCP stream (bounded synchronous open). Wrap `vTaskDelay`. -- Clock, Hostname, ProcessId: small callbacks (see the matrix). +- Clock, Hostname, ProcessId: small callbacks you supply. - AtomicCounter: only if you want RFC 5424 sequence-ids; otherwise it degrades to the Null counter (always 1). -For the exact wiring of each adapter's `_Create` config struct, follow the -platform guides: [lwIP](platforms/lwipraw/setup.md), [Mbed TLS](platforms/mbedtls/setup.md). +For the exact wiring of each adapter's `_Create` config struct, follow +that platform's own setup guide, reached from its page in +[Platforms](platforms/index.md). --- @@ -512,9 +478,7 @@ SOLIDSYSLOG_USER_TUNABLES := -DSOLIDSYSLOG_USER_TUNABLES_FILE=\"$(CURDIR)/$(APP_ ## Where to go next - [Building up the protection you need](hardening-path.md): what to wire and why, stage by stage -- [Integrating with lwIP (Raw API)](platforms/lwipraw/setup.md) -- [Integrating with Mbed TLS](platforms/mbedtls/setup.md) -- [Integrating with FreeRTOS-Plus-FAT](platforms/plusfat/setup.md) +- [Platforms](platforms/index.md): which platform fills which role, what each needs from your build, and how to wire it - [Porting to a new platform](porting.md): writing an adapter for an OS, network stack, filesystem, or crypto library we don't ship - [Structured data](structured-data.md) - [Error handling and severity](error-severity.md) diff --git a/docs/iec62443.md b/docs/iec62443.md index 912e5f50..2d126818 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -39,16 +39,16 @@ what the library does not do. | Control | What SolidSyslog provides | Gaps | |---|---|---| -| **CR 1.5** — Authenticator management | `SolidSyslogTlsStream` mTLS — caller supplies `clientCertChainPath` / `clientKeyPath`; `SSL_CTX_check_private_key` validates the key/cert pair before any bytes hit the wire. Rotation by file replacement plus reconnect (natural reconnect or explicit `SolidSyslogSender_Disconnect`) | Library ships no default authenticators; at-rest key protection (filesystem permissions, HSM) is the integrator's responsibility | -| **CR 1.8** — Public key infrastructure certificates | `SolidSyslogTlsStream` — `caBundlePath` loads trust anchors via `SSL_CTX_load_verify_locations`; `SSL_VERIFY_PEER` pinned on the CTX; hostname verification via `SSL_set1_host`. CTX rebuilt on every `Open` so file replacement + reconnect refreshes trust anchors | Revocation (CRL / OCSP) is not performed by the library; whether it is enforced depends on the TLS backend and platform you configure, and verifying that is the integrator's responsibility. Enrolment is the caller's PKI process | +| **CR 1.5** — Authenticator management | The Stream role carries transport security, and a TLS backend filling it can present a client credential so the collector authenticates the device. The credential is supplied by the integrator: the library holds no keys of its own and reads whatever material it is given. Refreshing it is a deployment operation — how, and whether a reconnection is needed, is a property of the backend | The library ships no default authenticators, and protection of the key at rest — file permissions, a secure element, a hardware security module — is outside it. Whether a partially supplied credential is refused or quietly ignored differs between backends; the [platform pages](platforms/index.md) state which | +| **CR 1.8** — Public key infrastructure certificates | A TLS backend filling the Stream role verifies the collector's certificate against trust anchors you supply, and checks the collector's identity against a name you declare. Both are integrator inputs; neither has a default | Revocation is not performed by any shipped backend, by certificate revocation list or by online status protocol. Where a deployment requires it, it must come from your own configuration of the underlying library, and confirming it is in force is yours. Enrolment is your public-key infrastructure's process | | **CR 2.8** — Auditable events | `SolidSyslog_Log` formats events per RFC 5424. Structured data attached via `SolidSyslogMetaSd` / `SolidSyslogTimeQualitySd` / `SolidSyslogOriginSd`, or caller-supplied SD | Which internal activity is security-relevant is your decision: the library carries whatever your application raises and has no view of what it omitted. The categories the control expects to see audited follow from your own risk assessment | -| **CR 2.9** — Audit storage capacity | `SolidSyslogBlockStore` — rotating blocks, configurable `max-blocks` and `max-block-size`, configurable discard policy (`oldest` / `newest` / `halt`). Backed by `SolidSyslogFileBlockDevice` over `SolidSyslogPosixFile` (POSIX), `SolidSyslogWindowsFile` (Windows), `SolidSyslogFatFsFile` (ChaN FatFs) or `SolidSyslogPlusFatFile` (FreeRTOS-Plus-FAT), or an integrator-supplied `SolidSyslogBlockDevice` over flash. The control's own requirement enhancement calls for a warning when the storage threshold is reached: `SolidSyslogStoreThresholdFunction` + `SolidSyslogStoreThresholdCallback` provide it — edge-triggered, fires once when used-bytes crosses the threshold, re-arms when usage falls back below | Capacity has to be sized to the deployment's outage budget, which is yours to know; the library enforces the number you give it. Durability of the medium — flash wear, filesystem behaviour on power loss — belongs to the block device and the platform beneath it | +| **CR 2.9** — Audit storage capacity | `SolidSyslogBlockStore` — rotating blocks, configurable `max-blocks` and `max-block-size`, configurable discard policy (`oldest` / `newest` / `halt`). It sits over `SolidSyslogFileBlockDevice`, which sits over the File role a platform fills, or over a BlockDevice you write against raw flash. The control's own requirement enhancement calls for a warning when the storage threshold is reached: `SolidSyslogStoreThresholdFunction` + `SolidSyslogStoreThresholdCallback` provide it — edge-triggered, fires once when used-bytes crosses the threshold, re-arms when usage falls back below | Capacity has to be sized to the deployment's outage budget, which is yours to know; the library enforces the number you give it. Durability of the medium — flash wear, filesystem behaviour on power loss — belongs to the platform beneath the File role, and no shipped filesystem backend is journalling | | **CR 2.10** — Response to audit processing failures | `SolidSyslogStoreFullCallback` (halt policy) and the discard-policy enum. Caller picks the policy that fits the deployment's audit-loss tolerance. The early-warning threshold callback (CR 2.9) fires before discard / halt engages, giving the application time to act (notify operator, reduce verbosity, tighten retention); at 100% with HALT both fire on the same Write with threshold first then `onStoreFull`. Failures elsewhere in the path surface through the error handler — see [error severity](error-severity.md) | The response itself is your application's. The library reports the failure and applies the policy you configured; deciding what an operator is told, and whether the device keeps running, is above it | -| **CR 2.11** — Timestamps | Caller-injected `SolidSyslogClockFunction` — `SolidSyslogPosixClock_GetTimestamp` (POSIX) or `SolidSyslogWindowsClock_GetTimestamp` (Windows). Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | -| **CR 2.12** — Non-repudiation | At the wire: `SolidSyslogTlsStream` mutual TLS cryptographically identifies the TLS peer to the receiver. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: cryptographic integrity via `SolidSyslogOpenSslHmacSha256Policy` / `SolidSyslogMbedTlsHmacSha256Policy`, or authenticated encryption via `SolidSyslogOpenSslAesGcmPolicy` / `SolidSyslogMbedTlsAesGcmPolicy`; `SolidSyslogCrc16Policy` remains for accidental-corruption detection | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the receiver authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Cryptographic at-rest policies are keyed — key custody, rotation, and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | -| **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed at-rest policy (`SolidSyslogOpenSslHmacSha256Policy` / `SolidSyslogMbedTlsHmacSha256Policy`) makes an unauthorised edit detectable. `SolidSyslogCrc16Policy` does not serve this control: it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit with `SolidSyslogTlsStream` and at rest with `SolidSyslogOpenSslAesGcmPolicy` / `SolidSyslogMbedTlsAesGcmPolicy`. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport `Send` synchronously on the caller's thread. The TCP socket is non-blocking from the moment it is opened, so `Send` returns immediately on a wedged peer or a full kernel buffer; the bounded blocking surface is the initial `connect()`, sized by `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms) or runtime-overridable per Stream via `GetConnectTimeoutMs(ConnectTimeoutContext)`. On the TLS path the same applies to the handshake, via `SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS` (default 5000 ms) or `GetHandshakeTimeoutMs(HandshakeTimeoutContext)` for both OpenSSL and Mbed TLS adapters. Long-term silent peer death is detected out-of-band via kernel `TCP_KEEPALIVE` + `TCP_USER_TIMEOUT`. Suitable when the application has no real-time deadline or the transport latency is known to be sub-millisecond. **Buffered (`SolidSyslogCircularBuffer` with an injected `SolidSyslogMutex` — Posix / Windows / FreeRTOS / Null / caller-supplied RTOS primitive — or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` is non-blocking: it formats and enqueues, returning before any I/O. `SolidSyslog_Service`, called on the integrator's chosen thread (typically a dedicated service thread), performs the transport I/O against the same non-blocking socket; the bounded surface is the same connect / handshake budget as the single-task wiring. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | -| **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment | +| **CR 2.11** — Timestamps | The clock is an injected `SolidSyslogClockFunction` rather than a component, so the time source is yours to choose; platforms that have one ship an implementation. Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | +| **CR 2.12** — Non-repudiation | At the wire: mutual TLS through the Stream role cryptographically identifies the TLS peer to the collector. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: the SecurityPolicy role, filled by a keyed policy, gives cryptographic integrity or authenticated encryption; Core's unkeyed CRC-16 policy detects accidental corruption only | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the collector authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Keyed policies are only as good as their key — custody, rotation and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | +| **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed SecurityPolicy makes an unauthorised edit detectable. Core's CRC-16 policy does not serve this control — it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit through a TLS backend and at rest through an authenticated-encryption policy. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send synchronously on the caller's thread. Shipped TCP backends make the socket non-blocking once open, so a send returns immediately against a wedged peer; the bounded surface is the initial connection, and the TLS handshake on a secured path. Both budgets are tunable, and overridable per instance at run time. Suitable where the application has no real-time deadline. **Buffered (`SolidSyslogCircularBuffer` with a Mutex, or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. The bounded surface is the same connection budget. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Detection of long-term silent peer death depends on keep-alive behaviour in the platform beneath the Stream, which not every stack provides. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. The sequence depends on the AtomicCounter role being filled; unfilled, it degrades to a constant and the signal silently stops meaning anything | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the device to the collector. Both controls have requirement enhancements calling for @@ -60,94 +60,53 @@ The library's error guards bear on **CR 3.7** — Error handling: every failure reports through the injected error handler rather than failing silently, and the severity convention is documented in [error severity](error-severity.md). -### The OpenSSL TLS substrate - -`SolidSyslogTlsStream` (the OpenSSL-backed client-side TLS substrate) ships -with the client-side half of the transport-security controls: - -- Hostname verification. `SolidSyslogTlsStreamConfig.serverName` is - forwarded to both SNI (`SSL_set_tlsext_host_name`) and the cert check - (`SSL_set1_host`). Both return values are checked; handshake is aborted on - any setup failure, preventing a silent "handshake succeeded without checking - the name" bypass. -- Trust chain. `caBundlePath` loads trust anchors via - `SSL_CTX_load_verify_locations`; `SSL_VERIFY_PEER` is pinned on the CTX. -- TLS 1.2 floor. `SSL_CTX_set_min_proto_version(TLS1_2_VERSION)` is - return-checked; Open fails if libssl refuses the floor. -- Cipher pinning. `SolidSyslogTlsStreamConfig.cipherList` is forwarded to - `SSL_CTX_set_cipher_list`. The library ships no baked-in list: the - cipher policy lives with the caller, which understands its own security - profile. A reasonable starting point is - `"ECDHE+AESGCM:ECDHE+CHACHA20"` (TLS 1.2 AEAD with forward secrecy); tune to - match the libssl build on the target platform. -- Mutual TLS (peer authentication; a partial contribution to CR 2.12). Optional - `clientCertChainPath` / `clientKeyPath` load a client cert and private key - via `SSL_CTX_use_certificate_chain_file` + `SSL_CTX_use_PrivateKey_file`, - with `SSL_CTX_check_private_key` confirming the pairing locally before any - bytes hit the wire. Both fields are opt-in and all-or-nothing: supply both - to enable mTLS, leave both NULL for server-auth TLS. Supplying only one is - rejected at Open time, so this adapter cannot silently downgrade. The Mbed TLS - adapter behaves differently — see below. It authenticates the TLS peer and does not - by itself evidence that a given record originated there; the CR 2.12 row above sets - out what it does and does not establish. -- Certificate rotation (CR 1.5 authenticator refresh, CR 1.8 PKI update). - `SolidSyslogTlsStream` rebuilds the `SSL_CTX` on every `Open`, re-reading the - cert material from `caBundlePath` / `clientCertChainPath` / `clientKeyPath` - from disk each time. Rotation is therefore a deployment operation: replace - the files on disk, and the new material takes effect on the next reconnect, - via natural churn (retry, outage recovery) or by calling - `SolidSyslogSender_Disconnect` to force one. No library-level callback or - version-fingerprint API is needed to satisfy the capability requirement. -- Resource lifecycle. `Close`/`Destroy` release `SSL_CTX`, `SSL`, and - `BIO_METHOD` idempotently: no leaks on partial Open failure, no double - frees if both are called. - -### The Mbed TLS substrate for embedded targets - -`SolidSyslogMbedTlsStream` is the Mbed TLS-backed reference adapter for -embedded / FreeRTOS / bare-metal deployments where OpenSSL is too -heavy. It satisfies the same CR 1.5 / CR 1.8 / CR 2.12 / CR 3.9 controls -as `SolidSyslogTlsStream` and surfaces the same `SolidSyslogStream` -vtable, so the rest of the wiring (`SolidSyslogStreamSender`, buffer, -store) is unchanged. - -- Hostname verification. `SolidSyslogMbedTlsStreamConfig.ServerName` - is forwarded to both SNI (`mbedtls_ssl_set_hostname`) and the cert SAN - check (mbedTLS verifies the peer cert against the same string by - default with `MBEDTLS_SSL_VERIFY_REQUIRED`). -- Trust chain. `CaChain` is a caller-built `mbedtls_x509_crt*` - passed through dependency injection; there is no filesystem coupling - inside the adapter (`MBEDTLS_FS_IO` is typically off on embedded - targets). `mbedtls_ssl_conf_authmode` is pinned to - `MBEDTLS_SSL_VERIFY_REQUIRED`. -- TLS 1.2+ floor. Inherited from `mbedtls_ssl_config_defaults(... - PRESET_DEFAULT)`. TLS 1.3 negotiates automatically when both peers - support it. -- Mutual TLS (peer authentication; a partial contribution to CR 2.12). Optional `ClientCertChain` - / `ClientKey` handles. Both NULL = server-auth-only. Both non-NULL = - mTLS. Supplying only one configures no client certificate and `Open` - proceeds with server-authenticated TLS rather than failing — unlike the - OpenSSL adapter above, which rejects partial credentials at `Open`. If a - half-supplied credential must be an error on this adapter, the caller checks - before `Open`. The CR 2.12 caveat above applies here equally. -- Certificate rotation (CR 1.5 / CR 1.8). Because the adapter - consumes pre-built handles rather than file paths, rotation is "parse - a new `mbedtls_x509_crt`, destroy and recreate the adapter (or the - parent `SolidSyslogStreamSender` so the next Connect picks it up)." A - deployment running a vendor key-rotation service drives this via its - existing reload hook. -- Coexistence with an existing Mbed TLS integration. Auditable - contract: `Platform/MbedTls/Source/` never calls - `mbedtls_platform_setup` / `_teardown`, never installs threading-alt - hooks, never calls `psa_crypto_init` itself, never resets the global - RNG, never replaces the integrator's debug callback. Devices that - already wire Mbed TLS for another subsystem (cloud, OTA, vendor - framework) keep that wiring intact. -- Resource lifecycle. `Close` / `Destroy` invoke - `mbedtls_ssl_close_notify` / `_ssl_free` / `_ssl_config_free` - idempotently and never free integrator-owned handles. - -Integrator guide: [Mbed TLS setup](platforms/mbedtls/setup.md). +## What your platform must provide + +Core is portable C and fills no role that touches hardware. Every control above +that reaches the outside world does so through a role a platform fills, so the +capability the assessment can claim depends on what you build in. Each entry +below is the obligation, and what happens if it is not met. + +**A Stream, if records leave the device over TCP.** Without a TLS backend +filling it, records travel in clear and are readable and alterable in transit: +CR 1.5, CR 1.8 and the transit half of CR 4.1 have nothing to rest on, and +CR 2.12's wire-side identification does not exist. A plaintext stream is a +legitimate choice on a closed network; it is not a silent one, and the +assessment has to say so. + +**A SecurityPolicy, if the storage medium is exposed.** Unfilled, records are +stored as written and an edit is undetectable. Core's unkeyed CRC-16 detects +accidental corruption but not deliberate modification, so it does not carry +CR 3.9. A keyed policy does, and its key becomes an asset your deployment must +protect — which is a cost, not a free upgrade. + +**A File or BlockDevice, if the device must survive an outage.** Without one +there is no store, so anything raised while the collector is unreachable is +lost rather than deferred, and CR 2.9 and CR 6.2's replay have nothing behind +them. The durability the store can promise is bounded by what the medium +beneath it actually guarantees on power loss. + +**A clock callback, always.** The library formats whatever time it is given and +cannot tell a wrong clock from a right one. A target with no synchronised time +source can still comply, by declaring the quality honestly through +`SolidSyslogTimeQualitySd` so a collector knows how far to trust the timestamp +— but a device that reports itself synchronised while drifting produces an +audit trail whose ordering cannot be defended. + +**An AtomicCounter, if loss is to be detectable.** This one fails quietly. +Unfilled, or with its pool exhausted, the role degrades to a counter that +returns the same value for every record. Nothing errors, the sequence numbers +are still emitted, and collector-side gap detection reports no gaps — while +proving nothing at all. CR 6.2 rests on this, so it is worth an explicit check +rather than an assumption. + +**A Mutex, if `Log` and `Service` run on different tasks.** Unfilled, the Null +mutex is a working no-op on a single-task target and an unsynchronised buffer +on a multi-task one. Nothing detects the difference at run time, and the +failure it produces is corruption of the audit trail itself. + +Which platform fills which role, and what each needs from your build, is the +[platform × capability matrix](platforms/index.md). ## Architecture for Security @@ -188,34 +147,7 @@ The development-process evidence IEC 62443-4-1 asks for is published separately: for coordinated disclosure, and the test and analysis gates described in [CI](ci.md). -## Deployment Considerations - -### Minimal footprint (bare-metal / RTOS) - -For resource-constrained targets, a working audit trail needs no more than: - -- No heap allocation -- No POSIX dependency (provide your own clock, hostname, and transport) -- Single-task operation with `SolidSyslogPassthroughBuffer`, or multi-task on - bare-metal / RTOS with `SolidSyslogCircularBuffer` driven by a - `SolidSyslogMutex` you supply (the library ships `SolidSyslogPosixMutex`, - `SolidSyslogWindowsMutex`, `SolidSyslogFreeRtosMutex`, and `SolidSyslogNullMutex`) -- Flash-based store-and-forward via an integrator-supplied - `SolidSyslogBlockDevice` driving `SolidSyslogBlockStore`. - The library ships `SolidSyslogFileBlockDevice` as a file-backed reference; - flash hardware drivers are out-of-scope and supplied by the platform. - -### RTOS integration - -`SolidSyslogCircularBuffer` is the portable choice for the producer / consumer -seam between the application thread (calling `Log()`) and the service thread -(calling `Service()`): caller-allocated ring memory, mutex injected via the -`SolidSyslogMutex` vtable, no POSIX dependency. Wrap your RTOS mutex primitive -in the vtable and you're done. `SolidSyslogPosixMessageQueueBuffer` remains -available where a POSIX `mqueue` is preferred for kernel-level back-pressure -semantics. - -### SIEM integration +## SIEM integration All output is RFC 5424 compliant structured syslog. Every release is verified in CI against syslog-ng and against an OpenTelemetry Collector, end to end through the BDD From 189fcc9c0ce704309f1336bd3767338450401672 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 22:39:31 +0100 Subject: [PATCH 30/57] docs: S23.22 stop the compliance guide behaving like an audit artefact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IEC 62443 page is a quick reference that reassures a developer or a security officer that the library can meet their needs. The "what your platform must provide" section written into it was the opposite of that: an exhaustive obligations list, cataloguing failure modes for six roles, which reads as an audit workbook and belongs nowhere near it. Removed. It also corrects an error I introduced and then repeated across four pages. I wrote that an unfilled or exhausted AtomicCounter fails silently. It does not. Create reports pool exhaustion through the error handler with its own category and error code, and the Null counter returning 1 is a safe fallback — delivery is unaffected and only sequence-based loss detection stops being useful. The same overstatement had been applied to the Mutex, where no mutex is the correct answer when Log and Service share a task rather than a hazard to warn about. Both are role detail besides, and role detail belongs where a reader meets it — the counter when they attach structured data, the mutex when they wire a circular buffer — not gathered onto a compliance page or a build page. Part of #708 --- docs/build-integration.md | 7 +---- docs/iec62443.md | 52 ++------------------------------ docs/platforms/atomics/index.md | 9 ------ docs/platforms/atomics/setup.md | 9 +++--- docs/platforms/freertos/index.md | 10 +++--- docs/platforms/freertos/setup.md | 4 +-- 6 files changed, 14 insertions(+), 77 deletions(-) diff --git a/docs/build-integration.md b/docs/build-integration.md index 26f5ff76..12f0d3e9 100644 --- a/docs/build-integration.md +++ b/docs/build-integration.md @@ -66,10 +66,6 @@ target you write them, and they are usually a line apiece. A sleep is required by the TLS adapters and by the lwIP TCP stream, which is the one that catches people, because it has no default. -Leaving the atomic counter unfilled is the exception worth calling out: it does -not fail, it degrades to a counter that returns the same value forever, and the -sequence numbers a collector uses to detect loss stop meaning anything. - --- ## CMake @@ -418,8 +414,7 @@ you. - Sleep: required by Mbed TLS (handshake retry) and the lwIP TCP stream (bounded synchronous open). Wrap `vTaskDelay`. - Clock, Hostname, ProcessId: small callbacks you supply. -- AtomicCounter: only if you want RFC 5424 sequence-ids; otherwise it - degrades to the Null counter (always 1). +- AtomicCounter: only if you want RFC 5424 sequence-ids. For the exact wiring of each adapter's `_Create` config struct, follow that platform's own setup guide, reached from its page in diff --git a/docs/iec62443.md b/docs/iec62443.md index 2d126818..de44d46f 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -47,8 +47,8 @@ what the library does not do. | **CR 2.11** — Timestamps | The clock is an injected `SolidSyslogClockFunction` rather than a component, so the time source is yours to choose; platforms that have one ship an implementation. Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | | **CR 2.12** — Non-repudiation | At the wire: mutual TLS through the Stream role cryptographically identifies the TLS peer to the collector. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: the SecurityPolicy role, filled by a keyed policy, gives cryptographic integrity or authenticated encryption; Core's unkeyed CRC-16 policy detects accidental corruption only | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the collector authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Keyed policies are only as good as their key — custody, rotation and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | | **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed SecurityPolicy makes an unauthorised edit detectable. Core's CRC-16 policy does not serve this control — it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit through a TLS backend and at rest through an authenticated-encryption policy. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send synchronously on the caller's thread. Shipped TCP backends make the socket non-blocking once open, so a send returns immediately against a wedged peer; the bounded surface is the initial connection, and the TLS handshake on a secured path. Both budgets are tunable, and overridable per instance at run time. Suitable where the application has no real-time deadline. **Buffered (`SolidSyslogCircularBuffer` with a Mutex, or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. The bounded surface is the same connection budget. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Detection of long-term silent peer death depends on keep-alive behaviour in the platform beneath the Stream, which not every stack provides. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | -| **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. The sequence depends on the AtomicCounter role being filled; unfilled, it degrades to a constant and the signal silently stops meaning anything | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send synchronously on the caller's thread. Shipped TCP backends make the socket non-blocking once open, so a send returns immediately against a wedged peer; the bounded surface is the initial connection, and the TLS handshake on a secured path. Both budgets are tunable, and overridable per instance at run time. Suitable where the application has no real-time deadline. **Buffered (`SolidSyslogCircularBuffer` with a Mutex, or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. The bounded surface is the same connection budget. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the device to the collector. Both controls have requirement enhancements calling for @@ -60,54 +60,6 @@ The library's error guards bear on **CR 3.7** — Error handling: every failure reports through the injected error handler rather than failing silently, and the severity convention is documented in [error severity](error-severity.md). -## What your platform must provide - -Core is portable C and fills no role that touches hardware. Every control above -that reaches the outside world does so through a role a platform fills, so the -capability the assessment can claim depends on what you build in. Each entry -below is the obligation, and what happens if it is not met. - -**A Stream, if records leave the device over TCP.** Without a TLS backend -filling it, records travel in clear and are readable and alterable in transit: -CR 1.5, CR 1.8 and the transit half of CR 4.1 have nothing to rest on, and -CR 2.12's wire-side identification does not exist. A plaintext stream is a -legitimate choice on a closed network; it is not a silent one, and the -assessment has to say so. - -**A SecurityPolicy, if the storage medium is exposed.** Unfilled, records are -stored as written and an edit is undetectable. Core's unkeyed CRC-16 detects -accidental corruption but not deliberate modification, so it does not carry -CR 3.9. A keyed policy does, and its key becomes an asset your deployment must -protect — which is a cost, not a free upgrade. - -**A File or BlockDevice, if the device must survive an outage.** Without one -there is no store, so anything raised while the collector is unreachable is -lost rather than deferred, and CR 2.9 and CR 6.2's replay have nothing behind -them. The durability the store can promise is bounded by what the medium -beneath it actually guarantees on power loss. - -**A clock callback, always.** The library formats whatever time it is given and -cannot tell a wrong clock from a right one. A target with no synchronised time -source can still comply, by declaring the quality honestly through -`SolidSyslogTimeQualitySd` so a collector knows how far to trust the timestamp -— but a device that reports itself synchronised while drifting produces an -audit trail whose ordering cannot be defended. - -**An AtomicCounter, if loss is to be detectable.** This one fails quietly. -Unfilled, or with its pool exhausted, the role degrades to a counter that -returns the same value for every record. Nothing errors, the sequence numbers -are still emitted, and collector-side gap detection reports no gaps — while -proving nothing at all. CR 6.2 rests on this, so it is worth an explicit check -rather than an assumption. - -**A Mutex, if `Log` and `Service` run on different tasks.** Unfilled, the Null -mutex is a working no-op on a single-task target and an unsynchronised buffer -on a multi-task one. Nothing detects the difference at run time, and the -failure it produces is corruption of the audit trail itself. - -Which platform fills which role, and what each needs from your build, is the -[platform × capability matrix](platforms/index.md). - ## Architecture for Security These are properties of the library itself. They describe how it is built, not what diff --git a/docs/platforms/atomics/index.md b/docs/platforms/atomics/index.md index 78b05a09..0373460a 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/atomics/index.md @@ -19,15 +19,6 @@ the AtomicCounter role. ## Security behaviour and obligations -### The sequence is what makes loss detectable - -`sequenceId` is assigned when a record is raised, so a gap seen by the collector -reflects loss anywhere in the pipeline. That signal is only as good as the -counter behind it: if the pool is exhausted, the role falls back to the Null -counter, whose increment returns 1 every time. Gap detection then reports -nothing wrong while delivering nothing useful. Size the pool for the instances -you create, and install an error handler so exhaustion is seen. - ### The sequence wraps, and a collector must expect it Values run in `[1, 2^31 - 1]` and skip zero on wrap. A long-lived device will diff --git a/docs/platforms/atomics/setup.md b/docs/platforms/atomics/setup.md index 2a02e54a..7c0194e8 100644 --- a/docs/platforms/atomics/setup.md +++ b/docs/platforms/atomics/setup.md @@ -32,10 +32,11 @@ It is assigned when a record is raised rather than when it is sent, so a gap reflects loss anywhere in the pipeline — the buffer, the store, or the transport — not only on the wire. -Leave the role unfilled and the Null counter stands in, returning the same -value every time. Nothing then reports a problem, and gap detection quietly -proves nothing. Install an error handler so that pool exhaustion is visible -rather than silent. +Leave the role unfilled, or exhaust its pool, and the Null counter stands in +and returns 1 for every record. That is a safe fallback: logging continues +unaffected, and only the sequence-based loss detection stops being useful. +Pool exhaustion is reported through the error handler when the counter is +created, so it is visible rather than something to discover later. ## When it does not work diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md index 60dd779a..78eb240f 100644 --- a/docs/platforms/freertos/index.md +++ b/docs/platforms/freertos/index.md @@ -20,13 +20,11 @@ Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. ## Security behaviour and obligations -### The mutex is what makes a shared buffer safe +### The mutex guards a buffer shared between tasks -Where the application task calling `Log` and the task calling `Service` are -different, the buffer between them needs this mutex. Leaving the role unfilled -resolves to the Null mutex, which is a working no-op on a single-task target and -a silent data race on a multi-task one. Nothing reports the difference, because -nothing can detect it. +The circular buffer uses it when the task calling `Log` is not the task calling +`Service`. Where both run on one task, the Null mutex is the correct choice and +costs nothing. ### Static allocation is required, and is the point diff --git a/docs/platforms/freertos/setup.md b/docs/platforms/freertos/setup.md index c1c6598d..1372724f 100644 --- a/docs/platforms/freertos/setup.md +++ b/docs/platforms/freertos/setup.md @@ -39,8 +39,8 @@ struct SolidSyslogBuffer* buffer = The ring memory and the mutex must both outlive the buffer. -If both calls happen on one task, you do not need a mutex and can leave the -role unfilled. Be certain of that: nothing detects the difference at run time. +If both calls happen on one task, leave the role unfilled — the Null mutex is +the right answer and costs nothing. ## Uptime From 569fbabba7d4f24a94ba88da0da269bd80be6f9f Mon Sep 17 00:00:00 2001 From: David Cozens Date: Fri, 7 Aug 2026 22:46:23 +0100 Subject: [PATCH 31/57] docs: S23.22 make the platform documentation convention enforceable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/check_platform_docs.py reads SOLIDSYSLOG_PLATFORM_REGISTRY — the one place a platform is declared — and asserts that each row has both its pages, its group file and @defgroup, an @ingroup on every public header, a nav entry, a meta description for each page, and a row in the capability matrix. It checks the reverse too: a docs folder with no registry row behind it. Registering a platform is then the only edit that can be forgotten, because forgetting any of the others fails the build. Verified both ways — it passes on the tree as it stands, and reports every omission when pages and a folder are removed. CLAUDE.md gains the rule this branch kept breaking. Verify before asserting, absolutely so for failure modes, because a claim that an error is not reported is the one most often wrong and the most damaging — it pushes an integrator into defending against a problem that does not exist. And one claim, one place, with a table saying where each kind of fact lives, because writing the same sentence twice is how a set of documents drifts out of agreement with itself. The mock page goes; the decisions it was built to settle are implemented. Part of #708 --- .github/workflows/ci.yml | 7 ++ CLAUDE.md | 52 ++++++++++ docs/_mock/template-options.md | 173 --------------------------------- hooks/page_descriptions.py | 5 - scripts/check_platform_docs.py | 107 ++++++++++++++++++++ 5 files changed, 166 insertions(+), 178 deletions(-) delete mode 100644 docs/_mock/template-options.md create mode 100644 scripts/check_platform_docs.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cabb7d7..636183b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -772,6 +772,13 @@ jobs: with: persist-credentials: false + # A platform is declared once, in SOLIDSYSLOG_PLATFORM_REGISTRY. This + # asserts that its pages, group, nav entry, description and matrix row + # all exist, and that nothing documents a platform that is not + # registered — so registering one is the only edit that can be forgotten. + - name: Check every platform is documented + run: python3 scripts/check_platform_docs.py + # Guard the build hooks against regression — the source-link rewrite's # Markdown parsing, and the meta-description map's nav validation. # Image digest below is mkdocs-mkdoxy sha-34173c0 (see docs/containers.md). diff --git a/CLAUDE.md b/CLAUDE.md index e84d89ce..a6438ed1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -513,6 +513,58 @@ See `docs/misra-deviations.md` for the project's stance on MISRA conformance. --- +## Documentation + +Two rules, and they matter more than anything about wording. A wrong claim +costs one edit to fix; a wrong claim that has been copied costs an audit of +every page to find, and the copies rot silently because nothing checks them. + +### Verify before asserting + +Every statement about what the code does is read out of the code, not inferred +from a name, a neighbouring document, or something written earlier in the same +session. This is absolute for **failure modes**: before writing that something +fails, degrades, is silent, or is not reported, open the function and confirm +it. Claims that an error is *not* reported are the ones most often wrong, and +the most damaging, because they push an integrator into defending against a +problem that does not exist. + +A document that is already in the repository is not evidence. It may be the +thing that is wrong. + +### One claim, one place + +Every fact has exactly one home. Everywhere else links to it, or omits it. + +| The fact | Its home | +|---|---| +| What a config field or parameter means, including its edge values | the doc comment on that field | +| What a role's contract requires | that role's `SolidSyslogDefinition.h` | +| What a platform ships, needs, guarantees, and leaves to the integrator | that platform's page under `docs/platforms//` | +| How to wire a platform, and what will catch you out | that platform's `setup.md` | +| What Core does | the Core documentation and the Core headers | +| How to get it building | `docs/build-integration.md` | + +Writing the same sentence on a second page is the signal that it belongs on +neither — find its home, put it there once, and link. Do not restate a fact to +make a page self-contained: self-contained pages are how a set of documents +drifts out of agreement with itself. + +This applies with particular force to the compliance guides, which attract +detail they should not hold. `docs/iec62443.md` is a quick reference that +reassures a developer or a security officer that the library can meet their +needs. It is **not** an audit artefact, and it is not a place to gather role +behaviour, platform behaviour, or catalogues of failure modes. + +### A platform page never describes another platform + +Naming a second platform to contrast behaviour, or to say where a capability +comes from, couples the two: the eleventh platform then has to be added to ten +pages. State this platform's own behaviour completely, and point at the +capability matrix in `docs/platforms/index.md` for who fills what. + +--- + ## Design Patterns These patterns are re-affirmed each time we do a code-hygiene pass. New diff --git a/docs/_mock/template-options.md b/docs/_mock/template-options.md deleted file mode 100644 index 1250fd34..00000000 --- a/docs/_mock/template-options.md +++ /dev/null @@ -1,173 +0,0 @@ -# Template options — scratch - -Not part of the documentation. A scratch page for choosing between layout -options in the real theme, deleted before this branch is finished. - - - - - - - -## Part 1 — getting to API reference and Setup from a platform page - -Both are currently short sections at the bottom. Four ways to move them to the -top. Each sample shows the real first screen of the Mbed TLS page. - -### A — quiet link row beneath the title - -
-

Mbed TLS

-

API reference·Setup

-

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

-

## What it ships …

-
- -Lightest touch. Reads as metadata about the page rather than as content, which -is what it is. Risk: quiet enough to be missed. - -### B — Material buttons - -
-

Mbed TLS

-

API reference Setup

-

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

-

## What it ships …

-
- -Impossible to miss, and native to the theme — no new CSS. But it pushes the -first paragraph down a long way, and two buttons on ten pages is a lot of -furniture for what is really navigation. - -### C — title suffix, your idea 2 - -
-

Mbed TLS API · Setup

-

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

-

## What it ships …

-
- -Costs no vertical space at all. Two problems: it lands in the table of contents -and the browser tab title unless worked around, and "API" alone is terse to the -point of cryptic for a first-time reader. - -### D — chips with glyphs - -
-

Mbed TLS

-

API referenceSetup

-

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

-

## What it ships …

-
- -Between A and B in weight, and the component glyph is the same one Part 2 uses, -so the two places agree visually. New CSS to own, and the wrench is decorative -rather than meaningful. - ---- - -## Part 2 — the platform banner on a generated API page - -Same four, against the current one. These sit at the very top of -`SolidSyslogMbedTlsStream_8h`, above the relationship diagram. - -### Current — full-width admonition - -
-

Part of the Mbed TLS platform

-

What the pack ships, what your build must provide, and the obligations it leaves to you.

-

SolidSyslogMbedTlsStream.h — File Reference …

-
- -### B — one quiet line, left justified - -
-

Part of the Mbed TLS platform

-

SolidSyslogMbedTlsStream.h — File Reference …

-
- -### C — post-it chip, in the kit's adapter blue - -
- Mbed TLS platform -

SolidSyslogMbedTlsStream.h — File Reference …

-
- -Blue is already the diagram kit's colour for "a backend that realises a role", -which is exactly what a platform adapter is — so this borrows a vocabulary the -site has rather than inventing one. - -### D — eyebrow label - -
-

Platform Mbed TLS

-

SolidSyslogMbedTlsStream.h — File Reference …

-
- -Smallest of the four. Reads as a category label, which is accurate. - - - ---- - -## Part 3 — two wording decisions, no mock needed - -**"Pack" goes.** I introduced it to avoid repeating "platform" and it reads as -jargon for no gain. The page is the platform, so: *"Every class in this -platform"*, or name it — *"Every class in Mbed TLS"*. The second is better -still, because it works when read out of context. - -**When is a class name a link?** Right now it is a link in the *What it ships* -table and plain code in prose, which is the inconsistency you spotted. The rule -I would propose: the table is the canonical linked index of what the platform -ships, and every mention in prose is plain code. One place to click, no -judgement call per sentence, and no page full of blue. - -The alternative — link the first mention anywhere — needs a decision on every -paragraph and drifts the moment anyone edits. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index 101558be..b1468e41 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -191,11 +191,6 @@ "The twelve vtable contracts SolidSyslog composes against, what fills " "each one, and the Null fallback that keeps an unfilled role safe." ), - # Scratch — deleted before this branch is finished. - "_mock/template-options.md": ( - "Scratch page for choosing platform-page layout options in the real " - "theme. Not part of the documentation." - ), "assets/postit/README.md": ( "The post-it diagram kit behind the SolidSyslog architecture pictures: " "what each colour and arrow means, and how the diagrams are generated." diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py new file mode 100644 index 00000000..7147ab38 --- /dev/null +++ b/scripts/check_platform_docs.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Assert every registered platform is documented, and nothing else claims to be. + +A platform is declared once, in SOLIDSYSLOG_PLATFORM_REGISTRY in the top-level +CMakeLists.txt. Everything else follows from its token: + + token LwipRaw + directory Platform/LwipRaw/ + docs docs/platforms/lwipraw/{index,setup}.md + group @defgroup platform_lwipraw + nav an entry in mkdocs.yml + description an entry in hooks/page_descriptions.py + +This checks that each of those exists for each row, and the reverse — a docs +folder or a group with no row behind it. Registering a platform is then the one +edit that cannot be forgotten, because forgetting anything else fails the build. + +Run: python3 scripts/check_platform_docs.py +""" + +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) +ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') + + +def read(*parts): + with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle: + return handle.read() + + +def registered(): + """[(token, directory)] from the registry, which is the single declaration.""" + found = REGISTRY.search(read("CMakeLists.txt")) + if found is None: + sys.exit("SOLIDSYSLOG_PLATFORM_REGISTRY not found in CMakeLists.txt") + return ROW.findall(found.group(1)) + + +def documented(): + """Slugs that have a docs folder, whether or not anything registered them.""" + platforms = os.path.join(ROOT, "docs", "platforms") + return { + name + for name in os.listdir(platforms) + if os.path.isdir(os.path.join(platforms, name)) + } + + +def check(): + faults = [] + nav = read("mkdocs.yml") + descriptions = read("hooks", "page_descriptions.py") + matrix = read("docs", "platforms", "index.md") + slugs = set() + + for token, directory in registered(): + slug = token.lower() + slugs.add(slug) + docs = os.path.join("docs", "platforms", slug) + + for page in ("index.md", "setup.md"): + if not os.path.isfile(os.path.join(ROOT, docs, page)): + faults.append(f"{token}: {docs}/{page} is missing") + + group = os.path.join(ROOT, directory, f"SolidSyslog{token}Platform.dox") + if not os.path.isfile(group): + faults.append(f"{token}: no group file at {directory}/SolidSyslog{token}Platform.dox") + elif f"@defgroup platform_{slug} " not in read(group): + faults.append(f"{token}: its group file does not declare @defgroup platform_{slug}") + + if f"platforms/{slug}/index.md" not in nav: + faults.append(f"{token}: no entry in the mkdocs.yml nav") + for page in ("index.md", "setup.md"): + if f'"platforms/{slug}/{page}"' not in descriptions: + faults.append(f"{token}: no meta description for platforms/{slug}/{page}") + + if f"]({slug}/index.md)" not in matrix: + faults.append(f"{token}: not a row in the docs/platforms/index.md matrix") + + interface = os.path.join(ROOT, directory, "Interface") + for header in sorted(os.listdir(interface)) if os.path.isdir(interface) else []: + if header.endswith(".h") and "@ingroup" not in read(directory, "Interface", header): + faults.append(f"{token}: {header} carries no @ingroup") + + for slug in sorted(documented() - slugs): + faults.append(f"docs/platforms/{slug}/ documents a platform that is not registered") + + return faults + + +if __name__ == "__main__": + problems = check() + for problem in problems: + print(f"error: {problem}", file=sys.stderr) + if problems: + print( + f"\n{len(problems)} problem(s). A platform is declared in " + "SOLIDSYSLOG_PLATFORM_REGISTRY; everything above follows from its token.", + file=sys.stderr, + ) + sys.exit(1) + print(f"platform docs: {len(registered())} platforms, all documented") From 264e9cd172b4de7d53a8a5e48f9fa3d212d936ac Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 08:15:47 +0100 Subject: [PATCH 32/57] docs: S23.22 put the platform doorway on data-structure pages too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chip reached a platform's header pages but not its data structures, so a reader landing on SolidSyslogMbedTlsStreamConfig — which is where a search for a config field puts them — had no way back to the platform. Struct names cannot be derived from a page name, so each platform's Interface headers are scanned for struct definitions and the names mapped. Only definitions count: SolidSyslogMbedTlsStream.h forward- declares struct SolidSyslogStream, and matching that would label Core's Stream as belonging to Mbed TLS. Core structs are absent by construction, since only the platform directories are scanned. Part of #708 --- hooks/platform_backlinks.py | 31 ++++++++++++++++++++++++++----- hooks/test_platform_backlinks.py | 16 ++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index e09912c9..96ba74fd 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -28,6 +28,9 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') MODULES_CRUMB = re.compile(r"\[\*\*Modules\*\*\]\([^)]*\)") +# A definition, not a forward declaration — the brace is what distinguishes +# `struct SolidSyslogMbedTlsStreamConfig {` from `struct SolidSyslogStream;`. +STRUCT_DEF = re.compile(r"struct\s+(SolidSyslog\w+)\s*\{", re.MULTILINE) CANONICAL = "https://docs.cososo.co.uk/solid-syslog/" GENERATED_PREFIX = "api/" @@ -54,7 +57,13 @@ def _group_slug(src_uri): def _index(config): - """Return (headers, slugs, labels) built from the platform registry.""" + """Return (headers, slugs, labels) built from the platform registry. + + ``headers`` maps both a header stem and a struct name to its platform, so + every generated page for a platform — file or data structure — can be + labelled. Core's structs are absent by construction: only the platform + Interface directories are scanned. + """ root = os.path.dirname(config["config_file_path"]) if root not in _CACHE: with open(os.path.join(root, "CMakeLists.txt"), encoding="utf-8") as cmake: @@ -69,8 +78,12 @@ def _index(config): pack = (labels[slug], slug) slugs[slug] = os.path.isfile(os.path.join(root, "docs", PLATFORM_PREFIX, slug, "setup.md")) for name in os.listdir(interface): - if name.endswith(".h"): - headers[name[: -len(".h")]] = pack + if not name.endswith(".h"): + continue + headers[name[: -len(".h")]] = pack + with open(os.path.join(interface, name), encoding="utf-8") as header: + for struct in STRUCT_DEF.findall(header.read()): + headers[struct] = pack _CACHE[root] = (headers, slugs, labels) return _CACHE[root] @@ -89,9 +102,17 @@ def _relativise(markdown, slug, label): def _stem(src_uri): - """api/SolidSyslogMbedTlsStream_8h.md -> SolidSyslogMbedTlsStream.""" + """The header or struct a generated page documents, else None. + + api/SolidSyslogMbedTlsStream_8h.md -> SolidSyslogMbedTlsStream + api/structSolidSyslogMbedTlsStreamConfig.md -> SolidSyslogMbedTlsStreamConfig + """ leaf = src_uri[len(GENERATED_PREFIX) : -len(".md")] - return leaf[: -len("_8h")] if leaf.endswith("_8h") else None + if leaf.endswith("_8h"): + return leaf[: -len("_8h")] + if leaf.startswith("struct"): + return leaf[len("struct") :] + return None def on_page_markdown(markdown, page, config, files, **kwargs): diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 16ac173f..9aa43cf1 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -38,6 +38,22 @@ def test_the_label_is_the_platform_pages_own_heading(self): # Not the registry token, which is "LwipRaw". self.assertIn("[lwIP (Raw API) platform]", render("api/SolidSyslogLwipRawTcpStream_8h.md")) + def test_a_platform_data_structure_is_labelled_too(self): + out = render("api/structSolidSyslogMbedTlsStreamConfig.md") + self.assertIn("[Mbed TLS platform](../platforms/mbedtls/index.md)", out) + + def test_a_core_data_structure_is_not_labelled(self): + # Only the platform Interface directories are scanned, so a Core struct + # cannot pick up a platform label. + markdown = "# Config\n\nBody.\n" + self.assertEqual(render("api/structSolidSyslogConfig.md", markdown), markdown) + + def test_a_forward_declaration_does_not_claim_the_struct(self): + # SolidSyslogMbedTlsStream.h forward-declares struct SolidSyslogStream; + # only definitions map, or Core's Stream would be labelled Mbed TLS. + headers, _slugs, _labels = h._index(CONFIG) + self.assertNotIn("SolidSyslogStream", headers) + def test_a_core_header_is_not_labelled(self): markdown = "# Core\n\nBody.\n" self.assertEqual(render("api/SolidSyslogConfig_8h.md", markdown), markdown) From b353a46e2d0da047fc4706fdfc8c06bb28aed2f1 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 12:50:16 +0100 Subject: [PATCH 33/57] docs: S23.22 hold the platform-naming boundary in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A platform describes only itself. Naming a sibling couples the two, so the eleventh platform means editing ten pages — and hand review kept losing the rule, most recently by treating a header brief as an exception to it. Fix the 20 sites that named another platform: 6 Interface @file briefs, 2 .dox group briefs, 2 platform pages, and 10 implementation comments. Generic reference stays ("under a TLS stream"); naming a sibling class or pack does not. The scope is everything under Platform/, not only what a reader sees. Then make it enforceable. scripts/check_platform_docs.py gains two assertions: no platform names another, and every non-Errors header is linked from its platform's index page. The vocabulary derives from the registry — tokens and class names in both spellings — so only the prose aliases are hand-kept, and a registered token missing from them is itself a failure. #include lines are not scanned, a term may have several owners (FreeRTOS belongs to the kernel pack and to both FreeRTOS-Plus-* platforms), and exemptions print on every run. The OpenSSL and Plus-TCP setup pages named a companion platform in their SOLIDSYSLOG_PLATFORMS line. The build models no dependency between platforms, so both now name only themselves and point at the capability matrix. misra_suppressions.txt line numbers recomputed from the diff hunks; cppcheck --addon=misra reports findings identical to HEAD. Part of #708 --- .github/workflows/ci.yml | 2 + .../FreeRtos/SolidSyslogFreeRtosPlatform.dox | 3 +- .../Source/SolidSyslogFreeRtosMutex.c | 4 +- .../Source/SolidSyslogFreeRtosMutexPrivate.h | 4 +- .../Interface/SolidSyslogLwipRawTcpStream.h | 2 +- .../Source/SolidSyslogLwipRawTcpStream.c | 2 +- .../SolidSyslogMbedTlsAesGcmPolicy.h | 2 +- .../Interface/SolidSyslogMbedTlsStream.h | 4 +- .../MbedTls/SolidSyslogMbedTlsPlatform.dox | 4 +- .../Source/SolidSyslogMbedTlsAesGcmPolicy.c | 2 +- .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 16 +- .../Interface/SolidSyslogPlusTcpTcpStream.h | 2 +- .../Source/SolidSyslogPlusTcpDatagram.c | 4 +- .../Source/SolidSyslogPlusTcpTcpStream.c | 2 +- .../Interface/SolidSyslogPosixTcpStream.h | 2 +- .../Interface/SolidSyslogWinsockTcpStream.h | 2 +- .../Source/SolidSyslogWindowsClockInternal.h | 2 +- .../Windows/Source/SolidSyslogWindowsFile.c | 4 - .../SolidSyslogWindowsHostnameInternal.h | 2 +- .../SolidSyslogWindowsProcessIdInternal.h | 2 +- .../SolidSyslogWindowsSysUpTimeInternal.h | 2 +- .../SolidSyslogWinsockDatagramInternal.h | 2 +- .../SolidSyslogWinsockResolverInternal.h | 2 +- .../Source/SolidSyslogWinsockTcpStream.c | 13 +- .../SolidSyslogWinsockTcpStreamInternal.h | 2 +- docs/platforms/fatfs/index.md | 4 +- docs/platforms/openssl/setup.md | 19 +-- docs/platforms/plustcp/setup.md | 6 +- misra_suppressions.txt | 12 +- scripts/check_platform_docs.py | 142 +++++++++++++++++- 30 files changed, 204 insertions(+), 67 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 636183b2..313e94ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -776,6 +776,8 @@ jobs: # asserts that its pages, group, nav entry, description and matrix row # all exist, and that nothing documents a platform that is not # registered — so registering one is the only edit that can be forgotten. + # It also holds the two boundaries hand review kept losing: no platform + # names another, and every class a platform ships is on its page. - name: Check every platform is documented run: python3 scripts/check_platform_docs.py diff --git a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox index 0c259a8b..c377a5d3 100644 --- a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox +++ b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox @@ -1,7 +1,6 @@ /** * @defgroup platform_freertos FreeRTOS platform - * @brief Kernel primitives: the Mutex role and the sysUpTime callback. Networking - * comes from FreeRTOS-Plus-TCP or lwIP alongside. + * @brief Kernel primitives: the Mutex role and the sysUpTime callback. * * What this platform ships, what your build must provide, the security * behaviour it guarantees and the obligations it leaves to you: diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c index 1f2867cb..8df9443f 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c @@ -30,8 +30,8 @@ void FreeRtosMutex_Initialise(struct SolidSyslogMutex* base) * configSUPPORT_STATIC_ALLOCATION is not 1 — a compile-time config * gate, not a runtime failure mode. Guarded anyway so a misconfigured * integrator falls back to the NullMutex vtable instead of corrupting - * Lock/Unlock with a dangling handle, mirroring PosixMutex's defence - * against pthread_mutex_init failure. */ + * Lock/Unlock with a dangling handle — the same defence every Mutex + * adapter applies to its own primitive's init failure. */ if (xSemaphoreCreateMutexStatic(&self->Buffer) != NULL) { self->Base.Lock = FreeRtosMutex_Lock; diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h index b0e3eff3..59bb6d2d 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutexPrivate.h @@ -13,8 +13,8 @@ /* xSemaphoreCreateMutexStatic returns a handle that is the same pointer * as the StaticSemaphore_t passed in, so the per-instance struct doesn't - * carry a separate SemaphoreHandle_t — the kernel-primitive layout matches - * the Posix (pthread_mutex_t) and Windows (CRITICAL_SECTION) adapters. */ + * carry a separate SemaphoreHandle_t — the primitive is embedded directly, + * as in every Mutex adapter. */ struct SolidSyslogFreeRtosMutex { struct SolidSyslogMutex Base; diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h index 44164ff7..54a60d56 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h @@ -1,6 +1,6 @@ /** @file * A TCP stream over the lwIP Raw API, for a StreamSender or as the byte - * transport under a TlsStream / MbedTlsStream. + * transport under a TLS stream. * * Every lwIP call runs under the SolidSyslogLwipRaw_Marshal hop; the callbacks * lwIP fires back (connected / recv / err) only flip flags, so they stay diff --git a/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c b/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c index d0d4e282..29e9640c 100644 --- a/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c +++ b/Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c @@ -339,7 +339,7 @@ static bool LwipRawTcpStream_OutputResultIsAcceptable(err_t outputErr) static SolidSyslogSsize LwipRawTcpStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size) { /* SolidSyslogStream_Read returns < 0 to signal EOF/error (socket closed - * internally); -1 is the in-tree convention shared with Posix/Winsock/PlusTcp. */ + * internally); -1 is the in-tree convention across the Stream adapters. */ static const SolidSyslogSsize READ_FAILED = -1; struct SolidSyslogLwipRawTcpStream* self = LwipRawTcpStream_SelfFromBase(base); diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h index b6f3d883..5b55ded7 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h @@ -35,7 +35,7 @@ SOLIDSYSLOG_EXTERN_C_BEGIN void* KeyContext; /**< Passed back to GetKey unchanged; NULL is fine. */ struct mbedtls_ctr_drbg_context* Rng; /**< Seeded CTR-DRBG each record's 12-byte nonce is drawn from; required and caller-owned. Injected because mbedTLS has no - context-free RNG, unlike the OpenSSL sibling's RAND_bytes. */ + context-free RNG. */ }; /** Draw an AES-GCM policy from the pool. Bad config (NULL GetKey or NULL Rng) diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index 18cbea50..46035b03 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -1,8 +1,8 @@ /** @file * TLS over an injected byte-transport Stream via Mbed TLS, itself a Stream — so * a StreamSender speaks TLS to a remote collector without knowing the transport - * underneath (PosixTcpStream, PlusTcpTcpStream, or any caller-supplied byte - * Stream). + * underneath, whether a TCP stream from a platform pack or one the caller + * supplies. * * What the stream does through its vtable is the substance: * diff --git a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox index db903048..2495b663 100644 --- a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox +++ b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox @@ -1,7 +1,7 @@ /** * @defgroup platform_mbedtls Mbed TLS platform - * @brief TLS transport and keyed at-rest crypto via Mbed TLS, for embedded targets - * where OpenSSL is too heavy. + * @brief TLS transport and keyed at-rest crypto via Mbed TLS, sized for + * constrained embedded targets. * * What this platform ships, what your build must provide, the security * behaviour it guarantees and the obligations it leaves to you: diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c index 27e17c19..3c9c3c72 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c @@ -149,7 +149,7 @@ static bool MbedTlsAesGcmPolicy_FetchKey(struct SolidSyslogMbedTlsAesGcmPolicy* * adjacent as same-typed scalar parameters; the trailer/header layout lives in * one place. The nonce is expected already in Trailer[0..GCM_NONCE_SIZE). One- * shot AEAD — mbedTLS computes the whole tag in a single call (output == input - * is permitted for GCM encryption), unlike OpenSSL's incremental EVP chain. */ + * is permitted for GCM encryption). */ static bool MbedTlsAesGcmPolicy_GcmEncrypt(const struct SolidSyslogSecurityRecord* record, const uint8_t* key) { uint8_t* body = &record->Content[record->HeaderLength]; diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index a29c64c8..273260f9 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -100,8 +100,8 @@ static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct void MbedTlsStream_Cleanup(struct SolidSyslogStream* base) { - /* Mirror the OpenSSL TlsStream pattern: an integrator who destroys a - * still-Open stream must not leak the underlying TLS state. */ + /* An integrator who destroys a still-Open stream must not leak the + * underlying TLS state. */ MbedTlsStream_Close(base); /* Overwrite the abstract base with the shared NullStream vtable so * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ @@ -168,7 +168,8 @@ static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* mbedtls_ssl_conf_authmode(&self->SslConfig, MBEDTLS_SSL_VERIFY_REQUIRED); /* Pin the floor at TLS 1.2 rather than inheriting MBEDTLS_SSL_PRESET_DEFAULT, * which can negotiate down to TLS 1.0/1.1 on permissive integrator builds. - * Matches the OpenSSL adapter's explicit floor (downgrade-resistance parity). */ + * The floor is stated here so downgrade resistance does not depend on the + * preset the integrator happens to have compiled in. */ mbedtls_ssl_conf_min_tls_version(&self->SslConfig, MBEDTLS_SSL_VERSION_TLS1_2); mbedtls_ssl_conf_ca_chain(&self->SslConfig, self->Config.CaChain, NULL); mbedtls_ssl_conf_rng(&self->SslConfig, mbedtls_ctr_drbg_random, self->Config.Rng); @@ -240,7 +241,7 @@ static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbe * spin) until either the handshake completes, hits a hard error, or the * bounded budget expires. Each non-success exit emits a distinct * protocol-level error code so the integrator can tell rejection from - * timeout. Same shape as OpenSSL's TlsStream_PerformHandshake. */ + * timeout. */ static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self) { uint32_t budgetMs = MbedTlsStream_ResolveHandshakeTimeoutMs(self); @@ -327,8 +328,8 @@ static int MbedTlsStream_BioRecv(void* ctx, unsigned char* buf, size_t len) } /* TLS-level write failure means the session state is unrecoverable — close - * so the StreamSender reconnect path runs on the next tick. Mirrors the - * OpenSSL TlsStream_Send fail-fast contract. */ + * so the StreamSender reconnect path runs on the next tick. Fail-fast is the + * contract every TLS stream adapter honours. */ static inline bool MbedTlsStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size) { struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base); @@ -346,8 +347,7 @@ static inline bool MbedTlsStream_Send(struct SolidSyslogStream* base, const void * right now → WANT_READ → return 0, mirroring the transport contract. * 2. Any other negative return (alerts, renegotiation surfacing as * WANT_WRITE, hard transport error) is fatal under fail-fast semantics - * — close internally; the caller reopens and store-and-forward replays. - * Same shape as the OpenSSL TlsStream_Read. */ + * — close internally; the caller reopens and store-and-forward replays. */ static inline SolidSyslogSsize MbedTlsStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size) { struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base); diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h index ff34a3ff..a3813e75 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h @@ -1,6 +1,6 @@ /** @file * A TCP stream over a FreeRTOS-Plus-TCP socket, for a StreamSender or as the - * byte transport under a TlsStream. + * byte transport under a TLS stream. * * What the stream does through its vtable is the substance: * diff --git a/Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c b/Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c index f505d603..45b87acc 100644 --- a/Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c +++ b/Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c @@ -109,8 +109,8 @@ static enum SolidSyslogDatagramSendResult PlusTcpDatagram_SendTo( } /* FreeRTOS-Plus-TCP does not queue datagrams while ARP resolves: a sendto to - * an unresolved peer drops at the IP layer. Linux/Windows kernels mask this - * with internal ARP queuing; FreeRTOS does not. So on cache miss we issue a + * an unresolved peer drops at the IP layer. Host-class kernels mask this + * with internal ARP queuing; this stack does not. So on cache miss we issue a * probe and yield once for the reply to land. If the reply hasn't arrived in * time the sendto is allowed to fail or be dropped — UDP is best-effort and * retry belongs in the store-and-forward layer above, not here. */ diff --git a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c index 0c86ecd6..39ad6565 100644 --- a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c +++ b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c @@ -29,7 +29,7 @@ const struct SolidSyslogErrorSource PlusTcpTcpStreamErrorSource = {"PlusTcpTcpSt struct SolidSyslogAddress; /* SolidSyslogStream_Read returns < 0 to signal EOF/error (socket closed - * internally); -1 is the in-tree convention shared with Posix/Winsock. */ + * internally); -1 is the in-tree convention across the Stream adapters. */ static const SolidSyslogSsize READ_FAILED = -1; enum diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h index 8d37a12e..e139865b 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h @@ -1,6 +1,6 @@ /** @file * A non-blocking TCP stream over a POSIX socket, for a StreamSender or as the - * byte transport under a TlsStream. + * byte transport under a TLS stream. * * What the stream does through its vtable is the substance: * diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h index 9af59048..53840bd2 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h @@ -1,6 +1,6 @@ /** @file * A non-blocking TCP stream over a Winsock socket, for a StreamSender or as the - * byte transport under a TlsStream. + * byte transport under a TLS stream. * * What the stream does through its vtable is the substance: * diff --git a/Platform/Windows/Source/SolidSyslogWindowsClockInternal.h b/Platform/Windows/Source/SolidSyslogWindowsClockInternal.h index 97cfdff8..7007f581 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsClockInternal.h +++ b/Platform/Windows/Source/SolidSyslogWindowsClockInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace this function pointer via CppUTest's UT_PTR_SET to inject a fake FILETIME source (MSVC does not - support GCC's weak/strong symbol override trick used by the POSIX fakes). */ + support GCC's weak/strong symbol override trick the fakes rely on). */ #include "SolidSyslogExternC.h" diff --git a/Platform/Windows/Source/SolidSyslogWindowsFile.c b/Platform/Windows/Source/SolidSyslogWindowsFile.c index 3f78fbf9..a67dd83a 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsFile.c +++ b/Platform/Windows/Source/SolidSyslogWindowsFile.c @@ -79,10 +79,6 @@ static inline struct SolidSyslogWindowsFile* WindowsFile_SelfFromBase(struct Sol static bool WindowsFile_Open(struct SolidSyslogFile* base, const char* path) { - /* _sopen_s is the non-deprecated MSVC equivalent of POSIX open(): the - * plain _open triggers C4996 (Microsoft's safe-CRT preference) and - * _CRT_SECURE_NO_WARNINGS is forbidden by the project's banned-API - * policy. _SH_DENYNO matches POSIX open()'s default of no share mode. */ struct SolidSyslogWindowsFile* self = WindowsFile_SelfFromBase(base); errno_t err = _sopen_s(&self->Fd, path, DEFAULT_OPEN_FLAGS, _SH_DENYNO, DEFAULT_FILE_PERMISSIONS); if (err != 0) diff --git a/Platform/Windows/Source/SolidSyslogWindowsHostnameInternal.h b/Platform/Windows/Source/SolidSyslogWindowsHostnameInternal.h index f529c1ed..ab6a2887 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsHostnameInternal.h +++ b/Platform/Windows/Source/SolidSyslogWindowsHostnameInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace this function pointer via CppUTest's UT_PTR_SET to inject a fake hostname source (MSVC does not - support GCC's weak/strong symbol override trick used by the POSIX fakes). */ + support GCC's weak/strong symbol override trick the fakes rely on). */ #include "SolidSyslogExternC.h" diff --git a/Platform/Windows/Source/SolidSyslogWindowsProcessIdInternal.h b/Platform/Windows/Source/SolidSyslogWindowsProcessIdInternal.h index d7b69fe7..0af0df41 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsProcessIdInternal.h +++ b/Platform/Windows/Source/SolidSyslogWindowsProcessIdInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace this function pointer via CppUTest's UT_PTR_SET to inject a deterministic PID (MSVC does not - support GCC's weak/strong symbol override trick used by the POSIX fakes). */ + support GCC's weak/strong symbol override trick the fakes rely on). */ #include "SolidSyslogExternC.h" diff --git a/Platform/Windows/Source/SolidSyslogWindowsSysUpTimeInternal.h b/Platform/Windows/Source/SolidSyslogWindowsSysUpTimeInternal.h index 85401bd4..2026d15b 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsSysUpTimeInternal.h +++ b/Platform/Windows/Source/SolidSyslogWindowsSysUpTimeInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace this function pointer via CppUTest's UT_PTR_SET to inject a fake tick source (MSVC does not - support GCC's weak/strong symbol override trick used by the POSIX fakes). */ + support GCC's weak/strong symbol override trick the fakes rely on). */ #include "SolidSyslogExternC.h" diff --git a/Platform/Windows/Source/SolidSyslogWinsockDatagramInternal.h b/Platform/Windows/Source/SolidSyslogWinsockDatagramInternal.h index 3264799c..2b479990 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockDatagramInternal.h +++ b/Platform/Windows/Source/SolidSyslogWinsockDatagramInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace these function pointers via CppUTest's UT_PTR_SET to inject fakes (MSVC does not support GCC's - weak/strong symbol override trick used by the POSIX SocketFake). */ + weak/strong symbol override trick the SocketFake relies on). */ #include "SolidSyslogExternC.h" diff --git a/Platform/Windows/Source/SolidSyslogWinsockResolverInternal.h b/Platform/Windows/Source/SolidSyslogWinsockResolverInternal.h index e219b7ca..6fa04283 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockResolverInternal.h +++ b/Platform/Windows/Source/SolidSyslogWinsockResolverInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace these function pointers via CppUTest's UT_PTR_SET to inject fakes (MSVC does not support GCC's - weak/strong symbol override trick used by the POSIX SocketFake). */ + weak/strong symbol override trick the SocketFake relies on). */ #include "SolidSyslogExternC.h" diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c index 5cd53fe4..5784a015 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStream.c @@ -110,10 +110,9 @@ enum but POSIX-portable callers must pass the highest fd + 1. Pass any positive value to keep the call well-formed against either ABI. */ WINSOCK_NFDS_IGNORED = 1, - /* Keepalive parameters mirror the POSIX TCP stream so the dead-peer - detection window is the same on both platforms: idle 45 + 4 * 10 = 85 s - worst case. Windows has no TCP_USER_TIMEOUT analogue, so the pending- - write case relies on the OS-default retransmit timeout. */ + /* Dead-peer detection window: idle 45 + 4 * 10 = 85 s worst case. + Windows has no TCP_USER_TIMEOUT analogue, so the pending-write case + relies on the OS-default retransmit timeout. */ KEEPALIVE_IDLE_SECONDS = 45, KEEPALIVE_INTERVAL_SECONDS = 10, KEEPALIVE_PROBE_COUNT = 4 @@ -244,9 +243,9 @@ static void WinsockTcpStream_EnableTcpNoDelay(SOCKET fd) WinsockTcpStream_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*) &enable, (int) sizeof(enable)); } -/* Mirrors the POSIX WinsockTcpStream_EnableKeepalive — Windows 10 1709+ exposes TCP_KEEPIDLE / - * TCP_KEEPINTVL / TCP_KEEPCNT via setsockopt (declared in ), so the - * shape matches the POSIX path one-for-one. No TCP_USER_TIMEOUT analogue. */ +/* Windows 10 1709+ exposes TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT via + * setsockopt (declared in ), so idle, interval and count are each + * set directly. No TCP_USER_TIMEOUT analogue. */ static void WinsockTcpStream_EnableKeepalive(SOCKET fd) { int enable = 1; diff --git a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamInternal.h b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamInternal.h index 2b712359..1cb9ff11 100644 --- a/Platform/Windows/Source/SolidSyslogWinsockTcpStreamInternal.h +++ b/Platform/Windows/Source/SolidSyslogWinsockTcpStreamInternal.h @@ -3,7 +3,7 @@ /* Library-internal test seam. Tests replace these function pointers via CppUTest's UT_PTR_SET to inject fakes (MSVC does not support GCC's - weak/strong symbol override trick used by the POSIX SocketFake). + weak/strong symbol override trick the SocketFake relies on). Names are namespaced WinsockTcpStream_* to avoid linker collisions with the un-namespaced Winsock_* symbols already exported by SolidSyslogWinsockDatagram and SolidSyslogWinsockResolver. */ diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index 8940ab58..c7a9e6ab 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -1,8 +1,8 @@ # FatFs `Platform/FatFs/` wraps ChaN FatFs as the File layer -([FatFs documentation](http://elm-chan.org/fsw/ff/)). RTOS-agnostic — bare-metal, -FreeRTOS, Zephyr, NuttX. +([FatFs documentation](http://elm-chan.org/fsw/ff/)). RTOS-agnostic — bare-metal +or under any RTOS. Fills the [File](../../api/structSolidSyslogFile.md) role — the primitive beneath a BlockDevice. diff --git a/docs/platforms/openssl/setup.md b/docs/platforms/openssl/setup.md index 992dc06d..23aeb49e 100644 --- a/docs/platforms/openssl/setup.md +++ b/docs/platforms/openssl/setup.md @@ -7,11 +7,12 @@ page is the wiring. ## What you need -OpenSSL 3.0 or later on the include and link path. With CMake, selecting the -platform is enough: +OpenSSL 3.0 or later on the include and link path, and a platform supplying the +TCP stream underneath — the [capability matrix](../index.md) shows which fill +that role. ```cmake -set(SOLIDSYSLOG_PLATFORMS "Posix;OpenSsl") +set(SOLIDSYSLOG_PLATFORMS "OpenSsl") # and the one supplying your TCP stream ``` OpenSSL is a stable system API rather than a header-configured upstream, so the @@ -22,11 +23,10 @@ Make and IDE routes. ## The layering TLS is a Stream wrapped around another Stream. The TLS adapter carries the -records; the transport underneath carries the bytes, and it can be any Stream — -here the POSIX TCP one. +records; the transport underneath carries the bytes, and it can be any Stream. ```text -StreamSender → SolidSyslogTlsStream → SolidSyslogPosixTcpStream → socket +StreamSender → SolidSyslogTlsStream → your TCP stream → socket ``` The TLS stream **borrows** its transport. It may close it, but it never destroys @@ -36,12 +36,13 @@ until `SolidSyslogTlsStream_Destroy`. ## Wiring it ```c -struct SolidSyslogStream* transport = SolidSyslogPosixTcpStream_Create(NULL); +/* Your TCP stream and sleep, from the platform that supplies them. */ +struct SolidSyslogStream* transport = CreateTcpStream(); static struct SolidSyslogTlsStreamConfig tlsConfig; tlsConfig = (struct SolidSyslogTlsStreamConfig) {0}; tlsConfig.Transport = transport; -tlsConfig.Sleep = SolidSyslogPosixSleep; /* required — no fallback */ +tlsConfig.Sleep = MySleep; /* required — no fallback */ tlsConfig.CaBundlePath = "/etc/ssl/collector-ca.pem"; tlsConfig.ServerName = "collector.example.net"; @@ -66,7 +67,7 @@ static struct SolidSyslogStreamSenderConfig senderConfig; senderConfig = (struct SolidSyslogStreamSenderConfig) {0}; senderConfig.Resolver = resolver; senderConfig.Stream = tls; -senderConfig.Address = SolidSyslogPosixAddress_Create(); +senderConfig.Address = CreateAddress(); /* your platform's Address */ senderConfig.Endpoint = GetEndpoint; struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig); ``` diff --git a/docs/platforms/plustcp/setup.md b/docs/platforms/plustcp/setup.md index bfd6bf89..dc8ea65d 100644 --- a/docs/platforms/plustcp/setup.md +++ b/docs/platforms/plustcp/setup.md @@ -7,10 +7,12 @@ structs themselves. ## What to link The stack is configured by a header you own, so the adapters compile inside -your target against your `FreeRTOSIPConfig.h`: +your target against your `FreeRTOSIPConfig.h`. This platform fills the network +role only, so select it alongside whichever platform supplies your mutex and +clock — the [capability matrix](../index.md) shows which fill those: ```cmake -set(SOLIDSYSLOG_PLATFORMS "FreeRtos;PlusTcp") +set(SOLIDSYSLOG_PLATFORMS "PlusTcp") # and the one supplying mutex and clock target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::PlusTcp) ``` diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 141e1ed9..8c779f69 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -79,18 +79,18 @@ misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsAtomicCounter.c:33 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsFile.c:77 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWindowsMutex.c:38 misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:98 -misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:185 +misra-c2012-11.3:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:184 misra-c2012-11.5:Core/Source/SolidSyslogUdpSender.c:227 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c:79 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:154 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:216 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:147 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:155 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:298 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:310 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:299 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:311 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:142 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:357 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:377 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:356 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:376 # D.003 — Rule 5.7: repeating struct tags (no-typedef-struct convention) # See docs/misra-deviations.md#d003 @@ -199,5 +199,5 @@ misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:20 # D.013 — Rule 11.5: void* ↔ unsigned char* at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:335 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:336 misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:354 diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index 7147ab38..b9b316a2 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -15,6 +15,17 @@ folder or a group with no row behind it. Registering a platform is then the one edit that cannot be forgotten, because forgetting anything else fails the build. +It also holds two boundaries that hand review kept losing: + +* **No platform names another.** A platform describes itself completely; where + a capability comes from is the capability matrix's job. Naming a sibling + couples the two, so the eleventh platform means editing ten pages. Applies to + the platform's whole tree and its docs folder alike. +* **Every class its platform ships is on its page.** A public header that is + not an Errors header declares something an integrator can wire, so it must be + reachable from the platform's index page — in the class table, or in a + section of its own where it is not a role. + Run: python3 scripts/check_platform_docs.py """ @@ -27,6 +38,50 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') +# What each platform is called in prose, where that differs from its registry +# token. Tokens and class names are derived, so this is the only hand-kept part +# of the vocabulary — and every registered token must appear, so adding a +# platform forces the decision rather than silently widening the gap. +ALIASES = { + "Atomics": [], + "FatFs": ["FatFs", "ChaN"], + "FreeRtos": ["FreeRTOS"], + "LwipRaw": ["lwIP"], + "MbedTls": ["Mbed TLS", "mbedTLS", "mbedtls"], + "OpenSsl": ["OpenSSL"], + # Plus-FAT and Plus-TCP are FreeRTOS-Plus-* products, so "FreeRTOS" is part + # of their own identity as much as it is the kernel pack's — each may name + # the kernel it sits on. The pack's token stays the pack's, so a page that + # points at the FreeRtos platform is still caught. + "PlusFat": ["FreeRTOS-Plus-FAT", "FreeRTOS"], + "PlusTcp": ["FreeRTOS-Plus-TCP", "FreeRTOS"], + "Posix": ["POSIX"], + "Windows": ["Winsock", "Win32"], +} + +# Deliberate exemptions, each with the reason it is not a boundary breach. +# Printed on every run: an exemption nobody sees is an exemption nobody +# revisits. +ALLOWED = [ + ( + "Platform/Windows/Source/SolidSyslogWindowsFile.c", + "POSIX", + "names the OS standard the MSVC call is measured against, not the Posix platform", + ), + ( + "Platform/Windows/Source/SolidSyslogWinsockTcpStream.c", + "POSIX", + "names the OS standard the MSVC call is measured against, not the Posix platform", + ), +] + +SCANNED_SUFFIXES = (".c", ".h", ".dox", ".md") + +# An #include names a header the compiler must find, not a platform the prose +# is describing. The boundary is editorial; what a translation unit depends on +# is the build's business and is governed there. +INCLUDE = re.compile(r"^\s*#\s*include") + def read(*parts): with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle: @@ -51,14 +106,93 @@ def documented(): } +def vocabulary(rows): + """Every term that names a platform, mapped to the tokens that may use it. + + Three sources, only one of them hand-kept: the registry token itself, the + prose aliases above, and the class names taken from the platform's own + Interface headers — in both their full and bare spellings, since prose says + PosixTcpStream where code says SolidSyslogPosixTcpStream. + + A term can have more than one owner: "FreeRTOS" belongs to the kernel pack + and to the two FreeRTOS-Plus-* platforms built on it alike. + """ + terms = {} + for token, directory in rows: + for alias in [token] + ALIASES.get(token, []): + terms.setdefault(alias, set()).add(token) + interface = os.path.join(ROOT, directory, "Interface") + for header in sorted(os.listdir(interface)) if os.path.isdir(interface) else []: + if header.endswith(".h"): + stem = header[: -len(".h")] + terms.setdefault(stem, set()).add(token) + terms.setdefault(stem[len("SolidSyslog") :], set()).add(token) + return terms + + +def scanned(directory, slug): + """Everything that speaks for one platform: its own tree, and its pages.""" + for base in (os.path.join(ROOT, directory), os.path.join(ROOT, "docs", "platforms", slug)): + for path, _, names in os.walk(base): + for name in sorted(names): + if name.endswith(SCANNED_SUFFIXES): + yield os.path.relpath(os.path.join(path, name), ROOT) + + +def naming_faults(rows, terms): + """Flag a platform naming another, longest term first so FreeRTOS-Plus-TCP + is read as itself rather than as FreeRTOS.""" + ordered = sorted(terms, key=len, reverse=True) + pattern = re.compile(r"(? Date: Sat, 8 Aug 2026 14:21:17 +0100 Subject: [PATCH 34/57] docs: S23.22 generate the platform manifest from the headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A platform had two pages answering "what is this platform": the hand-written one, and the Doxygen group page whose only unique content was a Files table. Move that table onto the platform page, generated, so the group page has nothing left to offer. Every header now appears, by filename, described by its own @file brief. That is a change in kind: the hand-written table listed classes with an editorial role label and omitted the *Errors.h headers entirely, so a platform's error codes and Source identity — what an integrator matches on when installing a handler — were reachable from no platform page at all. Nothing in the table is editorial once the role labels go, so it is generated rather than hand-kept across ten pages. The heading is the seam: page source carries it alone and hooks/platform_backlinks.py fills the section. check_platform_docs.py drops its "every header is linked" assertion, which read a source table that no longer exists, and asserts the heading instead — losing it would silently take the whole manifest with it. lwIP's two sentences about calling lwIP only and needing an injected SolidSyslogSleepFunction move to Requirements, where they belong. Part of #708 --- docs/platforms/atomics/index.md | 4 -- docs/platforms/fatfs/index.md | 4 -- docs/platforms/freertos/index.md | 5 --- docs/platforms/lwipraw/index.md | 14 ++----- docs/platforms/mbedtls/index.md | 6 --- docs/platforms/openssl/index.md | 6 --- docs/platforms/plusfat/index.md | 4 -- docs/platforms/plustcp/index.md | 7 ---- docs/platforms/posix/index.md | 15 -------- docs/platforms/windows/index.md | 15 -------- hooks/platform_backlinks.py | 54 +++++++++++++++++++++----- hooks/test_platform_backlinks.py | 65 +++++++++++++++++++++++++++++--- scripts/check_platform_docs.py | 27 ++++++------- 13 files changed, 118 insertions(+), 108 deletions(-) diff --git a/docs/platforms/atomics/index.md b/docs/platforms/atomics/index.md index 0373460a..30eb9fd9 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/atomics/index.md @@ -7,10 +7,6 @@ Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogStdAtomicCounter`](../../api/SolidSyslogStdAtomicCounter_8h.md) | atomic counter (`_Atomic uint32_t` CAS) | - ## Requirements A C11 compiler with ``. Where a toolchain lacks it, the diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index c7a9e6ab..afa59f09 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -9,10 +9,6 @@ BlockDevice. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogFatFsFile`](../../api/SolidSyslogFatFsFile_8h.md) | file — `f_sync` after every write | - ## Requirements Your `ffconf.h`, a `diskio.c` media driver, and — if `FF_FS_REENTRANT=1` — an diff --git a/docs/platforms/freertos/index.md b/docs/platforms/freertos/index.md index 78eb240f..a178124f 100644 --- a/docs/platforms/freertos/index.md +++ b/docs/platforms/freertos/index.md @@ -9,11 +9,6 @@ Fills the Mutex [role](../../roles/index.md), plus a sysUpTime callback. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogFreeRtosMutex`](../../api/SolidSyslogFreeRtosMutex_8h.md) | mutex (`xSemaphoreCreateMutexStatic`) | -| [`SolidSyslogFreeRtosSysUpTime`](../../api/SolidSyslogFreeRtosSysUpTime_8h.md) | uptime (`xTaskGetTickCount`) | - ## Requirements `configSUPPORT_STATIC_ALLOCATION=1` — the mutex uses static allocation. diff --git a/docs/platforms/lwipraw/index.md b/docs/platforms/lwipraw/index.md index 4f6ea56e..6cd9c3f8 100644 --- a/docs/platforms/lwipraw/index.md +++ b/docs/platforms/lwipraw/index.md @@ -11,17 +11,6 @@ address handle they share. A TLS platform layers over the TCP stream; the ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogLwipRawAddress`](../../api/SolidSyslogLwipRawAddress_8h.md) | destination handle | -| [`SolidSyslogLwipRawResolver`](../../api/SolidSyslogLwipRawResolver_8h.md) | numeric IPv4 resolver | -| [`SolidSyslogLwipRawDnsResolver`](../../api/SolidSyslogLwipRawDnsResolver_8h.md) | DNS resolver (`LWIP_DNS=1`) | -| [`SolidSyslogLwipRawDatagram`](../../api/SolidSyslogLwipRawDatagram_8h.md) | UDP sender | -| [`SolidSyslogLwipRawTcpStream`](../../api/SolidSyslogLwipRawTcpStream_8h.md) | TCP byte transport | - -The source calls lwIP only — no direct OS calls. The TCP stream's synchronous -Open needs a bounded sleep, injected as a `SolidSyslogSleepFunction`. - ## The marshal Not a role: [`SolidSyslogLwipRaw_SetMarshal`](../../api/SolidSyslogLwipRawMarshal_8h.md) @@ -41,6 +30,9 @@ moment the hop returns. `tcpip_callback_with_block(…, 1)` or a `LOCK_TCPIP_COR ## Requirements +The source calls lwIP only — no direct OS calls. The TCP stream's synchronous +Open needs a bounded sleep, injected as a `SolidSyslogSleepFunction`. + Your `lwipopts.h` must enable the features the adapter wraps: | Setting | For | diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index f7e3a088..58d1ad77 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -8,12 +8,6 @@ integrity and confidentiality. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogMbedTlsStream`](../../api/SolidSyslogMbedTlsStream_8h.md) | TLS stream over an injected byte transport | -| [`SolidSyslogMbedTlsHmacSha256Policy`](../../api/SolidSyslogMbedTlsHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | -| [`SolidSyslogMbedTlsAesGcmPolicy`](../../api/SolidSyslogMbedTlsAesGcmPolicy_8h.md) | at-rest AES-256-GCM | - ## Requirements The adapter sources compile in your target against your own diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index e23608b7..35d3c5e2 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -8,12 +8,6 @@ integrity and confidentiality. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogTlsStream`](../../api/SolidSyslogTlsStream_8h.md) | TLS stream over an injected byte transport | -| [`SolidSyslogOpenSslHmacSha256Policy`](../../api/SolidSyslogOpenSslHmacSha256Policy_8h.md) | at-rest HMAC-SHA256 | -| [`SolidSyslogOpenSslAesGcmPolicy`](../../api/SolidSyslogOpenSslAesGcmPolicy_8h.md) | at-rest AES-256-GCM | - ## Requirements OpenSSL 3.0 or later. diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index d58b302c..b1c672dd 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -8,10 +8,6 @@ BlockDevice. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogPlusFatFile`](../../api/SolidSyslogPlusFatFile_8h.md) | file — cache flush after every write | - ## Requirements FreeRTOS — Plus-FAT is FreeRTOS-coupled. Supply an `FF_Disk_t` media driver and diff --git a/docs/platforms/plustcp/index.md b/docs/platforms/plustcp/index.md index 72faee77..f7923d81 100644 --- a/docs/platforms/plustcp/index.md +++ b/docs/platforms/plustcp/index.md @@ -8,13 +8,6 @@ address handle they share. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogPlusTcpResolver`](../../api/SolidSyslogPlusTcpResolver_8h.md) | DNS resolver (`FreeRTOS_getaddrinfo`) | -| [`SolidSyslogPlusTcpAddress`](../../api/SolidSyslogPlusTcpAddress_8h.md) | address (`freertos_sockaddr`) | -| [`SolidSyslogPlusTcpDatagram`](../../api/SolidSyslogPlusTcpDatagram_8h.md) | UDP sender | -| [`SolidSyslogPlusTcpTcpStream`](../../api/SolidSyslogPlusTcpTcpStream_8h.md) | TCP stream (bounded connect) | - ## Requirements FreeRTOS-Plus-TCP, selected at CMake time with diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md index 94921a61..61a67a3f 100644 --- a/docs/platforms/posix/index.md +++ b/docs/platforms/posix/index.md @@ -11,21 +11,6 @@ callbacks. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogGetAddrInfoResolver`](../../api/SolidSyslogGetAddrInfoResolver_8h.md) | resolver (`getaddrinfo`) | -| [`SolidSyslogPosixAddress`](../../api/SolidSyslogPosixAddress_8h.md) | address | -| [`SolidSyslogPosixDatagram`](../../api/SolidSyslogPosixDatagram_8h.md) | UDP sender | -| [`SolidSyslogPosixTcpStream`](../../api/SolidSyslogPosixTcpStream_8h.md) | TCP stream (non-blocking, bounded connect) | -| [`SolidSyslogPosixFile`](../../api/SolidSyslogPosixFile_8h.md) | file | -| [`SolidSyslogPosixMessageQueueBuffer`](../../api/SolidSyslogPosixMessageQueueBuffer_8h.md) | message-queue buffer | -| [`SolidSyslogPosixMutex`](../../api/SolidSyslogPosixMutex_8h.md) | mutex | -| [`SolidSyslogPosixClock`](../../api/SolidSyslogPosixClock_8h.md) | clock | -| [`SolidSyslogPosixHostname`](../../api/SolidSyslogPosixHostname_8h.md) | hostname | -| [`SolidSyslogPosixProcessId`](../../api/SolidSyslogPosixProcessId_8h.md) | process-id | -| [`SolidSyslogPosixSleep`](../../api/SolidSyslogPosixSleep_8h.md) | sleep | -| [`SolidSyslogPosixSysUpTime`](../../api/SolidSyslogPosixSysUpTime_8h.md) | uptime (`CLOCK_BOOTTIME`) | - ## Requirements A POSIX-conformant OS; Linux is the tested target. The message-queue buffer needs diff --git a/docs/platforms/windows/index.md b/docs/platforms/windows/index.md index 94cafd11..cf15ca4d 100644 --- a/docs/platforms/windows/index.md +++ b/docs/platforms/windows/index.md @@ -9,21 +9,6 @@ callbacks. ## What it ships -| Class | Role | -|---|---| -| [`SolidSyslogWinsockResolver`](../../api/SolidSyslogWinsockResolver_8h.md) | resolver | -| [`SolidSyslogWinsockAddress`](../../api/SolidSyslogWinsockAddress_8h.md) | address | -| [`SolidSyslogWinsockDatagram`](../../api/SolidSyslogWinsockDatagram_8h.md) | UDP sender | -| [`SolidSyslogWinsockTcpStream`](../../api/SolidSyslogWinsockTcpStream_8h.md) | TCP stream (non-blocking + `select`, bounded connect) | -| [`SolidSyslogWindowsFile`](../../api/SolidSyslogWindowsFile_8h.md) | file | -| [`SolidSyslogWindowsMutex`](../../api/SolidSyslogWindowsMutex_8h.md) | mutex (`CRITICAL_SECTION`) | -| [`SolidSyslogWindowsAtomicCounter`](../../api/SolidSyslogWindowsAtomicCounter_8h.md) | atomic counter (`Interlocked`) | -| [`SolidSyslogWindowsClock`](../../api/SolidSyslogWindowsClock_8h.md) | clock | -| [`SolidSyslogWindowsHostname`](../../api/SolidSyslogWindowsHostname_8h.md) | hostname | -| [`SolidSyslogWindowsProcessId`](../../api/SolidSyslogWindowsProcessId_8h.md) | process-id | -| [`SolidSyslogWindowsSleep`](../../api/SolidSyslogWindowsSleep_8h.md) | sleep | -| [`SolidSyslogWindowsSysUpTime`](../../api/SolidSyslogWindowsSysUpTime_8h.md) | uptime (`GetTickCount64`) | - ## Requirements The MSVC toolchain and Winsock — call `WSAStartup` once at process init before diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 96ba74fd..188363a9 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -28,6 +28,14 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') MODULES_CRUMB = re.compile(r"\[\*\*Modules\*\*\]\([^)]*\)") +# The @file block down to its first blank comment line, and the first sentence +# of it — Doxygen's own implicit brief, so the page and the generated reference +# say the same thing because they read the same source. +FILE_BLOCK = re.compile(r"/\*\*\s*@file\s*\n(.*?)(?:\n[ \t]*\*[ \t]*\n|\*/)", re.DOTALL) +SENTENCE_END = re.compile(r"(?<=[a-z0-9)\]])\.(?:\s|$)") +# The section the manifest replaces. Everything to the next heading is +# generated, so the page source carries the heading alone. +SHIPS = re.compile(r"^(## What it ships[ \t]*\n)(.*?)(?=^## |\Z)", re.MULTILINE | re.DOTALL) # A definition, not a forward declaration — the brace is what distinguishes # `struct SolidSyslogMbedTlsStreamConfig {` from `struct SolidSyslogStream;`. STRUCT_DEF = re.compile(r"struct\s+(SolidSyslog\w+)\s*\{", re.MULTILINE) @@ -56,19 +64,34 @@ def _group_slug(src_uri): return leaf[len(prefix) :] if leaf.startswith(prefix) else None +def _brief(text): + """A header's implicit brief — the first sentence of its @file block.""" + block = FILE_BLOCK.search(text) + if block is None: + return "" + body = " ".join(line.strip().lstrip("*").strip() for line in block.group(1).splitlines()) + split = SENTENCE_END.search(body) + return (body[: split.end()] if split else body).strip() + + def _index(config): - """Return (headers, slugs, labels) built from the platform registry. + """Return (headers, slugs, labels, manifest) built from the platform registry. ``headers`` maps both a header stem and a struct name to its platform, so every generated page for a platform — file or data structure — can be labelled. Core's structs are absent by construction: only the platform Interface directories are scanned. + + ``manifest`` is the ordered (stem, brief) list per platform, which becomes + the *What it ships* table. Generated rather than written, because a list of + filenames and their own briefs is mechanical: hand-keeping one per platform + is clerical work that drifts the moment a header is added. """ root = os.path.dirname(config["config_file_path"]) if root not in _CACHE: with open(os.path.join(root, "CMakeLists.txt"), encoding="utf-8") as cmake: registry = REGISTRY.search(cmake.read()) - headers, slugs, labels = {}, {}, {} + headers, slugs, labels, manifest = {}, {}, {}, {} for token, directory in ROW.findall(registry.group(1)) if registry else []: interface = os.path.join(root, directory, "Interface") if not os.path.isdir(interface): @@ -77,17 +100,29 @@ def _index(config): labels[slug] = _label(root, slug) pack = (labels[slug], slug) slugs[slug] = os.path.isfile(os.path.join(root, "docs", PLATFORM_PREFIX, slug, "setup.md")) - for name in os.listdir(interface): + manifest[slug] = [] + for name in sorted(os.listdir(interface)): if not name.endswith(".h"): continue - headers[name[: -len(".h")]] = pack + stem = name[: -len(".h")] + headers[stem] = pack with open(os.path.join(interface, name), encoding="utf-8") as header: - for struct in STRUCT_DEF.findall(header.read()): - headers[struct] = pack - _CACHE[root] = (headers, slugs, labels) + text = header.read() + manifest[slug].append((stem, _brief(text))) + for struct in STRUCT_DEF.findall(text): + headers[struct] = pack + _CACHE[root] = (headers, slugs, labels, manifest) return _CACHE[root] +def _ships(entries): + """The manifest table: every header the platform publishes, by filename.""" + rows = ["| Header | What it is |", "|---|---|"] + for stem, brief in entries: + rows.append(f"| [`{stem}.h`](../../api/{stem}_8h.md) | {brief.replace('|', r'\|')} |") + return "\n".join(rows) + + def _relativise(markdown, slug, label): """Point the group's own back-link at this build, not at the live site. @@ -117,7 +152,7 @@ def _stem(src_uri): def on_page_markdown(markdown, page, config, files, **kwargs): src_uri = getattr(page.file, "src_uri", None) or page.file.src_path.replace(os.sep, "/") - headers, slugs, labels = _index(config) + headers, slugs, labels, manifest = _index(config) if src_uri.startswith(GENERATED_PREFIX): # A group page arrives titled in Doxygen's vocabulary — "Group @@ -160,6 +195,7 @@ def on_page_markdown(markdown, page, config, files, **kwargs): # One line, so the inline-flex chips sit side by side; placed under the # title rather than above it, so the page still opens with its name. title, _, body = markdown.partition("\n") - return f"{title}\n\n{' '.join(chips)}\n\n{body.lstrip()}" + markdown = f"{title}\n\n{' '.join(chips)}\n\n{body.lstrip()}" + return SHIPS.sub(lambda m: f"{m.group(1)}\n{_ships(manifest[slug])}\n\n", markdown, count=1) return markdown diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 9aa43cf1..37ececac 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -9,6 +9,7 @@ builds perfectly well. """ +import glob import os import sys import types @@ -51,7 +52,7 @@ def test_a_core_data_structure_is_not_labelled(self): def test_a_forward_declaration_does_not_claim_the_struct(self): # SolidSyslogMbedTlsStream.h forward-declares struct SolidSyslogStream; # only definitions map, or Core's Stream would be labelled Mbed TLS. - headers, _slugs, _labels = h._index(CONFIG) + headers, _slugs, _labels, _manifest = h._index(CONFIG) self.assertNotIn("SolidSyslogStream", headers) def test_a_core_header_is_not_labelled(self): @@ -74,7 +75,7 @@ def test_a_platform_with_a_setup_guide_gets_both_chips(self): self.assertIn("[Setup](setup.md){ .ss-chip .ss-chip--setup }", out) def test_every_platform_has_both_chips_today(self): - _, slugs, _labels = h._index(CONFIG) + _, slugs, _labels, _manifest = h._index(CONFIG) for slug in slugs: out = render(f"platforms/{slug}/index.md") self.assertIn(".ss-chip--api", out, slug) @@ -84,8 +85,8 @@ def test_the_setup_chip_appears_only_when_the_page_does(self): # Every platform has a setup page now, so the absent case has to be # constructed. The chip must never point at a page that is not there. root = os.path.dirname(CONFIG["config_file_path"]) - headers, slugs, labels = h._index(CONFIG) - h._CACHE[root] = (headers, {**slugs, "posix": False}, labels) + headers, slugs, labels, manifest = h._index(CONFIG) + h._CACHE[root] = (headers, {**slugs, "posix": False}, labels, manifest) try: out = render("platforms/posix/index.md") finally: @@ -106,6 +107,58 @@ def test_an_ordinary_page_is_untouched(self): self.assertEqual(render("porting.md", markdown), markdown) +class ShipsManifest(unittest.TestCase): + """The *What it ships* table is generated from the platform's own headers, + so a header added to a pack cannot go unlisted and a brief cannot go stale.""" + + PAGE = "# Mbed TLS\n\nIntro.\n\n## What it ships\n\n## Requirements\n\nNeeds things.\n" + + def ships(self, slug="mbedtls", markdown=None): + body = render(f"platforms/{slug}/index.md", markdown or self.PAGE) + return body.split("## What it ships")[1].split("## ")[0] + + def test_every_header_is_listed_by_filename(self): + table = self.ships() + for name in ("SolidSyslogMbedTlsStream.h", "SolidSyslogMbedTlsAesGcmPolicy.h", + "SolidSyslogMbedTlsHmacSha256Policy.h"): + self.assertIn(f"[`{name}`]", table) + + def test_error_headers_are_listed_too(self): + # The group page listed these; nothing else on the platform route did. + self.assertIn("[`SolidSyslogMbedTlsStreamErrors.h`]", self.ships()) + + def test_a_filename_links_to_its_generated_page(self): + self.assertIn("(../../api/SolidSyslogMbedTlsStream_8h.md)", self.ships()) + + def test_the_brief_is_the_headers_own_first_sentence(self): + self.assertIn("Error codes and Source identity for the MbedTlsStream adapter.", self.ships()) + + def test_a_multi_sentence_brief_stops_at_the_first_sentence(self): + row = [line for line in self.ships("atomics").splitlines() + if "SolidSyslogStdAtomicCounter.h" in line] + self.assertEqual(len(row), 1) + self.assertIn("backing the RFC 5424 sequenceId.", row[0]) + self.assertNotIn("Increment runs", row[0]) + + def test_the_heading_survives_and_the_body_is_replaced(self): + out = render("platforms/mbedtls/index.md", self.PAGE) + self.assertIn("## What it ships", out) + self.assertIn("## Requirements", out) + self.assertIn("Needs things.", out) + + def test_every_platform_lists_every_header_it_ships(self): + root = os.path.dirname(CONFIG["config_file_path"]) + _, _slugs, _labels, manifest = h._index(CONFIG) + for slug, entries in manifest.items(): + interface = glob.glob(os.path.join(root, "Platform", "*", "Interface")) + interface = [p for p in interface if os.path.basename(os.path.dirname(p)).lower() == slug] + expected = sorted(n[: -len(".h")] for n in os.listdir(interface[0]) if n.endswith(".h")) + self.assertEqual(sorted(stem for stem, _ in entries), expected, slug) + + def test_a_pipe_in_a_brief_cannot_break_the_table(self): + self.assertNotIn("| |", h._ships([("X", "a | b")]).splitlines()[-1].replace("| X", "")) + + class GroupPages(unittest.TestCase): # mkdoxy titles these in Doxygen's vocabulary and escapes the underscore. RAW = "# Group platform\\_atomics\n\n[**Modules**](index_groups.md) **>** [**platform\\_atomics**](x.md)\n" @@ -140,7 +193,7 @@ def test_a_group_page_gets_no_platform_chip(self): class Registry(unittest.TestCase): def test_every_registered_platform_has_a_docs_folder(self): - _, slugs, _labels = h._index(CONFIG) + _, slugs, _labels, _manifest = h._index(CONFIG) for slug in slugs: self.assertTrue( os.path.isfile(os.path.join(ROOT, "docs", "platforms", slug, "index.md")), @@ -148,7 +201,7 @@ def test_every_registered_platform_has_a_docs_folder(self): ) def test_the_registry_yields_all_ten_platforms(self): - _, slugs, _labels = h._index(CONFIG) + _, slugs, _labels, _manifest = h._index(CONFIG) self.assertEqual(len(slugs), 10, sorted(slugs)) diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index b9b316a2..da9abe22 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -21,10 +21,9 @@ a capability comes from is the capability matrix's job. Naming a sibling couples the two, so the eleventh platform means editing ten pages. Applies to the platform's whole tree and its docs folder alike. -* **Every class its platform ships is on its page.** A public header that is - not an Errors header declares something an integrator can wire, so it must be - reachable from the platform's index page — in the class table, or in a - section of its own where it is not a role. +* **Every header its platform ships is on its page.** The *What it ships* + manifest is generated from the Interface directory, so this asserts the + heading the generator writes into is still there. Run: python3 scripts/check_platform_docs.py """ @@ -164,19 +163,15 @@ def naming_faults(rows, terms): def unlisted_headers(rows): - """Every non-Errors public header must be reachable from the index page.""" + """The *What it ships* manifest is generated by hooks/platform_backlinks.py + from the platform's own Interface directory, so every header is listed by + construction. What can still go wrong is the heading disappearing, which + would silently take the whole manifest with it.""" faults = [] - for token, directory in rows: - slug = token.lower() - page = os.path.join("docs", "platforms", slug, "index.md") - if not os.path.isfile(os.path.join(ROOT, page)): - continue - listed = read(page) - interface = os.path.join(ROOT, directory, "Interface") - for header in sorted(os.listdir(interface)) if os.path.isdir(interface) else []: - stem = header[: -len(".h")] - if header.endswith(".h") and not stem.endswith("Errors") and stem not in listed: - faults.append(f"{token}: {header} is not linked from {page}") + for token, _ in rows: + page = os.path.join("docs", "platforms", token.lower(), "index.md") + if os.path.isfile(os.path.join(ROOT, page)) and "## What it ships" not in read(page): + faults.append(f"{token}: {page} has no '## What it ships' heading for the manifest") return faults From 418fcd4ccb4357a13d4753dca2f69eb7b7487b59 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 14:32:41 +0100 Subject: [PATCH 35/57] docs: S23.22 drop the Doxygen group pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the manifest on the platform page, a group page's only unique content is gone and it publishes a second, worse answer to "what is this platform" — worse because the platform page owns the prose, the meta description, the good /platforms// URL and the section order. Drop them in on_files rather than removing @defgroup from the source. The grouping costs nothing to keep and an integrator running Doxygen over the headers they ship with still gets it; only the published pages go. api/modules.md goes with them, existing only to list them, and mkdoxy's index-of-indexes loses its Modules entry so the strict build stays clean. The platform page loses its API reference chip. The route that mattered — from any API page back to its platform — is unaffected: those chips are built by scanning Platform//Interface, not from @ingroup. Removes the group-page title, breadcrumb and back-link rewriting, which existed only to make Doxygen's vocabulary presentable on pages no reader now sees. Part of #708 --- hooks/platform_backlinks.py | 101 ++++++++++++++----------------- hooks/test_platform_backlinks.py | 69 +++++++++++---------- 2 files changed, 82 insertions(+), 88 deletions(-) diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 188363a9..16551c48 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -1,16 +1,23 @@ -"""MkDocs build hook: the doorways between a platform and its API reference. +"""MkDocs build hook: a platform's page, its manifest, and the way back to it. -Three links, none of them hand-written: +Three jobs, none of them hand-written: * every generated API page for a header under ``Platform//`` gets a chip naming the platform it belongs to — the question a reader arriving from a search result has, and one the API reference could not previously answer; -* every platform page gets a chip to its Doxygen group, the generated reference - for everything that platform declares; -* and one to its setup guide, where the platform has one. - -They are injected rather than written because they are navigation furniture, not -content: generating them keeps the Markdown clean, keeps the links inside +* every platform page gets a chip to its setup guide, and its *What it ships* + section filled with the headers that platform publishes; +* and the Doxygen group pages are dropped from the build. + +The groups are dropped because a platform had two pages answering "what is this +platform", and the group page's only unique content was its Files table — which +is now the manifest, on the platform page, where the prose is hand-editable, the +links are validated, and the URL is the good one. ``@defgroup`` and ``@ingroup`` +stay in the source: they cost nothing, and an integrator running Doxygen over +the headers they ship with still gets the grouping. + +Chips are injected rather than written because they are navigation furniture, +not content: generating them keeps the Markdown clean, keeps the links inside MkDocs' own resolution and validation (raw HTML in a page is neither rewritten nor checked), and means adding a platform needs no markup at all. @@ -27,7 +34,6 @@ REGISTRY = re.compile(r"set\(SOLIDSYSLOG_PLATFORM_REGISTRY(.*?)^\)", re.DOTALL | re.MULTILINE) ROW = re.compile(r'"([^"|]+)\|[^"|]*\|[^"|]*\|[^"|]*\|([^"|]+)\|[^"]*"') -MODULES_CRUMB = re.compile(r"\[\*\*Modules\*\*\]\([^)]*\)") # The @file block down to its first blank comment line, and the first sentence # of it — Doxygen's own implicit brief, so the page and the generated reference # say the same thing because they read the same source. @@ -39,10 +45,16 @@ # A definition, not a forward declaration — the brace is what distinguishes # `struct SolidSyslogMbedTlsStreamConfig {` from `struct SolidSyslogStream;`. STRUCT_DEF = re.compile(r"struct\s+(SolidSyslog\w+)\s*\{", re.MULTILINE) -CANONICAL = "https://docs.cososo.co.uk/solid-syslog/" GENERATED_PREFIX = "api/" PLATFORM_PREFIX = "platforms/" +# The group pages and the index that lists them. Dropped from the build: their +# content lives on the platform pages now, and leaving them would publish two +# answers to the same question. +DROPPED = re.compile(r"^api/(group__platform__\w+|modules)\.md$") +# mkdoxy's index-of-indexes lists Modules among them, so dropping the page +# without dropping the entry leaves a dead link the strict build rejects. +MODULES_ENTRY = re.compile(r"^[ \t]*-[ \t]*\[Modules\]\(modules\.md\)[ \t]*\n", re.MULTILINE) _CACHE = {} @@ -57,13 +69,6 @@ def _label(root, slug): return slug -def _group_slug(src_uri): - """api/group__platform__atomics.md -> atomics, else None.""" - leaf = src_uri[len(GENERATED_PREFIX) : -len(".md")] - prefix = "group__platform__" - return leaf[len(prefix) :] if leaf.startswith(prefix) else None - - def _brief(text): """A header's implicit brief — the first sentence of its @file block.""" block = FILE_BLOCK.search(text) @@ -123,19 +128,6 @@ def _ships(entries): return "\n".join(rows) -def _relativise(markdown, slug, label): - """Point the group's own back-link at this build, not at the live site. - - The .dox block carries the canonical docs URL so that someone reading the - source tree can find the page. Doxygen auto-links it, which on any build - that is not production — a local preview, a pull-request artefact — sends - the reader to the published site instead of the one in front of them. - """ - url = re.escape(f"{CANONICAL}{PLATFORM_PREFIX}{slug}/") - linked = re.compile(rf"\[{url}\]\({url}\)|{url}") - return linked.sub(f"[{label}](../{PLATFORM_PREFIX}{slug}/index.md)", markdown) - - def _stem(src_uri): """The header or struct a generated page documents, else None. @@ -150,32 +142,28 @@ def _stem(src_uri): return None +def on_files(files, config): + """Drop the group pages and the index listing them. + + mkdoxy generates a page per @defgroup whether anything links to it or not. + Left in, they publish a second answer to "what is this platform" — and a + worse one, since the platform page owns the prose, the good URL and the + meta description. Removing them here rather than removing @defgroup from + the source keeps the grouping for anyone running Doxygen themselves. + """ + kept = [f for f in files if not DROPPED.match(f.src_uri.replace(os.sep, "/"))] + # Rebuild the collection MkDocs handed us — it is a Files object with its + # own API downstream, not a plain list. + return type(files)(kept) + + def on_page_markdown(markdown, page, config, files, **kwargs): src_uri = getattr(page.file, "src_uri", None) or page.file.src_path.replace(os.sep, "/") headers, slugs, labels, manifest = _index(config) if src_uri.startswith(GENERATED_PREFIX): - # A group page arrives titled in Doxygen's vocabulary — "Group - # platform_atomics", under a breadcrumb reading "Modules" — where the - # reader has been shown "C11 atomics" everywhere else. Group and Module - # are terms this documentation does not otherwise use, and the group's - # own @defgroup title is ignored by the template, so rewrite all three - # to the platform page's heading. The doubled underscores of the refid - # (group__platform__atomics) do not contain the single-underscore group - # name, so the bare-name replacement cannot damage a link target. - group = _group_slug(src_uri) - if group is not None and group in slugs: - title = f"{labels[group]} platform" - # mkdoxy backslash-escapes the underscore so Markdown does not read - # it as emphasis, so the name appears as platform\_atomics. - name = r"platform\\?_" + re.escape(group) - markdown = re.sub(rf"#\s+Group\s+{name}", f"# {title}", markdown, count=1) - markdown = MODULES_CRUMB.sub(f"[**Platforms**](../{PLATFORM_PREFIX}index.md)", markdown, count=1) - markdown = _relativise(markdown, group, labels[group]) - return re.sub(name, title, markdown) - - if src_uri == f"{GENERATED_PREFIX}modules.md": - return markdown.replace("# Modules", "# Platform API reference", 1) + if src_uri == f"{GENERATED_PREFIX}links.md": + return MODULES_ENTRY.sub("", markdown, count=1) stem = _stem(src_uri) pack = headers.get(stem) if stem else None @@ -189,13 +177,12 @@ def on_page_markdown(markdown, page, config, files, **kwargs): slug = src_uri[len(PLATFORM_PREFIX) : -len("/index.md")] if slug not in slugs: return markdown - chips = [f"[API reference](../../api/group__platform__{slug}.md){{ .ss-chip .ss-chip--api }}"] if slugs[slug]: - chips.append("[Setup](setup.md){ .ss-chip .ss-chip--setup }") - # One line, so the inline-flex chips sit side by side; placed under the - # title rather than above it, so the page still opens with its name. - title, _, body = markdown.partition("\n") - markdown = f"{title}\n\n{' '.join(chips)}\n\n{body.lstrip()}" + chip = "[Setup](setup.md){ .ss-chip .ss-chip--setup }" + # Placed under the title rather than above it, so the page still + # opens with its name. + title, _, body = markdown.partition("\n") + markdown = f"{title}\n\n{chip}\n\n{body.lstrip()}" return SHIPS.sub(lambda m: f"{m.group(1)}\n{_ships(manifest[slug])}\n\n", markdown, count=1) return markdown diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 37ececac..bce131fe 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -69,17 +69,19 @@ def test_chips_go_under_the_title_not_above_it(self): out = render("platforms/mbedtls/index.md") self.assertTrue(out.startswith("# Title\n\n["), out[:40]) - def test_a_platform_with_a_setup_guide_gets_both_chips(self): + def test_a_platform_with_a_setup_guide_gets_the_setup_chip(self): out = render("platforms/mbedtls/index.md") - self.assertIn("[API reference](../../api/group__platform__mbedtls.md){ .ss-chip .ss-chip--api }", out) self.assertIn("[Setup](setup.md){ .ss-chip .ss-chip--setup }", out) - def test_every_platform_has_both_chips_today(self): + def test_no_chip_points_at_a_group_page(self): + # The groups are dropped from the build; a chip to one would 404. + for slug in h._index(CONFIG)[1]: + self.assertNotIn("group__platform__", render(f"platforms/{slug}/index.md"), slug) + + def test_every_platform_has_its_setup_chip_today(self): _, slugs, _labels, _manifest = h._index(CONFIG) for slug in slugs: - out = render(f"platforms/{slug}/index.md") - self.assertIn(".ss-chip--api", out, slug) - self.assertIn(".ss-chip--setup", out, slug) + self.assertIn(".ss-chip--setup", render(f"platforms/{slug}/index.md"), slug) def test_the_setup_chip_appears_only_when_the_page_does(self): # Every platform has a setup page now, so the absent case has to be @@ -91,7 +93,6 @@ def test_the_setup_chip_appears_only_when_the_page_does(self): out = render("platforms/posix/index.md") finally: h._CACHE.pop(root, None) - self.assertIn(".ss-chip--api", out) self.assertNotIn(".ss-chip--setup", out) def test_the_platforms_overview_is_not_a_platform(self): @@ -159,36 +160,42 @@ def test_a_pipe_in_a_brief_cannot_break_the_table(self): self.assertNotIn("| |", h._ships([("X", "a | b")]).splitlines()[-1].replace("| X", "")) -class GroupPages(unittest.TestCase): - # mkdoxy titles these in Doxygen's vocabulary and escapes the underscore. - RAW = "# Group platform\\_atomics\n\n[**Modules**](index_groups.md) **>** [**platform\\_atomics**](x.md)\n" +class DroppedGroupPages(unittest.TestCase): + """A platform answers "what is this" in one place. The group pages said it + a second time, worse, so they never reach the site.""" - def test_the_doxygen_title_becomes_the_platform_name(self): - out = render("api/group__platform__atomics.md", self.RAW) - self.assertTrue(out.startswith("# C11 atomics platform"), out[:50]) + def files_after(self, *src_uris): + kept = h.on_files([types.SimpleNamespace(src_uri=u) for u in src_uris], CONFIG) + return [f.src_uri for f in kept] - def test_group_and_modules_do_not_reach_the_reader(self): - out = render("api/group__platform__atomics.md", self.RAW) - self.assertNotIn("Group", out) - self.assertNotIn("Modules", out) - self.assertNotIn("platform_atomics", out.replace("\\", "")) + def test_every_group_page_is_dropped(self): + _, slugs, _labels, _manifest = h._index(CONFIG) + pages = [f"api/group__platform__{slug}.md" for slug in slugs] + self.assertEqual(self.files_after(*pages), []) - def test_the_breadcrumb_points_at_the_platforms_overview(self): - out = render("api/group__platform__atomics.md", self.RAW) - self.assertIn("[**Platforms**](../platforms/index.md)", out) + def test_the_modules_index_goes_with_them(self): + # It exists only to list the groups, so it would link ten dead pages. + self.assertEqual(self.files_after("api/modules.md"), []) - def test_the_back_link_stays_inside_this_build(self): - raw = self.RAW + ( - "Obligations: [https://docs.cososo.co.uk/solid-syslog/platforms/atomics/]" - "(https://docs.cososo.co.uk/solid-syslog/platforms/atomics/)\n" + def test_a_header_page_is_kept(self): + self.assertEqual( + self.files_after("api/SolidSyslogMbedTlsStream_8h.md"), + ["api/SolidSyslogMbedTlsStream_8h.md"], ) - out = render("api/group__platform__atomics.md", raw) - self.assertIn("[C11 atomics](../platforms/atomics/index.md)", out) - self.assertNotIn("docs.cososo.co.uk", out) - def test_a_group_page_gets_no_platform_chip(self): - out = render("api/group__platform__atomics.md", self.RAW) - self.assertNotIn("ss-chip", out) + def test_a_platform_page_is_kept(self): + self.assertEqual(self.files_after("platforms/mbedtls/index.md"), ["platforms/mbedtls/index.md"]) + + def test_a_similarly_named_page_is_not_caught(self): + # group__platform__* only — a Core group, were one added, would stay. + self.assertEqual(self.files_after("api/group__core.md"), ["api/group__core.md"]) + + def test_the_index_of_indexes_stops_linking_modules(self): + raw = " - [Related Pages](pages.md)\n - [Modules](modules.md)\n - [Files](files.md)\n" + out = render("api/links.md", raw) + self.assertNotIn("modules.md", out) + self.assertIn("[Files](files.md)", out) + self.assertIn("[Related Pages](pages.md)", out) class Registry(unittest.TestCase): From f09fbdcdd941b156f08d122f1155e3b9cca8f9f9 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 14:43:35 +0100 Subject: [PATCH 36/57] docs: S23.22 chip the setup page back, and drop the unused Doxygen groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doorway only went one way. A platform page linked its setup guide; the setup guide linked nothing back, so a reader arriving at setup from a search result had no route to what the platform is. It now carries the same chip in reverse. Remove @defgroup and @ingroup. Nothing consumes them: the group pages they fed are gone, and the chips from an API page back to its platform are built by scanning Platform//Interface, never from the tags. Keeping them was justified by an integrator who might run Doxygen over the headers themselves — speculation, not a use. Ten .dox files, 78 tags, the mkdoxy *.dox file pattern, and the two assertions in check_platform_docs.py that held them in place. MISRA findings identical to HEAD; no suppression moved, every Interface entry being Core's. Part of #708 --- .../Interface/SolidSyslogStdAtomicCounter.h | 4 +--- .../SolidSyslogStdAtomicCounterErrors.h | 4 +--- Platform/Atomics/SolidSyslogAtomicsPlatform.dox | 9 --------- Platform/FatFs/Interface/SolidSyslogFatFsFile.h | 4 +--- .../FatFs/Interface/SolidSyslogFatFsFileErrors.h | 4 +--- Platform/FatFs/SolidSyslogFatFsPlatform.dox | 8 -------- .../Interface/SolidSyslogFreeRtosMutex.h | 4 +--- .../Interface/SolidSyslogFreeRtosMutexErrors.h | 4 +--- .../Interface/SolidSyslogFreeRtosSysUpTime.h | 4 +--- .../FreeRtos/SolidSyslogFreeRtosPlatform.dox | 8 -------- .../Interface/SolidSyslogLwipRawAddress.h | 4 +--- .../Interface/SolidSyslogLwipRawAddressErrors.h | 4 +--- .../Interface/SolidSyslogLwipRawDatagram.h | 4 +--- .../Interface/SolidSyslogLwipRawDatagramErrors.h | 4 +--- .../Interface/SolidSyslogLwipRawDnsResolver.h | 4 +--- .../SolidSyslogLwipRawDnsResolverErrors.h | 4 +--- .../Interface/SolidSyslogLwipRawMarshal.h | 4 +--- .../Interface/SolidSyslogLwipRawResolver.h | 4 +--- .../Interface/SolidSyslogLwipRawResolverErrors.h | 4 +--- .../Interface/SolidSyslogLwipRawTcpStream.h | 4 +--- .../SolidSyslogLwipRawTcpStreamErrors.h | 4 +--- Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox | 9 --------- .../Interface/SolidSyslogMbedTlsAesGcmPolicy.h | 4 +--- .../SolidSyslogMbedTlsAesGcmPolicyErrors.h | 4 +--- .../SolidSyslogMbedTlsHmacSha256Policy.h | 4 +--- .../SolidSyslogMbedTlsHmacSha256PolicyErrors.h | 4 +--- .../MbedTls/Interface/SolidSyslogMbedTlsStream.h | 4 +--- .../Interface/SolidSyslogMbedTlsStreamErrors.h | 4 +--- Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox | 9 --------- .../Interface/SolidSyslogOpenSslAesGcmPolicy.h | 4 +--- .../SolidSyslogOpenSslAesGcmPolicyErrors.h | 4 +--- .../SolidSyslogOpenSslHmacSha256Policy.h | 4 +--- .../SolidSyslogOpenSslHmacSha256PolicyErrors.h | 4 +--- .../OpenSsl/Interface/SolidSyslogTlsStream.h | 4 +--- .../Interface/SolidSyslogTlsStreamErrors.h | 4 +--- Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox | 9 --------- .../PlusFat/Interface/SolidSyslogPlusFatFile.h | 4 +--- .../Interface/SolidSyslogPlusFatFileErrors.h | 4 +--- Platform/PlusFat/SolidSyslogPlusFatPlatform.dox | 8 -------- .../Interface/SolidSyslogPlusTcpAddress.h | 4 +--- .../Interface/SolidSyslogPlusTcpAddressErrors.h | 4 +--- .../Interface/SolidSyslogPlusTcpDatagram.h | 4 +--- .../Interface/SolidSyslogPlusTcpDatagramErrors.h | 4 +--- .../Interface/SolidSyslogPlusTcpResolver.h | 4 +--- .../Interface/SolidSyslogPlusTcpResolverErrors.h | 4 +--- .../Interface/SolidSyslogPlusTcpTcpStream.h | 4 +--- .../SolidSyslogPlusTcpTcpStreamErrors.h | 4 +--- Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox | 8 -------- .../Interface/SolidSyslogGetAddrInfoResolver.h | 4 +--- .../SolidSyslogGetAddrInfoResolverErrors.h | 4 +--- .../Posix/Interface/SolidSyslogPosixAddress.h | 4 +--- .../Interface/SolidSyslogPosixAddressErrors.h | 4 +--- Platform/Posix/Interface/SolidSyslogPosixClock.h | 4 +--- .../Posix/Interface/SolidSyslogPosixDatagram.h | 4 +--- .../Interface/SolidSyslogPosixDatagramErrors.h | 4 +--- Platform/Posix/Interface/SolidSyslogPosixFile.h | 4 +--- .../Posix/Interface/SolidSyslogPosixFileErrors.h | 4 +--- .../Posix/Interface/SolidSyslogPosixHostname.h | 4 +--- .../SolidSyslogPosixMessageQueueBuffer.h | 4 +--- .../SolidSyslogPosixMessageQueueBufferErrors.h | 4 +--- Platform/Posix/Interface/SolidSyslogPosixMutex.h | 4 +--- .../Interface/SolidSyslogPosixMutexErrors.h | 4 +--- .../Posix/Interface/SolidSyslogPosixProcessId.h | 4 +--- Platform/Posix/Interface/SolidSyslogPosixSleep.h | 4 +--- .../Posix/Interface/SolidSyslogPosixSysUpTime.h | 4 +--- .../Posix/Interface/SolidSyslogPosixTcpStream.h | 4 +--- .../Interface/SolidSyslogPosixTcpStreamErrors.h | 4 +--- Platform/Posix/SolidSyslogPosixPlatform.dox | 9 --------- .../Interface/SolidSyslogWindowsAtomicCounter.h | 4 +--- .../SolidSyslogWindowsAtomicCounterErrors.h | 4 +--- .../Windows/Interface/SolidSyslogWindowsClock.h | 4 +--- .../Windows/Interface/SolidSyslogWindowsFile.h | 4 +--- .../Interface/SolidSyslogWindowsFileErrors.h | 4 +--- .../Interface/SolidSyslogWindowsHostname.h | 4 +--- .../Windows/Interface/SolidSyslogWindowsMutex.h | 4 +--- .../Interface/SolidSyslogWindowsMutexErrors.h | 4 +--- .../Interface/SolidSyslogWindowsProcessId.h | 4 +--- .../Windows/Interface/SolidSyslogWindowsSleep.h | 4 +--- .../Interface/SolidSyslogWindowsSysUpTime.h | 4 +--- .../Interface/SolidSyslogWinsockAddress.h | 4 +--- .../Interface/SolidSyslogWinsockAddressErrors.h | 4 +--- .../Interface/SolidSyslogWinsockDatagram.h | 4 +--- .../Interface/SolidSyslogWinsockDatagramErrors.h | 4 +--- .../Interface/SolidSyslogWinsockResolver.h | 4 +--- .../Interface/SolidSyslogWinsockResolverErrors.h | 4 +--- .../Interface/SolidSyslogWinsockTcpStream.h | 4 +--- .../SolidSyslogWinsockTcpStreamErrors.h | 4 +--- Platform/Windows/SolidSyslogWindowsPlatform.dox | 9 --------- hooks/platform_backlinks.py | 12 ++++++++++-- hooks/test_platform_backlinks.py | 16 +++++++++++++--- mkdocs.yml | 5 +---- scripts/check_platform_docs.py | 14 +------------- 92 files changed, 103 insertions(+), 342 deletions(-) delete mode 100644 Platform/Atomics/SolidSyslogAtomicsPlatform.dox delete mode 100644 Platform/FatFs/SolidSyslogFatFsPlatform.dox delete mode 100644 Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox delete mode 100644 Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox delete mode 100644 Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox delete mode 100644 Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox delete mode 100644 Platform/PlusFat/SolidSyslogPlusFatPlatform.dox delete mode 100644 Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox delete mode 100644 Platform/Posix/SolidSyslogPosixPlatform.dox delete mode 100644 Platform/Windows/SolidSyslogWindowsPlatform.dox diff --git a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h index 553a30a2..afc0c157 100644 --- a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h +++ b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounter.h @@ -2,9 +2,7 @@ * An AtomicCounter over C11 , backing the RFC 5424 sequenceId. * Increment runs an atomic_compare_exchange_strong_explicit CAS loop * on an _Atomic uint32_t; the sequence is wrap-aware in [1, 2^31 - 1] and - * skips zero on wrap, so a returned value is never 0. - * - * @ingroup platform_atomics */ + * skips zero on wrap, so a returned value is never 0. */ #ifndef SOLIDSYSLOGSTDATOMICCOUNTER_H #define SOLIDSYSLOGSTDATOMICCOUNTER_H diff --git a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h index e42958f7..0f49be64 100644 --- a/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h +++ b/Platform/Atomics/Interface/SolidSyslogStdAtomicCounterErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the StdAtomicCounter adapter. - * - * @ingroup platform_atomics */ + * Error codes and Source identity for the StdAtomicCounter adapter. */ #ifndef SOLIDSYSLOGSTDATOMICCOUNTERERRORS_H #define SOLIDSYSLOGSTDATOMICCOUNTERERRORS_H diff --git a/Platform/Atomics/SolidSyslogAtomicsPlatform.dox b/Platform/Atomics/SolidSyslogAtomicsPlatform.dox deleted file mode 100644 index d3fd7ed7..00000000 --- a/Platform/Atomics/SolidSyslogAtomicsPlatform.dox +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @defgroup platform_atomics C11 atomics platform - * @brief The AtomicCounter role over — the sequenceId source on any - * target with a C11 compiler, with no OS dependency. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/atomics/ - */ diff --git a/Platform/FatFs/Interface/SolidSyslogFatFsFile.h b/Platform/FatFs/Interface/SolidSyslogFatFsFile.h index 5bd5e913..3a3fafc2 100644 --- a/Platform/FatFs/Interface/SolidSyslogFatFsFile.h +++ b/Platform/FatFs/Interface/SolidSyslogFatFsFile.h @@ -3,9 +3,7 @@ * SolidSyslogFile vtable, for a file-backed BlockDevice or Store. f_sync runs * after every successful write so a power loss never loses a record the * BlockStore already claimed it stored. The integrator supplies diskio.c (and - * ffsystem.c when FF_FS_REENTRANT=1). - * - * @ingroup platform_fatfs */ + * ffsystem.c when FF_FS_REENTRANT=1). */ #ifndef SOLIDSYSLOGFATFSFILE_H #define SOLIDSYSLOGFATFSFILE_H diff --git a/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h b/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h index c916f281..7342284f 100644 --- a/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h +++ b/Platform/FatFs/Interface/SolidSyslogFatFsFileErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the FatFsFile adapter. - * - * @ingroup platform_fatfs */ + * Error codes and Source identity for the FatFsFile adapter. */ #ifndef SOLIDSYSLOGFATFSFILEERRORS_H #define SOLIDSYSLOGFATFSFILEERRORS_H diff --git a/Platform/FatFs/SolidSyslogFatFsPlatform.dox b/Platform/FatFs/SolidSyslogFatFsPlatform.dox deleted file mode 100644 index fb643074..00000000 --- a/Platform/FatFs/SolidSyslogFatFsPlatform.dox +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @defgroup platform_fatfs FatFs platform - * @brief The File role over ChaN FatFs, beneath a BlockDevice. RTOS-agnostic. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/fatfs/ - */ diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h index ea79b0d0..2ab56c67 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutex.h @@ -1,9 +1,7 @@ /** @file * A Mutex wrapping a statically-allocated FreeRTOS mutex semaphore, for * thread-safe buffers and pools on a FreeRTOS target. Requires - * configSUPPORT_STATIC_ALLOCATION=1. - * - * @ingroup platform_freertos */ + * configSUPPORT_STATIC_ALLOCATION=1. */ #ifndef SOLIDSYSLOGFREERTOSMUTEX_H #define SOLIDSYSLOGFREERTOSMUTEX_H diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h index 42adf7ad..3111c157 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosMutexErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the FreeRtosMutex adapter. - * - * @ingroup platform_freertos */ + * Error codes and Source identity for the FreeRtosMutex adapter. */ #ifndef SOLIDSYSLOGFREERTOSMUTEXERRORS_H #define SOLIDSYSLOGFREERTOSMUTEXERRORS_H diff --git a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h index 04111783..65d3c3a8 100644 --- a/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h +++ b/Platform/FreeRtos/Interface/SolidSyslogFreeRtosSysUpTime.h @@ -1,8 +1,6 @@ /** @file * The FreeRTOS SolidSyslogSysUpTimeFunction, for the MetaSd structured-data - * element. - * - * @ingroup platform_freertos */ + * element. */ #ifndef SOLIDSYSLOGFREERTOSSYSUPTIME_H #define SOLIDSYSLOGFREERTOSSYSUPTIME_H diff --git a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox b/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox deleted file mode 100644 index c377a5d3..00000000 --- a/Platform/FreeRtos/SolidSyslogFreeRtosPlatform.dox +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @defgroup platform_freertos FreeRTOS platform - * @brief Kernel primitives: the Mutex role and the sysUpTime callback. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/freertos/ - */ diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h index 466dc5fc..f15eb7ea 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddress.h @@ -3,9 +3,7 @@ * * A Resolver writes the resolved endpoint into it; a Datagram or TcpStream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. - * - * @ingroup platform_lwipraw */ + * object. */ #ifndef SOLIDSYSLOGLWIPRAWADDRESS_H #define SOLIDSYSLOGLWIPRAWADDRESS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h index c0112797..3806acc3 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawAddressErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the LwipRawAddress adapter. - * - * @ingroup platform_lwipraw */ + * Error codes and Source identity for the LwipRawAddress adapter. */ #ifndef SOLIDSYSLOGLWIPRAWADDRESSERRORS_H #define SOLIDSYSLOGLWIPRAWADDRESSERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h index 5aae31cd..14b46dbb 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h @@ -11,9 +11,7 @@ * caller's buffer instead of copying it, safe because the buffer outlives the * synchronous hop. Reports SENT on udp_sendto success, else FAILED. Cache-miss * recovery is left to lwIP's ARP_QUEUEING. - * - MaxPayload returns the IPv6-safe default. - * - * @ingroup platform_lwipraw */ + * - MaxPayload returns the IPv6-safe default. */ #ifndef SOLIDSYSLOGLWIPRAWDATAGRAM_H #define SOLIDSYSLOGLWIPRAWDATAGRAM_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h index 3cfa97a5..b2c665cc 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the LwipRawDatagram adapter. - * - * @ingroup platform_lwipraw */ + * Error codes and Source identity for the LwipRawDatagram adapter. */ #ifndef SOLIDSYSLOGLWIPRAWDATAGRAMERRORS_H #define SOLIDSYSLOGLWIPRAWDATAGRAMERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h index 10a57663..4036bfd3 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolver.h @@ -18,9 +18,7 @@ * - Any other immediate rejection, or the deadline elapsing, fails the Resolve * so the caller's unresolved-host error path runs. * - * The transport is ignored. Requires LWIP_DNS=1. See docs/platforms/lwipraw/setup.md. - * - * @ingroup platform_lwipraw */ + * The transport is ignored. Requires LWIP_DNS=1. See docs/platforms/lwipraw/setup.md. */ #ifndef SOLIDSYSLOGLWIPRAWDNSRESOLVER_H #define SOLIDSYSLOGLWIPRAWDNSRESOLVER_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h index 63c2dfd0..57adbd8b 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawDnsResolverErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the LwipRawDnsResolver adapter. - * - * @ingroup platform_lwipraw */ + * Error codes and Source identity for the LwipRawDnsResolver adapter. */ #ifndef SOLIDSYSLOGLWIPRAWDNSRESOLVERERRORS_H #define SOLIDSYSLOGLWIPRAWDNSRESOLVERERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h index e53ab3a8..3fad20aa 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h @@ -16,9 +16,7 @@ * The marshal MUST invoke its callback synchronously, before it returns: the * wrapper reads results the callback writes immediately after the hop, so an * asynchronous marshal is caller error. tcpip_callback_with_block(.., block=1) - * honours this; a bare tcpip_callback(..) does not. See docs/platforms/lwipraw/setup.md. - * - * @ingroup platform_lwipraw */ + * honours this; a bare tcpip_callback(..) does not. See docs/platforms/lwipraw/setup.md. */ #ifndef SOLIDSYSLOGLWIPRAWMARSHAL_H #define SOLIDSYSLOGLWIPRAWMARSHAL_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h index d654d8c7..a67defd1 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolver.h @@ -6,9 +6,7 @@ * whatever ipaddr_aton accepts is accepted, whatever it rejects (DNS names, * the empty string, and other non-address text) fails the Resolve, so the caller's * unresolved-host error path runs. The transport is ignored. The parse touches - * no lwIP core state, so unlike the DNS sibling it takes no marshal hop. - * - * @ingroup platform_lwipraw */ + * no lwIP core state, so unlike the DNS sibling it takes no marshal hop. */ #ifndef SOLIDSYSLOGLWIPRAWRESOLVER_H #define SOLIDSYSLOGLWIPRAWRESOLVER_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h index b577f0bb..1d066585 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawResolverErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the LwipRawResolver adapter. - * - * @ingroup platform_lwipraw */ + * Error codes and Source identity for the LwipRawResolver adapter. */ #ifndef SOLIDSYSLOGLWIPRAWRESOLVERERRORS_H #define SOLIDSYSLOGLWIPRAWRESOLVERERRORS_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h index 54a60d56..0215ac15 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStream.h @@ -28,9 +28,7 @@ * callback nulls the pcb pointer, and Close only calls tcp_close when the * pointer is still live, so a released pcb is never closed twice. Accepted * pbufs are always freed on close regardless of pcb state. See - * docs/platforms/lwipraw/setup.md for the full integrator guide. - * - * @ingroup platform_lwipraw */ + * docs/platforms/lwipraw/setup.md for the full integrator guide. */ #ifndef SOLIDSYSLOGLWIPRAWTCPSTREAM_H #define SOLIDSYSLOGLWIPRAWTCPSTREAM_H diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h index b7adcdde..556adb41 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawTcpStreamErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the LwipRawTcpStream adapter. - * - * @ingroup platform_lwipraw */ + * Error codes and Source identity for the LwipRawTcpStream adapter. */ #ifndef SOLIDSYSLOGLWIPRAWTCPSTREAMERRORS_H #define SOLIDSYSLOGLWIPRAWTCPSTREAMERRORS_H diff --git a/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox b/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox deleted file mode 100644 index 8f5dd280..00000000 --- a/Platform/LwipRaw/SolidSyslogLwipRawPlatform.dox +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @defgroup platform_lwipraw lwIP (Raw API) platform - * @brief Resolver, Datagram and Stream over the lwIP Raw API, compiled against your - * lwipopts.h. Wraps the Raw API specifically, not netconn or sockets. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/lwipraw/ - */ diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h index 5b55ded7..e0110d83 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicy.h @@ -14,9 +14,7 @@ * and returns false silently; only a genuine mbedTLS fault is reported. * * The key is fetched on demand via GetKey and wiped after every operation — it - * is never stored on the instance. - * - * @ingroup platform_mbedtls */ + * is never stored on the instance. */ #ifndef SOLIDSYSLOGMBEDTLSAESGCMPOLICY_H #define SOLIDSYSLOGMBEDTLSAESGCMPOLICY_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h index 9541e313..f6f8413f 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsAesGcmPolicyErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the MbedTlsAesGcmPolicy. - * - * @ingroup platform_mbedtls */ + * Error codes and Source identity for the MbedTlsAesGcmPolicy. */ #ifndef SOLIDSYSLOGMBEDTLSAESGCMPOLICYERRORS_H #define SOLIDSYSLOGMBEDTLSAESGCMPOLICYERRORS_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h index 565d56b8..53785390 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256Policy.h @@ -14,9 +14,7 @@ * expected tamper verdict — and is not reported. * * The key is fetched on demand via GetKey and wiped after every computation — it - * is never stored on the instance. - * - * @ingroup platform_mbedtls */ + * is never stored on the instance. */ #ifndef SOLIDSYSLOGMBEDTLSHMACSHA256POLICY_H #define SOLIDSYSLOGMBEDTLSHMACSHA256POLICY_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h index f3ae9c3b..c8ae3010 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHmacSha256PolicyErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the MbedTlsHmacSha256Policy. - * - * @ingroup platform_mbedtls */ + * Error codes and Source identity for the MbedTlsHmacSha256Policy. */ #ifndef SOLIDSYSLOGMBEDTLSHMACSHA256POLICYERRORS_H #define SOLIDSYSLOGMBEDTLSHMACSHA256POLICYERRORS_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index 46035b03..bd15816e 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -27,9 +27,7 @@ * ssl_config / ssl_context state and never calls process-global mbedTLS APIs * (platform setup/teardown, psa_crypto_init, threading-alt, debug hooks), so it * drops into an integrator process that already uses Mbed TLS elsewhere. See - * docs/platforms/mbedtls/setup.md. - * - * @ingroup platform_mbedtls */ + * docs/platforms/mbedtls/setup.md. */ #ifndef SOLIDSYSLOGMBEDTLSSTREAM_H #define SOLIDSYSLOGMBEDTLSSTREAM_H diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index a24f7edc..cdd68d70 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the MbedTlsStream adapter. - * - * @ingroup platform_mbedtls */ + * Error codes and Source identity for the MbedTlsStream adapter. */ #ifndef SOLIDSYSLOGMBEDTLSSTREAMERRORS_H #define SOLIDSYSLOGMBEDTLSSTREAMERRORS_H diff --git a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox b/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox deleted file mode 100644 index 2495b663..00000000 --- a/Platform/MbedTls/SolidSyslogMbedTlsPlatform.dox +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @defgroup platform_mbedtls Mbed TLS platform - * @brief TLS transport and keyed at-rest crypto via Mbed TLS, sized for - * constrained embedded targets. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/mbedtls/ - */ diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h index 66d49172..1643d42a 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicy.h @@ -15,9 +15,7 @@ * - OpenRecord decrypts the body and verifies the tag over header + ciphertext. * A tag mismatch is the expected tamper-detected outcome and returns false * silently; only a genuine OpenSSL error is reported. The key is likewise - * fetched on demand and wiped. - * - * @ingroup platform_openssl */ + * fetched on demand and wiped. */ #ifndef SOLIDSYSLOGOPENSSLAESGCMPOLICY_H #define SOLIDSYSLOGOPENSSLAESGCMPOLICY_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h index 21e49257..bef34848 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslAesGcmPolicyErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the OpenSslAesGcmPolicy adapter. - * - * @ingroup platform_openssl */ + * Error codes and Source identity for the OpenSslAesGcmPolicy adapter. */ #ifndef SOLIDSYSLOGOPENSSLAESGCMPOLICYERRORS_H #define SOLIDSYSLOGOPENSSLAESGCMPOLICYERRORS_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h index e992822d..b7d40d0d 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256Policy.h @@ -14,9 +14,7 @@ * - It fails closed — returns false, so nothing is sealed / verified — if the * key is unavailable, shorter than 32 bytes (RFC 2104 / NIST SP 800-107: an * HMAC key should be at least the hash output length), or the HMAC computation - * fails. - * - * @ingroup platform_openssl */ + * fails. */ #ifndef SOLIDSYSLOGOPENSSLHMACSHA256POLICY_H #define SOLIDSYSLOGOPENSSLHMACSHA256POLICY_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h index b7b116bd..05caa880 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogOpenSslHmacSha256PolicyErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the OpenSslHmacSha256Policy adapter. - * - * @ingroup platform_openssl */ + * Error codes and Source identity for the OpenSslHmacSha256Policy adapter. */ #ifndef SOLIDSYSLOGOPENSSLHMACSHA256POLICYERRORS_H #define SOLIDSYSLOGOPENSSLHMACSHA256POLICYERRORS_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h index f4fe9f86..aa282160 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h @@ -21,9 +21,7 @@ * - Read returns the bytes read, 0 for would-block (WANT_READ, connection kept), * or closes on anything else — including a mid-stream WANT_WRITE (renegotiation) * which fail-fast semantics treat as a transport failure; store-and-forward - * replays after the reopen. - * - * @ingroup platform_openssl */ + * replays after the reopen. */ #ifndef SOLIDSYSLOGTLSSTREAM_H #define SOLIDSYSLOGTLSSTREAM_H diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h index affada9a..5d1287ea 100644 --- a/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStreamErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the TlsStream adapter. - * - * @ingroup platform_openssl */ + * Error codes and Source identity for the TlsStream adapter. */ #ifndef SOLIDSYSLOGTLSSTREAMERRORS_H #define SOLIDSYSLOGTLSSTREAMERRORS_H diff --git a/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox b/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox deleted file mode 100644 index 4eb60e23..00000000 --- a/Platform/OpenSsl/SolidSyslogOpenSslPlatform.dox +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @defgroup platform_openssl OpenSSL platform - * @brief TLS transport for the Stream role and keyed at-rest crypto for the - * SecurityPolicy role, on hosted targets. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/openssl/ - */ diff --git a/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h b/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h index f4443809..cc6c4b52 100644 --- a/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h +++ b/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h @@ -10,9 +10,7 @@ * so a power loss never loses a record the BlockStore was told had been * stored. Open-state is carried by the FF_FILE* sentinel — no separate flag. * Plus-FAT is FreeRTOS-coupled; the integrator supplies the FF_Disk_t media - * driver and FreeRTOSFATConfig.h. - * - * @ingroup platform_plusfat */ + * driver and FreeRTOSFATConfig.h. */ #ifndef SOLIDSYSLOGPLUSFATFILE_H #define SOLIDSYSLOGPLUSFATFILE_H diff --git a/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h b/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h index cc7340fa..eac2b9ad 100644 --- a/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h +++ b/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PlusFatFile adapter. - * - * @ingroup platform_plusfat */ + * Error codes and Source identity for the PlusFatFile adapter. */ #ifndef SOLIDSYSLOGPLUSFATFILEERRORS_H #define SOLIDSYSLOGPLUSFATFILEERRORS_H diff --git a/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox b/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox deleted file mode 100644 index 7d3270c4..00000000 --- a/Platform/PlusFat/SolidSyslogPlusFatPlatform.dox +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @defgroup platform_plusfat FreeRTOS-Plus-FAT platform - * @brief The File role over FreeRTOS-Plus-FAT's ff_stdio, beneath a BlockDevice. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/plusfat/ - */ diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h index 758d441d..3c259289 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddress.h @@ -4,9 +4,7 @@ * * A Resolver writes the resolved IPv4 endpoint into it; a Datagram or Stream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. - * - * @ingroup platform_plustcp */ + * object. */ #ifndef SOLIDSYSLOGPLUSTCPADDRESS_H #define SOLIDSYSLOGPLUSTCPADDRESS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h index c19717a0..2ed03eb9 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpAddressErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PlusTcpAddress adapter. - * - * @ingroup platform_plustcp */ + * Error codes and Source identity for the PlusTcpAddress adapter. */ #ifndef SOLIDSYSLOGPLUSTCPADDRESSERRORS_H #define SOLIDSYSLOGPLUSTCPADDRESSERRORS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h index fd939a03..03ccac9a 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagram.h @@ -8,9 +8,7 @@ * cold-start packet would otherwise be dropped at the IP layer; if the reply * is late the send is left to fail, since UDP is best-effort and retry belongs * in the store-and-forward layer above. MaxPayload is the fixed IPv6-safe - * default. - * - * @ingroup platform_plustcp */ + * default. */ #ifndef SOLIDSYSLOGPLUSTCPDATAGRAM_H #define SOLIDSYSLOGPLUSTCPDATAGRAM_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h index ddbf68e3..44fea6cc 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpDatagramErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PlusTcpDatagram adapter. - * - * @ingroup platform_plustcp */ + * Error codes and Source identity for the PlusTcpDatagram adapter. */ #ifndef SOLIDSYSLOGPLUSTCPDATAGRAMERRORS_H #define SOLIDSYSLOGPLUSTCPDATAGRAMERRORS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h index b567d539..129b9b12 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolver.h @@ -5,9 +5,7 @@ * FreeRTOS_getaddrinfo and writes it, with the per-call port, into the * destination SolidSyslogAddress; the requested transport selects the socktype * hint (stream for TCP, datagram otherwise). A failed lookup returns false, so - * the caller's unresolved-host error path runs. - * - * @ingroup platform_plustcp */ + * the caller's unresolved-host error path runs. */ #ifndef SOLIDSYSLOGPLUSTCPRESOLVER_H #define SOLIDSYSLOGPLUSTCPRESOLVER_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h index cf7b4656..fff35238 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpResolverErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PlusTcpResolver adapter. - * - * @ingroup platform_plustcp */ + * Error codes and Source identity for the PlusTcpResolver adapter. */ #ifndef SOLIDSYSLOGPLUSTCPRESOLVERERRORS_H #define SOLIDSYSLOGPLUSTCPRESOLVERERRORS_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h index a3813e75..2d86300c 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStream.h @@ -15,9 +15,7 @@ * or any error is taken as a dead connection, so the stream closes itself and * the sender reconnects on its next pass. * - Read returns the bytes read, 0 for would-block (RCVTIMEO=0, connection - * kept), or tears the connection down on error. - * - * @ingroup platform_plustcp */ + * kept), or tears the connection down on error. */ #ifndef SOLIDSYSLOGPLUSTCPTCPSTREAM_H #define SOLIDSYSLOGPLUSTCPTCPSTREAM_H diff --git a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h index 4d258c6c..16f0933f 100644 --- a/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h +++ b/Platform/PlusTcp/Interface/SolidSyslogPlusTcpTcpStreamErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PlusTcpTcpStream adapter. - * - * @ingroup platform_plustcp */ + * Error codes and Source identity for the PlusTcpTcpStream adapter. */ #ifndef SOLIDSYSLOGPLUSTCPTCPSTREAMERRORS_H #define SOLIDSYSLOGPLUSTCPTCPSTREAMERRORS_H diff --git a/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox b/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox deleted file mode 100644 index 27383212..00000000 --- a/Platform/PlusTcp/SolidSyslogPlusTcpPlatform.dox +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @defgroup platform_plustcp FreeRTOS-Plus-TCP platform - * @brief Resolver, Datagram and Stream over the FreeRTOS-Plus-TCP stack. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/plustcp/ - */ diff --git a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h index 352ebd43..824d60bd 100644 --- a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h +++ b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolver.h @@ -5,9 +5,7 @@ * synchronous getaddrinfo call and writes it into the destination * SolidSyslogAddress; the requested transport selects the socktype hint. A * failed lookup returns false, so the caller's unresolved-host error path - * runs. - * - * @ingroup platform_posix */ + * runs. */ #ifndef SOLIDSYSLOGGETADDRINFORESOLVERH #define SOLIDSYSLOGGETADDRINFORESOLVERH diff --git a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h index 4dfcccc7..31a45333 100644 --- a/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h +++ b/Platform/Posix/Interface/SolidSyslogGetAddrInfoResolverErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the GetAddrInfoResolver adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the GetAddrInfoResolver adapter. */ #ifndef SOLIDSYSLOGGETADDRINFORESOLVERERRORS_H #define SOLIDSYSLOGGETADDRINFORESOLVERERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixAddress.h b/Platform/Posix/Interface/SolidSyslogPosixAddress.h index d508e09b..4d03a6ca 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixAddress.h +++ b/Platform/Posix/Interface/SolidSyslogPosixAddress.h @@ -3,9 +3,7 @@ * * A Resolver writes the resolved IPv4 endpoint into it; a Datagram or Stream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. - * - * @ingroup platform_posix */ + * object. */ #ifndef SOLIDSYSLOGPOSIXADDRESS_H #define SOLIDSYSLOGPOSIXADDRESS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h b/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h index c78ac880..35be3051 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixAddressErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PosixAddress adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the PosixAddress adapter. */ #ifndef SOLIDSYSLOGPOSIXADDRESSERRORS_H #define SOLIDSYSLOGPOSIXADDRESSERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixClock.h b/Platform/Posix/Interface/SolidSyslogPosixClock.h index 4186ada6..3015a7db 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixClock.h +++ b/Platform/Posix/Interface/SolidSyslogPosixClock.h @@ -1,7 +1,5 @@ /** @file - * The POSIX SolidSyslogClockFunction, for SolidSyslogConfig.Clock. - * - * @ingroup platform_posix */ + * The POSIX SolidSyslogClockFunction, for SolidSyslogConfig.Clock. */ #ifndef SOLIDSYSLOGPOSIXCLOCK_H #define SOLIDSYSLOGPOSIXCLOCK_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixDatagram.h b/Platform/Posix/Interface/SolidSyslogPosixDatagram.h index df521cb5..e198f3d4 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixDatagram.h +++ b/Platform/Posix/Interface/SolidSyslogPosixDatagram.h @@ -5,9 +5,7 @@ * UDP) and turns on path-MTU discovery (IP_PMTUDISC_DO). SendTo then reports * SENT, OVERSIZE (the datagram exceeds the path MTU, EMSGSIZE), or FAILED. * MaxPayload returns the IPv6-safe default until connected, then tracks the - * discovered path MTU. - * - * @ingroup platform_posix */ + * discovered path MTU. */ #ifndef SOLIDSYSLOGPOSIXDATAGRAM_H #define SOLIDSYSLOGPOSIXDATAGRAM_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h b/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h index 1d357260..36430901 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixDatagramErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PosixDatagram adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the PosixDatagram adapter. */ #ifndef SOLIDSYSLOGPOSIXDATAGRAMERRORS_H #define SOLIDSYSLOGPOSIXDATAGRAMERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixFile.h b/Platform/Posix/Interface/SolidSyslogPosixFile.h index 93fa9cf5..0c9fc51c 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixFile.h +++ b/Platform/Posix/Interface/SolidSyslogPosixFile.h @@ -1,8 +1,6 @@ /** @file * POSIX file I/O (open / read / write / lseek / ftruncate) behind the - * SolidSyslogFile vtable, for a file-backed BlockDevice or Store. - * - * @ingroup platform_posix */ + * SolidSyslogFile vtable, for a file-backed BlockDevice or Store. */ #ifndef SOLIDSYSLOGPOSIXFILE_H #define SOLIDSYSLOGPOSIXFILE_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h b/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h index 36600868..7c3796db 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixFileErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PosixFile adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the PosixFile adapter. */ #ifndef SOLIDSYSLOGPOSIXFILEERRORS_H #define SOLIDSYSLOGPOSIXFILEERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixHostname.h b/Platform/Posix/Interface/SolidSyslogPosixHostname.h index 0f212ed8..e2fa8f50 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixHostname.h +++ b/Platform/Posix/Interface/SolidSyslogPosixHostname.h @@ -1,8 +1,6 @@ /** @file * The POSIX SolidSyslogHeaderFieldFunction for RFC 5424 HOSTNAME, for - * SolidSyslogConfig.GetHostname. - * - * @ingroup platform_posix */ + * SolidSyslogConfig.GetHostname. */ #ifndef SOLIDSYSLOGPOSIXHOSTNAME_H #define SOLIDSYSLOGPOSIXHOSTNAME_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h index 77e7464d..926442b2 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBuffer.h @@ -6,9 +6,7 @@ * error when the queue is full, so SolidSyslog_Log never blocks; Read * (mq_receive) is a non-blocking poll where an empty queue is silent and any * other failure is reported. Each pool slot owns a distinct queue - * /solidsyslog__, unlinked on Destroy. - * - * @ingroup platform_posix */ + * /solidsyslog__, unlinked on Destroy. */ #ifndef SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFER_H #define SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFER_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h index 9ea4fd13..4cd53601 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMessageQueueBufferErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PosixMessageQueueBuffer adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the PosixMessageQueueBuffer adapter. */ #ifndef SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERERRORS_H #define SOLIDSYSLOGPOSIXMESSAGEQUEUEBUFFERERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMutex.h b/Platform/Posix/Interface/SolidSyslogPosixMutex.h index 1e02a2b4..683cd4ca 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMutex.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMutex.h @@ -1,8 +1,6 @@ /** @file * A Mutex wrapping pthread_mutex_t, for thread-safe buffers and pools on a - * POSIX host. - * - * @ingroup platform_posix */ + * POSIX host. */ #ifndef SOLIDSYSLOGPOSIXMUTEX_H #define SOLIDSYSLOGPOSIXMUTEX_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h b/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h index 91039450..8a4b03a6 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixMutexErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PosixMutex adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the PosixMutex adapter. */ #ifndef SOLIDSYSLOGPOSIXMUTEXERRORS_H #define SOLIDSYSLOGPOSIXMUTEXERRORS_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixProcessId.h b/Platform/Posix/Interface/SolidSyslogPosixProcessId.h index 25ceda21..9c58e74c 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixProcessId.h +++ b/Platform/Posix/Interface/SolidSyslogPosixProcessId.h @@ -1,8 +1,6 @@ /** @file * The POSIX SolidSyslogHeaderFieldFunction for RFC 5424 PROCID, for - * SolidSyslogConfig.GetProcessId. - * - * @ingroup platform_posix */ + * SolidSyslogConfig.GetProcessId. */ #ifndef SOLIDSYSLOGPOSIXPROCESSID_H #define SOLIDSYSLOGPOSIXPROCESSID_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixSleep.h b/Platform/Posix/Interface/SolidSyslogPosixSleep.h index 68bcc86a..3f50b3b0 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixSleep.h +++ b/Platform/Posix/Interface/SolidSyslogPosixSleep.h @@ -1,7 +1,5 @@ /** @file - * The POSIX SolidSyslogSleepFunction. - * - * @ingroup platform_posix */ + * The POSIX SolidSyslogSleepFunction. */ #ifndef SOLIDSYSLOGPOSIXSLEEP_H #define SOLIDSYSLOGPOSIXSLEEP_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h b/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h index 8e960e42..3d9bc7f9 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h +++ b/Platform/Posix/Interface/SolidSyslogPosixSysUpTime.h @@ -1,7 +1,5 @@ /** @file - * The POSIX SolidSyslogSysUpTimeFunction, for MetaSd. - * - * @ingroup platform_posix */ + * The POSIX SolidSyslogSysUpTimeFunction, for MetaSd. */ #ifndef SOLIDSYSLOGPOSIXSYSUPTIME_H #define SOLIDSYSLOGPOSIXSYSUPTIME_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h index e139865b..d5d5f7c9 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStream.h @@ -16,9 +16,7 @@ * * TCP_NODELAY is on, and kernel keepalive (idle ~45s, then 4 x 10s probes) plus * TCP_USER_TIMEOUT (30s) on unacked writes surface a wedged peer as a failed - * Send/Read rather than a hung service thread. - * - * @ingroup platform_posix */ + * Send/Read rather than a hung service thread. */ #ifndef SOLIDSYSLOGPOSIXTCPSTREAM_H #define SOLIDSYSLOGPOSIXTCPSTREAM_H diff --git a/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h b/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h index a55fc70b..06bf09f8 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h +++ b/Platform/Posix/Interface/SolidSyslogPosixTcpStreamErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the PosixTcpStream adapter. - * - * @ingroup platform_posix */ + * Error codes and Source identity for the PosixTcpStream adapter. */ #ifndef SOLIDSYSLOGPOSIXTCPSTREAMERRORS_H #define SOLIDSYSLOGPOSIXTCPSTREAMERRORS_H diff --git a/Platform/Posix/SolidSyslogPosixPlatform.dox b/Platform/Posix/SolidSyslogPosixPlatform.dox deleted file mode 100644 index 8dc020ea..00000000 --- a/Platform/Posix/SolidSyslogPosixPlatform.dox +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @defgroup platform_posix Posix platform - * @brief Sockets, name resolution, files, mutexes, message queues and the clock on - * POSIX hosts. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/posix/ - */ diff --git a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h index 5f9b2e51..468dfecc 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounter.h @@ -3,9 +3,7 @@ * sequenceId on Windows targets without C11 (legacy MSVC). * Increment runs a lock-free InterlockedCompareExchange CAS loop on a * volatile LONG; the sequence is wrap-aware in [1, 2^31 - 1] and skips zero on - * wrap, so a returned value is never 0. - * - * @ingroup platform_windows */ + * wrap, so a returned value is never 0. */ #ifndef SOLIDSYSLOGWINDOWSATOMICCOUNTER_H #define SOLIDSYSLOGWINDOWSATOMICCOUNTER_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h index e89c95a2..8851766e 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsAtomicCounterErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WindowsAtomicCounter adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WindowsAtomicCounter adapter. */ #ifndef SOLIDSYSLOGWINDOWSATOMICCOUNTERERRORS_H #define SOLIDSYSLOGWINDOWSATOMICCOUNTERERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsClock.h b/Platform/Windows/Interface/SolidSyslogWindowsClock.h index 9450fe96..902e1229 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsClock.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsClock.h @@ -1,7 +1,5 @@ /** @file - * The Windows SolidSyslogClockFunction, for SolidSyslogConfig.Clock. - * - * @ingroup platform_windows */ + * The Windows SolidSyslogClockFunction, for SolidSyslogConfig.Clock. */ #ifndef SOLIDSYSLOGWINDOWSCLOCK_H #define SOLIDSYSLOGWINDOWSCLOCK_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsFile.h b/Platform/Windows/Interface/SolidSyslogWindowsFile.h index 8dde19fe..cdbdec47 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsFile.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsFile.h @@ -4,9 +4,7 @@ * or Store. * * Files open in binary mode (_O_BINARY) so the CRT's CR/LF translation never - * corrupts arbitrary bytes — BlockStore frames round-trip unchanged. - * - * @ingroup platform_windows */ + * corrupts arbitrary bytes — BlockStore frames round-trip unchanged. */ #ifndef SOLIDSYSLOGWINDOWSFILE_H #define SOLIDSYSLOGWINDOWSFILE_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h b/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h index 5d0c782e..8e6aa764 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsFileErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WindowsFile adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WindowsFile adapter. */ #ifndef SOLIDSYSLOGWINDOWSFILEERRORS_H #define SOLIDSYSLOGWINDOWSFILEERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsHostname.h b/Platform/Windows/Interface/SolidSyslogWindowsHostname.h index ad29596e..ec7fc883 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsHostname.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsHostname.h @@ -1,8 +1,6 @@ /** @file * The Windows SolidSyslogHeaderFieldFunction for RFC 5424 HOSTNAME, for - * SolidSyslogConfig.GetHostname. - * - * @ingroup platform_windows */ + * SolidSyslogConfig.GetHostname. */ #ifndef SOLIDSYSLOGWINDOWSHOSTNAME_H #define SOLIDSYSLOGWINDOWSHOSTNAME_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsMutex.h b/Platform/Windows/Interface/SolidSyslogWindowsMutex.h index 3510bc17..0c86d4fa 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsMutex.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsMutex.h @@ -1,8 +1,6 @@ /** @file * A Mutex wrapping a Windows CRITICAL_SECTION, for thread-safe buffers and - * pools on a Windows host. - * - * @ingroup platform_windows */ + * pools on a Windows host. */ #ifndef SOLIDSYSLOGWINDOWSMUTEX_H #define SOLIDSYSLOGWINDOWSMUTEX_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h b/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h index 7b35a518..df1454fa 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsMutexErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WindowsMutex adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WindowsMutex adapter. */ #ifndef SOLIDSYSLOGWINDOWSMUTEXERRORS_H #define SOLIDSYSLOGWINDOWSMUTEXERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h b/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h index 63ba5f2a..75b00836 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h @@ -1,8 +1,6 @@ /** @file * The Windows SolidSyslogHeaderFieldFunction for RFC 5424 PROCID, for - * SolidSyslogConfig.GetProcessId. - * - * @ingroup platform_windows */ + * SolidSyslogConfig.GetProcessId. */ #ifndef SOLIDSYSLOGWINDOWSPROCESSID_H #define SOLIDSYSLOGWINDOWSPROCESSID_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsSleep.h b/Platform/Windows/Interface/SolidSyslogWindowsSleep.h index 02ab7ac9..f888ecca 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsSleep.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsSleep.h @@ -1,7 +1,5 @@ /** @file - * The Windows SolidSyslogSleepFunction. - * - * @ingroup platform_windows */ + * The Windows SolidSyslogSleepFunction. */ #ifndef SOLIDSYSLOGWINDOWSSLEEP_H #define SOLIDSYSLOGWINDOWSSLEEP_H diff --git a/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h b/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h index c254e96f..72be2918 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsSysUpTime.h @@ -1,7 +1,5 @@ /** @file - * The Windows SolidSyslogSysUpTimeFunction, for MetaSd. - * - * @ingroup platform_windows */ + * The Windows SolidSyslogSysUpTimeFunction, for MetaSd. */ #ifndef SOLIDSYSLOGWINDOWSSYSUPTIME_H #define SOLIDSYSLOGWINDOWSSYSUPTIME_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockAddress.h b/Platform/Windows/Interface/SolidSyslogWinsockAddress.h index dba4877a..9690dea7 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockAddress.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockAddress.h @@ -3,9 +3,7 @@ * * A Resolver writes the resolved IPv4 endpoint into it; a Datagram or Stream * reads it back to send. It is a value slot the two sides share, not a vtable - * object. - * - * @ingroup platform_windows */ + * object. */ #ifndef SOLIDSYSLOGWINSOCKADDRESS_H #define SOLIDSYSLOGWINSOCKADDRESS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h index 8a7f0fea..59f9bd13 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockAddressErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WinsockAddress adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WinsockAddress adapter. */ #ifndef SOLIDSYSLOGWINSOCKADDRESSERRORS_H #define SOLIDSYSLOGWINSOCKADDRESSERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h b/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h index 7af769a8..5d3325e2 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockDatagram.h @@ -8,9 +8,7 @@ * connected, then tracks the discovered path MTU (IP_MTU). * * The caller must invoke WSAStartup before use and WSACleanup on shutdown; the - * library does not manage the Winsock lifecycle. - * - * @ingroup platform_windows */ + * library does not manage the Winsock lifecycle. */ #ifndef SOLIDSYSLOGWINSOCKDATAGRAM_H #define SOLIDSYSLOGWINSOCKDATAGRAM_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h index f21e314c..a44f5c9f 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockDatagramErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WinsockDatagram adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WinsockDatagram adapter. */ #ifndef SOLIDSYSLOGWINSOCKDATAGRAMERRORS_H #define SOLIDSYSLOGWINSOCKDATAGRAMERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockResolver.h b/Platform/Windows/Interface/SolidSyslogWinsockResolver.h index 42d64b21..d4f15d30 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockResolver.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockResolver.h @@ -7,9 +7,7 @@ * failed lookup returns false, so the caller's unresolved-host error path runs. * * The caller must invoke WSAStartup before use and WSACleanup on shutdown; the - * library does not manage the Winsock lifecycle. - * - * @ingroup platform_windows */ + * library does not manage the Winsock lifecycle. */ #ifndef SOLIDSYSLOGWINSOCKRESOLVERH #define SOLIDSYSLOGWINSOCKRESOLVERH diff --git a/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h index 1112dc13..864043b4 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockResolverErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WinsockResolver adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WinsockResolver adapter. */ #ifndef SOLIDSYSLOGWINSOCKRESOLVERERRORS_H #define SOLIDSYSLOGWINSOCKRESOLVERERRORS_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h index 53840bd2..3126d493 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStream.h @@ -23,9 +23,7 @@ * OS-default retransmit behaviour. * * The caller must invoke WSAStartup before use and WSACleanup on shutdown; the - * library does not manage the Winsock lifecycle. - * - * @ingroup platform_windows */ + * library does not manage the Winsock lifecycle. */ #ifndef SOLIDSYSLOGWINSOCKTCPSTREAM_H #define SOLIDSYSLOGWINSOCKTCPSTREAM_H diff --git a/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h b/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h index 3ac95753..41929b99 100644 --- a/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h +++ b/Platform/Windows/Interface/SolidSyslogWinsockTcpStreamErrors.h @@ -1,7 +1,5 @@ /** @file - * Error codes and Source identity for the WinsockTcpStream adapter. - * - * @ingroup platform_windows */ + * Error codes and Source identity for the WinsockTcpStream adapter. */ #ifndef SOLIDSYSLOGWINSOCKTCPSTREAMERRORS_H #define SOLIDSYSLOGWINSOCKTCPSTREAMERRORS_H diff --git a/Platform/Windows/SolidSyslogWindowsPlatform.dox b/Platform/Windows/SolidSyslogWindowsPlatform.dox deleted file mode 100644 index 3da8b040..00000000 --- a/Platform/Windows/SolidSyslogWindowsPlatform.dox +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @defgroup platform_windows Windows platform - * @brief Winsock networking plus Win32 files, mutexes, atomics and the clock on - * MSVC targets. - * - * What this platform ships, what your build must provide, the security - * behaviour it guarantees and the obligations it leaves to you: - * https://docs.cososo.co.uk/solid-syslog/platforms/windows/ - */ diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index 16551c48..a4db144e 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -5,8 +5,8 @@ * every generated API page for a header under ``Platform//`` gets a chip naming the platform it belongs to — the question a reader arriving from a search result has, and one the API reference could not previously answer; -* every platform page gets a chip to its setup guide, and its *What it ships* - section filled with the headers that platform publishes; +* a platform page gets a chip to its setup guide and its setup guide one back, + and its *What it ships* section is filled with the headers it publishes; * and the Doxygen group pages are dropped from the build. The groups are dropped because a platform had two pages answering "what is this @@ -173,6 +173,14 @@ def on_page_markdown(markdown, page, config, files, **kwargs): chip = f"[{label} platform](../{PLATFORM_PREFIX}{slug}/index.md){{ .ss-chip .ss-chip--platform }}" return f"{chip}\n\n{markdown}" + if src_uri.startswith(PLATFORM_PREFIX) and src_uri.endswith("/setup.md"): + slug = src_uri[len(PLATFORM_PREFIX) : -len("/setup.md")] + if slug not in slugs: + return markdown + chip = f"[{labels[slug]} platform](index.md){{ .ss-chip .ss-chip--platform }}" + title, _, body = markdown.partition("\n") + return f"{title}\n\n{chip}\n\n{body.lstrip()}" + if src_uri.startswith(PLATFORM_PREFIX) and src_uri.endswith("/index.md"): slug = src_uri[len(PLATFORM_PREFIX) : -len("/index.md")] if slug not in slugs: diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index bce131fe..1d3d2cc7 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -99,9 +99,19 @@ def test_the_platforms_overview_is_not_a_platform(self): markdown = "# Platforms\n\nBody.\n" self.assertEqual(render("platforms/index.md", markdown), markdown) - def test_a_setup_page_gets_no_chips(self): - markdown = "# Mbed TLS setup\n\nBody.\n" - self.assertEqual(render("platforms/mbedtls/setup.md", markdown), markdown) + def test_a_setup_page_gets_a_chip_back_to_its_platform(self): + out = render("platforms/mbedtls/setup.md", "# Mbed TLS setup\n\nBody.\n") + self.assertIn("[Mbed TLS platform](index.md){ .ss-chip .ss-chip--platform }", out) + + def test_the_back_chip_goes_under_the_title(self): + out = render("platforms/mbedtls/setup.md", "# Mbed TLS setup\n\nBody.\n") + self.assertTrue(out.startswith("# Mbed TLS setup\n\n["), out[:40]) + + def test_every_setup_page_can_get_back_to_its_platform(self): + _, slugs, _labels, _manifest = h._index(CONFIG) + for slug in slugs: + out = render(f"platforms/{slug}/setup.md", "# Setup\n\nBody.\n") + self.assertIn("(index.md){ .ss-chip .ss-chip--platform }", out, slug) def test_an_ordinary_page_is_untouched(self): markdown = "# Porting\n\nBody.\n" diff --git a/mkdocs.yml b/mkdocs.yml index f97fcfe3..35654efc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -119,10 +119,7 @@ plugins: src-dirs: "Core/Interface Platform" full-doc: true doxy-cfg: - # *.dox carries the per-platform @defgroup blocks — see the - # SolidSyslogPlatform.dox file at each pack's root. They are - # documentation only: install() globs *.h, so none of them ship. - FILE_PATTERNS: "*.h *.dox" + FILE_PATTERNS: "*.h" RECURSIVE: true # Platform//Source/ holds the *Private.h / *Internal.h # headers, which are implementation detail and not public API. diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index da9abe22..1cda7e8b 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -7,7 +7,6 @@ token LwipRaw directory Platform/LwipRaw/ docs docs/platforms/lwipraw/{index,setup}.md - group @defgroup platform_lwipraw nav an entry in mkdocs.yml description an entry in hooks/page_descriptions.py @@ -74,7 +73,7 @@ ), ] -SCANNED_SUFFIXES = (".c", ".h", ".dox", ".md") +SCANNED_SUFFIXES = (".c", ".h", ".md") # An #include names a header the compiler must find, not a platform the prose # is describing. The boundary is editorial; what a translation unit depends on @@ -196,12 +195,6 @@ def check(): if not os.path.isfile(os.path.join(ROOT, docs, page)): faults.append(f"{token}: {docs}/{page} is missing") - group = os.path.join(ROOT, directory, f"SolidSyslog{token}Platform.dox") - if not os.path.isfile(group): - faults.append(f"{token}: no group file at {directory}/SolidSyslog{token}Platform.dox") - elif f"@defgroup platform_{slug} " not in read(group): - faults.append(f"{token}: its group file does not declare @defgroup platform_{slug}") - if f"platforms/{slug}/index.md" not in nav: faults.append(f"{token}: no entry in the mkdocs.yml nav") for page in ("index.md", "setup.md"): @@ -211,11 +204,6 @@ def check(): if f"]({slug}/index.md)" not in matrix: faults.append(f"{token}: not a row in the docs/platforms/index.md matrix") - interface = os.path.join(ROOT, directory, "Interface") - for header in sorted(os.listdir(interface)) if os.path.isdir(interface) else []: - if header.endswith(".h") and "@ingroup" not in read(directory, "Interface", header): - faults.append(f"{token}: {header} carries no @ingroup") - for slug in sorted(documented() - slugs): faults.append(f"docs/platforms/{slug}/ documents a platform that is not registered") From 65367d753726caf81640ed03adef57abde0d35b3 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 20:22:28 +0100 Subject: [PATCH 37/57] docs: S23.22 stop the porting guide restating the contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit porting.md carried a Vtable column transcribing each Definition.h struct and a Null fallback column transcribing each SolidSyslogNull.h. Both headers document themselves — the Null ones with rationale as well as behaviour — so the columns were copies, and one had already drifted: the guide said SendTo's three values distinguish a would-block from a hard failure, where the enum is SENT / OVERSIZE / FAILED and there is no would-block state. The tables now list each role linked to its generated contract page, which renders the vtable's own doc comments and diagrams every adapter that fills the role — so the hand-listed reference column had nothing left to add either. 2275 words to 1817, all of it duplication. Give Stream's vtable the implementor half of its contract: what a caller may expect is documented on SolidSyslogStream.h, what an implementer must do is now on the vtable. Not a mirror of the caller's view — Open bounds the connect because one servicing pass drives every sender, Send never reports a partial write as success because the record leaves the caller's hands on true, Read returns 0 rather than negative because a would-block read as an error costs a reconnect on an idle link. Drop the role count from all six places that asserted it (twice here, README.md, docs/README.md, CLAUDE.md, page_descriptions.py). A thirteenth role would have meant six edits and no failure. Cut the links out to GitHub across docs/: where an Interface header has a generated page, link that; where the target is Source, a build file or a directory, leave it as text. porting.md also linked a build-integration.md anchor that stopped existing when the capability matrix moved in Phase 4 — --strict validates pages, not fragments. SECURITY.md is deliberately still linked: it holds the vulnerability-reporting process, has no in-site copy, and is not code. Part of #708 --- CLAUDE.md | 2 +- Core/Interface/SolidSyslogStreamDefinition.h | 22 +++- README.md | 4 +- docs/README.md | 6 +- docs/bdd.md | 4 +- docs/build-integration.md | 8 +- docs/builds.md | 8 +- docs/ci.md | 4 +- docs/containers.md | 2 +- docs/porting.md | 115 ++++++++----------- docs/structured-data.md | 2 +- hooks/page_descriptions.py | 2 +- 12 files changed, 85 insertions(+), 94 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a6438ed1..d5939e0e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -427,7 +427,7 @@ Core headers live under `Core/Interface/`; each platform pack puts its own under **Each header's `@file` brief is the authoritative description of what it provides and why.** Read the header rather than a copy of it. For the wider map: -- `docs/roles/index.md` — the twelve roles, each with its vtable contract and the backends +- `docs/roles/index.md` — the roles, each with its vtable contract and the backends that realise it. - `docs/platforms/*.md` — what each platform pack supplies. - `docs/api-reference/` plus the generated Doxygen indexes (`docs/api/files.md`, diff --git a/Core/Interface/SolidSyslogStreamDefinition.h b/Core/Interface/SolidSyslogStreamDefinition.h index f480df16..4a837246 100644 --- a/Core/Interface/SolidSyslogStreamDefinition.h +++ b/Core/Interface/SolidSyslogStreamDefinition.h @@ -17,15 +17,29 @@ SOLIDSYSLOG_EXTERN_C_BEGIN /** The contract a byte-stream transport (TCP, TLS over TCP) fills in; the * library drives it from the servicing pass, so it need not be reentrant. - * Each slot's semantics are the corresponding SolidSyslogStream_* function - * in SolidSyslogStream.h; an implementer must honour them, notably the - * non-blocking bounded behaviour and the close-on-failure lifecycle that - * lets the caller reconnect with a bare Open. */ + * What a caller may expect of each call is documented on the corresponding + * SolidSyslogStream_* function in SolidSyslogStream.h; what follows is the + * same contract as obligations on the implementer. */ struct SolidSyslogStream { + /** Bound the connect, and any handshake above it. One servicing pass + * drives every sender, so a connect that blocks stalls the whole drain, + * not just this stream. Leave nothing open on a failed path: the caller + * retries with a bare Open and never calls Close first. */ bool (*Open)(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); + /** All-or-nothing. Never report a partial write as success — the record + * is gone from the caller's hands once you return true. If the whole + * buffer cannot go, close internally and return false; the caller + * reopens and store-and-forward replays. */ bool (*Send)(struct SolidSyslogStream* base, const void* buffer, size_t size); + /** Return 0 when nothing is available, never a negative — the two are + * acted on differently, and a would-block reported as an error costs a + * reconnect on an idle link. Reserve the negative return for a real + * teardown, and close internally before making it. */ SolidSyslogSsize (*Read)(struct SolidSyslogStream* base, void* buffer, size_t size); + /** Idempotent, and leaves the instance reusable — a later Open + * reconnects it. Called on a stream that is already closed, on one that + * never opened, and again from Destroy. */ void (*Close)(struct SolidSyslogStream* base); }; diff --git a/README.md b/README.md index ea2f20e9..e3ede7db 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ header per component it wires. The [API reference](https://docs.cososo.co.uk/solid-syslog/api-reference/) explains that split and links the generated reference for every header, type and symbol. The -[porting guide](https://docs.cososo.co.uk/solid-syslog/porting/) covers the twelve -roles, the anatomy of an adapter, and the Null object that stands in for any role you +[porting guide](https://docs.cososo.co.uk/solid-syslog/porting/) covers the roles, +the anatomy of an adapter, and the Null object that stands in for any role you leave unfilled. [`Bdd/Targets/`](Bdd/Targets/) holds one BDD-driven binary per platform — Linux, diff --git a/docs/README.md b/docs/README.md index f65089a2..9bd2283c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,7 @@ Everything you need to consume SolidSyslog in your product. - [Adding it to your build](build-integration.md): the build detail. The capability matrix, the three ways to consume the library (CMake, Make, and a source manifest for an IDE project), and the compile-time tunables. - [Authoring custom structured data](structured-data.md): attaching RFC 5424 SD-ELEMENTs. - [Error-event severity policy](error-severity.md): installing an error handler and reading the event axes. -- Tunables: the compile-time limits, all `#ifndef`-guarded. See [Adding it to your build → Tunables](build-integration.md#tunables) and [`Core/Interface/SolidSyslogTunablesDefaults.h`](../Core/Interface/SolidSyslogTunablesDefaults.h). +- Tunables: the compile-time limits, all `#ifndef`-guarded. See [Adding it to your build → Tunables](build-integration.md#tunables) and [`Core/Interface/SolidSyslogTunablesDefaults.h`](api/SolidSyslogTunablesDefaults_8h.md). ## Platforms @@ -44,8 +44,8 @@ what that platform ships and what wiring it needs; the pages above speak of them in the general case. - [Platform × capability matrix](platforms/index.md): start here. Read across a row for what a platform gives you, down a column for who provides a capability. -- [Porting guide](porting.md): nothing shipped fits your target. The role model, the anatomy of an adapter (instance shape, the no-`malloc` static pool, the error convention), the invariants every adapter must honour, and the twelve vtable role contracts, each with its Null fallback and shipped reference implementation. -- The contracts themselves: the `SolidSyslog*Definition.h` vtables under [`Core/Interface/`](../Core/Interface/). [`Platform/Posix/`](../Platform/Posix/) is the reference implementation to read alongside them. +- [Porting guide](porting.md): nothing shipped fits your target. The role model, the anatomy of an adapter (instance shape, the no-`malloc` static pool, the error convention), the invariants every adapter must honour, and the vtable role contracts, each with its shipped reference implementation. +- The contracts themselves: the `SolidSyslog*Definition.h` vtables under `Core/Interface/`. `Platform/Posix/` is the reference implementation to read alongside them. ## Compliance diff --git a/docs/bdd.md b/docs/bdd.md index b2da0672..a5c074dd 100644 --- a/docs/bdd.md +++ b/docs/bdd.md @@ -40,7 +40,7 @@ fail here. BDD targets pair with their own oracle so jobs and developers switching containers never interfere. Each pair lives on its own bridge network so the `syslog-ng` DNS alias is scoped per-pair; -see [`Bdd/README.md`](../Bdd/README.md) for the per-target compose +see `Bdd/README.md` for the per-target compose layout, the `BDD_TARGET` env-var contract, and FreeRTOS local fault-finding tips. @@ -229,7 +229,7 @@ docker compose -f .devcontainer/docker-compose.yml run --rm behave-linux \ ``` For the FreeRTOS pair (cross-build the ELF, then run Behave via QEMU -inside `freertos-target`), see [`Bdd/README.md`](../Bdd/README.md). +inside `freertos-target`), see `Bdd/README.md`. ## Verifying syslog-ng manually diff --git a/docs/build-integration.md b/docs/build-integration.md index 12f0d3e9..efd1fb54 100644 --- a/docs/build-integration.md +++ b/docs/build-integration.md @@ -204,7 +204,7 @@ build. You can drop the `set()` entirely and let auto-detection find the same stack; declare it when you would rather the build state its platforms than infer them. -[`ci/consumer-smoke/`](../ci/consumer-smoke/) is a working consumer of this +`ci/consumer-smoke/` is a working consumer of this shape, kept honest by CI: it cross-builds with the environment scrubbed, so `SOLIDSYSLOG_PLATFORMS` is the only thing that can select a platform, and it links them rather than merely checking the targets exist. @@ -214,8 +214,8 @@ SolidSyslog also has `SOLIDSYSLOG_LWIP_PATH` and siblings. Those build headers — a consumer never sets them. See the worked target wiring in -[`Bdd/Targets/FreeRtos/`](../Bdd/Targets/FreeRtos/) and -[`Bdd/Targets/FreeRtosLwip/`](../Bdd/Targets/FreeRtosLwip/) (both consume the +`Bdd/Targets/FreeRtos/` and +`Bdd/Targets/FreeRtosLwip/` (both consume the umbrellas), and the platform-specific guides: - [Integrating with lwIP (Raw API)](platforms/lwipraw/setup.md) @@ -425,7 +425,7 @@ that platform's own setup guide, reached from its page in ## Tunables All compile-time limits live in -[`Core/Interface/SolidSyslogTunablesDefaults.h`](../Core/Interface/SolidSyslogTunablesDefaults.h), +[`Core/Interface/SolidSyslogTunablesDefaults.h`](api/SolidSyslogTunablesDefaults_8h.md), multiple values, every one `#ifndef`-guarded so you override without editing the library. Two equivalent mechanisms (works the same for CMake and non-CMake): diff --git a/docs/builds.md b/docs/builds.md index 5fcd3d30..44f7eacf 100644 --- a/docs/builds.md +++ b/docs/builds.md @@ -87,7 +87,7 @@ The CI gate is 90% line and branch. The target is 100%. ## Static analysis — `tidy` Runs clang-tidy on all source files. All warnings are errors. -Checks are configured in [.clang-tidy](../.clang-tidy). +Checks are configured in `.clang-tidy`. ```bash cmake --preset tidy @@ -162,8 +162,8 @@ cmake --build --preset freertos-cross --target SolidSyslogBddTarget The ELF lands at `build/freertos-cross/Bdd/Targets/FreeRtos/SolidSyslogBddTarget.elf`. -See [`Bdd/Targets/FreeRtos/README.md`](../Bdd/Targets/FreeRtos/README.md) for run / -GDB-attach instructions and [`Bdd/README.md`](../Bdd/README.md) for driving +See `Bdd/Targets/FreeRtos/README.md` for run / +GDB-attach instructions and `Bdd/README.md` for driving it under Behave + the syslog-ng oracle. ## FreeRTOS + lwIP cross — `freertos-cross-lwip` @@ -205,7 +205,7 @@ behave Bdd/features/ In the behave-linux container, Ctrl+Shift+B runs `behave Bdd/features/` automatically. For the FreeRTOS pair (cross-build the BDD target ELF, then drive QEMU through -Behave inside `freertos-target`), see [`Bdd/README.md`](../Bdd/README.md). +Behave inside `freertos-target`), see `Bdd/README.md`. See [BDD testing](bdd.md) for architecture details and the `BDD_TARGET` / `@freertoswip` contract. diff --git a/docs/ci.md b/docs/ci.md index d294f4e7..ac980e16 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -38,7 +38,7 @@ without renaming what's already there. | `build-freertos-target-lwip` | `freertos-cross-lwip` | The same cross-build over lwIP with ChaN FatFs (`FreeRtos;LwipRaw;MbedTls;FatFs;Atomics`) | | `bdd-freertos-qemu-plustcp` | — | Pulls the Plus-TCP target ELF, brings up the freertos compose pair (`syslog-ng-freertos` + `behave-freertos`); Behave drives the target through `qemu-system-arm`'s UART | | `bdd-freertos-qemu-lwip` | — | The same scenarios against the lwIP target ELF | -| `consumer-smoke-linux` | — | Builds [`ci/consumer-smoke/`](../ci/consumer-smoke/) as a FetchContent consumer, proving the documented integration path still works | +| `consumer-smoke-linux` | — | Builds `ci/consumer-smoke/` as a FetchContent consumer, proving the documented integration path still works | | `consumer-smoke-freertos-cross` | — | The same consumer project cross-compiled for ARM with `LwipRaw;FreeRtos` | | `verify-manifest` | — | Regenerates the Core and per-platform source manifests and fails if they differ from the committed ones | | `docs-build` | — | Builds the MkDocs + mkdoxy site with `mkdocs build --strict`; on `main`, `deploy-docs-pages` publishes it to GitHub Pages | @@ -79,7 +79,7 @@ and weekly so that queries GitHub ships later are applied to unchanged code. Fin appear under **Security → Code scanning**, not in the job log. CodeQL analyses what the compiler compiled and nothing else, so the build it observes -is the analysis scope. The lane builds [`ci/consumer-smoke/`](../ci/consumer-smoke/) — +is the analysis scope. The lane builds `ci/consumer-smoke/` — the same FetchContent consumer documented in [Adding it to your build](build-integration.md) — so the code analysed is the code an integrator compiles. Consuming the library as a subproject also scopes the database diff --git a/docs/containers.md b/docs/containers.md index b0623388..1afb79e7 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -194,4 +194,4 @@ The same VS Code keys work across every service: QEMU run for sanity-checking the build, output to the integrated terminal. Use only in the `freertos-target` service. -For the FreeRTOS BDD target, see [Bdd/Targets/FreeRtos/README.md](../Bdd/Targets/FreeRtos/README.md) for build / run / GDB-attach instructions. +For the FreeRTOS BDD target, see `Bdd/Targets/FreeRtos/README.md` for build / run / GDB-attach instructions. diff --git a/docs/porting.md b/docs/porting.md index 2a8be0c9..8b4a61e1 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -2,7 +2,7 @@ Porting SolidSyslog to a new OS, network stack, filesystem, or crypto library is filling a role, not editing Core. Core never changes. You write a small -adapter that satisfies one of the twelve vtable contracts, drop it into your +adapter that satisfies one of the vtable contracts, drop it into your build, and wire it into your config. This page is the contract those adapters honour, written from the code that already ships. @@ -21,10 +21,9 @@ contract. [Talk to us about it](https://www.cososo.co.uk/?service=solidsyslog#co ## The role model Core is a fixed set of algorithms (the formatter/message pipeline, the Service -drain loop, the buffer/store machinery) plus twelve roles. A role is a +drain loop, the buffer/store machinery) plus a set of roles. A role is a `struct` of function pointers (a vtable) declared in a -`SolidSyslogDefinition.h` header under -[`Core/Interface/`](../Core/Interface/). An *adapter* is a concrete +`SolidSyslogDefinition.h` header under `Core/Interface/`. An *adapter* is a concrete implementation of one role for one platform (`SolidSyslogPosixMutex`, `SolidSyslogLwipRawDatagram`, …). @@ -35,15 +34,14 @@ running against a well-behaved do-nothing. So porting is additive: you provide the roles your deployment needs and leave the rest to their Nulls. You never edit Core, and you never touch a role you don't use. -The [capability matrix](build-integration.md#pick-your-stack--capability-matrix) -lists every role and the adapters that ship for it; this page is what you write -when none of the shipped adapters fits your platform. +The [platform × capability matrix](platforms/index.md) lists every role and the +adapters that ship for it; this page is what you write when none of the shipped +adapters fits your platform. ## Anatomy of an adapter -Take [`SolidSyslogPosixMutex`](../Platform/Posix/Source/SolidSyslogPosixMutex.c) -as the worked example: the simplest role, but the shape is identical for all -twelve. An adapter is four files: +Take `SolidSyslogPosixMutex` as the worked example: the simplest role, but the +shape is identical for every one. An adapter is four files: | File | Holds | |---|---| @@ -76,9 +74,8 @@ There is no heap. Each adapter owns a file-scope `static` array of instances and a parallel `InUse[]` flag array, sized by a role tunable. `_Create` acquires the first free slot, initialises it, and returns `&pool[i].Base`; on exhaustion it returns the shared Null sibling and reports an error. `_Destroy` finds the slot -by handle identity, cleans it up, and releases it. The -[`SolidSyslogPoolAllocator`](../Core/Source/SolidSyslogPoolAllocator.h) owns the -slot-walk so no adapter re-implements it: +by handle identity, cleans it up, and releases it. `SolidSyslogPoolAllocator` +owns the slot-walk so no adapter re-implements it: ```c static bool PosixMutex_InUse[SOLIDSYSLOG_MUTEX_POOL_SIZE]; @@ -105,11 +102,11 @@ struct SolidSyslogMutex* SolidSyslogPosixMutex_Create(void) } ``` -See [`SolidSyslogPosixMutexStatic.c`](../Platform/Posix/Source/SolidSyslogPosixMutexStatic.c) -for the matching `SolidSyslogPosixMutex_Destroy`. The pool size is a role-named tunable, +See `SolidSyslogPosixMutexStatic.c` for the matching +`SolidSyslogPosixMutex_Destroy`. The pool size is a role-named tunable, `SOLIDSYSLOG_MUTEX_POOL_SIZE`, not a per-platform name, because a build links one implementation per role. Every tunable lives in -[`SolidSyslogTunablesDefaults.h`](../Core/Interface/SolidSyslogTunablesDefaults.h), +[`SolidSyslogTunablesDefaults.h`](api/SolidSyslogTunablesDefaults_8h.md), `#ifndef`-guarded so integrators override without editing the library. ### Error reporting — the `*Errors.h` convention @@ -120,7 +117,7 @@ Each adapter ships a `SolidSyslogErrors.h` declaring an something fails, the adapter calls `SolidSyslog_Error(severity, source, category, detail)`: `source` is its own `ErrorSource` (matched by pointer identity in a handler), `category` is a portable reaction axis from -[`SolidSyslogErrorCategory.h`](../Core/Interface/SolidSyslogErrorCategory.h), and +[`SolidSyslogErrorCategory.h`](api/SolidSyslogErrorCategory_8h.md), and `detail` is the adapter's own enum value. A handler that doesn't care about your adapter simply never matches its source. The default handler is a silent no-op: adapters report and carry on, they never crash the caller. @@ -144,13 +141,14 @@ This is the only synchronisation primitive the pools use for their own walks. double-free if `Close` and `Destroy` are both called. Release each resource exactly once and null the handle. - Never free injected handles. An adapter frees only what it created. Handles - the integrator passed in (an `mbedtls_x509_crt*`, an RNG, a caller's socket) - are borrowed; the owner frees them. The [Mbed TLS coexistence - contract](platforms/mbedtls/index.md#coexistence-is-an-auditable-contract) is the template: - `Platform/MbedTls/Source/` never touches process-global Mbed TLS state. -- A Null must be safe to call. Whatever your role's Null returns (see each - contract below), it must let Core's algorithm proceed sanely: drop-on-the-floor - where a drop is harmless, `false` where the caller has an error path to run. + the integrator passed in (a certificate, an RNG, a caller's socket) are + borrowed; the owner frees them. The same applies to an upstream library's + process-global state: touch only what you were given, so the library drops + into a process already using that upstream elsewhere. +- A Null must be safe to call. Whatever your role's Null returns — each is + documented on its own `SolidSyslogNull.h` — it must let Core's algorithm + proceed sanely: drop-on-the-floor where a drop is harmless, `false` where the + caller has an error path to run. - Bounded blocking. Anything that can wedge (a `connect`, a handshake) is bounded by an explicit timeout or deadline: a timeout tunable (e.g. `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS`) or a caller-supplied deadline. A @@ -236,12 +234,12 @@ product and one further state, absent, which has no behaviour to test. [manifest](build-integration.md#ide-and-manifest-builds) generator lists the exact files for a chosen set of platforms. -## The twelve role contracts +## The role contracts -Each row is a vtable to implement. The reference column is the shipped -implementation to read alongside the contract: a `Platform/Posix/` adapter where -one exists, otherwise the Core composition over a lower role. The Null column -is the fallback Core substitutes when the role is unfilled. +Each entry is a vtable to implement, linked to its contract: every method, what +it must return, and what Core does with the answer. Each contract page also +diagrams the adapters that already fill that role, so it is where to find an +implementation to read alongside. ### Networking @@ -249,17 +247,10 @@ Most network ports implement Stream (TCP / TLS byte transport) and Datagram (UDP); `Sender` is a Core composition over them, so you rarely write one directly. -| Role | Contract | Vtable | Null fallback | Reference | -|---|---|---|---|---| -| **Resolver** | [`ResolverDefinition.h`](../Core/Interface/SolidSyslogResolverDefinition.h) | `Resolve(transport, host, port, *out)` | `Resolve` → `false`, so the caller's unresolved-host error path runs | [`GetAddrInfoResolver.c`](../Platform/Posix/Source/SolidSyslogGetAddrInfoResolver.c) | -| **Datagram** | [`DatagramDefinition.h`](../Core/Interface/SolidSyslogDatagramDefinition.h) | `Open` · `SendTo(...)→SendResult` · `MaxPayload` · `Close` | Open/Close no-op, `SendTo` → `SENT` (drop), `MaxPayload` → IPv6-safe default | [`PosixDatagram.c`](../Platform/Posix/Source/SolidSyslogPosixDatagram.c) | -| **Stream** | [`StreamDefinition.h`](../Core/Interface/SolidSyslogStreamDefinition.h) | `Open(addr)` · `Send` · `Read` · `Close` | Open/Close no-op, `Send` → `true` (drop), `Read` → `0` (would-block, no teardown) | [`PosixTcpStream.c`](../Platform/Posix/Source/SolidSyslogPosixTcpStream.c) | -| **Sender** | [`SenderDefinition.h`](../Core/Interface/SolidSyslogSenderDefinition.h) | `Send` · `Disconnect` | `Send` → `true` (drop), `Disconnect` no-op | [`StreamSender.c`](../Core/Source/SolidSyslogStreamSender.c) · [`UdpSender.c`](../Core/Source/SolidSyslogUdpSender.c) | - -`SendTo` returns a three-way `enum SolidSyslogDatagramSendResult` (not a bool) so -the sender can distinguish a would-block from a hard failure. A `Stream` owns its -connect/keepalive lifecycle; `Read` returns `0` for would-block and a negative -`SolidSyslogSsize` only for a real teardown. +- [Resolver](api/structSolidSyslogResolver.md) +- [Datagram](api/structSolidSyslogDatagram.md) +- [Stream](api/structSolidSyslogStream.md) +- [Sender](api/structSolidSyslogSender.md) ### Storage @@ -267,42 +258,28 @@ The store-and-forward stack is layered: Store (Core `BlockStore`) sits over BlockDevice, which sits over File. On a new platform you usually implement only File (and BlockDevice for raw flash); the rest is Core. -| Role | Contract | Vtable | Null fallback | Reference | -|---|---|---|---|---| -| **Store** | [`StoreDefinition.h`](../Core/Interface/SolidSyslogStoreDefinition.h) | `Write` · `ReadNextUnsent` · `MarkSent` · `HasUnsent` · `IsHalted` · `GetTotalBytes` · `GetUsedBytes` · `IsTransient` | No store-and-forward; `IsTransient` → `true` so a rejected `Write` falls through to the sender | [`BlockStore.c`](../Core/Source/SolidSyslogBlockStore.c) | -| **BlockDevice** | [`BlockDeviceDefinition.h`](../Core/Interface/SolidSyslogBlockDeviceDefinition.h) | `Acquire` · `Dispose` · `Exists` · `Read` · `Append` · `WriteAt` · `Size(block)` · `GetBlockSize` | Every method `false` / `0` — no disk | [`FileBlockDevice.c`](../Core/Source/SolidSyslogFileBlockDevice.c) | -| **File** | [`FileDefinition.h`](../Core/Interface/SolidSyslogFileDefinition.h) | `Open` · `Close` · `IsOpen` · `Read` · `Write` · `SeekTo` · `Size` · `Truncate` · `Exists` · `Delete` | Reads / `Exists` → `false`, `Write` / `Delete` → `true`, `Size` → `0`, seek/truncate/close no-op | [`PosixFile.c`](../Platform/Posix/Source/SolidSyslogPosixFile.c) | -| **Buffer** | [`BufferDefinition.h`](../Core/Interface/SolidSyslogBufferDefinition.h) | `Write` · `Read` | `Read` → `false` (empty), `Write` swallows | [`CircularBuffer.c`](../Core/Source/SolidSyslogCircularBuffer.c) · [`PassthroughBuffer.c`](../Core/Source/SolidSyslogPassthroughBuffer.c) | +- [Store](api/structSolidSyslogStore.md) +- [BlockDevice](api/structSolidSyslogBlockDevice.md) +- [File](api/structSolidSyslogFile.md) +- [Buffer](api/structSolidSyslogBuffer.md) -`Store.IsTransient` is the crucial hint: a *transient* store (like Null) never -retained the record, so Service may try the sender directly; a real store's `Write` -rejection is the discard policy speaking, and Service must not let a newer -record jump the queue past older ones. The portable in-memory `CircularBuffer` -takes an injected `Mutex`, so a `Buffer` port is often just a `Mutex` port. +The portable in-memory `CircularBuffer` takes an injected `Mutex`, so a `Buffer` +port is often just a `Mutex` port. ### OS primitives -| Role | Contract | Vtable | Null fallback | Reference | -|---|---|---|---|---| -| **Mutex** | [`MutexDefinition.h`](../Core/Interface/SolidSyslogMutexDefinition.h) | `Lock` · `Unlock` | No-op (single-task) | [`PosixMutex.c`](../Platform/Posix/Source/SolidSyslogPosixMutex.c) | -| **AtomicCounter** | [`AtomicCounterDefinition.h`](../Core/Interface/SolidSyslogAtomicCounterDefinition.h) | `Increment` — wrap-aware in `[1, 2³¹−1]`, never returns `0` (RFC 5424 §7.3.1) | `Increment` → `1` unconditionally | [`StdAtomicCounter.c`](../Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c) | +- [Mutex](api/structSolidSyslogMutex.md) +- [AtomicCounter](api/structSolidSyslogAtomicCounter.md) ### Evidence and integrity -| Role | Contract | Vtable | Null fallback | Reference | -|---|---|---|---|---| -| **StructuredData** | [`StructuredDataDefinition.h`](../Core/Interface/SolidSyslogStructuredDataDefinition.h) | `Format(element)` — write one `[SD-ID …]` via the `SolidSyslogSdElement` sink | No-op (element omitted) | [`MetaSd.c`](../Core/Source/SolidSyslogMetaSd.c) | -| **SecurityPolicy** | [`SecurityPolicyDefinition.h`](../Core/Interface/SolidSyslogSecurityPolicyDefinition.h) | `TrailerSize` + `SealRecord` · `OpenRecord` over a `SolidSyslogSecurityRecord` | No integrity check; `TrailerSize` `0`, seal/open pass through | [`Crc16Policy.c`](../Core/Source/SolidSyslogCrc16Policy.c) | - -A `StructuredData.Format` writes through the opaque `SolidSyslogSdElement` sink; -it owns the brackets, the `@`-enterprise SD-ID suffix, and the escaping, so a -producer cannot break the RFC 5424 framing. A `SecurityPolicy` is handed a -`SolidSyslogSecurityRecord` split into a cleartext header (associated data) and a -body. A keyed MAC policy authenticates the whole span (tamper-evident); a -checksum policy such as the vendor-free `Crc16Policy` covers the same span but -only detects accidental corruption, not an attacker; an AEAD policy encrypts the -body in place and writes its `TrailerSize`-byte trailer. `Crc16Policy` is the -reference to read first. +- [StructuredData](api/structSolidSyslogStructuredData.md) +- [SecurityPolicy](api/structSolidSyslogSecurityPolicy.md) + +A `SecurityPolicy` is the one role where the choice is a security decision, not +a portability one: a keyed MAC is tamper-evident, a checksum such as the +vendor-free [Crc16Policy](api/SolidSyslogCrc16Policy_8h.md) detects accidental +corruption but not an attacker, and an AEAD encrypts as well as authenticates. ## Where to go next diff --git a/docs/structured-data.md b/docs/structured-data.md index 2be9416f..d240e360 100644 --- a/docs/structured-data.md +++ b/docs/structured-data.md @@ -17,7 +17,7 @@ custom element therefore cannot produce malformed structured data, and a value c break out of its quotes; whatever bytes you hand it stay inside the `"..."`. The complete worked example below lives at -[`Bdd/Targets/Common/BddTargetCustomSd.c`](../Bdd/Targets/Common/BddTargetCustomSd.c). +`Bdd/Targets/Common/BddTargetCustomSd.c`. ## The two writer types diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index b1468e41..f740ab69 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -188,7 +188,7 @@ "drains a record, and the role implementations that need no platform." ), "roles/index.md": ( - "The twelve vtable contracts SolidSyslog composes against, what fills " + "The vtable contracts SolidSyslog composes against, what fills " "each one, and the Null fallback that keeps an unfilled role safe." ), "assets/postit/README.md": ( From 93ebbd65289d11bdb02e1555202db528844370ad Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 20:40:07 +0100 Subject: [PATCH 38/57] docs: S23.22 publish the root documents the site kept linking away to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SECURITY.md, LICENSE.md and SUPPORT.md are documentation the site needs but could not hold: they live at the repository root because GitHub looks for them there. Five pages linked SECURITY.md, so a reader wanting to report a vulnerability was sent off the site to find the process. hooks/root_pages.py reads them at build time and hands them to MkDocs as generated content — one source, two renderings, no copy in the tree and no symlink for the Windows side to trip over. Their links are written to resolve from the root, where docs/… reaches a page, so the prefix is stripped and the remainder made relative to wherever the page lands. A link to a root document we do not publish becomes plain text rather than a jump to the repository host. Publishing the target instead cascades: proved by trying it — CONTRIBUTING.md pulls in CODE_OF_CONDUCT.md and CLAUDE.md, which is a working agreement between maintainers, not part of the product's docs. With those pages in place, no link in any page body leaves for GitHub. The remaining out-of-site links were the last of the sweep: a scripts/ path three levels up that the earlier pattern did not reach, and the project README on the docs landing page. Part of #708 --- docs/README.md | 4 +- docs/assets/postit/README.md | 2 +- docs/cra.md | 2 +- docs/iec62443.md | 2 +- docs/security/threat-model.md | 2 +- docs/security/triage-runbook.md | 2 +- hooks/page_descriptions.py | 12 +++++ hooks/root_pages.py | 83 ++++++++++++++++++++++++++++++++ hooks/test_root_pages.py | 84 +++++++++++++++++++++++++++++++++ mkdocs.yml | 11 +++++ 10 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 hooks/root_pages.py create mode 100644 hooks/test_root_pages.py diff --git a/docs/README.md b/docs/README.md index 9bd2283c..fec3ed4a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ Pick a lane: ## Overview -- Why SolidSyslog, and capability at a glance: the [project README](../README.md). +- Why SolidSyslog, and capability at a glance: the project `README.md`. - [Compliance in one page](overview.md): the evaluator's one-screen orientation. What CRA and IEC 62443 ask of an audit-logging function, and how SolidSyslog helps. ## Integrate @@ -59,7 +59,7 @@ in the general case. - [Software Bill of Materials (SBOM)](security/sbom.md) - [Vulnerability triage runbook](security/triage-runbook.md) - [Release verification guide](security/release-verification.md) - - Reporting a vulnerability: [`SECURITY.md`](../SECURITY.md) + - Reporting a vulnerability: [`SECURITY.md`](security/policy.md) ## API reference diff --git a/docs/assets/postit/README.md b/docs/assets/postit/README.md index 7bd7a6df..353c662e 100644 --- a/docs/assets/postit/README.md +++ b/docs/assets/postit/README.md @@ -36,7 +36,7 @@ relationship. One exception: [`architecture-overview.svg`](architecture-overview.svg) is **generated**, not hand-drawn — its layout (boundary crossings, fan-out lines) -is computed. Edit [`scripts/gen_architecture_overview.py`](../../../scripts/gen_architecture_overview.py) +is computed. Edit `scripts/gen_architecture_overview.py` and re-run it; do not hand-edit the SVG. It keeps the same palette but drops the sticky rotation/shadow for a precise architectural read, and colours by provenance (Core / adapter / third party) with the boundary marking Core. diff --git a/docs/cra.md b/docs/cra.md index cd8b4bac..bbabe666 100644 --- a/docs/cra.md +++ b/docs/cra.md @@ -85,7 +85,7 @@ consuming. |---|---|---| | **(1)** | identify and document components, including a software bill of materials in a machine-readable format | A CycloneDX [SBOM](security/sbom.md) per release, to fold into your product's own | | **(2)**, **(4)** | remediate without delay; disclose fixed vulnerabilities | The [vulnerability triage runbook](security/triage-runbook.md) sets out how reports are assessed and published | -| **(5)**, **(6)** | a coordinated vulnerability disclosure policy, and a contact address | [`SECURITY.md`](../SECURITY.md) | +| **(5)**, **(6)** | a coordinated vulnerability disclosure policy, and a contact address | [`SECURITY.md`](security/policy.md) | | **(7)** | securely distribute updates | [Release verification](security/release-verification.md): signed, reproducible artefacts you can check before adopting | Secure-by-design evidence for your technical file is in the [threat diff --git a/docs/iec62443.md b/docs/iec62443.md index de44d46f..8bac5eb4 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -95,7 +95,7 @@ elimination removes unused components. The development-process evidence IEC 62443-4-1 asks for is published separately: the [threat model](security/threat-model.md), the [SBOM](security/sbom.md), the -[vulnerability triage runbook](security/triage-runbook.md), [`SECURITY.md`](../SECURITY.md) +[vulnerability triage runbook](security/triage-runbook.md), [`SECURITY.md`](security/policy.md) for coordinated disclosure, and the test and analysis gates described in [CI](ci.md). diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index e042a544..0280d6cd 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -184,7 +184,7 @@ security-conscious component supplier. See [`cra.md`](../cra.md) for the CRA Annex I audit-trail map, [`iec62443.md`](../iec62443.md) for the control-by-control map, and [`rfc-compliance.md`](../rfc-compliance.md) for the standards coverage matrix. The public disclosure process for issues found -against this model is in [`SECURITY.md`](../../SECURITY.md). +against this model is in [`SECURITY.md`](policy.md). ## Review policy diff --git a/docs/security/triage-runbook.md b/docs/security/triage-runbook.md index 2233269d..e5c4fbb5 100644 --- a/docs/security/triage-runbook.md +++ b/docs/security/triage-runbook.md @@ -1,6 +1,6 @@ # Vulnerability Triage Runbook -This is the maintainer's operational counterpart to [`SECURITY.md`](../../SECURITY.md): +This is the maintainer's operational counterpart to [`SECURITY.md`](policy.md): how a vulnerability report is handled end-to-end, from receipt to retrospective. `SECURITY.md` states the public promises (72-hour acknowledgement, 90+14 disclosure); this runbook is how they are met. diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index f740ab69..df29d406 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -178,6 +178,18 @@ "Verify a SolidSyslog release came from this repository: cosign signature " "checks, SBOM validation and the source-tree hash, in the order to run them." ), + "security/policy.md": ( + "How to report a SolidSyslog vulnerability, what we commit to in " + "response, and the disclosure process, scope and supported versions." + ), + "license.md": ( + "SolidSyslog is licensed under PolyForm Noncommercial 1.0.0: the full " + "terms, what noncommercial covers, and where commercial use needs a licence." + ), + "support.md": ( + "Where to get help with SolidSyslog — the documentation, questions and " + "bug reports, reporting a security issue, and commercial support." + ), # API reference doorways "api-reference/index.md": ( "The doorway to the SolidSyslog API: which headers your code includes to " diff --git a/hooks/root_pages.py b/hooks/root_pages.py new file mode 100644 index 00000000..6da4f9ad --- /dev/null +++ b/hooks/root_pages.py @@ -0,0 +1,83 @@ +"""MkDocs build hook: publish repository-root Markdown as pages of the site. + +A few documents have to live at the repository root — GitHub reads SECURITY.md +to drive its vulnerability-reporting UI, and a licence is looked for beside the +code — but they are documentation the site should hold rather than send a reader +away for. Publishing them here keeps one source with two renderings instead of a +root copy and a docs copy drifting apart. + +The file is read at build time and handed to MkDocs as generated content, so +nothing is copied into the working tree and nothing needs a symlink (which the +Windows side would not thank us for). + +Their links are written to resolve from the repository root, where `docs/…` is +how they reach a documentation page. Inside the site that prefix is wrong, so it +is stripped and the remainder made relative to wherever the page is published. +`mkdocs build --strict` fails on any that does not land. +""" + +import os +import re + +from mkdocs.structure.files import File + +# Root file → where it is published. Both halves matter: the source path is +# fixed by convention (GitHub looks for these names, in this place), and the +# destination is ours to choose so the page sits with its siblings. +PUBLISHED = { + "SECURITY.md": "security/policy.md", + "LICENSE.md": "license.md", + "SUPPORT.md": "support.md", +} + +# The site's landing page, which is what `docs/` means to a reader at the root. +HOME = "README.md" +# Any link in a root document. Whether it needs re-aiming depends on where it +# points, which _target decides. +LINK = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") + + +def _target(href): + """Where this link lands inside the site, or None if it is not ours to move. + + Two kinds re-aim: a path into the documentation tree — `docs/` alone being + the landing page — and a sibling root document that is also published. An + external URL is already right. + """ + if href.startswith("docs/"): + return href[len("docs/") :] or HOME + return PUBLISHED.get(href) + + +def _relink(markdown, published_at, root): + """Re-aim a root file's links at this page's position in the site. + + A link to a root document we do not publish becomes plain text rather than + a jump out to the repository host — the same treatment the documentation + gives a source file it cannot render. Publishing the target instead would + cascade: CONTRIBUTING.md reaches CLAUDE.md, which is a working agreement + between maintainers and not part of the product's documentation. + """ + page_dir = os.path.dirname(published_at) + + def rewrite(match): + text, href = match.group(1), match.group(2) + href, _, fragment = href.partition("#") + target = _target(href) + if target is None: + unpublished = "/" not in href and os.path.isfile(os.path.join(root, href)) + return text if unpublished else match.group(0) + relative = os.path.relpath(target, page_dir) if page_dir else target + return f"[{text}]({relative}{'#' + fragment if fragment else ''})" + + return LINK.sub(rewrite, markdown) + + +def on_files(files, config): + root = os.path.dirname(config["config_file_path"]) + for source, destination in PUBLISHED.items(): + with open(os.path.join(root, source), encoding="utf-8") as handle: + markdown = handle.read() + content = _relink(markdown, destination, root) + files.append(File.generated(config, destination, content=content)) + return files diff --git a/hooks/test_root_pages.py b/hooks/test_root_pages.py new file mode 100644 index 00000000..5555048c --- /dev/null +++ b/hooks/test_root_pages.py @@ -0,0 +1,84 @@ +"""Regression tests for the root-document hook (hooks/root_pages.py). + +Run: python3 hooks/test_root_pages.py + (or: python3 -m unittest discover -s hooks -p 'test_*.py') + +The strict build proves the published pages exist and their links resolve. What +it cannot prove is that the right files are published and that a link left +pointing at the repository root is caught rather than quietly rendered — a +SECURITY.md whose reporting route silently vanished still builds perfectly well. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import root_pages as h # noqa: E402 + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def relink(markdown, published_at): + return h._relink(markdown, published_at, ROOT) + + +class WhatIsPublished(unittest.TestCase): + def test_the_security_policy_is_published(self): + # Five pages link it and it has no in-site equivalent. + self.assertEqual(h.PUBLISHED["SECURITY.md"], "security/policy.md") + + def test_every_published_source_exists_at_the_root(self): + for source in h.PUBLISHED: + self.assertTrue(os.path.isfile(os.path.join(ROOT, source)), source) + + def test_the_maintainers_working_agreement_is_not_published(self): + # CLAUDE.md is how we work, not part of the product's documentation. + self.assertNotIn("CLAUDE.md", h.PUBLISHED) + + +class Relinking(unittest.TestCase): + def test_a_docs_link_is_made_relative_to_the_published_page(self): + out = relink("See [the model](docs/security/threat-model.md).", "security/policy.md") + self.assertIn("[the model](threat-model.md)", out) + + def test_a_docs_link_from_a_page_at_the_site_root_keeps_its_path(self): + out = relink("See [the model](docs/security/threat-model.md).", "support.md") + self.assertIn("[the model](security/threat-model.md)", out) + + def test_docs_on_its_own_means_the_landing_page(self): + self.assertIn("[docs](README.md)", relink("[docs](docs/)", "support.md")) + + def test_a_link_to_another_published_root_file_finds_its_new_home(self): + out = relink("[Security issues](SECURITY.md)", "support.md") + self.assertIn("[Security issues](security/policy.md)", out) + + def test_a_link_to_an_unpublished_root_file_becomes_plain_text(self): + # Publishing the target instead would cascade — CONTRIBUTING.md reaches + # CLAUDE.md — so the link is dropped rather than followed out of the site. + out = relink("See [Contributing](CONTRIBUTING.md) first.", "support.md") + self.assertEqual(out, "See Contributing first.") + + def test_an_external_url_is_left_alone(self): + url = "[Talk to us](https://www.cososo.co.uk/?service=solidsyslog#contact)" + self.assertEqual(relink(url, "support.md"), url) + + def test_a_fragment_survives_the_rewrite(self): + out = relink("[scope](docs/security/threat-model.md#scope)", "security/policy.md") + self.assertIn("[scope](threat-model.md#scope)", out) + + def test_a_link_already_inside_the_docs_tree_is_untouched(self): + # Only a docs/-prefixed path is the root file's way of reaching a page. + self.assertEqual(relink("[x](other.md)", "support.md"), "[x](other.md)") + + +class RealFiles(unittest.TestCase): + def test_the_published_security_policy_keeps_a_route_to_the_threat_model(self): + with open(os.path.join(ROOT, "SECURITY.md"), encoding="utf-8") as handle: + out = relink(handle.read(), h.PUBLISHED["SECURITY.md"]) + self.assertIn("(threat-model.md)", out) + self.assertNotIn("(docs/", out) + + +if __name__ == "__main__": + unittest.main() diff --git a/mkdocs.yml b/mkdocs.yml index 35654efc..6930e14d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,6 +74,10 @@ markdown_extensions: # before MkDocs validates links. Pin the ref per build with SOLIDSYSLOG_DOCS_REF # (a release tag or commit SHA); it defaults to main. hooks: + # Publishes the repository-root documents the site should hold rather than + # link away to — SECURITY.md and friends stay at the root because GitHub + # looks for them there, and are rendered here from that same source. + - hooks/root_pages.py - hooks/source_links.py # Injects post-it "realised by" relationship diagrams at the top of selected # generated API pages (experiment — see hooks/relationship_diagrams.py). @@ -157,6 +161,8 @@ plugins: nav: - Home: README.md + # Published from SUPPORT.md at the repository root by hooks/root_pages.py. + - Support: support.md - Overview: - Compliance in one page: overview.md - Integrate: @@ -194,6 +200,11 @@ nav: - SBOM: security/sbom.md - Vulnerability triage: security/triage-runbook.md - Release verification: security/release-verification.md + # Published from SECURITY.md at the repository root by hooks/root_pages.py. + - Security policy: security/policy.md + # Likewise LICENSE.md — the terms are a compliance question for a reader + # deciding whether they may use the library. + - Licence: license.md - API reference: # api-reference/index.md is the tab's own first child, so navigation.indexes # promotes it into the tab link — and the narrow-width drawer opens on this From 589adc6288d3107c9b4560174bf57e97aa7cf705 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 8 Aug 2026 22:40:45 +0100 Subject: [PATCH 39/57] docs: S23.22 assert the docs enumerate every declared role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing the role count left nothing checking the enumerations it used to (wrongly) stand for. A role is declared by its Core/Interface/SolidSyslogDefinition.h header and listed in three hand-written places — the porting guide, the roles index and the mkdocs nav — so a thirteenth role could be added and go unlisted in all three silently. Assert each declared role is linked from all three, and the reverse: a role page listed that no Definition.h declares, which is the rename nobody finished. Verified by injecting each fault in turn rather than by the check passing. The script now covers both things the code declares and the docs enumerate, so its summary and the CI step say that rather than platforms alone. The filename is now narrower than its contents. Part of #708 --- .github/workflows/ci.yml | 15 +++++----- scripts/check_platform_docs.py | 54 ++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 313e94ef..be31a75a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -772,13 +772,14 @@ jobs: with: persist-credentials: false - # A platform is declared once, in SOLIDSYSLOG_PLATFORM_REGISTRY. This - # asserts that its pages, group, nav entry, description and matrix row - # all exist, and that nothing documents a platform that is not - # registered — so registering one is the only edit that can be forgotten. - # It also holds the two boundaries hand review kept losing: no platform - # names another, and every class a platform ships is on its page. - - name: Check every platform is documented + # Platforms are declared in SOLIDSYSLOG_PLATFORM_REGISTRY and roles by + # their Core/Interface/SolidSyslogDefinition.h header. This asserts + # the docs enumerate exactly what the code declares — a platform's pages, + # nav entry, description and matrix row; a role's link from the porting + # guide, the roles index and the nav — and the reverse in both cases. It + # also holds the two boundaries hand review kept losing: no platform names + # another, and every class a platform ships is on its page. + - name: Check the docs match what the code declares run: python3 scripts/check_platform_docs.py # Guard the build hooks against regression — the source-link rewrite's diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index 1cda7e8b..76b7c5b1 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 -"""Assert every registered platform is documented, and nothing else claims to be. +"""Assert the documentation matches what the code declares. + +Two things are declared in the code and enumerated in the docs — the platforms +and the roles — and in both cases the enumeration is written by a person and +checked here, never generated. A missing entry is a decision not yet made, not a +mechanical gap; generating a placeholder would hide it. A platform is declared once, in SOLIDSYSLOG_PLATFORM_REGISTRY in the top-level CMakeLists.txt. Everything else follows from its token: @@ -24,6 +29,12 @@ manifest is generated from the Interface directory, so this asserts the heading the generator writes into is still there. +A role is declared by a Core/Interface/SolidSyslogDefinition.h header, and +listed in three hand-written places: the porting guide, the roles index, and the +mkdocs nav. Each must link the role's generated contract page. The count used to +be stated in prose in six places, so a thirteenth role meant six edits and no +failure; the count is gone and this is what replaces it. + Run: python3 scripts/check_platform_docs.py """ @@ -161,6 +172,41 @@ def naming_faults(rows, terms): return faults +def declared_roles(): + """[Role] from Core/Interface/SolidSyslogDefinition.h, the declaration.""" + interface = os.path.join(ROOT, "Core", "Interface") + suffix = "Definition.h" + return sorted( + name[len("SolidSyslog") : -len(suffix)] + for name in os.listdir(interface) + if name.startswith("SolidSyslog") and name.endswith(suffix) + ) + + +def role_faults(): + """Every declared role is listed wherever roles are enumerated, and nothing + is listed that is not declared.""" + faults = [] + # Where roles are enumerated, and how far each sits from the api/ tree. + listings = { + os.path.join("docs", "porting.md"): "api/", + os.path.join("docs", "roles", "index.md"): "../api/", + "mkdocs.yml": "api/", + } + roles = declared_roles() + + for listing, prefix in listings.items(): + text = read(listing) + for role in roles: + if f"{prefix}structSolidSyslog{role}.md" not in text: + faults.append(f"{role}: declared by its Definition.h but not linked from {listing}") + # A role page that outlived its contract — the rename nobody finished. + for orphan in re.findall(rf"{re.escape(prefix)}structSolidSyslog(\w+)\.md", text): + if orphan not in roles: + faults.append(f"{listing} links {orphan} as a role, but no SolidSyslog{orphan}Definition.h declares it") + return faults + + def unlisted_headers(rows): """The *What it ships* manifest is generated by hooks/platform_backlinks.py from the platform's own Interface directory, so every header is listed by @@ -209,6 +255,7 @@ def check(): faults.extend(unlisted_headers(rows)) faults.extend(naming_faults(rows, vocabulary(rows))) + faults.extend(role_faults()) return faults @@ -225,4 +272,7 @@ def check(): sys.exit(1) for path, term, reason in ALLOWED: print(f"allowed: {path} may say {term} — {reason}") - print(f"platform docs: {len(registered())} platforms, all documented, none naming another") + print( + f"docs match the code: {len(registered())} platforms, all documented and none naming " + f"another; {len(declared_roles())} roles, each listed everywhere roles are enumerated" + ) From 1f276b7ce143a28dd4bafd578603db85c2d955ec Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 09:52:11 +0100 Subject: [PATCH 40/57] docs: S23.22 make the RFC matrix state Core's coverage, not adapters' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compliance guide had the problem the IEC 62443 one had: adapter detail accumulating on a page that should say what the library guarantees against the RFC. The RFC 5425 section compared OpenSSL against Mbed TLS row by row, RFC 6587 documented POSIX and Winsock errno handling, and RFC 5426 named kernel socket options. 52 platform mentions, now none. Two of them were wrong, which is what the duplication buys: RFC 5425 §5.3 said the Mbed TLS floor is inherited from PRESET_DEFAULT. It is not — MbedTlsStream.c pins TLS 1.2 explicitly, precisely because the preset can negotiate down. The platform page was corrected during the restructure; this copy was not. RFC 5426 §3.2 attributed path-MTU discovery to SolidSyslogUdpSender, which is Core. The socket options live in the Datagram adapters. What Core actually does is respond to the contract's OVERSIZE result by asking for MaxPayload and resending trimmed — which is the portable statement, and the true one. Each row now states the contract and links the role that carries it, leaving what an adapter validates and what it leaves to the integrator on that platform's own page, in one direction only. Part of #708 --- docs/rfc-compliance.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index 372aa08c..a9106df1 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -29,8 +29,8 @@ Status key: | 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | | 7.1 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | | 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | -| 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | -| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Partial | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | +| 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths, the type giving RFC 3418's natural wrap; the [capability matrix](platforms/index.md) shows which platforms supply one. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | +| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Partial | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. [AtomicCounter](api/structSolidSyslogAtomicCounter.md) is a vtable abstraction, so the wrap is the contract's and not any one implementation's; the integrator wires a concrete counter at setup time and the [capability matrix](platforms/index.md) shows which platforms supply one. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | | 6.1 | Message size — max 2048 recommended | Supported | Default `SOLIDSYSLOG_MAX_MESSAGE_SIZE` = 2048, matching the largest message §6.1 says a transport receiver SHOULD accept; override it for memory-constrained MCUs via the standard tunable mechanism | | 6 | PRINTUSASCII in header fields (codes 33-126) | Supported | Non-compliant bytes substituted with `?` at format time (HOSTNAME, APP-NAME, PROCID, MSGID) | @@ -42,7 +42,7 @@ Status key: | 3.1 | One message per UDP datagram | Supported | `SolidSyslogUdpSender` sends one datagram per `Send` call | | 3.2 | Default port 514 | Supported | `SOLIDSYSLOG_UDP_DEFAULT_PORT` = 514 | | 3.2 | Message fits in single datagram | Supported | Bounded by `SOLIDSYSLOG_MAX_MESSAGE_SIZE` | -| 3.2 | Avoid IP fragmentation (respect MTU) | Supported | `SolidSyslogUdpSender` lazily connects on first send, enables Linux `IP_MTU_DISCOVER` / Windows equivalent with `IP_PMTUDISC_DO` so the kernel returns `EMSGSIZE` (Winsock `WSAEMSGSIZE`) for path-MTU oversize, queries the path MTU via `getsockopt(IP_MTU)`, and resends a UTF-8-safe trimmed datagram via `SolidSyslogUdpPayload_TrimToCodepointBoundary`. Falls back to `SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD = 1232` (RFC 8200 §5) when the MTU lookup fails | +| 3.2 | Avoid IP fragmentation (respect MTU) | Supported | The [Datagram](api/structSolidSyslogDatagram.md) contract carries this: an oversize payload is rejected rather than fragmented, and reported distinctly from a hard failure. `SolidSyslogUdpSender` responds by asking the Datagram for the path's largest payload and resending a UTF-8-safe trimmed datagram via `SolidSyslogUdpPayload_TrimToCodepointBoundary`, falling back to `SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD = 1232` (RFC 8200 §5) where no path MTU is available. How a platform discovers the MTU is on its own page | | 3.3 | Unreliable delivery — no confirmation | N/A | Inherent in UDP. Caller should be aware | | 4 | No authentication/integrity/confidentiality | N/A | Use TLS transport for security | @@ -57,21 +57,25 @@ Status key: | 3.5 | Session closure handling | Supported | On send failure the stream is closed; the next Send transparently reconnects | | 3.5 | Handle receiver-initiated close | Supported | Detected via send failure path — same reconnect-on-next-Send mechanism | | 3.5 | Address rotation without app restart | Supported | App bumps `endpointVersion`; sender Disconnects and reconnects on next Send | -| — | Partial write handling (send returns short) | Supported | A short return from `send()` is treated as failure: `Send` returns false, the caller closes and reconnects on the next attempt, store-and-forward replays the message on the fresh socket. The TCP socket is non-blocking from the moment it is created, so `Send` never blocks the service thread on a wedged peer or a full kernel send buffer — `EAGAIN`/`EWOULDBLOCK` (POSIX) and `WSAEWOULDBLOCK` (Winsock) propagate immediately as failure. On POSIX, `EINTR` is the only retried errno (portability shim for kernels without `SA_RESTART`); Winsock has no signal-interruption semantics on `send()`, so the EINTR retry path is omitted. Long-term wedge is detected out-of-band via kernel TCP keepalive (`TCP_KEEPIDLE` / `TCP_KEEPINTVL` / `TCP_KEEPCNT`) and `TCP_USER_TIMEOUT` for pending-write death; both are configured at socket open. Bounded connect is via non-blocking `connect()` + `select()`, with the timeout sourced from `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms) or runtime-overridable via the per-Stream `GetConnectTimeoutMs(ConnectTimeoutContext)` accessor — see `Platform/Posix/Source/SolidSyslogPosixTcpStream.c` and `Platform/Windows/Source/SolidSyslogWinsockTcpStream.c`. | +| — | Partial write handling (send returns short) | Supported | The [Stream](api/structSolidSyslogStream.md) contract makes `Send` all-or-nothing: a short write is a failure, never a partial success, so the stream closes itself, the sender reconnects on its next pass, and store-and-forward replays the message on the fresh connection. The same contract keeps steady-state `Send` and `Read` non-blocking and bounds `Open`, so a wedged peer or a full send buffer cannot stall the servicing pass. The connect bound is `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS` (default 200 ms), overridable at runtime through the per-Stream `GetConnectTimeoutMs(ConnectTimeoutContext)` accessor. How a transport detects a long-term wedge, and what it does about one, is on its own page | ## RFC 5425 — TLS Transport Mapping for Syslog -The library ships two reference TLS adapters that satisfy this RFC: `SolidSyslogTlsStream` (OpenSSL, the POSIX / Windows reference) and `SolidSyslogMbedTlsStream` (Mbed TLS, the embedded / FreeRTOS reference). Both implement the same `SolidSyslogStream` vtable, so the section-by-section requirements below apply to whichever the integrator selects. mbedTLS-specific integration guidance lives in [Mbed TLS setup](platforms/mbedtls/setup.md). +TLS is a [Stream](api/structSolidSyslogStream.md) wrapped around another Stream, +so these requirements are met by whichever TLS stream the integrator wires; the +[capability matrix](platforms/index.md) shows which platforms supply one. What +an adapter validates, what it leaves to you, and how credentials reach it are +stated on that platform's own page. | Section | Requirement | Status | Notes | |---|---|---|---| -| 4.1 | TLS over TCP | Supported | `SolidSyslogTlsStream` (OpenSSL) or `SolidSyslogMbedTlsStream` (Mbed TLS) wraps a TCP `Stream` (`SolidSyslogPosixTcpStream` / `SolidSyslogWinsockTcpStream` / `SolidSyslogPlusTcpTcpStream` / `SolidSyslogLwipRawTcpStream` / caller-supplied) | +| 4.1 | TLS over TCP | Supported | A TLS `Stream` wraps a byte-transport `Stream` — a TCP one from any platform, or a caller-supplied one | | 4.2 | Default port 6514 | Supported | `SOLIDSYSLOG_TLS_DEFAULT_PORT` constant in `SolidSyslogTransport.h`, alongside the UDP and TCP defaults. Caller-supplied via the endpoint callback so multi-port deployments can override | -| 5.1 | Server certificate validation | Supported | OpenSSL adapter: `SSL_VERIFY_PEER` + `SSL_CTX_load_verify_locations` + `SSL_set1_host`. Mbed TLS adapter: `MBEDTLS_SSL_VERIFY_REQUIRED` + `mbedtls_ssl_conf_ca_chain` against the caller's `mbedtls_x509_crt*` + `mbedtls_ssl_set_hostname` | -| 5.2 | Mutual TLS (client certificate) | Supported | OpenSSL: optional `clientCertChainPath` / `clientKeyPath` on `SolidSyslogTlsStreamConfig`; `SSL_CTX_check_private_key` confirms pairing. Mbed TLS: optional `ClientCertChain` / `ClientKey` handles on `SolidSyslogMbedTlsStreamConfig`; `mbedtls_ssl_conf_own_cert` | -| 5.3 | TLS 1.2+ cipher suites | Supported | OpenSSL: `SSL_CTX_set_min_proto_version(TLS1_2_VERSION)` pinned; caller-supplied `cipherList` via `SSL_CTX_set_cipher_list`. Mbed TLS: inherited from `mbedtls_ssl_config_defaults(... PRESET_DEFAULT)`; cipher policy owned by the integrator's `mbedtls_config.h` | -| 5.4 | Octet counting framing (mandatory for TLS) | Supported | Reuses `SolidSyslogStreamSender` (RFC 6587 framing is identical) for both adapters | -| 5.5 | TLS close_notify handling | Supported | OpenSSL: `SSL_shutdown` in `TlsStream_Close`. Mbed TLS: `mbedtls_ssl_close_notify` in `MbedTlsStream_Close` | +| 5.1 | Server certificate validation | Supported | Peer verification is required, not optional: the certificate must chain to the trust anchors the caller supplies, and the server identity is checked against it. What an adapter does when no identity is given — and whether it says so — is on its page | +| 5.2 | Mutual TLS (client certificate) | Supported | A client certificate and its key are optional config on the TLS stream, and are presented only when both are given. Whether an adapter validates the pair locally, and what a half-supplied credential does, is on its page | +| 5.3 | TLS 1.2+ cipher suites | Supported | The floor is pinned to TLS 1.2 by the adapter rather than inherited from the TLS library's defaults, so a permissive build cannot negotiate below it. Cipher selection within that floor is the integrator's | +| 5.4 | Octet counting framing (mandatory for TLS) | Supported | Reuses `SolidSyslogStreamSender` — RFC 6587 framing is identical | +| 5.5 | TLS close_notify handling | Supported | Close sends `close_notify` before tearing the connection down | ## Summary From 58a291a82b9365da8d991f947125fe3936c78b13 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 16:51:02 +0100 Subject: [PATCH 41/57] docs: S23.22 follow the platform rename through the restructured docs Part of #708 --- docs/build-integration.md | 24 +++++++++---------- docs/ci.md | 4 ++-- docs/misra-deviations.md | 2 +- docs/platforms/index.md | 2 +- docs/platforms/openssl/index.md | 2 +- docs/platforms/openssl/setup.md | 12 +++++----- docs/platforms/posix/setup.md | 2 +- .../platforms/{atomics => stdatomic}/index.md | 2 +- .../platforms/{atomics => stdatomic}/setup.md | 2 +- hooks/page_descriptions.py | 6 ++--- hooks/test_platform_backlinks.py | 2 +- mkdocs.yml | 2 +- scripts/check_platform_docs.py | 2 +- 13 files changed, 32 insertions(+), 32 deletions(-) rename docs/platforms/{atomics => stdatomic}/index.md (91%) rename docs/platforms/{atomics => stdatomic}/setup.md (97%) diff --git a/docs/build-integration.md b/docs/build-integration.md index efd1fb54..580e0a42 100644 --- a/docs/build-integration.md +++ b/docs/build-integration.md @@ -79,7 +79,7 @@ FetchContent_MakeAvailable(SolidSyslog) Named platforms are on, unnamed platforms are off — set it before `FetchContent_MakeAvailable`, or pass `-DSOLIDSYSLOG_PLATFORMS=...` on the -command line. Each platform also has its own switch (`-DSOLIDSYSLOG_LWIP=ON`, +command line. Each platform also has its own switch (`-DSOLIDSYSLOG_LWIPRAW=ON`, `-DSOLIDSYSLOG_PLUSTCP=OFF`, …) when you want to adjust one without restating the list. @@ -87,7 +87,7 @@ the list. |---|---|---| | `Posix` | network, file, mutex, clock | POSIX sockets, `pthread`, `mqueue` | | `Windows` | network, file, mutex, atomics, clock | Winsock, `CRITICAL_SECTION`, Win32 | -| `Atomics` | atomics | C11 `` | +| `StdAtomic` | atomics | C11 `` | | `OpenSsl` | tls | OpenSSL 3.0+ | | `MbedTls` | tls | Mbed TLS | | `LwipRaw` | network | lwIP Raw API | @@ -117,7 +117,7 @@ The variable takes three kinds of answer: | Value | Selects | |---|---| -| `Auto` (the default) | the host platforms this toolchain can provide, decided by a compile probe: `Posix`, `Windows`, `Atomics`, `OpenSsl` | +| `Auto` (the default) | the host platforms this toolchain can provide, decided by a compile probe: `Posix`, `Windows`, `StdAtomic`, `OpenSsl` | | `""` | nothing — Core alone, for an integrator supplying every adapter themselves | | a list | exactly those, host or upstream | @@ -161,7 +161,7 @@ FetchContent_Declare(SolidSyslog GIT_REPOSITORY https://github.com/cososo-ltd/solid-syslog.git GIT_TAG main) -set(SOLIDSYSLOG_PLATFORMS "FreeRtos;LwipRaw;MbedTls;FatFs;Atomics") +set(SOLIDSYSLOG_PLATFORMS "FreeRtos;LwipRaw;MbedTls;FatFs;StdAtomic") FetchContent_MakeAvailable(SolidSyslog) add_executable(my_logger main.c diskio.c) @@ -184,7 +184,7 @@ target_include_directories(my_logger PRIVATE ${FATFS_DIR}/source) ``` -`Atomics` is named but never linked — it compiles the C11 counter into +`StdAtomic` is named but never linked — it compiles the C11 counter into `libSolidSyslog.a`. Naming it says "my toolchain has working `_Atomic`"; get it wrong and the configure stops rather than silently degrading to the Null counter. @@ -230,7 +230,7 @@ The library ships `solidsyslog.mk` at its root. Name the platforms you want and it: ```make -SOLIDSYSLOG_PLATFORMS := LwipRaw Atomics FreeRtos MbedTls FatFs +SOLIDSYSLOG_PLATFORMS := LwipRaw StdAtomic FreeRtos MbedTls FatFs include third_party/solid-syslog/solidsyslog.mk ``` @@ -275,7 +275,7 @@ $(BUILD)/libSolidSyslog.a: $(SOLIDSYSLOG_CORE_OBJS) There is no separate link step and no distinction between platforms that are named and platforms that are linked. That distinction exists only for CMake consumers, who receive some platforms inside `libSolidSyslog.a`; a Make build has no such library, so naming -`Atomics` compiles its sources exactly as naming `LwipRaw` compiles its own. +`StdAtomic` compiles its sources exactly as naming `LwipRaw` compiles its own. Each adapter gates itself on the upstream option it needs, so naming a platform is safe whatever your configuration. A build with `LWIP_DNS=0` compiles the lwIP DNS resolver to @@ -338,8 +338,8 @@ target: ```bash cmake -S . -B build/manifest \ - -DSOLIDSYSLOG_PLATFORMS="LwipRaw;MbedTls;FreeRtos;FatFs;Atomics" \ - -DSOLIDSYSLOG_MANIFEST_PLATFORMS="LwipRaw;MbedTls;FreeRtos;FatFs;Atomics" + -DSOLIDSYSLOG_PLATFORMS="LwipRaw;MbedTls;FreeRtos;FatFs;StdAtomic" \ + -DSOLIDSYSLOG_MANIFEST_PLATFORMS="LwipRaw;MbedTls;FreeRtos;FatFs;StdAtomic" cmake --build build/manifest --target manifest # prints the manifest ``` @@ -347,10 +347,10 @@ cmake --build build/manifest --target manifest # prints the manifest `SOLIDSYSLOG_PLATFORMS`: `Auto` describes every platform this configuration selected, empty describes none. The Core `.c` set is always included. -Platforms selected by a toolchain capability probe — `Atomics`, `Posix`, +Platforms selected by a toolchain capability probe — `StdAtomic`, `Posix`, `Windows`, `OpenSsl` — get their own section. A CMake consumer receives them inside `libSolidSyslog.a`, but a manifest build has no such library, so compile -them alongside everything else. `Atomics` is the one that matters on a +them alongside everything else. `StdAtomic` is the one that matters on a cross-toolchain: without it there is no atomic counter, and every RFC 5424 `sequenceId` is `1`. @@ -381,7 +381,7 @@ For this stack that comes to: ### 3. Language standard The manifest's *Language* section states the floor and any platform that raises -it. The library is C99; the `Atomics` platform uses `` and needs +it. The library is C99; the `StdAtomic` platform uses `` and needs C11. Anything at or above that works — C11, C17 and C23 are all fine — so the manifest names the standard, not a `-std=` flag. diff --git a/docs/ci.md b/docs/ci.md index ac980e16..ace38c16 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -35,7 +35,7 @@ without renaming what's already there. | `bdd-windows-otel` | — | Windows-eligible BDD scenarios driven against an OTel Collector oracle | | `build-freertos-host-tdd-plustcp` | `debug` | Host-TDD of the FreeRTOS, FreeRTOS-Plus-TCP, Plus-FAT, FatFs and Mbed TLS adapters against fakes; runs inside `cpputest-freertos` (upstream sources at fixed paths) | | `build-freertos-target-plustcp` | `freertos-cross` | ARM cross-build (Cortex-M3, mps2-an385) of the BDD target ELF over FreeRTOS-Plus-TCP; uploads it as an artifact | -| `build-freertos-target-lwip` | `freertos-cross-lwip` | The same cross-build over lwIP with ChaN FatFs (`FreeRtos;LwipRaw;MbedTls;FatFs;Atomics`) | +| `build-freertos-target-lwip` | `freertos-cross-lwip` | The same cross-build over lwIP with ChaN FatFs (`FreeRtos;LwipRaw;MbedTls;FatFs;StdAtomic`) | | `bdd-freertos-qemu-plustcp` | — | Pulls the Plus-TCP target ELF, brings up the freertos compose pair (`syslog-ng-freertos` + `behave-freertos`); Behave drives the target through `qemu-system-arm`'s UART | | `bdd-freertos-qemu-lwip` | — | The same scenarios against the lwIP target ELF | | `consumer-smoke-linux` | — | Builds `ci/consumer-smoke/` as a FetchContent consumer, proving the documented integration path still works | @@ -64,7 +64,7 @@ The lane names say the platform and toolchain but not the adapter, so: | Adapter | Where it is exercised | |---|---| -| OpenSSL (`SolidSyslogTlsStream`, security policies) | `integration-linux-openssl`, `integration-windows-openssl` against real libssl | +| OpenSSL (`SolidSyslogOpenSslStream`, security policies) | `integration-linux-openssl`, `integration-windows-openssl` against real libssl | | Mbed TLS (`SolidSyslogMbedTlsStream`, security policies) | `integration-linux-mbedtls` against real Mbed TLS; both FreeRTOS QEMU BDD lanes over a real handshake | | FreeRTOS-Plus-TCP | `build-freertos-host-tdd-plustcp` against fakes; `bdd-freertos-qemu-plustcp` end to end under QEMU | | lwIP | `bdd-freertos-qemu-lwip` end to end under QEMU; static analysis via the `*-freertos-lwip` lanes | diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 31ad2e74..4fa1bb9f 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -286,7 +286,7 @@ third-party API contract (the public `Send` / `SendTo` interface) is class, and the Formatter storage cast of sub-case (b). 14 sites. - **Pragmatic tier** — `Platform/*/Source/`: the same `SelfFromBase` shape in each adapter, the per-platform Address downcasts, and the callback `void*` - casts of sub-case (c). 66 sites, across the Atomics, FatFs, FreeRtos, + casts of sub-case (c). 66 sites, across the StdAtomic, FatFs, FreeRtos, LwipRaw, MbedTls, OpenSsl, PlusFat, PlusTcp, Posix and Windows packs. 80 line-specific suppressions in total — 12 against rule 11.2, 57 against 11.3 diff --git a/docs/platforms/index.md b/docs/platforms/index.md index eef38310..afd856f7 100644 --- a/docs/platforms/index.md +++ b/docs/platforms/index.md @@ -22,7 +22,7 @@ vtable. | [Mbed TLS](mbedtls/index.md) | Mbed TLS | | ● | ● | | | | | [FatFs](fatfs/index.md) | ChaN FatFs | | | | ● | | | | [FreeRTOS-Plus-FAT](plusfat/index.md) | FreeRTOS-Plus-FAT | | | | ● | | | -| [C11 atomics](atomics/index.md) | `` | | | | | ● | | +| [C11 atomics](stdatomic/index.md) | `` | | | | | ● | | The at-rest-crypto column is the keyed policies (HMAC-SHA256, AES-256-GCM); the unkeyed CRC-16 policy is Core. Buffer, Store, and Structured Data are roles Core diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 35d3c5e2..82faa9d8 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -19,7 +19,7 @@ and for mutual TLS a PEM client certificate chain and private key. A ## Security behaviour and obligations The per-field detail is in -[`SolidSyslogTlsStream.h`](../../api/SolidSyslogTlsStream_8h.md), alongside the +[`SolidSyslogOpenSslStream.h`](../../api/SolidSyslogOpenSslStream_8h.md), alongside the fields themselves. What follows is the behaviour of the adapter as a whole, and the work it leaves to you. diff --git a/docs/platforms/openssl/setup.md b/docs/platforms/openssl/setup.md index 23aeb49e..1d799bae 100644 --- a/docs/platforms/openssl/setup.md +++ b/docs/platforms/openssl/setup.md @@ -1,6 +1,6 @@ # OpenSSL setup -Wiring `SolidSyslogTlsStream` so a `SolidSyslogStreamSender` delivers RFC 5425 +Wiring `SolidSyslogOpenSslStream` so a `SolidSyslogStreamSender` delivers RFC 5425 syslog over TLS. [OpenSSL](index.md) covers what the adapter guarantees and what it leaves to you; the config fields are documented on the struct itself. This page is the wiring. @@ -26,12 +26,12 @@ TLS is a Stream wrapped around another Stream. The TLS adapter carries the records; the transport underneath carries the bytes, and it can be any Stream. ```text -StreamSender → SolidSyslogTlsStream → your TCP stream → socket +StreamSender → SolidSyslogOpenSslStream → your TCP stream → socket ``` The TLS stream **borrows** its transport. It may close it, but it never destroys it: the transport is yours to create and to destroy, and it must stay valid -until `SolidSyslogTlsStream_Destroy`. +until `SolidSyslogOpenSslStream_Destroy`. ## Wiring it @@ -39,14 +39,14 @@ until `SolidSyslogTlsStream_Destroy`. /* Your TCP stream and sleep, from the platform that supplies them. */ struct SolidSyslogStream* transport = CreateTcpStream(); -static struct SolidSyslogTlsStreamConfig tlsConfig; -tlsConfig = (struct SolidSyslogTlsStreamConfig) {0}; +static struct SolidSyslogOpenSslStreamConfig tlsConfig; +tlsConfig = (struct SolidSyslogOpenSslStreamConfig) {0}; tlsConfig.Transport = transport; tlsConfig.Sleep = MySleep; /* required — no fallback */ tlsConfig.CaBundlePath = "/etc/ssl/collector-ca.pem"; tlsConfig.ServerName = "collector.example.net"; -struct SolidSyslogStream* tls = SolidSyslogTlsStream_Create(&tlsConfig); +struct SolidSyslogStream* tls = SolidSyslogOpenSslStream_Create(&tlsConfig); ``` Zero-initialise the config before filling it. diff --git a/docs/platforms/posix/setup.md b/docs/platforms/posix/setup.md index 379a2d0d..89468224 100644 --- a/docs/platforms/posix/setup.md +++ b/docs/platforms/posix/setup.md @@ -20,7 +20,7 @@ do not need it. ## Wiring a sender ```c -struct SolidSyslogResolver* resolver = SolidSyslogGetAddrInfoResolver_Create(); +struct SolidSyslogResolver* resolver = SolidSyslogPosixResolver_Create(); struct SolidSyslogAddress* address = SolidSyslogPosixAddress_Create(); struct SolidSyslogStream* stream = SolidSyslogPosixTcpStream_Create(NULL); diff --git a/docs/platforms/atomics/index.md b/docs/platforms/stdatomic/index.md similarity index 91% rename from docs/platforms/atomics/index.md rename to docs/platforms/stdatomic/index.md index 30eb9fd9..9f7e6ce6 100644 --- a/docs/platforms/atomics/index.md +++ b/docs/platforms/stdatomic/index.md @@ -1,6 +1,6 @@ # C11 atomics -`Platform/Atomics/` is a portable AtomicCounter built on C11 `` — the +`Platform/StdAtomic/` is a portable AtomicCounter built on C11 `` — the sequenceId source on any target with a C11 compiler, no OS dependency. Fills the [AtomicCounter](../../api/structSolidSyslogAtomicCounter.md) role. diff --git a/docs/platforms/atomics/setup.md b/docs/platforms/stdatomic/setup.md similarity index 97% rename from docs/platforms/atomics/setup.md rename to docs/platforms/stdatomic/setup.md index 7c0194e8..4057921d 100644 --- a/docs/platforms/atomics/setup.md +++ b/docs/platforms/stdatomic/setup.md @@ -9,7 +9,7 @@ and what it leaves to you. the adapter compiles straight into the static library: ```cmake -set(SOLIDSYSLOG_PLATFORMS "Atomics") +set(SOLIDSYSLOG_PLATFORMS "StdAtomic") ``` The compiler must support C11 atomics. Where a toolchain does not, the diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index df29d406..f7aef041 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -105,7 +105,7 @@ "at-rest crypto for the SecurityPolicy role, on hosted targets." ), "platforms/openssl/setup.md": ( - "Wire SolidSyslogTlsStream over a TCP stream for RFC 5425 syslog over " + "Wire SolidSyslogOpenSslStream over a TCP stream for RFC 5425 syslog over " "TLS: what to link, the layering, the config, and mutual TLS." ), "platforms/mbedtls/index.md": ( @@ -132,11 +132,11 @@ "Back store-and-forward with FreeRTOS-Plus-FAT: what the ff_stdio File " "adapter needs from your build and media, and how the store sits above it." ), - "platforms/atomics/index.md": ( + "platforms/stdatomic/index.md": ( "The portable C11 stdatomic.h AtomicCounter — the sequenceId source on " "any target with a C11 compiler, with no OS dependency." ), - "platforms/atomics/setup.md": ( + "platforms/stdatomic/setup.md": ( "Wire the C11 atomics sequence-number source: what to link, creating " "the counter, and why an unfilled role silently disables gap detection." ), diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 1d3d2cc7..8c04e847 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -145,7 +145,7 @@ def test_the_brief_is_the_headers_own_first_sentence(self): self.assertIn("Error codes and Source identity for the MbedTlsStream adapter.", self.ships()) def test_a_multi_sentence_brief_stops_at_the_first_sentence(self): - row = [line for line in self.ships("atomics").splitlines() + row = [line for line in self.ships("stdatomic").splitlines() if "SolidSyslogStdAtomicCounter.h" in line] self.assertEqual(len(row), 1) self.assertIn("backing the RFC 5424 sequenceId.", row[0]) diff --git a/mkdocs.yml b/mkdocs.yml index 6930e14d..2ce0f84c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -181,7 +181,7 @@ nav: - Platforms: - platforms/index.md - Port a new platform: porting.md - - C11 atomics: platforms/atomics/index.md + - C11 atomics: platforms/stdatomic/index.md - FatFs: platforms/fatfs/index.md - FreeRTOS: platforms/freertos/index.md - FreeRTOS-Plus-FAT: platforms/plusfat/index.md diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index 76b7c5b1..2f069617 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -52,7 +52,7 @@ # of the vocabulary — and every registered token must appear, so adding a # platform forces the decision rather than silently widening the gap. ALIASES = { - "Atomics": [], + "StdAtomic": [], "FatFs": ["FatFs", "ChaN"], "FreeRtos": ["FreeRTOS"], "LwipRaw": ["lwIP"], From 5d66770373678558b2a48ecea90d7e64a96eb6fd Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 17:06:00 +0100 Subject: [PATCH 42/57] docs: S23.22 say what the release actually signs Part of #708 --- docs/cra.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cra.md b/docs/cra.md index bbabe666..89fb06f3 100644 --- a/docs/cra.md +++ b/docs/cra.md @@ -86,7 +86,7 @@ consuming. | **(1)** | identify and document components, including a software bill of materials in a machine-readable format | A CycloneDX [SBOM](security/sbom.md) per release, to fold into your product's own | | **(2)**, **(4)** | remediate without delay; disclose fixed vulnerabilities | The [vulnerability triage runbook](security/triage-runbook.md) sets out how reports are assessed and published | | **(5)**, **(6)** | a coordinated vulnerability disclosure policy, and a contact address | [`SECURITY.md`](security/policy.md) | -| **(7)** | securely distribute updates | [Release verification](security/release-verification.md): signed, reproducible artefacts you can check before adopting | +| **(7)** | securely distribute updates | [Release verification](security/release-verification.md): a signed source-tree hash and signed CycloneDX SBOM, both verifiable against the workflow and tag that produced them | Secure-by-design evidence for your technical file is in the [threat model](security/threat-model.md) and [at-rest From 1d27bfae695ae5887fd3e38d3cac0d6b35589f23 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 17:09:57 +0100 Subject: [PATCH 43/57] docs: S23.22 give Core the top-level place the product has Part of #708 --- docs/README.md | 6 +++--- mkdocs.yml | 28 ++++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/README.md b/docs/README.md index fec3ed4a..4ffb687a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ and [solid-syslog-example-make](https://github.com/cososo-ltd/solid-syslog-examp Pick a lane: -- [Overview](#overview): what SolidSyslog is and how it helps with CRA and IEC 62443 compliance. +- [Core](#core): the library itself — the protocol, the pipeline, and the portable pieces that run the same everywhere. - [Integrate it](#integrate): get a syslog stack compiling and sending in your product. - [Platforms](#platforms): what reaches your hardware — the shipped adapters, and how to write one for a target we don't cover. - [Compliance](#compliance): the CRA, IEC 62443, the RFCs, and the security posture. @@ -21,10 +21,10 @@ Pick a lane: --- -## Overview +## Core +- [Core](core/index.md): what is always compiled and depends on nothing external — the syslog protocol, the assembly, buffering, storage and sending pipeline, and the portable role implementations. Where a platform exists to reach your hardware, Core exists to be the same everywhere. - Why SolidSyslog, and capability at a glance: the project `README.md`. -- [Compliance in one page](overview.md): the evaluator's one-screen orientation. What CRA and IEC 62443 ask of an audit-logging function, and how SolidSyslog helps. ## Integrate diff --git a/mkdocs.yml b/mkdocs.yml index 2ce0f84c..6332d887 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -163,8 +163,13 @@ nav: - Home: README.md # Published from SUPPORT.md at the repository root by hooks/root_pages.py. - Support: support.md - - Overview: - - Compliance in one page: overview.md + # Core is the product: the portable library everything else exists to serve. + # It sits at the top level beside Platforms, which is the convenience layer, + # rather than below the generated indexes inside API reference — the nav said + # the opposite of the product for as long as it did. One child, so + # navigation.indexes promotes it into the tab link. + - Core: + - core/index.md - Integrate: - Building up the protection you need: hardening-path.md - Adding it to your build: build-integration.md @@ -192,6 +197,10 @@ nav: - Posix: platforms/posix/index.md - Windows: platforms/windows/index.md - Compliance: + # The one-screen orientation on both frameworks, so it leads the section + # the frameworks are in rather than sitting alone under a tab called + # Overview, where its own title contradicted the label above it. + - Compliance in one page: overview.md - CRA guide: cra.md - IEC 62443 guide: iec62443.md - RFC matrix: rfc-compliance.md @@ -208,22 +217,17 @@ nav: - API reference: # api-reference/index.md is the tab's own first child, so navigation.indexes # promotes it into the tab link — and the narrow-width drawer opens on this - # landing at the tab level, where Core / Platforms / Roles are its siblings. - # Nesting it inside a same-named section (as before) made the drawer open one - # level deeper, hiding those doorways behind the back arrow. + # landing at the tab level, where Headers and Symbols / Roles are its + # siblings. Nesting it inside a same-named section (as before) made the + # drawer open one level deeper, hiding those doorways behind the back arrow. - api-reference/index.md - # The raw Doxygen indexes as one peer section beside Core / Platforms / Roles; - # labelled distinctly (not "API reference") so it doesn't shadow the tab name. + # The raw Doxygen indexes as one peer section beside Roles; labelled + # distinctly (not "API reference") so it doesn't shadow the tab name. - Headers and Symbols: - Headers: api/files.md - Data structures: api/annotated.md - Functions: api/functions.md - Macros: api/macros.md - # A section (not a bare page) so Material gives it md-nav__item--section and - # it reads with the same weight as Platforms / Roles. Its index is its only - # child — navigation.indexes promotes it into the section link. - - Core: - - core/index.md # A role *is* its vtable, so each entry links straight to the generated # contract page; detail belongs in the SolidSyslogDefinition.h # doc comment, not a hand-maintained copy here. From 0cc658bb4c6b3087369b2408526967592d806e8d Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 19:35:45 +0100 Subject: [PATCH 44/57] docs: S23.22 stop the manifest swallowing a header brief's angle brackets Part of #708 --- hooks/platform_backlinks.py | 18 +++++++++++++++++- hooks/test_platform_backlinks.py | 8 ++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/hooks/platform_backlinks.py b/hooks/platform_backlinks.py index a4db144e..9db9c0df 100644 --- a/hooks/platform_backlinks.py +++ b/hooks/platform_backlinks.py @@ -120,11 +120,27 @@ def _index(config): return _CACHE[root] +def _cell(brief): + """A header's brief, safe to drop into a Markdown table cell. + + Two characters cannot travel as themselves. A pipe would end the cell. An + angle bracket opens an HTML tag, so a brief naming a standard header — + ``An AtomicCounter over C11 `` — rendered as *over C11 ,* with + the include swallowed and the punctuation around it left stranded. It read + like a typo rather than a missing word, which is why it survived review. + Doxygen escapes these on its own pages; the brief only lost them on the + route through here. + """ + for raw, escaped in (("&", "&"), ("<", "<"), (">", ">"), ("|", r"\|")): + brief = brief.replace(raw, escaped) + return brief + + def _ships(entries): """The manifest table: every header the platform publishes, by filename.""" rows = ["| Header | What it is |", "|---|---|"] for stem, brief in entries: - rows.append(f"| [`{stem}.h`](../../api/{stem}_8h.md) | {brief.replace('|', r'\|')} |") + rows.append(f"| [`{stem}.h`](../../api/{stem}_8h.md) | {_cell(brief)} |") return "\n".join(rows) diff --git a/hooks/test_platform_backlinks.py b/hooks/test_platform_backlinks.py index 8c04e847..c090f49a 100644 --- a/hooks/test_platform_backlinks.py +++ b/hooks/test_platform_backlinks.py @@ -144,6 +144,14 @@ def test_a_filename_links_to_its_generated_page(self): def test_the_brief_is_the_headers_own_first_sentence(self): self.assertIn("Error codes and Source identity for the MbedTlsStream adapter.", self.ships()) + def test_a_brief_naming_a_standard_header_keeps_it(self): + # is an HTML tag to the Markdown renderer, so unescaped it + # vanished and took the sentence's sense with it. + self.assertIn("C11 <stdatomic.h>", self.ships("stdatomic")) + + def test_a_cell_escapes_what_would_break_it(self): + self.assertEqual(h._cell("a c | d & e"), r"a <b> c \| d & e") + def test_a_multi_sentence_brief_stops_at_the_first_sentence(self): row = [line for line in self.ships("stdatomic").splitlines() if "SolidSyslogStdAtomicCounter.h" in line] From 08f96735d77cff8657a9be7a1d2019b3f68697ca Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 19:50:20 +0100 Subject: [PATCH 45/57] docs: S23.22 bring the porting guide's error convention up to the prefix Part of #708 --- docs/porting.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/porting.md b/docs/porting.md index 6c4d1922..9e2f3fae 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -112,8 +112,12 @@ implementation per role. Every tunable lives in ### Error reporting — the `*Errors.h` convention Each adapter ships a `SolidSyslogErrors.h` declaring an -`enum SolidSyslogErrors` (`_ERROR_*` codes plus an -`_ERROR_MAX` bookend) and an `extern const struct SolidSyslogErrorSource`. When +`enum SolidSyslogErrors` (`SOLIDSYSLOG__ERROR_*` codes plus a +`SOLIDSYSLOG__ERROR_MAX` bookend) and an +`extern const struct SolidSyslogErrorSource`. How the class name is spelled +inside those constants — one word per PascalCase word, except that your pack's +registry token stays whole — is in +[Naming conventions](NAMING.md#spelling-a-class-name-inside-a-screaming_snake-identifier). When something fails, the adapter calls `SolidSyslog_Error(severity, source, category, detail)`: `source` is its own `ErrorSource` (matched by pointer identity in a handler), `category` is a portable reaction axis from From 20f4f2a223481295959ed823bb00b9a53ade660c Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 20:05:17 +0100 Subject: [PATCH 46/57] docs: S23.22 drop the deviation register's link to a section that moved Part of #708 --- docs/misra-deviations.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 4fa1bb9f..6de3347d 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -594,9 +594,8 @@ Two distinct site categories trigger this rule: `const void*` — the contract is read-only inside the library. lwIP's `struct pbuf::payload` is declared `void*` (no `const` variant in the lwIP headers); `udp_sendto` only reads the - payload — that is the `PBUF_REF` zero-copy contract, set out under - [Datagram — pbuf strategy](platforms/lwipraw/setup.md#datagram--pbuf-strategy) - — but the field type does not encode that. Assigning + payload — that is the `PBUF_REF` zero-copy contract — but the field + type does not encode that. Assigning our `const void*` parameter to lwIP's `void*` field strips the qualifier at the platform-API boundary, same shape as the Winsock `select()` site above. Alternatives considered and From 60e80f24b70b568f2edcb946cf200946469e2b14 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 21:42:52 +0100 Subject: [PATCH 47/57] docs: S23.22 correct the durability and blocking claims CodeRabbit caught Part of #708 --- docs/platforms/fatfs/index.md | 13 +++++--- docs/platforms/fatfs/setup.md | 11 +++++-- docs/platforms/plusfat/index.md | 19 +++++++---- docs/platforms/plusfat/setup.md | 26 ++++++++------- docs/platforms/posix/index.md | 7 ++-- docs/rfc-compliance.md | 2 +- hooks/page_descriptions.py | 6 ++-- scripts/check_platform_docs.py | 57 ++++++++++++++++++++++++++------- 8 files changed, 95 insertions(+), 46 deletions(-) diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index afa59f09..c6ad579b 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -24,11 +24,14 @@ keeping it unreadable, is the SecurityPolicy role's job, not this one — see ### Durability is bounded by the write, not guaranteed by it -`f_sync` runs after every write, so at most the record in flight is lost on -power failure. Whether that reaches the medium, and what the FAT metadata looks -like afterwards, is a property of your `diskio.c` driver and the hardware under -it. FAT is not a journalling filesystem, and a partially written directory entry -is possible on a device that loses power mid-update. +`f_sync` runs after every write. It writes back the cached data, updates the +directory entry so the recorded file size includes the record, and issues +`CTRL_SYNC` to your driver — so the loss window is one incomplete write rather +than everything since the last close. Whether the sync reaches the medium, and +what the FAT metadata looks like afterwards, is a property of your `diskio.c` +driver and the hardware under it. FAT is not a journalling filesystem, and a +partially written directory entry is possible on a device that loses power +mid-update. ### The media driver is yours diff --git a/docs/platforms/fatfs/setup.md b/docs/platforms/fatfs/setup.md index 6ae6504a..f17fc498 100644 --- a/docs/platforms/fatfs/setup.md +++ b/docs/platforms/fatfs/setup.md @@ -47,9 +47,14 @@ volume up belongs to your start-up code. ## Durability -The adapter flushes after every complete write, so a power loss never discards -a record the store was told had been written. Whether that flush reaches the -medium is a property of your disk I/O driver. +The adapter calls `f_sync` after every complete write. That writes back the +cached data, updates the directory entry so the file's recorded size includes +the record, and asks your driver to sync. The loss window is therefore one +incomplete write rather than everything written since the file was last closed. + +Whether the sync reaches the medium is your driver's `CTRL_SYNC` and the +hardware beneath it: a driver that reports success without flushing a device +cache narrows nothing. FAT is not a journalling file system. A device that loses power partway through a directory update can leave that entry inconsistent, which is a property of diff --git a/docs/platforms/plusfat/index.md b/docs/platforms/plusfat/index.md index b1c672dd..5320b031 100644 --- a/docs/platforms/plusfat/index.md +++ b/docs/platforms/plusfat/index.md @@ -23,13 +23,18 @@ keeping it unreadable, is the SecurityPolicy role's job, not this one — see ### Durability is bounded by the write, not guaranteed by it -The adapter flushes after every write, so at most the record in flight is lost -on power failure. It flushes the IO manager's cache rather than the file: -`ff_stdio.h` declares `ff_fflush` but the library never defines it, so -`FF_FlushCache` is the real durability primitive. Whether that reaches the -medium is a property of your `FF_Disk_t` driver and the hardware under it. FAT is not a journalling filesystem, and a -partially written directory entry is possible on a device that loses power -mid-update. +The adapter flushes after every write, and that flush persists the record's data +sectors. It does not commit the file's recorded size: the directory entry is +written when the file is closed, and the store holds a block file open across +appends. A power cut can therefore leave records on the medium that the recorded +size excludes, and the store will not read those back. + +It flushes the IO manager's cache rather than the file: `ff_stdio.h` declares +`ff_fflush` but the library never defines it, so `FF_FlushCache` is the real +durability primitive. Whether that reaches the medium is a property of your +`FF_Disk_t` driver and the hardware under it. FAT is not a journalling +filesystem, and a partially written directory entry is possible on a device that +loses power mid-update. ### The media driver is yours diff --git a/docs/platforms/plusfat/setup.md b/docs/platforms/plusfat/setup.md index 637d1eda..fb8e3fd8 100644 --- a/docs/platforms/plusfat/setup.md +++ b/docs/platforms/plusfat/setup.md @@ -58,18 +58,20 @@ filename mode is sufficient for that naming. ## Durability -The adapter flushes after every complete write, so a power loss never discards -a record the store was told had been written. - -Two things are specific to Plus-FAT and worth knowing. There is **no per-file -flush**: `ff_stdio.h` declares `ff_fflush` but the library never defines it, so -the adapter flushes the IO manager's cache instead, which is the real -durability primitive. And the **directory entry is committed on close, not on -each flush** — the cache flush persists the file's data sectors, while the -recorded file size is written when the file is closed. A graceful shutdown -therefore leaves data and metadata consistent. If the device must survive a -hard power cut mid-record, size your records and choose your discard policy -with that in mind. +The adapter flushes after every complete write, and that flush persists the +record's data sectors. It does not commit the file's recorded size — the +**directory entry is written on close, not on each flush** — and the store holds +a block file open across appends. So a hard power cut can leave records on the +medium that the recorded size excludes, and the store will not read those back. +A graceful shutdown closes the file and leaves data and metadata consistent. + +One thing here is specific to Plus-FAT. There is **no per-file flush**: +`ff_stdio.h` declares `ff_fflush` but the library never defines it, so the +adapter flushes the IO manager's cache instead, which is the real durability +primitive. + +If the device must survive a hard power cut mid-record, size your records and +choose your discard policy with that in mind. ## Memory diff --git a/docs/platforms/posix/index.md b/docs/platforms/posix/index.md index 61a67a3f..4e334bdf 100644 --- a/docs/platforms/posix/index.md +++ b/docs/platforms/posix/index.md @@ -41,7 +41,8 @@ it, and the library performs no independent check. ### The blocking surface is bounded but not zero -Sockets are non-blocking once open, so a send returns immediately against a -wedged peer. The initial connection is the bounded exception, and its budget is -a tunable. A deployment with a hard real-time deadline should drive delivery +The TCP stream's socket is non-blocking once open, so a send returns immediately +against a wedged peer. The initial connection is the bounded exception, and its +budget is a tunable. The UDP socket is left blocking: a datagram send is not +expected to wait, but nothing here bounds it if the kernel's send buffer fills. A deployment with a hard real-time deadline should drive delivery from a service thread rather than the calling one. diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index a9106df1..8584348d 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -25,7 +25,7 @@ Status key: | 6.2.6 | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | | 6.2.7 | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | | 6.3 | STRUCTURED-DATA — SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | -| 6.3.2 | SD-ID / SD-NAME syntax validation | Planned | Not performed. It only bites once callers can supply their own names: the three standard SDs (meta / timeQuality / origin) use compile-time-constant names that are valid by construction. Tracked under Custom Structured Data ([#64](https://github.com/cososo-ltd/solid-syslog/issues/64)), which is what introduces caller-supplied SD-IDs and PARAM names | +| 6.3.2 | SD-ID / SD-NAME syntax validation | Planned | Not performed. It only bites once callers can supply their own names: the three standard SDs (meta / timeQuality / origin) use compile-time-constant names that are valid by construction. Tracked under Custom Structured Data (`#64`), which is what introduces caller-supplied SD-IDs and PARAM names | | 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | | 7.1 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | | 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | diff --git a/hooks/page_descriptions.py b/hooks/page_descriptions.py index f7aef041..f737a67f 100644 --- a/hooks/page_descriptions.py +++ b/hooks/page_descriptions.py @@ -78,7 +78,7 @@ ), "platforms/freertos/index.md": ( "The FreeRTOS adapter pack: kernel primitives filling the Mutex role and " - "the sysUpTime callback, with networking from Plus-TCP or lwIP." + "the sysUpTime callback. Networking comes from a separate platform." ), "platforms/freertos/setup.md": ( "Wire the FreeRTOS kernel primitives: what to link, static allocation, " @@ -114,11 +114,11 @@ ), "platforms/mbedtls/setup.md": ( "Wire Mbed TLS for RFC 5425 syslog over TLS on an embedded target: the " - "layering, bringing the library up, and the FreeRTOS sizing traps." + "layering, bringing the library up, and the sizing traps." ), "platforms/fatfs/index.md": ( "The ChaN FatFs adapter pack, filling the File role beneath a BlockDevice " - "— RTOS-agnostic, for bare-metal, FreeRTOS, Zephyr and NuttX targets." + "— RTOS-agnostic, for bare-metal targets or under any RTOS." ), "platforms/fatfs/setup.md": ( "Give store-and-forward a FatFs file backend: what to link, the disk " diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index 2f069617..e14e9dfb 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -148,6 +148,40 @@ def scanned(directory, slug): yield os.path.relpath(os.path.join(path, name), ROOT) +DESCRIPTIONS = os.path.join("hooks", "page_descriptions.py") + + +def described(slug): + """The meta descriptions a platform's pages publish, with line numbers. + + They are page content — the snippet a search result shows — but they live in + a hook rather than in the page, so the walk above never reached them. That + gap is not theoretical: three platform descriptions named a sibling platform + while this check reported the boundary clean. + """ + keys = (f'"platforms/{slug}/index.md":', f'"platforms/{slug}/setup.md":') + inside = False + for number, line in enumerate(read(DESCRIPTIONS).splitlines(), 1): + if line.strip().startswith(keys): + inside = True + if inside: + yield number, line + if line.rstrip().endswith("),"): + inside = False + + +def speaks_for(directory, slug): + """Every line that speaks for one platform, as (source, line number, text). + + Its own tree, its pages, and the descriptions published for those pages. + """ + for relative in scanned(directory, slug): + for number, line in enumerate(read(relative).splitlines(), 1): + yield relative, number, line + for number, line in described(slug): + yield DESCRIPTIONS, number, line + + def naming_faults(rows, terms): """Flag a platform naming another, longest term first so FreeRTOS-Plus-TCP is read as itself rather than as FreeRTOS.""" @@ -157,18 +191,17 @@ def naming_faults(rows, terms): faults = [] for token, directory in rows: - for relative in scanned(directory, token.lower()): - for number, line in enumerate(read(relative).splitlines(), 1): - if INCLUDE.match(line): - continue - for term in {m.group(1) for m in pattern.finditer(line)}: - owners = terms[term] - if token not in owners and (relative, term) not in exempt: - named = "/".join(sorted(owners)) - faults.append( - f"{token}: {relative}:{number} names {named} " - f'("{term}") — a platform describes only itself' - ) + for relative, number, line in speaks_for(directory, token.lower()): + if INCLUDE.match(line): + continue + for term in {m.group(1) for m in pattern.finditer(line)}: + owners = terms[term] + if token not in owners and (relative, term) not in exempt: + named = "/".join(sorted(owners)) + faults.append( + f"{token}: {relative}:{number} names {named} " + f'("{term}") — a platform describes only itself' + ) return faults From 50f2cb32c1b84883274db14166b8f520d291a45e Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 21:51:09 +0100 Subject: [PATCH 48/57] docs: S23.22 fix the marshal contract advice and two wrong failure modes Part of #708 --- .../Source/SolidSyslogFreeRtosMutex.c | 12 +++---- .../Interface/SolidSyslogLwipRawMarshal.h | 10 +++--- docs/platforms/lwipraw/index.md | 6 ++-- docs/platforms/lwipraw/setup.md | 36 ++++++++++++++++--- docs/platforms/stdatomic/setup.md | 21 ++++++++--- docs/porting.md | 5 +-- 6 files changed, 66 insertions(+), 24 deletions(-) diff --git a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c index 8df9443f..2d4d0e09 100644 --- a/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c +++ b/Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c @@ -26,12 +26,12 @@ static inline SemaphoreHandle_t FreeRtosMutex_AsHandle(struct SolidSyslogFreeRto void FreeRtosMutex_Initialise(struct SolidSyslogMutex* base) { struct SolidSyslogFreeRtosMutex* self = FreeRtosMutex_SelfFromBase(base); - /* xSemaphoreCreateMutexStatic returns NULL only when - * configSUPPORT_STATIC_ALLOCATION is not 1 — a compile-time config - * gate, not a runtime failure mode. Guarded anyway so a misconfigured - * integrator falls back to the NullMutex vtable instead of corrupting - * Lock/Unlock with a dangling handle — the same defence every Mutex - * adapter applies to its own primitive's init failure. */ + /* The storage is ours, so this cannot fail for want of memory; the kernel + * returns NULL only when handed a NULL buffer, which this call never does. + * configSUPPORT_STATIC_ALLOCATION is a compile-time requirement rather than + * a runtime one — without it the function does not exist to call. The + * branch is therefore defensive: an unexpected NULL leaves the NullMutex + * vtable in place rather than a dangling handle in Lock/Unlock. */ if (xSemaphoreCreateMutexStatic(&self->Buffer) != NULL) { self->Base.Lock = FreeRtosMutex_Lock; diff --git a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h index 3fad20aa..99f71457 100644 --- a/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h +++ b/Platform/LwipRaw/Interface/SolidSyslogLwipRawMarshal.h @@ -10,13 +10,15 @@ * - NO_SYS=1 (bare metal, no RTOS): the default direct-call marshal is correct * — one execution context, no core to protect. * - NO_SYS=0 (RTOS with a tcpip thread): the integrator installs a marshal that - * hops onto that thread — e.g. tcpip_callback_with_block, or a - * LOCK_TCPIP_CORE / UNLOCK_TCPIP_CORE pair. + * hops onto that thread — a LOCK_TCPIP_CORE / UNLOCK_TCPIP_CORE pair, or a + * mailbox post that waits for the callback to run. * * The marshal MUST invoke its callback synchronously, before it returns: the * wrapper reads results the callback writes immediately after the hop, so an - * asynchronous marshal is caller error. tcpip_callback_with_block(.., block=1) - * honours this; a bare tcpip_callback(..) does not. See docs/platforms/lwipraw/setup.md. */ + * asynchronous marshal is caller error. Core locking satisfies this directly. + * A mailbox post does not on its own — tcpip_callback_with_block(.., block=1) + * blocks until the message is accepted, not until it is executed — so such a + * marshal must wait for completion itself. See docs/platforms/lwipraw/setup.md. */ #ifndef SOLIDSYSLOGLWIPRAWMARSHAL_H #define SOLIDSYSLOGLWIPRAWMARSHAL_H diff --git a/docs/platforms/lwipraw/index.md b/docs/platforms/lwipraw/index.md index 6cd9c3f8..7c24f84d 100644 --- a/docs/platforms/lwipraw/index.md +++ b/docs/platforms/lwipraw/index.md @@ -25,8 +25,10 @@ the Datagram and TcpStream make is routed through one marshal hop. adapter, passing a function that runs its callback on the core-owning thread. The marshal must invoke its callback synchronously — the adapter reads results the -moment the hop returns. `tcpip_callback_with_block(…, 1)` or a `LOCK_TCPIP_CORE` / -`UNLOCK_TCPIP_CORE` pair satisfy that; a bare `tcpip_callback` does not. +moment the hop returns. A `LOCK_TCPIP_CORE` / `UNLOCK_TCPIP_CORE` pair satisfies +that directly. Posting to lwIP's mailbox does not, in any of its forms: +`tcpip_callback_with_block` blocks until the message is accepted, not until the +callback runs, so a mailbox marshal has to wait for completion itself. ## Requirements diff --git a/docs/platforms/lwipraw/setup.md b/docs/platforms/lwipraw/setup.md index 3e9de1c6..73f4956a 100644 --- a/docs/platforms/lwipraw/setup.md +++ b/docs/platforms/lwipraw/setup.md @@ -65,14 +65,29 @@ void MyCoreLockMarshal(SolidSyslogLwipRawCallback callback, void* context) } ``` -Otherwise post to lwIP's mailbox and block until it runs — `block = 1` is what -satisfies the synchronous contract, and a bare `tcpip_callback` does not: +Core locking is the route to prefer, and it is what the reference target uses. +It runs the callback in your own task under the lock, so it is synchronous by +construction, independent of task priority, and costs no mailbox message. + +Without core locking you must post to lwIP's mailbox — and then wait for the +callback to *run*, which the post alone does not do. `tcpip_callback_with_block` +blocks until the mailbox accepts the message, not until the tcpip thread +executes it, so returning at that point would break the contract and leave the +hop's stack frame dangling under the callback. Carry a semaphore: ```c +struct MarshalHop +{ + SolidSyslogLwipRawCallback callback; + void* context; + sys_sem_t done; +}; + static void RunHop(void* ctx) { struct MarshalHop* hop = ctx; hop->callback(hop->context); + sys_sem_signal(&hop->done); } void MyTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) @@ -82,12 +97,23 @@ void MyTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) callback(context); return; } - struct MarshalHop hop = {callback, context}; - (void) tcpip_callback_with_block(RunHop, &hop, 1); + struct MarshalHop hop = {callback, context, {0}}; + if (sys_sem_new(&hop.done, 0) == ERR_OK) + { + if (tcpip_callback_with_block(RunHop, &hop, 1) == ERR_OK) + { + sys_arch_sem_wait(&hop.done, 0); + } + sys_sem_free(&hop.done); + } } ``` -Two things to check in your port: the mailbox must be sized for a blocking +The `err_t` matters: a failed post means `RunHop` never runs, so waiting on the +semaphore would hang and returning without waiting would let the adapter read +result state nothing wrote. + +Two more things to check in your port: the mailbox must be sized for a blocking post, and lwIP exposes no portable "am I on the lwIP thread?" predicate, so most ports compare the current task handle against the one given to `tcpip_init`. Without that guard, an adapter call made from inside a callback diff --git a/docs/platforms/stdatomic/setup.md b/docs/platforms/stdatomic/setup.md index 4057921d..eb561c2a 100644 --- a/docs/platforms/stdatomic/setup.md +++ b/docs/platforms/stdatomic/setup.md @@ -32,11 +32,22 @@ It is assigned when a record is raised rather than when it is sent, so a gap reflects loss anywhere in the pipeline — the buffer, the store, or the transport — not only on the wire. -Leave the role unfilled, or exhaust its pool, and the Null counter stands in -and returns 1 for every record. That is a safe fallback: logging continues -unaffected, and only the sequence-based loss detection stops being useful. -Pool exhaustion is reported through the error handler when the counter is -created, so it is visible rather than something to discover later. +Two different things happen if the counter is missing, and they are worth +telling apart. + +Exhaust the pool and `Create` hands back the Null counter, which returns 1 for +every record. The meta element is still emitted, so you still get `sysUpTime` +and `language`; only the sequence stops distinguishing records. Exhaustion is +reported through the error handler at `Create`, so it is visible rather than +something to discover later. + +Leave the counter out of the meta element's config altogether and the element +itself does not build: `SolidSyslogMetaSd_Create` reports a `WARNING` and falls +back to the Null structured data, so no meta element is attached at all — no +`sequenceId`, and no `sysUpTime` either. + +Both are safe in the sense that logging continues, but only the first still +carries the metadata. ## When it does not work diff --git a/docs/porting.md b/docs/porting.md index 9e2f3fae..2725408a 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -172,8 +172,9 @@ configuration macro — lwIP's `LWIP_DNS`, FreeRTOS's **Prefer a seam.** Where the adapter is thin, take the dependency as an injected function pointer with a safe default and never name the upstream symbol. `SolidSyslogLwipRaw_SetMarshal` covers `NO_SYS=0` against `NO_SYS=1` this way: -the library calls a callback, and the integrator installs `LOCK_TCPIP_CORE` or a -`tcpip_callback_with_block` shim. Nothing to select at build time. +the library calls a callback, and the integrator installs a `LOCK_TCPIP_CORE` +pair, or a mailbox shim that waits for the callback to run. Nothing to select at +build time. **Otherwise gate the translation unit.** Where the adapter carries logic that belongs in the library — the DNS resolver's async callback handling, poll From 421490a81bb0024b2c4eefd1bdc71c140043f5ad Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 22:15:16 +0100 Subject: [PATCH 49/57] docs: S23.22 make the MISRA register cover the casts it suppresses Part of #708 --- docs/misra-deviations.md | 44 +++++++++++++++++++++-------------- docs/platforms/posix/setup.md | 7 +++--- misra_suppressions.txt | 8 +++---- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 6de3347d..063e902e 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -1103,7 +1103,7 @@ Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded unde --- -## D.013 — Rule 11.5: `void*` ↔ `unsigned char*` at third-party byte-buffer API boundaries +## D.013 — Rule 11.5: `void*` ↔ a byte pointer at third-party byte-buffer API boundaries ### Guideline @@ -1115,27 +1115,33 @@ Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded unde `SolidSyslogStream::Send` takes `const void*` and `SolidSyslogStream::Read` takes `void*` — the project-wide byte-buffer contract used by every -Stream implementation. Some third-party C libraries (notably mbedTLS) -type their byte buffers as `const unsigned char*` / `unsigned char*` -rather than `void*`. The implementation cast bridging the two is -unavoidable at the API boundary: +Stream implementation, and `SolidSyslogDatagram::SendTo` takes `const void*` +likewise. Some third-party C libraries type their byte buffers as a character +pointer rather than `void*`: mbedTLS uses `const unsigned char*` / +`unsigned char*`, and the Winsock socket calls use `const char*` / `char*` +where their POSIX counterparts use `void*`. The implementation cast bridging +the two is unavoidable at the API boundary: ```c -int rc = mbedtls_ssl_write(&self->SslContext, (const unsigned char*) buffer, size); +int rc = mbedtls_ssl_write(&self->SslContext, (const unsigned char*) buffer, size); +int sent = WinsockTcpStream_send(self->Fd, (const char*) buffer, (int) size, 0); ``` Rule 11.5 fires on each such adapter cast. ### Scope -`Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c` — two sites -(`MbedTlsStream_Send`, `MbedTlsStream_Read`). +- `Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c` — two sites + (`MbedTlsStream_Send`, `MbedTlsStream_Read`), `unsigned char*`. +- `Platform/Windows/Source/SolidSyslogWinsockTcpStream.c` — two sites + (`WinsockTcpStream_Send`, `WinsockTcpStream_Read`), `char*`. +- `Platform/Windows/Source/SolidSyslogWinsockDatagram.c` — one site + (`WinsockDatagram_SendTo`), `char*`. A future Stream, Datagram, hash or MAC implementation wrapping a byte-typed -third-party C API (`unsigned char*` rather than `void*`) will meet the same -boundary, but is not covered by this record until reviewed and added to it — -or given its own entry. The OpenSSL adapter -(`Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c`) does not fall +third-party C API will meet the same boundary, but is not covered by this +record until reviewed and added to it — or given its own entry. The OpenSSL +adapter (`Platform/OpenSsl/Source/SolidSyslogOpenSslStream.c`) does not fall under this deviation — `SSL_write` / `SSL_read` take `void*` and so no cast is needed. @@ -1149,8 +1155,8 @@ The alternatives all regress: | Copy through an `unsigned char` scratch buffer per call | Runtime cost on the hot send/receive path; adds a fixed-size scratch or a stack-allocated VLA in a critical-path function. Defeats the zero-copy intent of the Stream contract. | | Inline `cppcheck-suppress misra-c2012-11.5` at each site | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | -The cast is well-defined: `unsigned char` may alias any object type -(§6.5 ¶7), so reinterpreting a `void*` byte buffer as +The cast is well-defined: a character type may alias any object type +(§6.5 ¶7), so reinterpreting a `void*` byte buffer as `char*` or `unsigned char*` and back is a no-op at the abstract-machine level. ### Risk and mitigation @@ -1180,10 +1186,12 @@ Raised and approved 2026-05-23 by the project owner, David Cozens. Recorded unde `_Report` wrapper confined every emission to the source's own `*Messages.c`, a single translation unit. S12.26 decoupled error text from the library (deleting the `*Messages.c` message tables) and unwound the `_Report` wrapper, -so each source is now defined in its class's vtable TU and referenced from both -that TU's emit sites and its `*Static.c` lifecycle code, genuinely cross-TU — -which is the resolution the entry's risk analysis anticipated before it was -collapsed to this note; see the revision prior to retirement for that text. +so each source is now defined in its class's vtable TU and reached from the +`_Report` inline in its `*Private.h`, which both that TU and its +`*Static.c` lifecycle code include — so the object is used from more than one +translation unit, which is the resolution the entry's risk analysis anticipated +before it was collapsed to this note; see the revision prior to retirement for +that text. cppcheck-misra reports no 8.7 finding for any error source; the suppression lines were removed. diff --git a/docs/platforms/posix/setup.md b/docs/platforms/posix/setup.md index 89468224..fea4bab2 100644 --- a/docs/platforms/posix/setup.md +++ b/docs/platforms/posix/setup.md @@ -13,9 +13,10 @@ link: set(SOLIDSYSLOG_PLATFORMS "Posix") ``` -The one exception is the message-queue buffer, which needs POSIX message queues -— on glibc that means linking `rt`. If you use the circular buffer instead, you -do not need it. +Selecting this platform links `rt` publicly, which older glibc needs for POSIX +message queues; from glibc 2.34 they are in `libc` and the library is empty but +harmless. You do not have to add it yourself, and it is linked whether or not +you use the message-queue buffer. ## Wiring a sender diff --git a/misra_suppressions.txt b/misra_suppressions.txt index f025baf7..2802bc59 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -88,9 +88,6 @@ misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:147 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:155 misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:299 misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:311 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:142 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:356 -misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:376 # D.003 — Rule 5.7: repeating struct tags (no-typedef-struct convention) # See docs/misra-deviations.md#d003 @@ -197,7 +194,10 @@ misra-c2012-2.5:Core/Interface/SolidSyslogCircularBuffer.h:41 # See docs/misra-deviations.md#d012 misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:20 -# D.013 — Rule 11.5: void* ↔ unsigned char* at third-party byte-buffer API boundaries +# D.013 — Rule 11.5: void* <-> a byte pointer at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:336 misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:354 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:142 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:356 +misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:376 From b1b9dd68d4356b5fc14978b4ac5e54f24dbfd4b2 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 22:19:57 +0100 Subject: [PATCH 50/57] docs: S23.22 give the PSA entropy requirement both its routes Part of #708 --- docs/platforms/mbedtls/setup.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/platforms/mbedtls/setup.md b/docs/platforms/mbedtls/setup.md index 115bfc27..d1d6425f 100644 --- a/docs/platforms/mbedtls/setup.md +++ b/docs/platforms/mbedtls/setup.md @@ -66,13 +66,21 @@ board-specific source. state transition returns `MBEDTLS_ERR_ERROR_GENERIC_ERROR` before any byte reaches the socket. -**On a target with no platform entropy, define -`MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG`** and provide -`mbedtls_psa_external_get_random` wrapping the DRBG you just seeded. This keeps -PSA and the classic API on one entropy chain. If you have defined -`MBEDTLS_NO_PLATFORM_ENTROPY` — usual on embedded — this is effectively -mandatory, and omitting it makes `psa_crypto_init` return -`PSA_ERROR_INSUFFICIENT_ENTROPY`. +**On a target with no platform entropy, give PSA a strong source.** With +`MBEDTLS_NO_PLATFORM_ENTROPY` defined — usual on embedded — `mbedtls_entropy_init` +registers no source of its own, and `psa_crypto_init` then fails with +`PSA_ERROR_INSUFFICIENT_ENTROPY`. Two routes out, and either is enough: + +- Define `MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG` and provide + `mbedtls_psa_external_get_random` wrapping the DRBG you just seeded. PSA + bypasses the entropy subsystem entirely, so PSA and the classic API run off + one chain. +- Or define `MBEDTLS_ENTROPY_HARDWARE_ALT` and provide `mbedtls_hardware_poll`. + `mbedtls_entropy_init` then registers it as a strong source, and PSA seeds its + own DRBG through the standard path. + +The first keeps one chain and is the simpler thing to reason about; the second +suits a target whose randomness already arrives through a hardware poll. **Parse the CA chain, and the client credential if you are using mutual TLS**, by whatever route suits the build: a filesystem, a baked-in array, a blob pulled From 4a11c616a545ed4007c2ac1987c048743229aea5 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 22:42:41 +0100 Subject: [PATCH 51/57] docs: S23.22 fix the copy-and-it-breaks examples and the remaining review findings Part of #708 --- CLAUDE.md | 4 ++-- docs/README.md | 2 +- docs/cra.md | 4 ++-- docs/iec62443.md | 4 ++-- docs/platforms/fatfs/setup.md | 8 +++++++- docs/platforms/freertos/setup.md | 9 ++++++++- docs/platforms/lwipraw/setup.md | 7 +++++-- docs/platforms/openssl/index.md | 5 +++-- docs/platforms/openssl/setup.md | 7 ++++++- docs/platforms/plustcp/setup.md | 8 +++++++- docs/platforms/stdatomic/setup.md | 9 ++++++++- docs/platforms/windows/setup.md | 9 ++++++--- docs/rfc-compliance.md | 4 ++-- docs/security/threat-model.md | 2 +- docs/security/triage-runbook.md | 2 +- docs/structured-data.md | 5 +++-- 16 files changed, 64 insertions(+), 25 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d5939e0e..0b531654 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -515,7 +515,7 @@ See `docs/misra-deviations.md` for the project's stance on MISRA conformance. ## Documentation -Two rules, and they matter more than anything about wording. A wrong claim +Three rules, and they matter more than anything about wording. A wrong claim costs one edit to fix; a wrong claim that has been copied costs an audit of every page to find, and the copies rot silently because nothing checks them. @@ -542,7 +542,7 @@ Every fact has exactly one home. Everywhere else links to it, or omits it. | What a role's contract requires | that role's `SolidSyslogDefinition.h` | | What a platform ships, needs, guarantees, and leaves to the integrator | that platform's page under `docs/platforms//` | | How to wire a platform, and what will catch you out | that platform's `setup.md` | -| What Core does | the Core documentation and the Core headers | +| What Core does | the Core headers; `docs/core/index.md` curates and links them | | How to get it building | `docs/build-integration.md` | Writing the same sentence on a second page is the signal that it belongs on diff --git a/docs/README.md b/docs/README.md index 4ffb687a..e00ef553 100644 --- a/docs/README.md +++ b/docs/README.md @@ -59,7 +59,7 @@ in the general case. - [Software Bill of Materials (SBOM)](security/sbom.md) - [Vulnerability triage runbook](security/triage-runbook.md) - [Release verification guide](security/release-verification.md) - - Reporting a vulnerability: [`SECURITY.md`](security/policy.md) + - Reporting a vulnerability: [Security policy](security/policy.md) ## API reference diff --git a/docs/cra.md b/docs/cra.md index 89fb06f3..62064be3 100644 --- a/docs/cra.md +++ b/docs/cra.md @@ -85,8 +85,8 @@ consuming. |---|---|---| | **(1)** | identify and document components, including a software bill of materials in a machine-readable format | A CycloneDX [SBOM](security/sbom.md) per release, to fold into your product's own | | **(2)**, **(4)** | remediate without delay; disclose fixed vulnerabilities | The [vulnerability triage runbook](security/triage-runbook.md) sets out how reports are assessed and published | -| **(5)**, **(6)** | a coordinated vulnerability disclosure policy, and a contact address | [`SECURITY.md`](security/policy.md) | -| **(7)** | securely distribute updates | [Release verification](security/release-verification.md): a signed source-tree hash and signed CycloneDX SBOM, both verifiable against the workflow and tag that produced them | +| **(5)**, **(6)** | a coordinated vulnerability disclosure policy, and a contact address | [Security policy](security/policy.md) | +| **(7)** | securely distribute updates | [Release verification](security/release-verification.md): a signed source-tree hash and signed CycloneDX SBOM, both verifiable against the workflow and tag that produced them. They are published by the release workflow, so verify their presence for the release you are adopting rather than assuming it | Secure-by-design evidence for your technical file is in the [threat model](security/threat-model.md) and [at-rest diff --git a/docs/iec62443.md b/docs/iec62443.md index 8bac5eb4..7fe71f99 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -47,7 +47,7 @@ what the library does not do. | **CR 2.11** — Timestamps | The clock is an injected `SolidSyslogClockFunction` rather than a component, so the time source is yours to choose; platforms that have one ship an implementation. Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | | **CR 2.12** — Non-repudiation | At the wire: mutual TLS through the Stream role cryptographically identifies the TLS peer to the collector. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: the SecurityPolicy role, filled by a keyed policy, gives cryptographic integrity or authenticated encryption; Core's unkeyed CRC-16 policy detects accidental corruption only | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the collector authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Keyed policies are only as good as their key — custody, rotation and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | | **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed SecurityPolicy makes an unauthorised edit detectable. Core's CRC-16 policy does not serve this control — it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit through a TLS backend and at rest through an authenticated-encryption policy. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models. **Single-task (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send synchronously on the caller's thread. Shipped TCP backends make the socket non-blocking once open, so a send returns immediately against a wedged peer; the bounded surface is the initial connection, and the TLS handshake on a secured path. Both budgets are tunable, and overridable per instance at run time. Suitable where the application has no real-time deadline. **Buffered (`SolidSyslogCircularBuffer` with a Mutex, or `SolidSyslogPosixMessageQueueBuffer`):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. The bounded surface is the same connection budget. Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (any buffering Buffer):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. Either way the bounded surface is the transport's connection and handshake budget, both tunable and overridable per instance at run time; what a given transport actually blocks on is its platform's to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | | **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the @@ -95,7 +95,7 @@ elimination removes unused components. The development-process evidence IEC 62443-4-1 asks for is published separately: the [threat model](security/threat-model.md), the [SBOM](security/sbom.md), the -[vulnerability triage runbook](security/triage-runbook.md), [`SECURITY.md`](security/policy.md) +[vulnerability triage runbook](security/triage-runbook.md), [Security policy](security/policy.md) for coordinated disclosure, and the test and analysis gates described in [CI](ci.md). diff --git a/docs/platforms/fatfs/setup.md b/docs/platforms/fatfs/setup.md index f17fc498..92a384a5 100644 --- a/docs/platforms/fatfs/setup.md +++ b/docs/platforms/fatfs/setup.md @@ -25,10 +25,16 @@ FatFs is configured by a header you own, so the adapter compiles inside your target against your configuration: ```cmake -set(SOLIDSYSLOG_PLATFORMS "FatFs") +set(SOLIDSYSLOG_PLATFORMS "FatFs;;") target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FatFs) ``` +The list is the whole selection rather than an addition to it — see +[naming your platforms](../../build-integration.md#cmake) — so the placeholders +are not optional shorthand. This platform fills the File role only; substitute +whichever platforms the [capability matrix](../index.md) says fill the rest of +what your build needs, and drop a placeholder for a role you are not using. + ## What you must provide **The FatFs sources**, compiled into your image, and a `ffconf.h` where the diff --git a/docs/platforms/freertos/setup.md b/docs/platforms/freertos/setup.md index 1372724f..9eb70a77 100644 --- a/docs/platforms/freertos/setup.md +++ b/docs/platforms/freertos/setup.md @@ -10,10 +10,17 @@ precompiled: its sources compile inside your target, against your `FreeRTOSConfig.h`. Select it and link the target it exports: ```cmake -set(SOLIDSYSLOG_PLATFORMS "FreeRtos") +set(SOLIDSYSLOG_PLATFORMS "FreeRtos;;") target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FreeRtos) ``` +The list is the whole selection rather than an addition to it — see +[naming your platforms](../../build-integration.md#cmake) — so the placeholders +are not optional shorthand. This platform fills the Mutex role and the sysUpTime +callback; substitute whichever platforms the +[capability matrix](../index.md) says fill the rest of what your build needs, +and drop a placeholder for a role you are not using. + Your target supplies the kernel's include path, because it is your kernel and your configuration. diff --git a/docs/platforms/lwipraw/setup.md b/docs/platforms/lwipraw/setup.md index 73f4956a..224b1411 100644 --- a/docs/platforms/lwipraw/setup.md +++ b/docs/platforms/lwipraw/setup.md @@ -36,8 +36,11 @@ sleep. Your `Sleep` must keep the stack running: ```c void MyLwipSleep(int milliseconds) { - uint32_t deadline = MyTimebase_NowMs() + (uint32_t) milliseconds; - while (MyTimebase_NowMs() < deadline) + /* Elapsed rather than a deadline, so the loop is correct across a + * timebase wrap; unsigned subtraction wraps with it. */ + uint32_t start = MyTimebase_NowMs(); + uint32_t duration = (milliseconds > 0) ? (uint32_t) milliseconds : 0U; + while ((MyTimebase_NowMs() - start) < duration) { sys_check_timeouts(); MyNetif_DrivePolledRx(); /* your board's receive pump */ diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 82faa9d8..4814551e 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -62,8 +62,9 @@ deployment is held to. ### Rotation is a file replacement and a reconnection -The `SSL_CTX` is rebuilt each time the stream is opened, re-reading all three -files, so replacing them takes effect on the next connection — either through +The `SSL_CTX` is rebuilt each time the stream is opened, re-reading every +credential file the config names — the trust anchors always, the client +certificate and key only where mutual TLS is configured. So replacing them takes effect on the next connection — either through ordinary reconnection after an outage, or by calling `SolidSyslogSender_Disconnect` to force one. No reload callback is needed. diff --git a/docs/platforms/openssl/setup.md b/docs/platforms/openssl/setup.md index 1d799bae..0829ebdd 100644 --- a/docs/platforms/openssl/setup.md +++ b/docs/platforms/openssl/setup.md @@ -12,9 +12,14 @@ TCP stream underneath — the [capability matrix](../index.md) shows which fill that role. ```cmake -set(SOLIDSYSLOG_PLATFORMS "OpenSsl") # and the one supplying your TCP stream +set(SOLIDSYSLOG_PLATFORMS "OpenSsl;") ``` +The list is the whole selection rather than an addition to it — see +[naming your platforms](../../build-integration.md#cmake) — so `` is +not optional shorthand. Substitute whichever platform the +[capability matrix](../index.md) says fills the Network role on your target. + OpenSSL is a stable system API rather than a header-configured upstream, so the adapter compiles straight into `libSolidSyslog.a` and there is no separate target to link. [Adding it to your build](../../build-integration.md) covers the diff --git a/docs/platforms/plustcp/setup.md b/docs/platforms/plustcp/setup.md index dc8ea65d..b38e9c40 100644 --- a/docs/platforms/plustcp/setup.md +++ b/docs/platforms/plustcp/setup.md @@ -12,10 +12,16 @@ role only, so select it alongside whichever platform supplies your mutex and clock — the [capability matrix](../index.md) shows which fill those: ```cmake -set(SOLIDSYSLOG_PLATFORMS "PlusTcp") # and the one supplying mutex and clock +set(SOLIDSYSLOG_PLATFORMS "PlusTcp;") target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::PlusTcp) ``` +The list is the whole selection rather than an addition to it — see +[naming your platforms](../../build-integration.md#cmake) — so `` +is not optional shorthand. Substitute whichever platform the +[capability matrix](../index.md) says fills the Mutex and clock roles on your +target. + Bring the stack up and let it acquire an address before creating any sender. Resolving the collector by name needs DNS compiled into the stack. If you give diff --git a/docs/platforms/stdatomic/setup.md b/docs/platforms/stdatomic/setup.md index eb561c2a..566901b4 100644 --- a/docs/platforms/stdatomic/setup.md +++ b/docs/platforms/stdatomic/setup.md @@ -9,9 +9,16 @@ and what it leaves to you. the adapter compiles straight into the static library: ```cmake -set(SOLIDSYSLOG_PLATFORMS "StdAtomic") +set(SOLIDSYSLOG_PLATFORMS "StdAtomic;;") ``` +The list is the whole selection rather than an addition to it — see +[naming your platforms](../../build-integration.md#cmake) — so the placeholders +are not optional shorthand. This platform fills the AtomicCounter role only; +substitute whichever platforms the [capability matrix](../index.md) says fill +the rest of what your build needs, and drop a placeholder for a role you are not +using. + The compiler must support C11 atomics. Where a toolchain does not, the [platform × capability matrix](../index.md) shows which other platforms fill the same role. diff --git a/docs/platforms/windows/setup.md b/docs/platforms/windows/setup.md index 84d2173b..61ae89b6 100644 --- a/docs/platforms/windows/setup.md +++ b/docs/platforms/windows/setup.md @@ -15,9 +15,12 @@ set(SOLIDSYSLOG_PLATFORMS "Windows") ## Initialise Winsock first -Winsock must be started before any sender is created, and stopped when you are -finished with it. The adapter does not do this for you, because a process that -already uses sockets has done it once and must not have it done again: +Winsock must be started before any sender is created, and stopped once every +socket is closed. The adapter does not do this for you: Winsock belongs to the +process, not to this library, and a process that already uses sockets has +started it for its own reasons. Startup and cleanup are reference-counted, so +match each successful `WSAStartup` with one `WSACleanup` and let the last one +out do the teardown: ```c WSADATA wsaData; diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index 8584348d..f3d909df 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -29,8 +29,8 @@ Status key: | 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | | 7.1 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | | 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | -| 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths, the type giving RFC 3418's natural wrap; the [capability matrix](platforms/index.md) shows which platforms supply one. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | -| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Partial | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. [AtomicCounter](api/structSolidSyslogAtomicCounter.md) is a vtable abstraction, so the wrap is the contract's and not any one implementation's; the integrator wires a concrete counter at setup time and the [capability matrix](platforms/index.md) shows which platforms supply one. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | +| 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths, the type giving RFC 3418's natural wrap; the [capability matrix](platforms/index.md) shows which platforms supply one. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. `sysUpTime` and `language` are independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter. The counter is not: `SolidSyslogMetaSd_Create` rejects a NULL `Counter` with a `WARNING` and returns the Null structured data, so the element is not emitted at all | +| 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Partial | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. [AtomicCounter](api/structSolidSyslogAtomicCounter.md) is a vtable abstraction, so the wrap is the contract's and not any one implementation's; the integrator wires a concrete counter at setup time and the [capability matrix](platforms/index.md) shows which platforms supply one. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). IDs from a wired counter remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp. Uniqueness is the counter's, not the contract's: exhaust a counter's pool and `Create` falls back to the Null counter, which returns 1 for every record, so gap detection stops being meaningful while logging continues | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | | 6.1 | Message size — max 2048 recommended | Supported | Default `SOLIDSYSLOG_MAX_MESSAGE_SIZE` = 2048, matching the largest message §6.1 says a transport receiver SHOULD accept; override it for memory-constrained MCUs via the standard tunable mechanism | | 6 | PRINTUSASCII in header fields (codes 33-126) | Supported | Non-compliant bytes substituted with `?` at format time (HOSTNAME, APP-NAME, PROCID, MSGID) | diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index 0280d6cd..8e1aef1d 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -184,7 +184,7 @@ security-conscious component supplier. See [`cra.md`](../cra.md) for the CRA Annex I audit-trail map, [`iec62443.md`](../iec62443.md) for the control-by-control map, and [`rfc-compliance.md`](../rfc-compliance.md) for the standards coverage matrix. The public disclosure process for issues found -against this model is in [`SECURITY.md`](policy.md). +against this model is in [Security policy](policy.md). ## Review policy diff --git a/docs/security/triage-runbook.md b/docs/security/triage-runbook.md index e5c4fbb5..d0865c5d 100644 --- a/docs/security/triage-runbook.md +++ b/docs/security/triage-runbook.md @@ -1,6 +1,6 @@ # Vulnerability Triage Runbook -This is the maintainer's operational counterpart to [`SECURITY.md`](policy.md): +This is the maintainer's operational counterpart to [Security policy](policy.md): how a vulnerability report is handled end-to-end, from receipt to retrospective. `SECURITY.md` states the public promises (72-hour acknowledgement, 90+14 disclosure); this runbook is how they are met. diff --git a/docs/structured-data.md b/docs/structured-data.md index d240e360..a80ac1c6 100644 --- a/docs/structured-data.md +++ b/docs/structured-data.md @@ -16,8 +16,9 @@ spelling, the `PARAM-NAME="..."` punctuation, and the RFC 5424 §6.3.3 value esc custom element therefore cannot produce malformed structured data, and a value can never break out of its quotes; whatever bytes you hand it stay inside the `"..."`. -The complete worked example below lives at -`Bdd/Targets/Common/BddTargetCustomSd.c`. +The complete worked example below is drawn from the BDD target source at +`Bdd/Targets/Common/BddTargetCustomSd.c`, which is test infrastructure rather +than shipped example code. ## The two writer types From 217722492e6652de54f5d58adf20b2ed70b60b48 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 22:43:37 +0100 Subject: [PATCH 52/57] docs: S23.22 scope the static-assert comment to C++11 Part of #708 --- Core/Source/SolidSyslogMacros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Source/SolidSyslogMacros.h b/Core/Source/SolidSyslogMacros.h index 27425184..6aefbfd0 100644 --- a/Core/Source/SolidSyslogMacros.h +++ b/Core/Source/SolidSyslogMacros.h @@ -1,7 +1,7 @@ #ifndef SOLIDSYSLOGMACROS_H #define SOLIDSYSLOGMACROS_H -/* Compile-time assertion. C++ and C11 have native primitives that carry the +/* Compile-time assertion. C++11 and C11 have native primitives that carry the message into the diagnostic; a strict C99 toolchain — the conformance baseline, built on every pull request by the `build-linux-c99` lane (see docs/builds.md) — has neither, so it falls back to declaring an array From 8b87b26aaa60d77140495e4ab958dfbbd137301e Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 23:03:41 +0100 Subject: [PATCH 53/57] docs: S23.22 stop the second review round's duplication and wording slips Part of #708 --- docs/iec62443.md | 2 +- docs/misra-deviations.md | 9 +++++---- docs/platforms/fatfs/index.md | 4 ++-- docs/platforms/fatfs/setup.md | 10 +++++----- docs/platforms/freertos/setup.md | 11 +++++------ docs/platforms/openssl/index.md | 2 +- docs/platforms/openssl/setup.md | 8 ++++---- docs/platforms/plusfat/setup.md | 23 +++++++++-------------- docs/platforms/plustcp/setup.md | 11 +++++------ docs/platforms/stdatomic/setup.md | 15 +++++++-------- docs/rfc-compliance.md | 2 +- 11 files changed, 45 insertions(+), 52 deletions(-) diff --git a/docs/iec62443.md b/docs/iec62443.md index 7fe71f99..6ecc2dcb 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -47,7 +47,7 @@ what the library does not do. | **CR 2.11** — Timestamps | The clock is an injected `SolidSyslogClockFunction` rather than a component, so the time source is yours to choose; platforms that have one ship an implementation. Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | | **CR 2.12** — Non-repudiation | At the wire: mutual TLS through the Stream role cryptographically identifies the TLS peer to the collector. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: the SecurityPolicy role, filled by a keyed policy, gives cryptographic integrity or authenticated encryption; Core's unkeyed CRC-16 policy detects accidental corruption only | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the collector authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Keyed policies are only as good as their key — custody, rotation and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | | **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed SecurityPolicy makes an unauthorised edit detectable. Core's CRC-16 policy does not serve this control — it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit through a TLS backend and at rest through an authenticated-encryption policy. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (any buffering Buffer):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. Either way the bounded surface is the transport's connection and handshake budget, both tunable and overridable per instance at run time; what a given transport actually blocks on is its platform's to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (any buffering Buffer):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. Either way, the bounded surface is the transport's connection and handshake budget, both tunable and overridable per instance at run time; what a given transport actually blocks on is its platform's to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | | **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 063e902e..bdeed713 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -1162,14 +1162,15 @@ The cast is well-defined: a character type may alias any object type ### Risk and mitigation - **Alignment** — Both representations are byte-addressed; no - alignment promotion occurs. The cast targets `unsigned char*`, which + alignment promotion occurs. The cast targets a character pointer, which has the weakest alignment requirement of any object pointer. - **Type safety** — The caller-supplied buffer originates as a contiguous byte sequence (typically the formatted syslog record); - treating it as `unsigned char*` at the third-party API boundary is - the same byte sequence under a different pointer type. + treating it as `char*` or `unsigned char*` at the third-party API + boundary is the same byte sequence under a different pointer type. - **Elimination path** — A future revision of the Stream API that - adopts `unsigned char*` directly would retire this deviation. + adopts a character pointer directly would retire this deviation for + whichever of the two spellings it chose. Tracked as a possible E10-successor refactor, not scheduled. ### Approval diff --git a/docs/platforms/fatfs/index.md b/docs/platforms/fatfs/index.md index c6ad579b..3b8cf9f3 100644 --- a/docs/platforms/fatfs/index.md +++ b/docs/platforms/fatfs/index.md @@ -24,9 +24,9 @@ keeping it unreadable, is the SecurityPolicy role's job, not this one — see ### Durability is bounded by the write, not guaranteed by it -`f_sync` runs after every write. It writes back the cached data, updates the +`f_sync` runs after every write: it writes back the cached data, updates the directory entry so the recorded file size includes the record, and issues -`CTRL_SYNC` to your driver — so the loss window is one incomplete write rather +`CTRL_SYNC` to your driver. The loss window is therefore one incomplete write rather than everything since the last close. Whether the sync reaches the medium, and what the FAT metadata looks like afterwards, is a property of your `diskio.c` driver and the hardware under it. FAT is not a journalling filesystem, and a diff --git a/docs/platforms/fatfs/setup.md b/docs/platforms/fatfs/setup.md index 92a384a5..a29778a6 100644 --- a/docs/platforms/fatfs/setup.md +++ b/docs/platforms/fatfs/setup.md @@ -29,11 +29,11 @@ set(SOLIDSYSLOG_PLATFORMS "FatFs;;") target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FatFs) ``` -The list is the whole selection rather than an addition to it — see -[naming your platforms](../../build-integration.md#cmake) — so the placeholders -are not optional shorthand. This platform fills the File role only; substitute -whichever platforms the [capability matrix](../index.md) says fill the rest of -what your build needs, and drop a placeholder for a role you are not using. +This platform fills the File role only; the placeholders are whichever platforms +the [capability matrix](../index.md) says fill the rest of what your build +needs, and one you do not need comes out. See +[naming your platforms](../../build-integration.md#cmake) for how the list is +read. ## What you must provide diff --git a/docs/platforms/freertos/setup.md b/docs/platforms/freertos/setup.md index 9eb70a77..a8022e12 100644 --- a/docs/platforms/freertos/setup.md +++ b/docs/platforms/freertos/setup.md @@ -14,12 +14,11 @@ set(SOLIDSYSLOG_PLATFORMS "FreeRtos;;") target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FreeRtos) ``` -The list is the whole selection rather than an addition to it — see -[naming your platforms](../../build-integration.md#cmake) — so the placeholders -are not optional shorthand. This platform fills the Mutex role and the sysUpTime -callback; substitute whichever platforms the -[capability matrix](../index.md) says fill the rest of what your build needs, -and drop a placeholder for a role you are not using. +This platform fills the Mutex role and the sysUpTime callback; the placeholders +are whichever platforms the [capability matrix](../index.md) says fill the rest +of what your build needs, and one you do not need comes out. See +[naming your platforms](../../build-integration.md#cmake) for how the list is +read. Your target supplies the kernel's include path, because it is your kernel and your configuration. diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 4814551e..a7eb48b4 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -63,7 +63,7 @@ deployment is held to. ### Rotation is a file replacement and a reconnection The `SSL_CTX` is rebuilt each time the stream is opened, re-reading every -credential file the config names — the trust anchors always, the client +credential file named in the configuration — the trust anchors always, the client certificate and key only where mutual TLS is configured. So replacing them takes effect on the next connection — either through ordinary reconnection after an outage, or by calling `SolidSyslogSender_Disconnect` to force one. No reload callback is needed. diff --git a/docs/platforms/openssl/setup.md b/docs/platforms/openssl/setup.md index 0829ebdd..825248ca 100644 --- a/docs/platforms/openssl/setup.md +++ b/docs/platforms/openssl/setup.md @@ -15,10 +15,10 @@ that role. set(SOLIDSYSLOG_PLATFORMS "OpenSsl;") ``` -The list is the whole selection rather than an addition to it — see -[naming your platforms](../../build-integration.md#cmake) — so `` is -not optional shorthand. Substitute whichever platform the -[capability matrix](../index.md) says fills the Network role on your target. +`` is whichever platform the [capability matrix](../index.md) says +fills that role on your target — see +[naming your platforms](../../build-integration.md#cmake) for how the list is +read. OpenSSL is a stable system API rather than a header-configured upstream, so the adapter compiles straight into `libSolidSyslog.a` and there is no separate diff --git a/docs/platforms/plusfat/setup.md b/docs/platforms/plusfat/setup.md index fb8e3fd8..f05789a4 100644 --- a/docs/platforms/plusfat/setup.md +++ b/docs/platforms/plusfat/setup.md @@ -58,20 +58,15 @@ filename mode is sufficient for that naming. ## Durability -The adapter flushes after every complete write, and that flush persists the -record's data sectors. It does not commit the file's recorded size — the -**directory entry is written on close, not on each flush** — and the store holds -a block file open across appends. So a hard power cut can leave records on the -medium that the recorded size excludes, and the store will not read those back. -A graceful shutdown closes the file and leaves data and metadata consistent. - -One thing here is specific to Plus-FAT. There is **no per-file flush**: -`ff_stdio.h` declares `ff_fflush` but the library never defines it, so the -adapter flushes the IO manager's cache instead, which is the real durability -primitive. - -If the device must survive a hard power cut mid-record, size your records and -choose your discard policy with that in mind. +What the flush does and does not commit is on the platform page, under +[durability](index.md#durability-is-bounded-by-the-write-not-guaranteed-by-it). +Read it before choosing your sizes: it decides two settings here. + +A graceful shutdown closes the file and leaves data and metadata consistent, so +give the application a path that tears the logger down rather than relying on +power being cut cleanly. And if the device must survive a hard cut mid-record, +size your records and choose the store's discard policy with the loss window in +mind. ## Memory diff --git a/docs/platforms/plustcp/setup.md b/docs/platforms/plustcp/setup.md index b38e9c40..93a3120b 100644 --- a/docs/platforms/plustcp/setup.md +++ b/docs/platforms/plustcp/setup.md @@ -9,18 +9,17 @@ structs themselves. The stack is configured by a header you own, so the adapters compile inside your target against your `FreeRTOSIPConfig.h`. This platform fills the network role only, so select it alongside whichever platform supplies your mutex and -clock — the [capability matrix](../index.md) shows which fill those: +clock — the [capability matrix](../index.md) shows which platforms fill those: ```cmake set(SOLIDSYSLOG_PLATFORMS "PlusTcp;") target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::PlusTcp) ``` -The list is the whole selection rather than an addition to it — see -[naming your platforms](../../build-integration.md#cmake) — so `` -is not optional shorthand. Substitute whichever platform the -[capability matrix](../index.md) says fills the Mutex and clock roles on your -target. +`` is whichever platform the [capability matrix](../index.md) +says fills the Mutex and clock roles on your target — see +[naming your platforms](../../build-integration.md#cmake) for how the list is +read. Bring the stack up and let it acquire an address before creating any sender. diff --git a/docs/platforms/stdatomic/setup.md b/docs/platforms/stdatomic/setup.md index 566901b4..a60b1423 100644 --- a/docs/platforms/stdatomic/setup.md +++ b/docs/platforms/stdatomic/setup.md @@ -12,12 +12,11 @@ the adapter compiles straight into the static library: set(SOLIDSYSLOG_PLATFORMS "StdAtomic;;") ``` -The list is the whole selection rather than an addition to it — see -[naming your platforms](../../build-integration.md#cmake) — so the placeholders -are not optional shorthand. This platform fills the AtomicCounter role only; -substitute whichever platforms the [capability matrix](../index.md) says fill -the rest of what your build needs, and drop a placeholder for a role you are not -using. +This platform fills the AtomicCounter role only; the placeholders are whichever +platforms the [capability matrix](../index.md) says fill the rest of what your +build needs, and one you do not need comes out. See +[naming your platforms](../../build-integration.md#cmake) for how the list is +read. The compiler must support C11 atomics. Where a toolchain does not, the [platform × capability matrix](../index.md) shows which other platforms fill @@ -39,8 +38,8 @@ It is assigned when a record is raised rather than when it is sent, so a gap reflects loss anywhere in the pipeline — the buffer, the store, or the transport — not only on the wire. -Two different things happen if the counter is missing, and they are worth -telling apart. +An exhausted counter pool and an unconfigured counter are different failures, +and they are worth telling apart. Exhaust the pool and `Create` hands back the Null counter, which returns 1 for every record. The meta element is still emitted, so you still get `sysUpTime` diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index f3d909df..3e7d1958 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -28,7 +28,7 @@ Status key: | 6.3.2 | SD-ID / SD-NAME syntax validation | Planned | Not performed. It only bites once callers can supply their own names: the three standard SDs (meta / timeQuality / origin) use compile-time-constant names that are valid by construction. Tracked under Custom Structured Data (`#64`), which is what introduces caller-supplied SD-IDs and PARAM names | | 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogSdValue` — every SD-PARAM value is written through this sink, which applies the escaping: RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). `OriginSd` streams software, swVersion, enterpriseId, and each ip into it; `MetaSd` streams language via the integrator's `SolidSyslogSdValueFunction` callback. Both get the same escaping. | | 7.1 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | -| 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | +| 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; the config strings are borrowed for the SD's lifetime and each is escaped per §6.3.3 by the `SolidSyslogSdValue` writer it is streamed into at Format time (no pre-formatted scratch storage). `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0 to N-1, opening an `ip` param per token (with a leading space) while the integrator's at-callback writes one IP value per call into the `SolidSyslogSdValue` it is handed, which applies the escaping. All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. The library frames and escapes; the IP value length is the integrator's to bound (ultimately by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`), as is the IP count. Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | | 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths, the type giving RFC 3418's natural wrap; the [capability matrix](platforms/index.md) shows which platforms supply one. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. `sysUpTime` and `language` are independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter. The counter is not: `SolidSyslogMetaSd_Create` rejects a NULL `Counter` with a `WARNING` and returns the Null structured data, so the element is not emitted at all | | 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Partial | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. [AtomicCounter](api/structSolidSyslogAtomicCounter.md) is a vtable abstraction, so the wrap is the contract's and not any one implementation's; the integrator wires a concrete counter at setup time and the [capability matrix](platforms/index.md) shows which platforms supply one. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). IDs from a wired counter remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp. Uniqueness is the counter's, not the contract's: exhaust a counter's pool and `Create` falls back to the Null counter, which returns 1 for every record, so gap detection stops being meaningful while logging continues | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one. Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint, and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU. TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | From 09004fe0b66c74eda87609895919a9d61c91f0ba Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 23:18:28 +0100 Subject: [PATCH 54/57] docs: S23.22 scope the buffering claim and finish the D.013 widening Part of #708 --- docs/iec62443.md | 2 +- docs/misra-deviations.md | 18 +++++++++++------- docs/platforms/fatfs/setup.md | 2 +- docs/platforms/freertos/setup.md | 2 +- docs/platforms/stdatomic/setup.md | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/iec62443.md b/docs/iec62443.md index 6ecc2dcb..5ff5ad7c 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -47,7 +47,7 @@ what the library does not do. | **CR 2.11** — Timestamps | The clock is an injected `SolidSyslogClockFunction` rather than a component, so the time source is yours to choose; platforms that have one ship an implementation. Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | | **CR 2.12** — Non-repudiation | At the wire: mutual TLS through the Stream role cryptographically identifies the TLS peer to the collector. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: the SecurityPolicy role, filled by a keyed policy, gives cryptographic integrity or authenticated encryption; Core's unkeyed CRC-16 policy detects accidental corruption only | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the collector authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Keyed policies are only as good as their key — custody, rotation and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | | **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed SecurityPolicy makes an unauthorised edit detectable. Core's CRC-16 policy does not serve this control — it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit through a TLS backend and at rest through an authenticated-encryption policy. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (any buffering Buffer):** `SolidSyslog_Log` formats and enqueues, returning before any I/O, and `SolidSyslog_Service` performs the transport work on a thread you choose. Either way, the bounded surface is the transport's connection and handshake budget, both tunable and overridable per instance at run time; what a given transport actually blocks on is its platform's to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (a buffering Buffer):** `SolidSyslog_Log` formats and enqueues, and every buffering Buffer the library ships returns before any transport I/O; `SolidSyslog_Service` then performs that work on a thread you choose. The role's contract does not itself forbid a blocking `Write`, so a Buffer you write defines this for you. Either way, the bounded surface is the transport's connection and handshake budget, both tunable and overridable per instance at run time; what a given transport actually blocks on is for its platform page to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | | **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index bdeed713..c38a329a 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -1164,13 +1164,17 @@ The cast is well-defined: a character type may alias any object type - **Alignment** — Both representations are byte-addressed; no alignment promotion occurs. The cast targets a character pointer, which has the weakest alignment requirement of any object pointer. -- **Type safety** — The caller-supplied buffer originates as a - contiguous byte sequence (typically the formatted syslog record); - treating it as `char*` or `unsigned char*` at the third-party API - boundary is the same byte sequence under a different pointer type. -- **Elimination path** — A future revision of the Stream API that - adopts a character pointer directly would retire this deviation for - whichever of the two spellings it chose. +- **Type safety** — Both directions carry a contiguous byte sequence and + neither is interpreted as anything else: on the send path it is the + formatted record the caller hands over, and on the read path it is the + caller's destination, written into and never read as a wider type. + Treating either as `char*` or `unsigned char*` at the third-party API + boundary is the same bytes under a different pointer type. +- **Elimination path** — The deviation retires per API, not as a whole. A + future `SolidSyslogStream::Send` / `Read` typed to a character pointer + would retire the Stream sites; `SolidSyslogDatagram::SendTo` would need + the same change to retire `WinsockDatagram_SendTo`. Either would only + retire the sites whose third-party spelling it matched. Tracked as a possible E10-successor refactor, not scheduled. ### Approval diff --git a/docs/platforms/fatfs/setup.md b/docs/platforms/fatfs/setup.md index a29778a6..b534c81e 100644 --- a/docs/platforms/fatfs/setup.md +++ b/docs/platforms/fatfs/setup.md @@ -31,7 +31,7 @@ target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FatFs) This platform fills the File role only; the placeholders are whichever platforms the [capability matrix](../index.md) says fill the rest of what your build -needs, and one you do not need comes out. See +needs. See [naming your platforms](../../build-integration.md#cmake) for how the list is read. diff --git a/docs/platforms/freertos/setup.md b/docs/platforms/freertos/setup.md index a8022e12..4e01d69d 100644 --- a/docs/platforms/freertos/setup.md +++ b/docs/platforms/freertos/setup.md @@ -16,7 +16,7 @@ target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::FreeRtos) This platform fills the Mutex role and the sysUpTime callback; the placeholders are whichever platforms the [capability matrix](../index.md) says fill the rest -of what your build needs, and one you do not need comes out. See +of what your build needs. See [naming your platforms](../../build-integration.md#cmake) for how the list is read. diff --git a/docs/platforms/stdatomic/setup.md b/docs/platforms/stdatomic/setup.md index a60b1423..3bc8d313 100644 --- a/docs/platforms/stdatomic/setup.md +++ b/docs/platforms/stdatomic/setup.md @@ -14,7 +14,7 @@ set(SOLIDSYSLOG_PLATFORMS "StdAtomic;;") This platform fills the AtomicCounter role only; the placeholders are whichever platforms the [capability matrix](../index.md) says fill the rest of what your -build needs, and one you do not need comes out. See +build needs. See [naming your platforms](../../build-integration.md#cmake) for how the list is read. From 3019ae123a7169fe76044f8b5e5821a3a3148378 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 23:29:01 +0100 Subject: [PATCH 55/57] docs: S23.22 scope the timeout budgets to the phases that have them Part of #708 --- docs/iec62443.md | 2 +- docs/misra-deviations.md | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/iec62443.md b/docs/iec62443.md index 5ff5ad7c..6334ca33 100644 --- a/docs/iec62443.md +++ b/docs/iec62443.md @@ -47,7 +47,7 @@ what the library does not do. | **CR 2.11** — Timestamps | The clock is an injected `SolidSyslogClockFunction` rather than a component, so the time source is yours to choose; platforms that have one ship an implementation. Quality metadata via `SolidSyslogTimeQualitySd` (`tzKnown` / `isSynced` / `syncAccuracy`), so a collector can tell how far to trust the time rather than assuming it. A clock that cannot answer yields NILVALUE rather than a plausible wrong time | Clock accuracy and synchronisation are the platform's: the library reports the quality you declare, it does not establish it, and it cannot detect a clock that reports itself synchronised while wrong. Time-source integrity is likewise outside the library | | **CR 2.12** — Non-repudiation | At the wire: mutual TLS through the Stream role cryptographically identifies the TLS peer to the collector. Loss detection: `SolidSyslogMetaSd` sequenceId, gap detection at the SIEM — informational, not cryptographically bound, so it evidences loss rather than origin. At rest: the SecurityPolicy role, filled by a keyed policy, gives cryptographic integrity or authenticated encryption; Core's unkeyed CRC-16 policy detects accidental corruption only | Mutual TLS authenticates the TLS peer, so where a relay terminates the connection the collector authenticates the relay rather than the originating device, and no library mechanism binds origin across that hop. Keyed policies are only as good as their key — custody, rotation and tamper-evident storage are integrator responsibilities. See [at-rest cryptography](security/at-rest-cryptography.md) and the [threat model](security/threat-model.md) | | **CR 3.9** — Protection of audit information | The library's contribution is tamper evidence: where the medium is exposed, a keyed SecurityPolicy makes an unauthorised edit detectable. Core's CRC-16 policy does not serve this control — it is unkeyed, so anyone able to modify a record can recompute the checksum. Related but distinct controls: **CR 3.4** — Software and information integrity, and **CR 4.1** — Information confidentiality, in transit through a TLS backend and at rest through an authenticated-encryption policy. See [at-rest cryptography](security/at-rest-cryptography.md) | The access and deletion half of the control is not the library's: it neither sets nor checks filesystem permissions, privileged access or retention on the store, and those remain yours to configure and to verify. The cryptographic mechanisms are integrator choices, not CR 3.9 mandates; key management is the integrator's responsibility. The control's own requirement enhancement calls for audit records on write-once media, which is a property of the storage medium and outside anything a library can supply | -| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (a buffering Buffer):** `SolidSyslog_Log` formats and enqueues, and every buffering Buffer the library ships returns before any transport I/O; `SolidSyslog_Service` then performs that work on a thread you choose. The role's contract does not itself forbid a blocking `Write`, so a Buffer you write defines this for you. Either way, the bounded surface is the transport's connection and handshake budget, both tunable and overridable per instance at run time; what a given transport actually blocks on is for its platform page to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | +| **CR 6.1** — Audit log accessibility | Two wirings, two cost models, chosen by which Buffer you fill the role with. **Inline (`SolidSyslogPassthroughBuffer`):** `SolidSyslog_Log` performs the transport send on the caller's thread, so the call costs whatever delivery costs. Suitable where the application has no real-time deadline. **Queued (a buffering Buffer):** `SolidSyslog_Log` formats and enqueues, and every buffering Buffer the library ships returns before any transport I/O; `SolidSyslog_Service` then performs that work on a thread you choose. The role's contract does not itself forbid a blocking `Write`, so a Buffer you write defines this for you. On a stream transport the phases that can wait are the connection and, on a secured path, the handshake; each has a tunable budget, overridable per instance at run time. Neither bounds the transfer of a record once the connection is up, and a datagram transport has neither phase. What a given transport actually waits on is for its platform page to state — see the [platform pages](platforms/index.md). Caller picks the wiring that fits the deployment's audit-loss / latency trade-off | Neither wiring is chosen for you, and the choice has consequences the library cannot make for you. Access to the records once delivered — query, retention, export — belongs to the collector, not to this library. The control's own requirement enhancement calls for programmatic access to the audit log, which on this design is the collector's interface rather than the device's | | **CR 6.2** — Continuous monitoring | TCP / TLS delivery confirmation via `SolidSyslogStreamSender`. Replay across outages via `SolidSyslogBlockStore` store-and-forward. `SolidSyslogMetaSd` sequenceId is assigned at the point of raise, so a gap reflects loss anywhere in the pipeline rather than transport loss alone | The monitoring is the collector's: the library emits a sequence that makes loss detectable, it does not detect it, alert on it, or know whether anyone is watching. Continuous monitoring in the control's sense is a property of the deployment. | The identity controls (CR 1.5, CR 1.8) are met by mutual TLS, which authenticates the diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index c38a329a..31292a7f 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -1113,9 +1113,9 @@ Raised and approved 2026-05-22 by the project owner, David Cozens. Recorded unde ### Construct -`SolidSyslogStream::Send` takes `const void*` and `SolidSyslogStream::Read` +`SolidSyslogStream_Send` takes `const void*` and `SolidSyslogStream_Read` takes `void*` — the project-wide byte-buffer contract used by every -Stream implementation, and `SolidSyslogDatagram::SendTo` takes `const void*` +Stream implementation, and `SolidSyslogDatagram_SendTo` takes `const void*` likewise. Some third-party C libraries type their byte buffers as a character pointer rather than `void*`: mbedTLS uses `const unsigned char*` / `unsigned char*`, and the Winsock socket calls use `const char*` / `char*` @@ -1151,7 +1151,7 @@ The alternatives all regress: | Alternative | Why rejected | |-------------|--------------| -| Refactor `SolidSyslogStream::Send`/`Read` to use `unsigned char*` | Public-API ABI change that propagates to every Stream implementation (Posix TCP, Winsock TCP, FreeRTOS TCP, OpenSSL TLS, mbedTLS TLS, NullStream) and every Stream caller (`SolidSyslogStreamSender`). The `void*` byte-buffer contract is the conventional C idiom for transport interfaces and matches POSIX `send`/`recv`, OpenSSL `SSL_write`/`SSL_read`, etc. Changing it for the sake of one third-party API's typing choice is the wrong direction. | +| Refactor `SolidSyslogStream_Send` / `SolidSyslogStream_Read` to use `unsigned char*` | Public-API ABI change that propagates to every Stream implementation (Posix TCP, Winsock TCP, FreeRTOS TCP, OpenSSL TLS, mbedTLS TLS, NullStream) and every Stream caller (`SolidSyslogStreamSender`). The `void*` byte-buffer contract is the conventional C idiom for transport interfaces and matches POSIX `send`/`recv`, OpenSSL `SSL_write`/`SSL_read`, etc. Changing it for the sake of one third-party API's typing choice is the wrong direction. | | Copy through an `unsigned char` scratch buffer per call | Runtime cost on the hot send/receive path; adds a fixed-size scratch or a stack-allocated VLA in a critical-path function. Defeats the zero-copy intent of the Stream contract. | | Inline `cppcheck-suppress misra-c2012-11.5` at each site | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | @@ -1171,8 +1171,9 @@ The cast is well-defined: a character type may alias any object type Treating either as `char*` or `unsigned char*` at the third-party API boundary is the same bytes under a different pointer type. - **Elimination path** — The deviation retires per API, not as a whole. A - future `SolidSyslogStream::Send` / `Read` typed to a character pointer - would retire the Stream sites; `SolidSyslogDatagram::SendTo` would need + future `SolidSyslogStream_Send` / `SolidSyslogStream_Read` typed to a + character pointer would retire the Stream sites; `SolidSyslogDatagram_SendTo` + would need the same change to retire `WinsockDatagram_SendTo`. Either would only retire the sites whose third-party spelling it matched. Tracked as a possible E10-successor refactor, not scheduled. From 26294a1b407d58cf25ebd8dd0df4fdce34908d66 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 9 Aug 2026 23:31:27 +0100 Subject: [PATCH 56/57] docs: S23.22 write C members in C, not C++ scope notation Part of #708 --- Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c | 2 +- docs/misra-deviations.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c index 39ad6565..81877111 100644 --- a/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c +++ b/Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c @@ -215,7 +215,7 @@ static uint32_t PlusTcpTcpStream_ResolveConnectTimeoutMs(struct SolidSyslogPlusT * before FreeRTOS_connect runs. Without this, the cold-start SYN is dropped * at the IP layer (FreeRTOS-Plus-TCP does not queue while ARP resolves) and * the bounded 200 ms connect timeout expires before the SYN-and-resend - * cycle completes. Symmetric with SolidSyslogPlusTcpDatagram::SendTo. */ + * cycle completes. Symmetric with PlusTcpDatagram_SendTo. */ static inline void PlusTcpTcpStream_PrimeArpIfMissing(uint32_t ip) { /* Yield window for the IP task to receive an ARP reply and populate diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index 31292a7f..eb12b3f7 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -592,7 +592,7 @@ Two distinct site categories trigger this rule: `SolidSyslogDatagram_SendTo` takes the caller's buffer as `const void*` — the contract is read-only inside the library. - lwIP's `struct pbuf::payload` is declared `void*` (no `const` + the `payload` field of lwIP's `struct pbuf` is declared `void*` (no `const` variant in the lwIP headers); `udp_sendto` only reads the payload — that is the `PBUF_REF` zero-copy contract — but the field type does not encode that. Assigning @@ -636,7 +636,8 @@ alternative to bending the code around a tool. The two platform-API sites are the standard case of a const-correct interior forced to strip qualification at a fixed third-party API boundary. Both upstream declarations (Microsoft's `select()` timeout, -lwIP's `pbuf::payload`) are fixed by their vendors; the SolidSyslog seam +lwIP's `struct pbuf` `payload` field) are fixed by their vendors; the +SolidSyslog seam keeps the const-correctness contract on the caller's side of the boundary. From ea43847b5c666d495553f5982ef2586a0debf00f Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 10 Aug 2026 05:59:56 +0100 Subject: [PATCH 57/57] docs: S23.22 keep the const qualification in D.013's proposed types Part of #708 --- docs/misra-deviations.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/misra-deviations.md b/docs/misra-deviations.md index eb12b3f7..e00b951d 100644 --- a/docs/misra-deviations.md +++ b/docs/misra-deviations.md @@ -1152,7 +1152,7 @@ The alternatives all regress: | Alternative | Why rejected | |-------------|--------------| -| Refactor `SolidSyslogStream_Send` / `SolidSyslogStream_Read` to use `unsigned char*` | Public-API ABI change that propagates to every Stream implementation (Posix TCP, Winsock TCP, FreeRTOS TCP, OpenSSL TLS, mbedTLS TLS, NullStream) and every Stream caller (`SolidSyslogStreamSender`). The `void*` byte-buffer contract is the conventional C idiom for transport interfaces and matches POSIX `send`/`recv`, OpenSSL `SSL_write`/`SSL_read`, etc. Changing it for the sake of one third-party API's typing choice is the wrong direction. | +| Refactor `SolidSyslogStream_Send` to `const unsigned char*` and `SolidSyslogStream_Read` to `unsigned char*` | Public-API ABI change that propagates to every Stream implementation (Posix TCP, Winsock TCP, FreeRTOS TCP, OpenSSL TLS, mbedTLS TLS, NullStream) and every Stream caller (`SolidSyslogStreamSender`). The `void*` byte-buffer contract is the conventional C idiom for transport interfaces and matches POSIX `send`/`recv`, OpenSSL `SSL_write`/`SSL_read`, etc. Changing it for the sake of one third-party API's typing choice is the wrong direction. | | Copy through an `unsigned char` scratch buffer per call | Runtime cost on the hot send/receive path; adds a fixed-size scratch or a stack-allocated VLA in a critical-path function. Defeats the zero-copy intent of the Stream contract. | | Inline `cppcheck-suppress misra-c2012-11.5` at each site | **Project preference.** Deviations are recorded structurally in this document so the rationale is centrally auditable rather than scattered across call sites. | @@ -1171,12 +1171,12 @@ The cast is well-defined: a character type may alias any object type caller's destination, written into and never read as a wider type. Treating either as `char*` or `unsigned char*` at the third-party API boundary is the same bytes under a different pointer type. -- **Elimination path** — The deviation retires per API, not as a whole. A - future `SolidSyslogStream_Send` / `SolidSyslogStream_Read` typed to a - character pointer would retire the Stream sites; `SolidSyslogDatagram_SendTo` - would need - the same change to retire `WinsockDatagram_SendTo`. Either would only - retire the sites whose third-party spelling it matched. +- **Elimination path** — The deviation retires per API, not as a whole, and + each direction keeps its qualification. `SolidSyslogStream_Send` typed to + `const unsigned char*` and `SolidSyslogStream_Read` to `unsigned char*` + would retire the Stream sites; `SolidSyslogDatagram_SendTo` typed to + `const unsigned char*` would retire `WinsockDatagram_SendTo`. Either would + only retire the sites whose third-party spelling it matched. Tracked as a possible E10-successor refactor, not scheduled. ### Approval