Skip to content

feat: S28.04 SolidSyslogLwipRawDatagram#464

Merged
DavidCozens merged 7 commits into
mainfrom
feat/s28-04-lwip-raw-datagram
May 27, 2026
Merged

feat: S28.04 SolidSyslogLwipRawDatagram#464
DavidCozens merged 7 commits into
mainfrom
feat/s28-04-lwip-raw-datagram

Conversation

@DavidCozens

@DavidCozens DavidCozens commented May 27, 2026

Copy link
Copy Markdown
Owner

Purpose

Story S28.04 — UDP datagram adapter for the lwIP Raw API. Fourth
story in E28 (parent #439); follow-on to S28.03's Address +
Resolver pair. Closes #463.

Change Description

Implements SolidSyslogLwipRawDatagram mirroring the
SolidSyslogPlusTcpDatagram shape — three-TU pool split + Messages.c
for the error source. New tunable SOLIDSYSLOG_LWIP_RAW_DATAGRAM_POOL_SIZE
(default 1U).

Design decisions (DEVLOG 2026-05-27 has the full record):

  1. pbuf via PBUF_REF + per-send pbuf_alloc/pbuf_free. Zero-copy
    send — pbuf->payload points at the caller's buffer. Discussed
    mid-commit-3 whether to embed a struct pbuf in our datagram and
    skip pbuf_alloc entirely (MISRA-flavoured); concluded PBUF_REF is
    the right call because lwIP's udp_sendto internally allocates a
    header pbuf regardless, so the asymmetry doesn't pay for the
    manual-init complexity.
  2. ARP cache miss — delegate to lwIP. No manual probe; ARP_QUEUEING=1
    (lwIP default) keeps the first datagram alive. Wrapper stays
    netif-agnostic.
  3. Failure mapping. ERR_OKSENT; any other err_tFAILED.
    No OVERSIZE (lwIP has no clean signal; Service honours
    MaxPayload() upstream).
  4. MaxPayload() = 1232. SOLIDSYSLOG_UDP_IPV6_SAFE_PAYLOAD
    unconditionally, same as PlusTcp. No netif MTU lookup.

Test-shape work worth highlighting (applicable to every future
pool-allocated adapter):

  • TEST_BASE + two TEST_GROUP_BASE derivations
    (SolidSyslogLwipRawDatagram Created-only; …Open with Open lifted
    into setup) so most lifecycle tests drop the boilerplate Open call.
  • Leak invariants in the shared teardown. LwipUdpFake_OutstandingPcbCount
    and LwipPbufFake_OutstandingPbufCount are asserted zero after every
    test. Earned its keep mid-commit-3 — the pbuf invariant caught the
    missing pbuf_free before the explicit "frees pbuf" test landed.
  • sendBytes(size_t length = 1U) helper + shared sendBuffer member —
    most SendTo tests reduce to a single helper call.

CI gap addressed. The analyze-cppcheck lane did not previously
scan Platform/LwipRaw/Source/ — an oversight from S28.03's landing.
This PR adds -IPlatform/LwipRaw/Interface + Platform/LwipRaw/Source/
to all three cppcheck invocations and bundles the suppressions for the
four S28.03-era findings (LwipRawAddressPrivate.h 11.2/11.3 ×2;
LwipRawAddressStatic.c 11.3 ×2) alongside the two S28.04 findings
(LwipRawDatagram.c:43 SelfFromBase 11.3; LwipRawDatagram.c:94
pbuf->payload const-strip 11.8). docs/misra-deviations.md D.006
gains a sub-section for the lwIP pbuf->payload site as the second
"forced by an external interface" entry alongside the Winsock
select() case.

Test Evidence

TDD cadence — 27 new red→green→refactor cycles across the three
commits that drove behaviour. Distinct groups in the final test file:

  • SolidSyslogLwipRawDatagram — 8 Created-only tests (Create, Destroy
    release, Open success, Close-without-open no-op, Open-calls-udp_new,
    MaxPayload, SendTo-fails-before-open, Open-returns-false-on-udp_new-fail)
  • SolidSyslogLwipRawDatagramOpen — 19 already-opened tests (Close
    semantics, idempotence, SendTo happy path, SendTo argument
    forwarding, SendTo failure modes, Destroy lifecycle)
  • SolidSyslogLwipRawDatagramPool — 9 pool tests (mirror PlusTcp)

Total 36 tests / 113 checks in SolidSyslogLwipRawDatagramTest,
all green locally.

Two new host-side fakes under Tests/Support/LwipFakes/Source/:
LwipUdpFake (udp_new / udp_remove / udp_sendto + spies +
failure injection + OutstandingPcbCount) and LwipPbufFake
(pbuf_alloc / pbuf_free + spies + alloc-fail injection +
OutstandingPbufCount). Sources inlined into the test exe via a
LWIP_FAKE_SOURCES variable in Tests/Lwip/CMakeLists.txt, matching
the FreeRtosFakes / FatFsFakes precedent.

Local pre-PR check evidence

  • ctest --preset debug -j$(nproc) → 16/16 PASSED (full suite)
  • IWYU clean on the four touched files (three header adds, zero
    removes)
  • clang-format clean (one minor reflow in the test file)
  • cppcheck-misra clean against the CI invocation (no lwIP headers, as
    CI sees it) — exit 0 after the suppressions land
  • No regressions in SolidSyslogPlusTcpDatagramTest or other lifecycle
    tests

Areas Affected

  • Platform/LwipRaw/ — new production class + new error source +
    new pool tunable (additive; no existing production class touched)
  • Tests/Lwip/ — new test executable wired into CMake; new junit
    registration
  • Tests/Support/LwipFakes/Source/ directory created; two new
    fake files
  • Core/Interface/SolidSyslogTunablesDefaults.h — single new
    tunable block (SOLIDSYSLOG_LWIP_RAW_DATAGRAM_POOL_SIZE)
  • CLAUDE.md / DEVLOG.md / top-level CMakeLists.txt STATUS message
    doc updates per project convention
  • .github/workflows/ci.ymlanalyze-cppcheck extended to scan
    Platform/LwipRaw/ (three invocations updated symmetrically)
  • misra_suppressions.txt / docs/misra-deviations.md — six new
    suppressions; D.006 extended to cover the lwIP pbuf->payload
    const-strip

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added lwIP Raw UDP datagram transport support with configurable pool sizing
    • Documentation for new lwIP Raw datagram APIs and error handling
  • Tests

    • Comprehensive test coverage for lwIP Raw datagram functionality
    • Test infrastructure fakes for lwIP UDP and buffer operations
  • Documentation

    • Updated configuration guidance reflecting lwIP backend capabilities
    • Added MISRA deviation records for platform-specific code patterns

Review Change Stack

DavidCozens and others added 7 commits May 27, 2026 05:59
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Split the per-test Open/Close boilerplate into a TEST_BASE +
TEST_GROUP_BASE pair: SolidSyslogLwipRawDatagram (Created-only) and
SolidSyslogLwipRawDatagramOpen (Open lifted into setup). Test bodies now
focus on the single behaviour under observation.

Add LwipUdpFake_OutstandingPcbCount and check it in the shared teardown:
every udp_pcb handed out by udp_new must return via udp_remove by the
end of the test. Catches PCB leaks across all 13 lifecycle tests at
zero per-test cost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…axPayload

SendTo allocates a PBUF_REF pbuf pointing at the caller's buffer, calls
udp_sendto, and frees the pbuf — on success and on every failure path.
Guards: not-open returns FAILED; pbuf_alloc-NULL returns FAILED without
sendto; udp_sendto error returns FAILED. MaxPayload returns the
IPv6-safe 1232-byte default unconditionally (no netif MTU lookup).

Test fixture grew an LwipPbufFake with the same shape as LwipUdpFake
(spies, failure injection, OutstandingPbufCount). The shared teardown
asserts both pcb and pbuf outstanding counts are zero, catching leaks
across every lifecycle test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ytes helper

Add a 1232-byte sendBuffer member and a sendBytes(length = 1) helper on
LwipRawDatagramTestBase. Most SendTo tests reduce to a single helper
call; tests asserting on length pass it explicitly. Cuts ~7 lines of
boilerplate per test without losing any check.

SendToPbufPayloadPointsAtCallerBuffer keeps its own distinct local
buffer so the pointer-identity assertion stays meaningful — the test
must demonstrate the production forwarded the exact pointer the test
handed in, not a fixture address that always round-trips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…endBuffer

Drop the local callerBuffer in favour of writing the known content into
the fixture's sendBuffer and going through the shared sendBytes helper.
Same pointer-identity proof, consistent with every other SendTo test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-of-story bundle: CLAUDE.md public-headers rows for the new
LwipRawDatagram + LwipRawDatagramErrors headers; CMake STATUS message
updated to "Address+Resolver+Datagram only; TcpStream/BDD arrive in
S28.05-S28.06"; DEVLOG entry covering the pbuf strategy / ARP
delegation / leak-invariant test pattern and the next-up pointer.

IWYU pass over the four touched files (production + two fakes + the
test cpp) — three header adds, zero removes. clang-format reflow on the
test file.

cppcheck-misra surfaced six findings in Platform/LwipRaw/ that the CI
analyze-cppcheck step does not yet scan. CI is extended in this PR to
add Platform/LwipRaw/{Interface,Source/} to all three cppcheck
invocations; suppressions land for the four S28.03-era findings
(LwipRawAddressPrivate.h: 11.2 / 11.3; LwipRawAddressStatic.c: 11.3)
that have been invisible since S28.03 merged, plus the two S28.04
findings (LwipRawDatagram.c:43 SelfFromBase 11.3; LwipRawDatagram.c:94
pbuf->payload const-strip 11.8). D.006 in docs/misra-deviations.md
gains a sub-section for the lwIP pbuf->payload site as the second
"forced by an external interface" entry alongside the Winsock select()
case.

S28-04-HANDOFF.md removed.

Closes #463.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements S28.04 of the E28 epic: a new lwIP Raw UDP datagram transport (SolidSyslogLwipRawDatagram) with pool-backed factory, zero-copy pbuf strategy, comprehensive test fakes and coverage, build integration, and MISRA compliance documentation.

Changes

lwIP Raw Datagram Implementation

Layer / File(s) Summary
Public API Contract and Data Types
Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h, Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h, Platform/LwipRaw/Source/SolidSyslogLwipRawDatagramPrivate.h, Core/Interface/SolidSyslogTunablesDefaults.h
SolidSyslogLwipRawDatagram_Create(void) and _Destroy(base) factory functions declared; SolidSyslogLwipRawDatagramErrors enum and LwipRawDatagramErrorSource defined; private struct embeds udp_pcb* into base datagram vtable; pool-size tunable added with default 1 and compile-time validation.
Datagram Lifecycle and SendTo Logic
Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c, Platform/LwipRaw/Source/SolidSyslogLwipRawDatagramMessages.c
Initialise() wires vtable; Cleanup() prevents use-after-destroy via NullDatagram overwrite; Open() allocates UDP PCB via udp_new(); Close() calls udp_remove(); SendTo() allocates PBUF_REF pbuf pointing to caller buffer, calls udp_sendto(), and frees pbuf; MaxPayload() returns IPv6-safe 1232; error messages provide string mapping and source constant.
Static Pool and Factory Functions
Platform/LwipRaw/Source/SolidSyslogLwipRawDatagramStatic.c
Fixed-size pool (tunable size) with in-use bitmap; Create() allocates first free slot and initializes base, falling back to NullDatagram on exhaustion; Destroy() maps base pointer to pool index and frees if valid; helpers provide index lookup and per-slot cleanup.
lwIP Test Fakes: pbuf and UDP
Tests/Support/LwipFakes/Interface/LwipPbufFake.h, Tests/Support/LwipFakes/Source/LwipPbufFake.c, Tests/Support/LwipFakes/Interface/LwipUdpFake.h, Tests/Support/LwipFakes/Source/LwipUdpFake.c
Host-side fakes intercept pbuf_alloc/pbuf_free and udp_new/udp_remove/udp_sendto with call counters, argument logging, configurable failure injection, and leak detection; reset and spy functions isolate tests.
Comprehensive Test Suite
Tests/Lwip/SolidSyslogLwipRawDatagramTest.cpp
CppUTest suite with shared fixtures validating created-but-not-opened (allocation, idempotency, failure paths), already-opened (close/reopen idempotency, SendTo success and error handling, pbuf freeing on error), and pool behavior (exhaustion, fallback vtable, lock acquisition, destroy scenarios).
Build Integration: CMake and Test Registration
Tests/Lwip/CMakeLists.txt, Tests/CMakeLists.txt, .github/workflows/ci.yml
LWIP_FAKE_SOURCES variable lists pbuf and UDP fakes; new SolidSyslogLwipRawDatagramTest executable compiles datagram + fakes + test framework; JUnit XML registration added; CI cppcheck paths expanded to cover Platform/LwipRaw/Interface and Source for both standard and MISRA analysis.
Design Documentation and MISRA Compliance
DEVLOG.md, CMakeLists.txt, CLAUDE.md, docs/misra-deviations.md, misra_suppressions.txt
DEVLOG records S28.04 design (pbuf strategy, error mapping, tunable, test refactoring, fakes); CMakeLists status updated; CLAUDE.md API inventory added; D.006 deviation expanded to document lwIP raw datagram const-strip boundary alongside Winsock; MISRA suppressions added for D.002/D.003/D.006 across new lwIP source/header files.

Sequence Diagram

sequenceDiagram
    participant App
    participant Datagram as SolidSyslogLwipRawDatagram
    participant pbuf as pbuf_alloc/free
    participant UDP as udp_new/sendto/remove
    App->>Datagram: Create()
    Datagram->>UDP: udp_new()
    UDP-->>Datagram: PCB
    Datagram-->>App: datagram handle
    App->>Datagram: Open()
    Datagram->>UDP: udp_new() [idempotent]
    UDP-->>Datagram: existing PCB
    App->>Datagram: SendTo(buffer, len, ip, port)
    Datagram->>pbuf: pbuf_alloc(TRANSPORT, len, REF)
    pbuf-->>Datagram: pbuf (or NULL)
    alt success
        Datagram->>UDP: udp_sendto(pcb, pbuf, ip, port)
        UDP-->>Datagram: ERR_OK or error
        Datagram->>pbuf: pbuf_free(pbuf)
        pbuf-->>Datagram: freed
        Datagram-->>App: SENT or FAILED
    else pbuf allocation fails
        Datagram-->>App: FAILED
    end
    App->>Datagram: Close()
    Datagram->>UDP: udp_remove(pcb)
    UDP-->>Datagram: ok
    App->>Datagram: Destroy()
    Datagram-->>App: ok
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #439: The main changes directly implement the lwIP Raw API platform feature (Platform/LwipRaw) including SolidSyslogLwipRawDatagram that corresponds to the S28.04 "lwIP Raw API support" story in the epic.

Possibly related PRs

  • DavidCozens/solid-syslog#417: Both PRs modify the shared MISRA suppression baseline and deviation mapping; the new lwIP entries in this PR depend on the renumbering/rebuild work in #417.
  • DavidCozens/solid-syslog#348: Both PRs extend the build-tunables infrastructure; this PR adds SOLIDSYSLOG_LWIP_RAW_DATAGRAM_POOL_SIZE to the defaults mechanism introduced in #348.
  • DavidCozens/solid-syslog#139: Both PRs use the shared SolidSyslogDatagram vtable abstraction; #139 defined the interface and refactored UdpSender, while this PR adds a new SolidSyslogLwipRawDatagram implementation.

Poem

🐰 A lwIP Raw UDP journey
From pool to pbuf, PCBs dance free,
Zero-copy sends across the lwIP sea,
Fakes test each path without native hardware's plea,
Datagram transport springs forth, ready to be!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: implementing the S28.04 SolidSyslogLwipRawDatagram feature, which is the primary objective of this PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering Purpose, Change Description with design decisions, Test Evidence with specific test counts, and Areas Affected sections.
Linked Issues check ✅ Passed The code changes fully implement all primary objectives from issue #463: SolidSyslogLwipRawDatagram class with Create/Destroy APIs, zero-copy pbuf handling, idempotent PCB lifecycle, error mapping, static pool with tunable, error enum/header, test fakes (LwipUdpFake, LwipPbufFake), comprehensive unit tests (36 tests/113 checks), and CMake integration for LWIP presets.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #463 requirements: production files under Platform/LwipRaw, test infrastructure (fakes, test executable, CMake), tunable addition, documentation updates (DEVLOG, CLAUDE, MISRA), and CI workflow enhancement for Platform/LwipRaw scanning.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/s28-04-lwip-raw-datagram

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1344 passed)
   🚦   build-freertos-host-tdd-plustcp: 100% successful (✔️ 1652 passed)
   🚦   build-linux-clang: 100% successful (✔️ 1296 passed)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1296 passed)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-linux-mbedtls: 100% successful (✔️ 7 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu-plustcp: 86% successful (✔️ 42 passed, 🙈 7 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 1149 passed)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1296 passed)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
