Skip to content

S08.03 slice 4: set command for in-RAM configuration injection #302

Description

@DavidCozens

Slice 4 of S08.03 (parent: #268). Builds on #296.

Why

Slice 3b.2 (#296 → PR #301) hardcoded the FreeRTOS example's identity (TEST_HOSTNAME, TEST_APP_NAME, TEST_PROCESS_ID, TEST_MESSAGE_ID, TEST_MESSAGE) and timestamp into Example/FreeRtos/SingleTask/main.c because the FreeRTOS port deliberately skips Example/Common/ExampleCommandLine.c (no getopt). #296 deferred configuration injection to slice 4 via the interactive command grammar. Slice 4 delivers exactly that.

A FreeRTOS scenario that wants to verify "set hostname to Foo, send a message, oracle sees hostname=Foo" needs a way to drive that config in. The walking-skeleton TEST_* values are now mutable in-RAM via a set NAME VALUE command on the existing interactive UART channel.

Scope — set NAME VALUE parallel to switch

Grow Example/Common/ExampleInteractive with a set dispatcher parallel to the existing switch dispatcher. The library splits args at the first whitespace and calls an integrator-supplied handler with (name, value); the library doesn't know what fields are settable.

typedef bool (*ExampleInteractiveSetHandler)(const char* name, const char* value);

void ExampleInteractive_Run(
    const struct SolidSyslogMessage* message,
    FILE* input,
    ExampleInteractiveSwitchHandler onSwitch,
    ExampleInteractiveSetHandler   onSet);

UX:

  • Handler returns true → runner echoes set name=value.
  • Handler returns false → runner prints set: invalid (catchall, no echo of input — minimum viable; a refactor can grow it if a smoke wants the input echoed).
  • onSet == NULLset lines silently ignored (mirrors switch).

Field set — v1

The FreeRTOS-side handler in Example/FreeRtos/SingleTask/main.c accepts:

Name Maps to Validation
hostname static backing → GetHostname callback non-empty, ≤ 255 (RFC 5424 max)
appname static backing → GetAppName callback non-empty, ≤ 48
procid static backing → GetProcessId callback non-empty, ≤ 128
facility message.facility integer 0–23
severity message.severity integer 0–7
msgid static backing → message.messageId non-empty, ≤ 32
msg static backing → message.msg non-empty (storage-bounded)
host static backing — see below non-empty, dotted-quad shape
port endpoint port + bumps GetEndpointVersion uint16_t, > 0

In-RAM only; flash persistence is a separate later slice. Rejected mutations leave the prior value untouched.

host is a known no-op for now

The slice-3a SolidSyslogFreeRtosStaticResolver ignores the host string and uses a hardcoded IPv4. Slice 4 accepts set host … into the static backing for completeness but the destination IP doesn't change yet. A follow-up slice teaches the resolver to parse dotted-quads, after which the same set host line takes effect.

Endpoint version bump

GetEndpointVersion (today returns 0U) becomes a read of a static uint32_t that the FreeRTOS handler increments when port (or eventually host) successfully mutates. Without this, SolidSyslogUdpSender keeps the cached resolver result forever. Counter-location decision (inline vs Example/Common helper) deferred to TDD step.

Test strategy

ExampleInteractive's set parsing/dispatch is host-TDD'd in Tests/Example/ — a new ExampleInteractiveTest.cpp (no equivalent today; switch is tested via ExampleSwitchConfig_SetByName directly, but the new (name, value) split is parsing logic that lives in the runner and wants its own test target). ZOMBIES order, strict red/green/refactor, test list at the bottom of the test file.

The FreeRTOS-side mutable backing storage and version-bumping is glue on top of host-TDD'd primitives; QEMU smoke is the integration check:

  • set hostname Foosend 1 → host UDP listener receives a frame with hostname=Foo.
  • set port 5515 → host listener on the new port receives a frame.
  • Invalid input (set facility 99, set port 99999) → set: invalid, prior value preserved.

Acceptance

  • Example/Common/ExampleInteractive.{h,c} grows the onSet handler dispatch.
  • All four call sites updated (Example/SingleTask/SolidSyslogExample.c, Example/Threaded/main.c, Example/Windows/SolidSyslogWindowsExample.c, Example/FreeRtos/SingleTask/main.c) — POSIX/Windows/Threaded pass NULL.
  • FreeRTOS handler in Example/FreeRtos/SingleTask/main.c mutates static backing for the v1 field set with the validation rules above; GetHostname/GetAppName/GetProcessId/GetEndpoint/GetEndpointVersion read from the same statics.
  • Tests for set parsing/dispatch in Tests/Example/, ZOMBIES order, 100% line+branch on the new code.
  • QEMU smoke verifies set hostname Foo; send 1 reaches the host listener with hostname=Foo, set port 5515; send 1 reaches a listener on 5515, invalid inputs print set: invalid and preserve prior values.
  • DEVLOG entry summarises decisions and surprises.

Out of scope

  • Flash-persistent config (later slice).
  • SolidSyslogFreeRtosStaticResolver dotted-quad parsing — separate follow-up so set host actually changes the destination IP.
  • BDD harness pointed at the FreeRTOS target — slice 4 produces deterministic confirmation lines for a future BDD job, but wiring behave to QEMU is a sibling slice.
  • Service-thread / non-NullBuffer FreeRTOS-side wiring (slice 5+).
  • Real RTC-backed clock callback (still hardcoded TEST_TIMESTAMP).

Metadata

Metadata

Assignees

No one assigned

    Labels

    storyStory issue

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions