diff --git a/CLAUDE.md b/CLAUDE.md index 0b531654..dcbeb1af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,9 +101,19 @@ include them), run `addSubIssue` retroactively; it's idempotent-safe on closed i ## Project Board Membership The `SolidSyslog` project board (`gh project list --owner DavidCozens` → project 1) has a -`Status` single-select field with options **Todo**, **In Progress**, **Done**. Adding an -issue to the repo does **not** add it to the board — that is a separate step and must be -done explicitly. +`Status` single-select field with options **Todo**, **In Progress**, **Done**. + +Project workflows keep membership and status; there is no manual step. Linking a story +under its epic with `addSubIssue` puts it on the board at `Todo`, opening a pull request +that links the issue moves it to `In Progress`, and closing it sets `Done`. The workflows +add nothing that has no parent, so a chore or docs issue raised without an epic stays off +the board; a few early items predate them. + +Confirm any of that by reading the board rather than the workflow list — an automation +being enabled says nothing about which field it writes, and the API exposes each +workflow's name and enabled flag but not its action. Pass +`archivedStates: [ARCHIVED, NOT_ARCHIVED]` when you read: `projectV2.items` omits +archived items by default, so a board read without it is a partial one. ### Convention @@ -124,7 +134,10 @@ done explicitly. housekeeping step, not a status transition. Archived items stay on the project and still count in the epic's sub-issue roll-up. -### Add-to-board recipe +### Repairing board state by hand + +Nothing routine needs this — the workflows above place items and set status. It is here +for the case where one has not fired, or a status is wrong and needs correcting. ```bash # Project and Status field IDs (stable for this repo): @@ -132,24 +145,28 @@ done explicitly. # statusField = PVTSSF_lAHOAPhEnM4BTETqzhAat7w # options = Todo:f75ad846 In Progress:47fc9ee4 Done:98236657 -# 1. Get the issue's node ID. -gh api graphql -f query=' -query { - repository(owner: "cososo-ltd", name: "solid-syslog") { - issue(number: ) { id } +# Read the board. `issue.projectItems` returns 0 here -- the project is user-owned and +# the repository org-owned -- so enumerate the project's items and paginate past 100. +# archivedStates is required: without it the archived items are silently missing. +gh api graphql --paginate -f query=' +query($endCursor: String) { + user(login: "DavidCozens") { + projectV2(number: 1) { + items(first: 100, after: $endCursor, archivedStates: [ARCHIVED, NOT_ARCHIVED]) { + pageInfo { hasNextPage endCursor } + nodes { + id + content { ... on Issue { number state } } + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { name } + } + } + } + } } }' -# 2. Add to project (returns the new project item's id). -gh api graphql -f query=' -mutation { - addProjectV2ItemById(input: { - projectId: "PVT_kwHOAPhEnM4BTETq", - contentId: "" - }) { item { id } } -}' - -# 3. Set Status (use the option id matching Todo / In Progress / Done). +# Correct a status, using the item id the query above returns. gh api graphql -f query=' mutation { updateProjectV2ItemFieldValue(input: { @@ -203,21 +220,21 @@ For every new story: Pad both the epic and story numbers to two digits. 2. `addSubIssue` it under the parent epic (see **Issue / Epic Linking** above). The Parent-issue link is what groups the story into the correct swimlane. -3. Add the story to the project board with `Status = Todo` using the recipe above. Do - **not** add the parent epic itself — the swimlane appears automatically once a child - story is on the board. + +There is no third step. Step 2 puts the story on the board at `Todo`, and the swimlane +appears with it. Do **not** add the parent epic — it is not an item. ### Work-in-progress limit `In Progress` holds at most two items — typically one functional story and one BDD -story. **Check the count before moving anything into `In Progress`, and say so if the -move would make it three.** The limit is only worth having if someone is watching it, -and the board does not enforce it. +story. A linked pull request is what moves a story there, so this is in practice a limit +on open PRs: **check the count before opening one that would make it three, and say so.** +The limit is only worth having if someone is watching it, and the board does not enforce +it. ### When closing a story -When a PR merges and closes a story, flip its project Status to `Done`. Closing the -issue does not update the Status field automatically. +A merged PR that closes the story sets `Done` for you. Epics don't have a Status field on the board (they aren't items). When every child story is Done, the swimlane naturally becomes all-Done; the epic issue itself should @@ -429,7 +446,7 @@ why.** Read the header rather than a copy of it. For the wider map: - `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/platforms//index.md` — what each platform supplies. - `docs/api-reference/` plus the generated Doxygen indexes (`docs/api/files.md`, `annotated.md`, `functions.md`, `macros.md`) — every header and symbol. @@ -493,6 +510,31 @@ Deliberate deviations from the MISRA rule set are recorded in - cppcheck runs with `--error-exitcode=1`. Inline suppressions (`// cppcheck-suppress`) must include a comment explaining why. +### Characters in source + +Write what a UK keyboard types. In `.c`, `.h` and `.cpp` — comments included — that +means `-` and not an em or en dash, `...` and not an ellipsis, `->` and not an arrow, +and the ASCII spelling of a symbol: `<=`, `+/-`, `x`, `||`, "sum of". A continuation +ellipsis takes no leading comma: `first, second...`, not `first, second, ...`. + +Three exceptions stand, each authorised rather than assumed: + +- `µ` in a unit, and `§` in an RFC citation. Both read better as themselves and + neither is ambiguous. +- A character that *is* the subject of its comment. The UTF-8 tests name U+00E9, the + euro sign and an emoji to explain the byte sequences they encode; replacing them + would delete the point. +- String literals were left alone in the sweep that established this. Seven carry an + em dash and are runtime or test output, where the character is data rather than + typography. + +**Anything else non-ASCII: ask.** Do not quietly pick a typographic character, and do +not quietly rewrite a sentence to avoid one — either way the decision goes unrecorded. + +This is a rule about source. Documentation under `docs/` keeps typographic characters, +on the reasoning that prose of that length is written with an authoring tool; `README.md` +is hyphenated by hand and is the deliberate exception. + ### MISRA-load-bearing `.clang-format` settings Two settings in `.clang-format` are not merely stylistic — they enforce MISRA C:2012 rules at @@ -563,6 +605,23 @@ 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. +### Link the record, not the source + +A documentation page does not send the reader into the source tree. Where a +symbol has a generated API page, link that; otherwise name the file in code font +and leave it there. The page's job is to say what the library does, not to show +where it is implemented. + +Issues and pull requests are the opposite case, and are linked. They are the +tracking record, and a reader who has just been told that a platform diverges +from a contract wants to see whether that is still true. + +The repository-root documents are outside this rule rather than an exception to +it. `README.md`, `SECURITY.md`, `SUPPORT.md` and `LICENSE.md` are read on GitHub +as well as published into the site by `hooks/root_pages.py`, so their links stay +repo-relative; `hooks/source_links.py` rewrites whatever escapes `docs/` to a +canonical URL at build time. + --- ## Design Patterns diff --git a/README.md b/README.md index 09b0330b..da2b76f4 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,22 @@ A structured syslog client library for embedded and industrial systems, implementing RFC 5424 (structured syslog) with RFC 5426 (UDP) and RFC 6587 (TCP) transports. -TLS per RFC 5425 ships two ways — `SolidSyslogOpenSslStream` over OpenSSL and -`SolidSyslogMbedTlsStream` over Mbed TLS — and any other TLS library (wolfSSL, -hardware offload, …) plugs in behind the same Stream vtable. TLS is not a core -dependency: Core carries no reference to any TLS library. +TLS per RFC 5425 is supplied by a platform, so any TLS library plugs in behind the +same Stream vtable. TLS is not a core dependency: Core carries no reference to any +TLS library. It exists to give shipping embedded products the security audit trail the EU -Cyber Resilience Act and IEC 62443 expect — as a component you add, not a -redesign. +Cyber Resilience Act and IEC 62443 expect: a component you add, not a redesign. ## What it costs **+5 KB flash** for a valid, timestamped RFC 5424 record on the wire, and **0.4 KB -of RAM**. **+13.5 KB flash** for the whole path — store-and-forward, device identity, -mutual TLS and AES-GCM encryption at rest — and **37 KB of RAM**, mainly TLS buffers. -Figures measured on FreeRTOS with lwIP on a Cortex-M3, run under QEMU — a -representative device, not a specification. +of RAM**. **+13.5 KB flash** for the whole path - store-and-forward, device identity, +mutual TLS and AES-GCM encryption at rest - and **37 KB of RAM**, mainly TLS buffers. +Each figure is what SolidSyslog adds to a device already running FreeRTOS, lwIP, +FatFs and Mbed TLS: a baseline built to carry the third-party code a real-world +device would already have. Measured on a Cortex-M3 under QEMU, a representative +device rather than a specification. Both are measured by a worked integration, published in full as [solid-syslog-example](https://github.com/cososo-ltd/solid-syslog-example) (consumed @@ -28,28 +28,28 @@ gives the diff and the measured cost. Designed for resource-constrained environments: -- C99, no dynamic memory allocation — every instance lives in a library-internal static pool, sized at compile time -- Transport-agnostic — UDP, TCP, TLS, or bring your own -- Buffer-agnostic — PassthroughBuffer (direct send), portable CircularBuffer (mutex-injected ring), POSIX message queue, or bring your own +- C99, no dynamic memory allocation - every instance lives in a library-internal static pool, sized at compile time +- Transport-agnostic - UDP, TCP, TLS, or bring your own +- Buffer-agnostic - PassthroughBuffer (direct send), portable CircularBuffer (mutex-injected ring), POSIX message queue, or bring your own - MISRA C:2012 informed ## Capabilities RFC 5424 structured formatting over UDP (RFC 5426), TCP (RFC 6587), and TLS / mutual TLS (RFC 5425). Asynchronous buffering, rotating block store-and-forward, -and at-rest record protection — CRC-16 for accidental corruption, or keyed +and at-rest record protection: CRC-16 for accidental corruption, or keyed HMAC-SHA256 / AES-256-GCM where a local attacker is in scope. The audit-logging capabilities an IEC 62443 deployment draws on are mapped control by control in the [IEC 62443 guide](https://docs.cososo.co.uk/solid-syslog/iec62443/). -SolidSyslog is built for embedded and RTOS targets. Every platform dependency — -TCP/IP stack, TLS library, filesystem, OS primitives, clock — is injected through +SolidSyslog is built for embedded and RTOS targets. Every platform dependency - +TCP/IP stack, TLS library, filesystem, OS primitives, clock - is injected through a vtable, so the library ports to an embedded OS by filling roles rather -than editing Core. Reference adapters ship for FreeRTOS on Cortex-M (networking -via FreeRTOS-Plus-TCP or lwIP, transport security via `SolidSyslogMbedTlsStream` -over Mbed TLS, persistent store-and-forward over ChaN FatFs or FreeRTOS-Plus-FAT) -— and for POSIX and Windows, fully supported as development, test, and edge / -gateway hosts. Bring your own stack and the same Core runs unchanged. +than editing Core. Platforms ship for FreeRTOS on Cortex-M, and for POSIX and +Windows, fully supported as development, test, and edge / gateway hosts. Which +upstream fills which capability is in the +[platform matrix](https://docs.cososo.co.uk/solid-syslog/platforms/). Bring your +own stack and the same Core runs unchanged. TLS revocation (CRL / OCSP) is not performed by the library. Whether it is enforced depends on the TLS backend and platform you configure. @@ -58,7 +58,7 @@ enforced depends on the TLS backend and platform you configure. Full documentation lives at [docs.cososo.co.uk/solid-syslog](https://docs.cososo.co.uk/solid-syslog/), organised -around what you came to do: **Overview**, **Adopt**, **Port a new platform**, +around what you came to do: **Core**, **Integrate**, **Platforms**, **Compliance**, **API reference**, and **Maintaining**. New here? [Compliance in one page](https://docs.cososo.co.uk/solid-syslog/overview/) is the fastest orientation for evaluators. @@ -71,15 +71,15 @@ time, stating what each stage adds, the question that decides whether you need i and an indication of what it costs. [Adding it to your build](https://docs.cososo.co.uk/solid-syslog/build-integration/) -is the build detail behind it: 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. +is the build detail behind it: how the library composes, how to pick your stack, the +three ways to consume it - CMake, Make, and a source manifest for an IDE project - +and the compile-time tunables. ## Building and testing Developing the library itself? See -[Building and testing](https://docs.cososo.co.uk/solid-syslog/builds/) — the -contributor/maintainer preset catalogue — alongside the pre-PR check budget, the BDD +[Building and testing](https://docs.cososo.co.uk/solid-syslog/builds/) - the +contributor/maintainer preset catalogue - alongside the pre-PR check budget, the BDD infrastructure, the CI pipeline, and the container images, all under **Maintaining** on the documentation site. (Consuming the library in your product is the integration path above.) @@ -88,7 +88,7 @@ path above.) SolidSyslog is OO-in-C. Every platform dependency and every optional feature is a vtable role, injected at setup and composed at link time, so a feature you do not -wire is dropped by the linker rather than excluded by the preprocessor — Core's +wire is dropped by the linker rather than excluded by the preprocessor. Core's implementation contains no conditional compilation at all. Public headers are split by audience: application code that logs events includes `SolidSyslog.h` and nothing else, while the setup that builds a logger includes `SolidSyslogConfig.h` plus one @@ -100,17 +100,18 @@ that split and links the generated reference for every header, type and symbol. 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, -Windows, and FreeRTOS on QEMU — each exercising the library end to end against a real -syslog server; see [BDD testing](https://docs.cososo.co.uk/solid-syslog/bdd/). +[`Bdd/Targets/`](Bdd/Targets/) holds one BDD-driven binary per platform - Linux, +Windows, and two FreeRTOS-on-QEMU builds, one per network stack - each exercising +the library end to end against a real syslog server; see +[BDD testing](https://docs.cososo.co.uk/solid-syslog/bdd/). ## Compliance -- [CRA guide](https://docs.cososo.co.uk/solid-syslog/cra/) — the Annex I map: the requirement that names logging, the requirements an audit trail contributes to, what the project publishes for your vulnerability handling, and the dates the Regulation applies from -- [Compliance in one page](https://docs.cososo.co.uk/solid-syslog/overview/) — the evaluator's one-screen orientation on CRA and IEC 62443 -- [IEC 62443 compliance guide](https://docs.cososo.co.uk/solid-syslog/iec62443/) — the audit-logging-relevant controls, and what the library provides against each -- [RFC compliance matrix](https://docs.cososo.co.uk/solid-syslog/rfc-compliance/) — sender-side coverage of RFC 5424, 5426, 6587, and 5425 -- [Threat model](https://docs.cososo.co.uk/solid-syslog/security/threat-model/) — the division of responsibility between the library and your product +- [CRA guide](https://docs.cososo.co.uk/solid-syslog/cra/) - the Annex I map: the requirement that names logging, the requirements an audit trail contributes to, what the project publishes for your vulnerability handling, and the dates the Regulation applies from +- [Compliance in one page](https://docs.cososo.co.uk/solid-syslog/overview/) - the evaluator's one-screen orientation on CRA and IEC 62443 +- [IEC 62443 compliance guide](https://docs.cososo.co.uk/solid-syslog/iec62443/) - the audit-logging-relevant controls, and what the library provides against each +- [RFC compliance matrix](https://docs.cososo.co.uk/solid-syslog/rfc-compliance/) - sender-side coverage of RFC 5424, 5426, 6587, and 5425 +- [Threat model](https://docs.cososo.co.uk/solid-syslog/security/threat-model/) - the division of responsibility between the library and your product Reporting a vulnerability: [`SECURITY.md`](SECURITY.md). @@ -121,7 +122,7 @@ Copyright 2026 Cozens Software Solutions Limited. Licensed under the [PolyForm Noncommercial License 1.0.0](LICENSE.md). Free for noncommercial, personal, educational, and government use. -For commercial licensing — including pricing, the early-adopter programme, and -the platform adapter policy — see the +For commercial licensing - including pricing, the early-adopter programme, and +the platform adapter policy - see the [SolidSyslog product page](https://www.cososo.co.uk/products/solid-syslog/), or use the contact form at [cososo.co.uk](https://www.cososo.co.uk/#contact). diff --git a/SKILL.md b/SKILL.md index 3ed0c955..2f0b1e66 100644 --- a/SKILL.md +++ b/SKILL.md @@ -10,13 +10,9 @@ series by Cozens Software Solutions Limited. This file holds **how we work together**. [`CLAUDE.md`](CLAUDE.md) holds **how the repository works** — git and issue workflow, project structure, naming, code style, -design patterns, and the board and milestone conventions. Neither restates the other. - -When two sources disagree, the repository wins: `.clang-format`, `.clang-tidy`, -`.markdownlint-cli2.jsonc`, `CMakePresets.json` and `.github/workflows/ci.yml` are -executable and cannot drift from what actually happens. A briefing in conversation -outranks both files — it is the most recent intent — but say so when it contradicts a -file, so the file gets fixed rather than quietly bypassed. +design patterns, and the board and milestone conventions. Neither restates the other, +precedence included: what wins when two sources disagree is stated once, in CLAUDE.md +under **Precedence, when two sources disagree**. ## Collaboration modes diff --git a/docs/hardening-path.md b/docs/hardening-path.md index aa458784..aca86e46 100644 --- a/docs/hardening-path.md +++ b/docs/hardening-path.md @@ -112,7 +112,7 @@ its Null object and reported — so the only evidence is what the handler says: [syslog] CRITICAL SolidSyslog bad-config (detail 2) ``` -Doing it in this order is the point. Wire everything at once and see nothing, and you +The order matters. Wire everything at once and see nothing, and you cannot tell a working logger from a silent one. Seeing the faults first, then watching them go quiet as each collaborator arrives, is the difference between believing it works and knowing. @@ -287,7 +287,7 @@ static uint8_t s_ring[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(SYSLOG_BUFFER_RECOR .Buffer = SolidSyslogCircularBuffer_Create(SolidSyslogFreeRtosMutex_Create(), s_ring, sizeof(s_ring)), ``` -This separates logging an event from sending it, and that is the point. +This separates logging an event from sending it. `SolidSyslog_Log` becomes safe to call from any number of tasks, and potentially cheap enough to call from the place the event actually happens rather than from somewhere convenient later. Nothing that logs waits on the network. @@ -410,7 +410,7 @@ struct SolidSyslogOriginSdConfig originConfig = { sd[3] = SolidSyslogOriginSd_Create(&originConfig); ``` -This lands after storage, and the reason is the point. While records went straight out, +This lands after storage for a reason. While records went straight out, "who sent this" was implied by the connection they arrived on. Once records can replay hours later that stops being true, and the record has to say so itself. @@ -549,8 +549,8 @@ product knows. A collector can use it to confirm a record really did arrive over really was sealed at rest, and to alert on a device whose pipeline has weakened. Those are the values in force at this stage. They change as the remaining stages land — -`transport="mtls"` at stage 19, `atRest="aes-256-gcm"` at stage 20 — which is the point: -derive both from the handles the device actually holds, not from what you intended to +`transport="mtls"` at stage 19, `atRest="aes-256-gcm"` at stage 20 — so derive both from +the handles the device actually holds, not from what you intended to configure. A credential that failed to load leaves the device less protected than its configuration suggests, and an element claiming protection that is not in force is worse than no element at all, because that claim is exactly what a collector is watching for. diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index bf033880..cc066343 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -61,29 +61,32 @@ A client certificate is presented only when both `ClientCertChain` and connection proceeds with server-authenticated TLS, and nothing is reported — so a device configured for mutual TLS can run without presenting its certificate, and without anyone on the device knowing. The contract requires this to be reported. -Until it is, check for a half-supplied pair before you open the stream. Tracked -as `#718`. +Until it is, check for a half-supplied pair before you open the stream. Tracked as +[#718](https://github.com/cososo-ltd/solid-syslog/issues/718). ### The key is not checked against its certificate No local check confirms that `ClientKey` matches `ClientCertChain`, and a failure to install the pair is not reported either. A mismatch therefore surfaces as a handshake rejection from the collector rather than as a setup error on the -device, which sends you looking in the wrong place. Tracked as `#719`. +device, which sends you looking in the wrong place. Tracked as +[#719](https://github.com/cososo-ltd/solid-syslog/issues/719). ### An expired certificate stops delivery A peer certificate that is expired or not yet valid fails the handshake, even where it still chains to a trusted anchor. The contract asks for it to be reported with delivery continuing, because clock skew is the dominant cause and a -device with a wrong clock is one whose logs you still want. Tracked as `#731`. +device with a wrong clock is one whose logs you still want. Tracked as +[#731](https://github.com/cososo-ltd/solid-syslog/issues/731). ### The cipher policy cannot be expressed The configuration carries no cipher or ciphersuite field, so the ciphersuites your `mbedtls_config.h` enables, filtered by the preset, are what gets negotiated. The contract asks for an integrator's policy to be passed through -where the library allows one to be selected. Tracked as `#733`. +where the library allows one to be selected. Tracked as +[#733](https://github.com/cososo-ltd/solid-syslog/issues/733). ### The configuration is not checked when the stream is created @@ -91,4 +94,5 @@ A configuration missing something the stream cannot work without is accepted, an the fault appears on the first connection attempt rather than at setup. The random source and the trust chain are installed through calls that return no status, so a missing one becomes a handshake failure rather than the -configuration error it is. Tracked as `#732`. +configuration error it is. Tracked as +[#732](https://github.com/cososo-ltd/solid-syslog/issues/732). diff --git a/docs/platforms/openssl/index.md b/docs/platforms/openssl/index.md index 6bf4f4ce..8d7065ed 100644 --- a/docs/platforms/openssl/index.md +++ b/docs/platforms/openssl/index.md @@ -47,14 +47,16 @@ The contract asks for it to be reported with delivery continuing, on the grounds that the collector is the enforcement point for our own credential. This adapter is stricter than the contract rather than weaker, and the stricter -behaviour is safe. Tracked as `#734`. +behaviour is safe. Tracked as +[#734](https://github.com/cososo-ltd/solid-syslog/issues/734). ### An expired certificate stops delivery A peer certificate that is expired or not yet valid fails the handshake, even where it still chains to a trusted anchor. The contract asks for it to be reported with delivery continuing, because clock skew is the dominant cause and a -device with a wrong clock is one whose logs you still want. Tracked as `#731`. +device with a wrong clock is one whose logs you still want. Tracked as +[#731](https://github.com/cososo-ltd/solid-syslog/issues/731). ### The cipher policy does not bind a TLS 1.3 connection @@ -63,10 +65,11 @@ own, as the contract asks. It governs TLS 1.2 and below only. OpenSSL has kept TLS 1.3 ciphersuites in a separate list since 1.1.1, and this adapter sets a protocol floor without a ceiling, so against a modern peer the negotiated connection uses OpenSSL's own TLS 1.3 defaults and the configured list has no -effect on it. Tracked as `#733`. +effect on it. Tracked as +[#733](https://github.com/cososo-ltd/solid-syslog/issues/733). ### The configuration is not checked when the stream is created A configuration missing something the stream cannot work without is accepted, and -the fault appears on the first connection attempt rather than at setup. Tracked -as `#732`. +the fault appears on the first connection attempt rather than at setup. Tracked as +[#732](https://github.com/cososo-ltd/solid-syslog/issues/732). diff --git a/docs/platforms/plustcp/index.md b/docs/platforms/plustcp/index.md index e53454fc..b4b50d5a 100644 --- a/docs/platforms/plustcp/index.md +++ b/docs/platforms/plustcp/index.md @@ -58,8 +58,9 @@ record at its cursor and offers the same one on every servicing pass. Nothing behind it is delivered. `SOLIDSYSLOG_MAX_MESSAGE_SIZE` defaults to 2048, so this reaches any record over -about 1.2 KB rather than only unusual ones. Until `#736` lands, keep records on -this UDP path inside the payload it carries — but note that limit is library-wide +about 1.2 KB rather than only unusual ones. Until +[#736](https://github.com/cososo-ltd/solid-syslog/issues/736) lands, keep records +on this UDP path inside the payload it carries — but note that the limit is library-wide rather than per-transport, so lowering it truncates records on every transport the instance uses, not only this one. diff --git a/docs/porting.md b/docs/porting.md index 2725408a..66863391 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -144,6 +144,15 @@ This is the only synchronisation primitive the pools use for their own walks. - Idempotent `Close` / `Destroy`. No leak on a partial `Open` failure, no double-free if `Close` and `Destroy` are both called. Release each resource exactly once and null the handle. +- Cleanup runs under the config lock. `FreeIfInUse` holds + `SolidSyslog_LockConfig()` across your cleanup callback, so whatever `Destroy` + does to release a resource happens inside whichever primitive the integrator + installed — a FreeRTOS critical section, at the recommendation above. Blocking + there is not safe: a mutex may not be taken inside `taskENTER_CRITICAL`, and + work that needs another task to run cannot complete while interrupts are off. + The shipped lwIP TCP stream does block this way, tracked as + [#754](https://github.com/cososo-ltd/solid-syslog/issues/754); until that is + resolved, keep teardown in your own adapter non-blocking. - Never free injected handles. An adapter frees only what it created. Handles 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 @@ -157,8 +166,11 @@ This is the only synchronisation primitive the pools use for their own walks. bounded by an explicit timeout or deadline: a timeout tunable (e.g. `SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS`) or a caller-supplied deadline. A `SolidSyslogSleepFunction`, where one is used, only paces the poll loop between - checks; it does not bound the total wait. Steady-state `Send` / `Read` are - non-blocking. + checks; it does not bound the total wait. Steady-state `Send` / `Read` do not + wait on the peer, so a wedged collector cannot stall the servicing pass. That + bounds peer behaviour, not the local stack: a datagram send may still wait on a + full kernel send buffer, and each platform page states where its own blocking + surface lies. - Production-C discipline. Tier 1/2 code is single-return, fully braced, and MISRA-leaning, see [MISRA deviations](misra-deviations.md) and [Naming conventions](NAMING.md). diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index 733cb3fb..8b24663b 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -105,7 +105,7 @@ requirement in force, rather than tabulating it separately. | [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Certificate-based authentication — client | Supported | A client certificate and its key are optional configuration on the TLS stream, presented only when both are given, and a partially configured pair is reported rather than silently ignored | | [4.2.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.1) | Means to generate a key pair and self-signed certificate | N/A | Deliberately excluded. The library consumes trust material and does not mint it, so key generation belongs to the deployment's provisioning. Directed at a syslog application rather than at a component one is built from | | [4.2.2](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.2) | Certificate fingerprints published through a management interface | N/A | Directed at a syslog application, not a component one is built from: the library has no management interface, and the certificate is the integrator's to hold and to publish. The fingerprint form §4.2.2 defines matters where a peer is authorised by one, which is §5.1 | -| [4.2.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.3) | Administrators may select the cryptographic level | Partial | The contract requires an integrator's cipher policy to be passed through where the underlying library allows one to be selected. Neither shipped TLS platform delivers that on the connection actually negotiated — see each platform's page, and `#733` | +| [4.2.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.2.3) | Administrators may select the cryptographic level | Partial | The contract requires an integrator's cipher policy to be passed through where the underlying library allows one to be selected. Neither shipped TLS platform delivers that on the connection actually negotiated — see each platform's page, and [#733](https://github.com/cososo-ltd/solid-syslog/issues/733) | | [4.3](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.3) | All syslog messages MUST be sent as TLS application data | Supported | The TLS `Stream` carries the frames the sender writes as ordinary application data; nothing is sent outside the session, and §4.3's `APPLICATION-DATA = 1*SYSLOG-FRAME` is what the octet-counting sender produces | | [4.3.1](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.3.1) | Octet-counting framing, and the message length | Supported | Reuses `SolidSyslogStreamSender`, so the frame is `MSG-LEN SP MSG`. `SOLIDSYSLOG_MAX_MESSAGE_SIZE` defaults to 2048, the length §4.3.1 requires every transport receiver to accept | | [4.4](https://www.rfc-editor.org/rfc/rfc5425.html#section-4.4) | `close_notify` before closing | Supported | Close sends `close_notify` before tearing the connection down | diff --git a/docs/roles/index.md b/docs/roles/index.md index e52a8ead..9931af27 100644 --- a/docs/roles/index.md +++ b/docs/roles/index.md @@ -5,8 +5,8 @@ A role is one capability the library needs filled, defined as a vtable in your own code fills it. Every role has a Null fallback, so an unfilled slot degrades safely instead of dangling at link time. -There are twelve. Each page below is that role's contract — the vtable itself, -and a generated diagram of the backends that realise it. +Each page below is that role's contract — the vtable itself, and a generated +diagram of the backends that realise it. ## Networking @@ -42,9 +42,9 @@ and a generated diagram of the backends that realise it. ## Bring your own -Filling a role is implementing its vtable. [Porting](../porting.md) puts the -twelve contracts side by side, each with its Null fallback and a reference -implementation. +Filling a role is implementing its vtable. [Porting](../porting.md) covers the +anatomy of an adapter and the invariants every one honours, and links each +contract to the backends that already realise it. Which platform fills a role on your target is the [platform × capability matrix](../platforms/index.md). diff --git a/docs/structured-data.md b/docs/structured-data.md index a80ac1c6..547f6957 100644 --- a/docs/structured-data.md +++ b/docs/structured-data.md @@ -44,7 +44,7 @@ which the library calls with an `SD-ELEMENT` writer when it builds a message: static void ExampleSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogSdElement* element) { - (void) base; /* stateless here — see "Carrying data" below */ + (void) base; /* stateless here — see "Carrying instance or per-call data" below */ SolidSyslogSdElement_Begin(element, "example", 32473U); SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "detail"), "Hello World"); @@ -135,12 +135,15 @@ first example) is never affected. Write values with `SolidSyslogSdValue`: - `SolidSyslogSdValue_String(value, source)`: a NUL-terminated string. -- `SolidSyslogSdValue_BoundedString(value, source, maxLength)`: at most `maxLength` bytes. +- `SolidSyslogSdValue_BoundedString(value, source, maxDecodedLength)`: capped for a + receiver that parses into a width-limited field. The bound counts what the reader's + un-escaping decoder extracts, not the on-wire bytes. - `SolidSyslogSdValue_Uint32(value, number)`: decimal digits. The library applies the RFC 5424 §6.3.3 escaping for you (`"`, `\`, and `]` are -backslash-escaped) and validates UTF-8; you pass the raw value and the receiver gets it -back unchanged. Output is bounded by the message buffer, so a value can never overrun it. +backslash-escaped) and substitutes ill-formed UTF-8 with U+FFFD; you pass the raw value +and a receiver that un-escapes gets well-formed text back. Output is bounded by the +message buffer, so a value can never overrun it. ## What the library owns, and what you own diff --git a/scripts/check_platform_docs.py b/scripts/check_platform_docs.py index 89db9869..3b899157 100644 --- a/scripts/check_platform_docs.py +++ b/scripts/check_platform_docs.py @@ -100,6 +100,14 @@ SCANNED_SUFFIXES = (".c", ".h", ".md") +# A spelt-out count of the roles, which prose must not carry: the enumeration +# checks below are what a thirteenth role trips, and a number beside them just +# rots. Spelt-out only -- a bare digit would match version numbers and sizes. +ROLE_COUNT_IN_PROSE = re.compile( + r"\b(?:eleven|twelve|thirteen|fourteen)\b(?=[^.]{0,40}?\b(?:roles?|contracts?)\b)", + re.IGNORECASE, +) + # 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. @@ -257,6 +265,13 @@ def role_faults(): 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") + # A count in prose is the thing this check replaced. It cannot be + # asserted, so a thirteenth role would leave it quietly wrong. + for spelt in re.findall(ROLE_COUNT_IN_PROSE, text): + faults.append( + f"{listing} states the number of roles in prose ('{spelt}') — " + "the listing above is what keeps the set honest, so leave the count out" + ) return faults