CMakeLists.txt (1)

170-177: ⚡ Quick win

Align the nearby milestone comment with the new LWIP status text.

The status strings now say Datagram is already available, but the explanatory block around Line 153 still says “Datagram/TcpStream/BDD arrive in S28.04-S28.06.” Updating that comment in the same edit will avoid roadmap confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CMakeLists.txt` around lines 170 - 177, Update the explanatory milestone
comment that references the SOLIDSYSLOG_FREERTOS_NET status so it matches the
new message(STATUS) text for LWIP and BOTH (which now indicate Datagram is
available); find the block that previously said “Datagram/TcpStream/BDD arrive
in S28.04-S28.06” and change it to reflect the new timeline wording used in the
message(STATUS) strings (e.g., indicate Datagram is already available and
TcpStream/BDD arrive in S28.05-S28.06), ensuring the comment and the
message(STATUS) outputs for SOLIDSYSLOG_FREERTOS_NET (values "LWIP" and "BOTH")
are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@CMakeLists.txt`:
- Around line 170-177: Update the explanatory milestone comment that references
the SOLIDSYSLOG_FREERTOS_NET status so it matches the new message(STATUS) text
for LWIP and BOTH (which now indicate Datagram is available); find the block
that previously said “Datagram/TcpStream/BDD arrive in S28.04-S28.06” and change
it to reflect the new timeline wording used in the message(STATUS) strings
(e.g., indicate Datagram is already available and TcpStream/BDD arrive in
S28.05-S28.06), ensuring the comment and the message(STATUS) outputs for
SOLIDSYSLOG_FREERTOS_NET (values "LWIP" and "BOTH") are consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93d5a997-e78f-4d70-a33a-d5ec6d6d5b86

📥 Commits

Reviewing files that changed from the base of the PR and between da60e6f and 89c2bcd.

📒 Files selected for processing (20)
  • .github/workflows/ci.yml
  • CLAUDE.md
  • CMakeLists.txt
  • Core/Interface/SolidSyslogTunablesDefaults.h
  • DEVLOG.md
  • Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagram.h
  • Platform/LwipRaw/Interface/SolidSyslogLwipRawDatagramErrors.h
  • Platform/LwipRaw/Source/SolidSyslogLwipRawDatagram.c
  • Platform/LwipRaw/Source/SolidSyslogLwipRawDatagramMessages.c
  • Platform/LwipRaw/Source/SolidSyslogLwipRawDatagramPrivate.h
  • Platform/LwipRaw/Source/SolidSyslogLwipRawDatagramStatic.c
  • Tests/CMakeLists.txt
  • Tests/Lwip/CMakeLists.txt
  • Tests/Lwip/SolidSyslogLwipRawDatagramTest.cpp
  • Tests/Support/LwipFakes/Interface/LwipPbufFake.h
  • Tests/Support/LwipFakes/Interface/LwipUdpFake.h
  • Tests/Support/LwipFakes/Source/LwipPbufFake.c
  • Tests/Support/LwipFakes/Source/LwipUdpFake.c
  • docs/misra-deviations.md
  • misra_suppressions.txt

@DavidCozens DavidCozens merged commit 77dfc03 into main May 27, 2026
23 checks passed
@DavidCozens DavidCozens deleted the feat/s28-04-lwip-raw-datagram branch May 27, 2026 13:40
@coderabbitai coderabbitai Bot mentioned this pull request May 28, 2026
7 tasks
DavidCozens added a commit that referenced this pull request May 28, 2026
* chore: S28.05 LwipRawTcpStream pool plumbing + tunables + tests

Lays the three-TU pool scaffolding for the upcoming
SolidSyslogLwipRawTcpStream adapter — Interface header, Errors
header, Private header, .c (Initialise/Cleanup only — vtable
methods land in commits 2/3 alongside Open/Send/Read), Messages.c
(ErrorSource), Static.c (pool + Create/Destroy with the
ConfigLock-wrapped slot walks). Adds three new tunables
(SOLIDSYSLOG_LWIP_RAW_TCP_STREAM_POOL_SIZE default 2,
SOLIDSYSLOG_LWIP_RAW_TCP_CONNECT_POLL_MS default 10,
SOLIDSYSLOG_LWIP_RAW_TCP_RX_QUEUE_SIZE default 8).

Bad-config (NULL config or NULL Sleep) resolves silently to the
shared SolidSyslogNullStream — matches the sibling-backend
contract; only POOL_EXHAUSTED and UNKNOWN_DESTROY are reported.

