Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ It builds on a baseline that simulates the sort of device you might be adding th
measures itself: see [docs/baseline.md](docs/baseline.md) for what the baseline is, how the
figures are made, and how to run it.

## This stage — Logger created
## This stage — First record

The logger exists, with nothing wired into it. Both collaborators that decide where a record goes
are absent, so the handler from the previous stage reports them and the run report carries the
faults — which is the point of creating it empty first.
A UDP sender over lwIP, with a passthrough buffer in front of it: `Log` sends inline on the calling
task, so there is no queue and nothing to drain. The collector receives a valid RFC 5424 record
with timestamp, hostname, app-name and procid all left as `-`, which the RFC allows and a
collector accepts.

<!-- STAGE-COST:START (generated by scripts/gen-cost-table.py — do not edit by hand) -->

**Cost above baseline: Flash +1,036 B, RAM +180 B.**
**Cost above baseline: Flash +4,716 B, RAM +372 B.**

<!-- STAGE-COST:END -->

Expand All @@ -35,6 +36,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage.
| Linked | the core library and lwIP raw-mode networking, linked but not yet called | +0 | +0 |
| Error handler | a fault inside the logger reaches the console instead of being silent | +404 | +8 |
| Logger created | the logger object, reporting exactly what is still missing from it | +1,036 | +180 |
| First record | a valid RFC 5424 record on the wire, over UDP | +4,716 | +372 |

*Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.*

Expand Down
14 changes: 11 additions & 3 deletions app/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ static void HarnessTask(void* parameters)
bool logIdle = LogTask_WaitIdle(2000U);
bool serviceIdle = ServiceTask_WaitIdle(2000U);

/* Emitted before the figures are taken: the record goes out inline on the
* log task's stack, so its stack figure only means anything afterwards. */
bool logged = LogTask_EmitOnce(5000U);
(void) printf("[device] first record logged: %s\n", logged ? "yes" : "FAILED");

(void) Measure_Report();

bool ready = simReady && logIdle && serviceIdle;
bool ready = simReady && logIdle && serviceIdle && logged;
(void) printf("[device] %s\n", ready ? "ready" : "FAILED");
SemihostingExit(ready ? 0 : 1);
}
Expand Down Expand Up @@ -98,8 +103,6 @@ int main(void)
/* Before the first _Create — see SyslogErrorHandler.h for why that matters. */
SyslogErrorHandler_Install();

Syslog_Start();

/* lwIP tcpip thread + core-lock mutex + mbox. Pre-scheduler safe. */
tcpip_init(NULL, NULL);

Expand All @@ -111,6 +114,11 @@ int main(void)
SemihostingExit(1);
}

/* After tcpip_init, not before: the marshal Syslog_Start installs takes the
* lwIP core lock, and tcpip_init is what creates it. Nothing is sent here —
* the sender resolves and opens lazily on its first record. */
Syslog_Start();

if (!LogTask_Create() || !ServiceTask_Create())
{
(void) printf("[device] FATAL: application task create failed\n");
Expand Down
78 changes: 69 additions & 9 deletions app/syslog/Syslog.c
Original file line number Diff line number Diff line change
@@ -1,26 +1,86 @@
/* See Syslog.h. Created with nothing wired into it, on purpose: a missing
* collaborator is substituted with its Null object and reported, and the run
* report is where that shows. */
/* See Syslog.h.
*
* The smallest wiring that delivers: a UDP sender over lwIP, with a passthrough
* buffer in front of it. Passthrough means Log sends inline on the calling task
* — no queue, no background drain, nothing to service.
*
* Timestamp, hostname, app-name and procid are left unset. RFC 5424 defines a
* NILVALUE for each, so a record carrying "-" for them is valid and a collector
* accepts it. */

#include "Syslog.h"

#include "SolidSyslogConfig.h"
#include "SolidSyslogEndpoint.h"
#include "SolidSyslogEndpointHost.h"
#include "SolidSyslogLwipRawAddress.h"
#include "SolidSyslogLwipRawDatagram.h"
#include "SolidSyslogLwipRawMarshal.h"
#include "SolidSyslogLwipRawResolver.h"
#include "SolidSyslogNullStore.h"
#include "SolidSyslogPassthroughBuffer.h"
#include "SolidSyslogUdpSender.h"

#include "lwip/tcpip.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

/* The collector, reached through QEMU's slirp gateway. A numeric literal keeps
* the resolver numeric-only — no DNS, so no LWIP_DNS and no DNS resolver
* component to compile. */
#define SYSLOG_COLLECTOR_HOST "10.0.2.2"
#define SYSLOG_COLLECTOR_PORT ((uint16_t) 5514U)

static struct SolidSyslog* s_logger = NULL;

/* Every lwIP Raw call the datagram makes has to happen on the thread that owns
* the lwIP core. lwipopts.h sets LWIP_TCPIP_CORE_LOCKING, so taking the core
* lock in the caller's own task is simpler and cheaper than posting to the tcpip
* mailbox — and unconditionally synchronous, which the marshal contract
* requires. The lock is recursive and these callbacks never re-marshal, so it
* cannot deadlock against itself. */
static void LwipCoreLockMarshal(SolidSyslogLwipRawCallback callback, void* context)
{
LOCK_TCPIP_CORE();
callback(context);
UNLOCK_TCPIP_CORE();
}

/* Pulled by the sender when it connects, not on every send. Host is a bounded
* sink rather than a raw buffer, so a destination cannot overrun the field. */
static void CollectorEndpoint(struct SolidSyslogEndpoint* endpoint, void* context)
{
(void) context;

SolidSyslogEndpointHost_String(endpoint->Host, SYSLOG_COLLECTOR_HOST, strlen(SYSLOG_COLLECTOR_HOST));
endpoint->Port = SYSLOG_COLLECTOR_PORT;
}

void Syslog_Start(void)
{
/* Buffer and Sender decide where a record goes. NULL is "not supplied" and
* is reported; a collaborator deliberately done without is passed as its
* Null object instead, which is how the library tells the two apart. */
SolidSyslogLwipRaw_SetMarshal(LwipCoreLockMarshal);

/* A numeric resolver to parse the literal, a datagram for the socket, and an
* address slot for the resolver to write into. No EndpointVersion — this
* collector never moves, so the sender resolves once and pins it. */
struct SolidSyslogUdpSenderConfig senderConfig = {
.Resolver = SolidSyslogLwipRawResolver_Create(),
.Datagram = SolidSyslogLwipRawDatagram_Create(),
.Address = SolidSyslogLwipRawAddress_Create(),
.Endpoint = CollectorEndpoint,
};
struct SolidSyslogSender* sender = SolidSyslogUdpSender_Create(&senderConfig);

struct SolidSyslogConfig config = {
.Buffer = NULL,
.Sender = NULL,
.Buffer = SolidSyslogPassthroughBuffer_Create(sender),
.Sender = sender,
/* No store-and-forward here. The Null object rather than NULL is how
* that is said out loud — NULL is reported as a fault. */
.Store = SolidSyslogNullStore_Get(),
};

/* No null check — Create returns a shared null instance rather than NULL. */
s_logger = SolidSyslog_Create(&config);
}

Expand Down
16 changes: 15 additions & 1 deletion app/tasks/LogTask.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

#include "LogTask.h"

#include "Syslog.h"

#include "SolidSyslog.h"
#include "SolidSyslogPrival.h"

#include "AppConfig.h"

#include "semphr.h"
Expand All @@ -26,7 +31,16 @@ static void LogTask_Entry(void* parameters)
{
if (xSemaphoreTake(s_emitRequested, portMAX_DELAY) == pdTRUE)
{
/* Nothing to say: this device has no logger. */
const struct SolidSyslogMessage message = {
.Facility = SOLIDSYSLOG_FACILITY_LOCAL0,
.Severity = SOLIDSYSLOG_SEVERITY_INFORMATIONAL,
.MessageId = "BOOT",
.Msg = "device started",
};

/* Sends inline on this stack and returns once it is done. */
SolidSyslog_Log(Syslog_Handle(), &message);

(void) xSemaphoreGive(s_emitDone);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Expand Down
3 changes: 2 additions & 1 deletion app/tasks/LogTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ extern "C"
* high-water mark reflects something real. */
bool LogTask_WaitIdle(uint32_t timeoutMs);

/* Emit one record and wait for it to finish. Nothing to emit yet. */
/* Emit one record and wait for it to finish. */
bool LogTask_EmitOnce(uint32_t timeoutMs);

#ifdef __cplusplus
}
#endif

#endif /* APP_TASKS_LOG_TASK_H */

1 change: 1 addition & 0 deletions measurements/stages.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Baseline Baseline a device that already networks, stores, and holds an mTLS sess
linked Linked the core library and lwIP raw-mode networking, linked but not yet called
error-handler Error handler a fault inside the logger reaches the console instead of being silent
logger Logger created the logger object, reporting exactly what is still missing from it
udp First record a valid RFC 5424 record on the wire, over UDP
13 changes: 13 additions & 0 deletions measurements/udp.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# udp figures (bytes) — captured by scripts/run.sh (CAPTURE=1).
# The device reads measurements/Baseline.csv as its frozen baseline and reports current-minus-Baseline.
flash_text,354576
flash_data,448
static_bss,111116
heap_used,4440
mbedtls_peak,21292
mbedtls_free,11476
lwip_mem_free,7576
lwip_pbufs_free,13
stack_log,136
stack_service,52
stack_harness,2848
45 changes: 22 additions & 23 deletions run-report.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
# solid-syslog-example — run (logger)
# solid-syslog-example — run (udp)

## Device (self-measured)

```text
[device] solid-syslog-example (FreeRTOS + lwIP + mbedTLS + FatFs)
[syslog] CRITICAL SolidSyslog bad-config (detail 1)
[syslog] CRITICAL SolidSyslog bad-config (detail 2)
[syslog] CRITICAL SolidSyslog bad-config (detail 3)
[device] starting simulated existing application...
[sim] broker session to 10.0.2.2:8883: TLSv1.3, TLS1-3-CHACHA20-POLY1305-SHA256
[device] sim app (lwIP up, FatFs mounted, broker session held over mTLS): ready
[device] first record logged: yes
[report] --- SolidSyslog cost above baseline (simulated existing application) ---
[report] key,current,baseline,used_above_baseline
[report] flash_text,350960,349992,968
[report] flash_data,384,316,68
[report] static_bss,110988,110876,112
[report] flash_text,354576,349992,4584
[report] flash_data,448,316,132
[report] static_bss,111116,110876,240
[report] heap_used,4440,4440,0
[report] mbedtls_peak,21228,21332,-104
[report] mbedtls_free,11540,11436,104
[report] mbedtls_peak,21336,21332,4
[report] mbedtls_free,11432,11436,-4
[report] lwip_mem_free,7576,7576,0
[report] lwip_pbufs_free,13,14,-1
[report] stack_log,120,120,0
[report] lwip_pbufs_free,14,14,0
[report] stack_log,136,120,16
[report] stack_service,52,52,0
[report] stack_harness,2840,2840,0
[report] stack_harness,2848,2840,8
[report] --- end ---
[device] ready
```
Expand All @@ -31,7 +29,7 @@

```text
text data bss dec hex filename
350952 392 110988 462332 70dfc /w/build/baseline-cross/baseline.elf
354568 456 111116 466140 71cdc /w/build/baseline-cross/baseline.elf
```

## Listeners (proved before the device ran)
Expand All @@ -49,23 +47,24 @@
## Collector (syslog-ng) received

```text
(nothing — this device sends no records yet)
wire <134>1 - - - - BOOT - device started
parsed PRIORITY=134 TIMESTAMP=2026-07-29T07:13:03+00:00 HOSTNAME=localhost APP_NAME= PROCID= MSGID=BOOT STRUCTURED_DATA= MSG=device started
```

## Self-check (vs measurements/logger.csv)
## Self-check (vs measurements/udp.csv)

```text
OK flash_text: 350960 (expected 350960, Δ0)
OK flash_data: 384 (expected 384, Δ0)
OK static_bss: 110988 (expected 110988, Δ0)
OK flash_text: 354576 (expected 354576, Δ0)
OK flash_data: 448 (expected 448, Δ0)
OK static_bss: 111116 (expected 111116, Δ0)
OK heap_used: 4440 (expected 4440, Δ0)
OK mbedtls_peak: 21228 (expected 21192, Δ36)
OK mbedtls_free: 11540 (expected 11576, Δ36)
OK mbedtls_peak: 21336 (expected 21292, Δ44)
OK mbedtls_free: 11432 (expected 11476, Δ44)
OK lwip_mem_free: 7576 (expected 7576, Δ0)
OK lwip_pbufs_free: 13 (expected 13, Δ0)
OK stack_log: 120 (expected 120, Δ0)
OK lwip_pbufs_free: 14 (expected 13, Δ1)
OK stack_log: 136 (expected 136, Δ0)
OK stack_service: 52 (expected 52, Δ0)
OK stack_harness: 2840 (expected 2840, Δ0)
OK stack_harness: 2848 (expected 2848, Δ0)
```

**RESULT: PASS**
Loading