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 == NULL → set 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 Foo → send 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
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).
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 intoExample/FreeRtos/SingleTask/main.cbecause the FreeRTOS port deliberately skipsExample/Common/ExampleCommandLine.c(nogetopt). #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-skeletonTEST_*values are now mutable in-RAM via aset NAME VALUEcommand on the existing interactive UART channel.Scope —
set NAME VALUEparallel toswitchGrow
Example/Common/ExampleInteractivewith asetdispatcher parallel to the existingswitchdispatcher. 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.UX:
true→ runner echoesset name=value.false→ runner printsset: invalid(catchall, no echo of input — minimum viable; a refactor can grow it if a smoke wants the input echoed).onSet == NULL→setlines silently ignored (mirrorsswitch).Field set — v1
The FreeRTOS-side handler in
Example/FreeRtos/SingleTask/main.caccepts:hostnameGetHostnamecallbackappnameGetAppNamecallbackprocidGetProcessIdcallbackfacilitymessage.facilityseveritymessage.severitymsgidmessage.messageIdmsgmessage.msghostportGetEndpointVersionuint16_t, > 0In-RAM only; flash persistence is a separate later slice. Rejected mutations leave the prior value untouched.
hostis a known no-op for nowThe slice-3a
SolidSyslogFreeRtosStaticResolverignores the host string and uses a hardcoded IPv4. Slice 4 acceptsset 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 sameset hostline takes effect.Endpoint version bump
GetEndpointVersion(today returns0U) becomes a read of astatic uint32_tthat the FreeRTOS handler increments whenport(or eventuallyhost) successfully mutates. Without this,SolidSyslogUdpSenderkeeps the cached resolver result forever. Counter-location decision (inline vs Example/Common helper) deferred to TDD step.Test strategy
ExampleInteractive's
setparsing/dispatch is host-TDD'd inTests/Example/— a newExampleInteractiveTest.cpp(no equivalent today;switchis tested viaExampleSwitchConfig_SetByNamedirectly, 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 Foo→send 1→ host UDP listener receives a frame withhostname=Foo.set port 5515→ host listener on the new port receives a frame.set facility 99,set port 99999) →set: invalid, prior value preserved.Acceptance
Example/Common/ExampleInteractive.{h,c}grows theonSethandler dispatch.Example/SingleTask/SolidSyslogExample.c,Example/Threaded/main.c,Example/Windows/SolidSyslogWindowsExample.c,Example/FreeRtos/SingleTask/main.c) — POSIX/Windows/Threaded passNULL.Example/FreeRtos/SingleTask/main.cmutates static backing for the v1 field set with the validation rules above;GetHostname/GetAppName/GetProcessId/GetEndpoint/GetEndpointVersionread from the same statics.setparsing/dispatch inTests/Example/, ZOMBIES order, 100% line+branch on the new code.set hostname Foo; send 1reaches the host listener withhostname=Foo,set port 5515; send 1reaches a listener on 5515, invalid inputs printset: invalidand preserve prior values.Out of scope
SolidSyslogFreeRtosStaticResolverdotted-quad parsing — separate follow-up soset hostactually changes the destination IP.behaveto QEMU is a sibling slice.TEST_TIMESTAMP).