13 tests: CreateReturnsNonNullStream,
CreatedStreamIsNotTheNullStreamSingleton, DestroyReleasesSlotToPool,
CreateWithNullConfigReturnsFallback,
CreateWithNullSleepReturnsFallback, plus the 9-test pool block
mirroring SolidSyslogPlusTcpTcpStream's shape.

Coverage: 100% line on TcpStream.c + TcpStreamStatic.c; Messages.c
exercised only via integrator-installed error handlers (matches
S28.04 Datagram).

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: S28.05 LwipRawTcpStream Open/Close lifecycle + connect callback bridge

Wires Open / Close against lwIP Raw's tcp_new / tcp_arg / tcp_recv /
tcp_sent / tcp_err / tcp_connect / tcp_close / tcp_abort. Bridges
the lwIP callback Raw TCP API to SolidSyslogStream's synchronous
Open(addr) contract via a bounded spin loop driven by the
integrator-injected SolidSyslogSleepFunction — each iteration sleeps
SOLIDSYSLOG_LWIP_RAW_TCP_CONNECT_POLL_MS so lwIP's timer / RX paths
keep getting cycles to advance the SYN/SYN-ACK exchange. Deadline
is the existing per-instance GetConnectTimeoutMs getter (S12.17
pattern), with a Null Object falling back to SOLIDSYSLOG_TCP_CONNECT_TIMEOUT_MS.

Encapsulates lwIP's tcp_close-after-tcp_err gotcha: the wrapper's
ErrCallback nulls self->Pcb when lwIP releases the pcb upstream,
and Close checks Pcb != NULL before calling tcp_close — integrators
never see the rule. Sets SOF_KEEPALIVE on every pcb (idle / intvl /
cnt come from the integrator's lwipopts.h — LWIP_TCP_KEEPALIVE=1
is the recommended-on setting, documented in S28.05 commit 4's
docs/integrating-lwip.md).

Send / Read / RX queue land in the next commit; for now Recv / Sent
callback slots hold no-op stubs (lwIP requires them set, but the
synchronous API surface doesn't exercise their behaviour yet).

LwipTcpFake.{h,c} added — captures registered callbacks so tests
can drive connected_cb / err_cb directly; default tcp_connect
behaviour synchronously fires connected_cb with ERR_OK so happy-path
Open tests don't loop. OutstandingPcbCount leak invariant pinned in
shared teardown.

26 new tests across two TEST_GROUP_BASE groups (created-only +
Open-already), covering: tcp_new fail, keepalive, callback
registration, immediate connect-error path, errored-callback path,
timeout-with-abort path, sleep-between-polls behaviour, runtime-tunable
deadline, idempotency, close-after-tcp_err, destroy-after-tcp_err.

Coverage: 100% line on TcpStream.c + TcpStreamStatic.c (35 tests
in the exe; full debug suite 17/17 green).

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: S28.05 LwipRawTcpStream Send/Read + RX queue + tcp_err handling

Wires Send / Read against lwIP Raw's tcp_write / tcp_output /
tcp_recved + a bounded pbuf ring drained by Stream_Read. Send
takes the TCP_WRITE_FLAG_COPY path — lwIP copies the caller's
buffer into its own pbufs before tcp_write returns, so the
synchronous Stream_Send(buf, len) lifetime contract is honoured.
ERR_MEM from tcp_output after a successful tcp_write is queued-not-
failed (matches POSIX's "kernel accepted into the send buffer"
semantics); anything else closes internally.

RX queue is a fixed ring sized by
SOLIDSYSLOG_LWIP_RAW_TCP_RX_QUEUE_SIZE (default 8). The tcp_recv
callback enqueues incoming pbufs, returning ERR_MEM when full so
lwIP retains the pbuf and replays the callback later (flow control).
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.

Peer FIN (tcp_recv with p == NULL) sets Errored but the wrapper
drains any already-queued bytes before signalling EOF — next Read
with an empty queue returns -1 and ClosePcb's the connection
internally per the Stream contract. tcp_err's Pcb-already-nulled
state is handled by IsOpen() — Read returns -1 without a
double-tcp_close.

ClosePcb now drains the queue unconditionally before tcp_close
(under the existing Pcb != NULL guard) — pbufs we accepted via
tcp_recv are ours to free regardless of pcb state.

20 new tests across the Connected group: Send happy/fail/COPY-flag/
ERR_MEM-queued/ERR_CONN-closes, post-tcp_err Send-false. Read
queue-empty/drain-head/recved-ACK/partial-then-finish/multi-pbuf-
advance/peer-FIN-after-drain/post-tcp_err. RX backpressure when
queue full. Destroy-drains-queue + Close-drains-queue leak
invariants.

Coverage: 100% line + function on TcpStream.c + TcpStreamStatic.c
(55 tests in the exe; full debug suite 17/17 green).

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: S28.05 LwipRawTcpStream tests — sendBytes/readBytes helpers, auto-balanced pushIncomingPbuf, CHECK_FORWARDED_PCB

Three test-side cleanups, no production code change:

1. sendBytes() / readBytes() on the shared TEST_BASE fixture wrap the
   abstract Stream Send/Read against the existing sendBuffer/readBuffer
   fields. Matches the Datagram precedent's sendBytes helper. Removes
   ~18 repetitions of SolidSyslogStream_{Send,Read}(stream, buffer, N).

2. pushIncomingPbuf now returns the err_t the wrapper's tcp_recv
   callback returned and only bumps LwipPbufFake_NoteIncomingPbuf when
   that return was ERR_OK. The semantic "if the wrapper accepted the
   pbuf the test counts it; otherwise lwIP retains it and the count
   stays put" is now encoded in one place, not duplicated in each
   call site. RecvCallbackBackpressuresWhenQueueFull drops its inline
   fabrication + manual drain, both now subsumed by the helper's
   auto-balancing (queue drain runs in shared teardown via Destroy).

3. CHECK_FORWARDED_PCB(getter) macro replaces the recurring
   POINTERS_EQUAL(LwipTcpFake_LastTcpNewReturned(), getter()) intent
   ("the lwIP call received our pcb"). Used across Open/Send/Read/
   Close pcb-forwarding asserts.

Coverage unchanged: 100% line + function on TcpStream.c + Static.c.
All 55 tests still green; full debug suite 17/17 green.

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: S28.05 SolidSyslogLwipRawTcpStream + integrating-lwip.md

Closes #465.

Housekeeping bundle closing the S28.05 PR:

- docs/integrating-lwip.md new file. Covers NO_SYS=1 vs NO_SYS=0,
  the tcpip_callback() threading rule, lwipopts.h expectations
  (LWIP_RAW / LWIP_UDP / LWIP_TCP / ARP_QUEUEING / LWIP_TCP_KEEPALIVE
  / TCP_MSS / PBUF_POOL_SIZE / MEMP_NUM_TCP_PCB), a bare-metal
  NO_SYS=1 wiring example, per-adapter design notes (pbuf-strategy
  split UDP REF / TCP COPY, synchronous-Open spin, RX queue,
  tcp_close-after-tcp_err encapsulation), and the SolidSyslog
  tunables relevant to lwIP integrators.

- CLAUDE.md gains two rows for SolidSyslogLwipRawTcpStream.h and
  SolidSyslogLwipRawTcpStreamErrors.h, slotted next to the other
  TCP-stream rows.

- Top-level CMakeLists.txt STATUS strings for SOLIDSYSLOG_FREERTOS_NET
  =LWIP and =BOTH update to "Address+Resolver+Datagram+TcpStream only;
  BDD arrives in S28.06". The comment block above (the deferred
  PR #464 nit per project_s28_04_pr464_deferred_cmake_comment) is
  brought into sync.

- DEVLOG entry capturing the five locked-in design calls (TCP_WRITE_FLAG_COPY,
  spin-with-injected-sleep, tcp_close-after-tcp_err encapsulation,
  bounded RX queue, SOF_KEEPALIVE), the commit shape, the
  LwipTcpFake's default-fires-cb behaviour that simplifies happy-path
  tests, and the new tunables/error source.

- Pre-PR three-step:
  - IWYU on touched files (clang-19, freertos-host preset). Two
    findings applied: LwipTcpFake.h drops transitive lwip/arch.h
    + lwip/err.h; TcpStream.c drops SolidSyslogStreamDefinition.h
    + lwip/ip_addr.h and adds the public SolidSyslogLwipRawTcpStream.h
    header. Test file adjusts include set.
  - clang-format -i on every touched .c/.h/.cpp.
  - cppcheck-misra surfaced eight findings on the new TcpStream
    code. Six were resolved by code refactor (READ_FAILED moved
    into Read, RxQueueCount > 0U, pointer-arith → array indexing
    in DrainHeadBytes, == true on the IndexIsValid predicate,
    plus a new LwipRawTcpStream_SelfFromArg helper that
    concentrates the three lwIP-callback void*-arg casts into a
    single named site). Two new suppressions added under D.002 —
    11.3 on SelfFromBase and 11.5 on SelfFromArg — matching the
    sibling-backend precedent.

- docs/misra-deviations.md D.002 gets a sub-section (c) explicitly
  documenting the third-party-callback void*-arg pattern that the
  suppression file has been treating as D.002 since S08.07's
  MbedTls landed. Closes a pre-existing audit-trail gap and gives
  my new SelfFromArg entry the documented backing it needs.

- scripts/misra_renumber.py extended to include the Platform/LwipRaw
  tree (-IPlatform/LwipRaw/Interface + Platform/LwipRaw/Source/).
  Oversight from S28.03; the script was previously blind to LwipRaw
  findings, which made the AMBIGUOUS-noise output during this PR's
  pre-push checks impossible to interpret. Pre-existing AMBIGUOUS
  entries on other rules are unchanged — they're orthogonal stale-
  suppression noise.

- S28-05-HANDOFF.md removed (planning artefact carried from the
  session start; its job is done now that #465 is closing).

Coverage: 100% line + function on Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c
and SolidSyslogLwipRawTcpStreamStatic.c (Messages.c stays at 0%,
matching the S28.04 Datagram established pattern — exercised only
via integrator-installed error handlers). Full debug suite 17/17
green; the 55 TcpStream tests are inside SolidSyslogLwipRawTcpStreamTest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: S28.05 PR #466 review — IWYU fwd-decl, tidy, CodeRabbit

Three review-feedback fixes bundled into one commit:

- IWYU: restore the `struct SolidSyslogAddress;` forward declaration
  in `Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c`. The
  earlier IWYU pass dropped it while adding `struct SolidSyslogStream;`
  — I misread "should add" as "replace" instead of additive. CI's
  IWYU lane caught it; local re-run on the freertos-host preset now
  clean. Both forward decls are needed (Open takes Address, the
  vtable methods take Stream).

- analyze-tidy-freertos-plustcp (5 errors on
  Tests/Lwip/SolidSyslogLwipRawTcpStreamTest.cpp):
  - `[[nodiscard]]` on `sendBytes` and `readBytes` per
    modernize-use-nodiscard. Two test sites that didn't capture the
    return get explicit `(void)` casts.
  - `static` on `pushIncomingPbuf` / `pushPeerFin` / `pushTcpErr`
    per readability-convert-member-functions-to-static — none
    access `this`, all delegate to the LwipTcpFake/LwipPbufFake
    free functions.
  - `NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)` on the
    one `const_cast<void*>(data)` inside `pushIncomingPbuf` — pbuf's
    payload field is `void*`; callers pass string literals, so the
    cast is structural, not a const-strip.

- CodeRabbit two inline comments, both valid:
  - NOLINTBEGIN/NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while)
    wrapper around the three test macros (`CHECK_IS_FALLBACK`,
    `CHECK_REPORTED`, `CHECK_FORWARDED_PCB`). Matches the established
    project convention in DEVLOG ("wrap with NOLINTBEGIN and use
    do { ... } while (0) for safe single-statement use"). I forgot
    the wrapper.
  - Extract the composite condition
    `(tcpConnectError == ERR_OK) && connectCallbackFires && (connected != NULL)`
    in `tcp_connect` (LwipTcpFake.c) into a `ShouldFireConnectCallback`
    static-inline predicate. Matches the project's "intent-naming
    static-inline predicates" pattern.

Tests: 55/55 TcpStream tests green; full debug suite 17/17 green;
clang-tidy now clean on the freertos-plustcp preset locally.

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: S28.05 PR #466 review — clang-format + MISRA line drift

Two CI follow-ups on top of 5d579c2:

- analyze-format: clang-format in the cpputest CI container
  reformatted `CHECK_FORWARDED_PCB` from a one-line `#define`
  onto two lines with a line-continuation backslash. My local
  clang-format-19 thought the one-liner was fine; the cpputest
  image's version disagrees. Apply the two-line form by hand —
  matches the visual style of the other CHECK_* macros above.
- analyze-cppcheck: the IWYU fwd-decl I added in 5d579c2 shifted
  the SelfFromBase + SelfFromArg lines down by one. Bump the
  matching `misra_suppressions.txt` entries (11.3 :110→:111,
  11.5 :119→:120) to match.

Tests still 55/55 green; cppcheck-misra clean on
`Platform/LwipRaw/Source/` locally.

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: S28.05 PR #466 — clang-format off/on around CHECK_FORWARDED_PCB

Workaround for a divergent clang-format binary between containers:
my freertos-host devcontainer (cpputest-freertos image) ships
clang-format-19 and is happy with both the one-line and two-line
forms of the macro; CI's analyze-format runs in the cpputest image
and ships a different clang-format that wants a different
alignment (first try: failed at col 95 on the one-liner; second
try: failed at col 36 on the backslash position of the two-line
form).

Bracket the macro with `// clang-format off` / `// clang-format on`
markers so neither version reformats it. The macro is a single
expression — formatting it manually is fine.

Follow-up to investigate: align the clang-format binaries across
cpputest / cpputest-clang / cpputest-freertos images so dev and
CI agree, or run analyze-format inside cpputest-freertos so the
lwIP-touching files don't see a tooling mismatch.

Part of #465.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

S28.04: SolidSyslogLwipRawDatagram

1 participant