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
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ set(LWIP_CONTRIB_FREERTOS_DIR "${LWIP_DIR}/contrib/ports/freertos")
# link target — only the header-configured packs below do.
# https://docs.cososo.co.uk/solid-syslog/getting-started/#path-a--cmake-consumer
# Pinned to a commit until there is a release tag to pin to.
set(SOLIDSYSLOG_PLATFORMS "LwipRaw;StdAtomic;FreeRtos;FatFs" CACHE STRING "" FORCE)
set(SOLIDSYSLOG_PLATFORMS "LwipRaw;StdAtomic;FreeRtos;FatFs;MbedTls" CACHE STRING "" FORCE)

include(FetchContent)
FetchContent_Declare(SolidSyslog
Expand Down Expand Up @@ -167,7 +167,7 @@ target_include_directories(baseline PRIVATE
# library, or context struct sizes diverge between consumer and library.
target_compile_definitions(baseline PRIVATE MBEDTLS_USER_CONFIG_FILE=${MBEDTLS_USER_CONFIG_HEADER})

target_link_libraries(baseline PRIVATE mbedtls mbedx509 mbedcrypto SolidSyslog SolidSyslog::LwipRaw SolidSyslog::FreeRtos SolidSyslog::FatFs)
target_link_libraries(baseline PRIVATE mbedtls mbedx509 mbedcrypto SolidSyslog SolidSyslog::LwipRaw SolidSyslog::FreeRtos SolidSyslog::FatFs SolidSyslog::MbedTls)

target_link_options(baseline PRIVATE
-mcpu=cortex-m3 -mthumb
Expand Down
46 changes: 26 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,44 @@ 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 — Origin address
## This stage — TLS

Add the `ip` PARAM to the origin element, sourced from the same interface address the HOSTNAME field
reports.
Wrap the byte stream in TLS, layered over the TCP stream from the previous stage. The device
verifies the collector against a trust anchor it already holds, so records can be read only by that
collector and cannot be altered in transit. The collector is authenticated to the device; the device
is not yet authenticated to the collector.

```c
struct SolidSyslogOriginSdConfig originConfig = {
/* ... as the previous stage ... */
.GetIpCount = SyslogOriginIpCount,
.GetIpAt = SyslogOriginIpAt,
struct SolidSyslogMbedTlsStreamConfig tlsConfig = {
.Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig),
.Sleep = SyslogSleep,
.Rng = DeviceCertStore_Rng(),
.CaChain = DeviceCertStore_CaChain(),
.ServerName = SYSLOG_COLLECTOR_HOST,
};
```

```text
... [origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started
.Stream = SolidSyslogMbedTlsStream_Create(&tlsConfig),
```

A relay or NAT between the device and the collector rewrites the address the collector observes.
`ip` is what the device says about itself, and that survives the hop.
`ServerName` is checked against the collector's certificate, so it has to match a name that
certificate carries. The trust anchor and the DRBG are passed as handles rather than paths or PEM,
so the device's crypto is initialised before the scheduler starts.

The PARAM is repeatable, so the library asks for a count and then one value per index rather than
taking a single string. This device has one address and returns one, and returns none before the
interface has an address — a count of zero omits the PARAM rather than emitting an empty one.
A second concurrent session has to be paid for upstream. The mbedTLS allocator and the task that
carries the handshake both need sizing for it; both fail loudly when they are not, and neither can
be sized from the run that fails.

`SyslogFields_IpAddress` becomes the single place that reads the address, and HOSTNAME formats the
same string through it. Two fields that must agree now cannot disagree.
**When you need it.** If the log path crosses a network you do not control, or if someone reading
records in transit would learn something they should not. Also if the device needs to know it is
talking to the real collector rather than to whatever answered on that address.

**When you need it.** If anything sits between the device and the collector — a relay, a gateway, or
NAT — and the source address the collector sees can no longer be trusted to identify the device.
> If your device does not already run TLS, the library and its trust material will dominate
> everything on this page. This device already holds a TLS session for its own broker, so what this
> stage adds is the adapter and a second session.

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

**Cost above baseline: Flash +12,380 B, RAM +9,172 B.**
**Cost above baseline: Flash +13,084 B, RAM +37,452 B.**

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

Expand All @@ -68,6 +73,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage.
| File store | records that survive a failed send, spooled to disk with a checksum at rest | +11,584 | +9,128 |
| Origin | the device named in the record itself, not inferred from the source address | +11,988 | +9,172 |
| Origin address | the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite | +12,380 | +9,172 |
| TLS | a collector the device authenticates, and records no longer readable on the wire | +13,084 | +37,452 |

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

Expand Down
4 changes: 2 additions & 2 deletions app/AppConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* beneath, and neither fits the FreeRTOS floor. Sized generously here and
* tightened against measured high-water marks once the pipeline is complete. */
#define LOG_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 4U)
#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 4U)
#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 16U)
#define LOG_TASK_PRIORITY (tskIDLE_PRIORITY + 1U)
#define SERVICE_TASK_PRIORITY (tskIDLE_PRIORITY + 1U)

Expand All @@ -31,6 +31,6 @@
* headroom, not spare capacity: buffer_alloc hands out contiguous space, so a
* buffer only a little over the peak fails on fragmentation rather than on
* capacity. Applied again wherever more is asked of mbedTLS. */
#define SIMULATED_APP_MBEDTLS_HEAP_BYTES (32 * 1024)
#define SIMULATED_APP_MBEDTLS_HEAP_BYTES (53 * 1024)

#endif /* APP_CONFIG_H */
3 changes: 2 additions & 1 deletion app/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ static void HarnessTask(void* parameters)
* before the figures are taken. What arrived is the collector's word. */
bool logged = LogTask_EmitOnce(5000U);
(void) printf("[device] first record logged: %s\n", logged ? "yes" : "FAILED");
vTaskDelay(pdMS_TO_TICKS(500U));
/* Long enough for the TLS negotiation, not just the send. */
vTaskDelay(pdMS_TO_TICKS(3000U));

(void) Measure_Report();

Expand Down
20 changes: 16 additions & 4 deletions app/syslog/Syslog.c
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/* See Syslog.h.
*
* A TCP stream over lwIP behind a circular buffer: Log enqueues and returns, and
* the service task drains and sends. The mutex is what makes those two sides
* A TLS stream over lwIP TCP behind a circular buffer: Log enqueues and returns,
* and the service task drains and sends. The mutex is what makes those two sides
* safe on different tasks.
*
* Unlike a header field, an SD PARAM has no NILVALUE: an unset one is omitted
* entirely rather than written as "-". */

#include "Syslog.h"

#include "DeviceCertStore.h"

#include "SolidSyslogBlockStore.h"
#include "SolidSyslogCircularBuffer.h"
#include "SolidSyslogConfig.h"
Expand All @@ -23,6 +25,7 @@
#include "SolidSyslogLwipRawMarshal.h"
#include "SolidSyslogLwipRawResolver.h"
#include "SolidSyslogLwipRawTcpStream.h"
#include "SolidSyslogMbedTlsStream.h"
#include "SolidSyslogMetaSd.h"
#include "SolidSyslogOriginSd.h"
#include "SolidSyslogSdValue.h"
Expand All @@ -47,7 +50,7 @@
* 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) 5601U)
#define SYSLOG_COLLECTOR_PORT ((uint16_t) 6514U)

/* Depth enough to absorb a burst while the sender is busy, without sizing for a
* backlog the store is there to hold. */
Expand Down Expand Up @@ -133,11 +136,20 @@ void Syslog_Start(void)

struct SolidSyslogLwipRawTcpStreamConfig tcpConfig = {.Sleep = SyslogSleep};

/* ServerName must match the name in the collector's certificate. */
struct SolidSyslogMbedTlsStreamConfig tlsConfig = {
.Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig),
.Sleep = SyslogSleep,
.Rng = DeviceCertStore_Rng(),
.CaChain = DeviceCertStore_CaChain(),
.ServerName = SYSLOG_COLLECTOR_HOST,
};

/* No EndpointVersion — this collector never moves, so the sender resolves
* once and pins it. */
struct SolidSyslogStreamSenderConfig senderConfig = {
.Resolver = SolidSyslogLwipRawResolver_Create(),
.Stream = SolidSyslogLwipRawTcpStream_Create(&tcpConfig),
.Stream = SolidSyslogMbedTlsStream_Create(&tlsConfig),
.Address = SolidSyslogLwipRawAddress_Create(),
.Endpoint = CollectorEndpoint,
};
Expand Down
1 change: 1 addition & 0 deletions measurements/stages.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ time-quality Time quality a timestamp the collector knows how far to trust, and
file-store File store records that survive a failed send, spooled to disk with a checksum at rest
origin Origin the device named in the record itself, not inferred from the source address
origin-ip Origin address the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite
tls TLS a collector the device authenticates, and records no longer readable on the wire
13 changes: 13 additions & 0 deletions measurements/tls.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# tls 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,362744
flash_data,648
static_bss,147996
heap_used,4440
mbedtls_peak,35992
mbedtls_free,18280
lwip_mem_free,7576
lwip_pbufs_free,14
stack_log,800
stack_service,3820
stack_harness,2848
36 changes: 18 additions & 18 deletions run-report.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# solid-syslog-example — run (origin-ip)
# solid-syslog-example — run (tls)

## Device (self-measured)

Expand All @@ -10,16 +10,16 @@
[device] first record logged: yes
[report] --- SolidSyslog cost above baseline (simulated existing application) ---
[report] key,current,baseline,used_above_baseline
[report] flash_text,362048,349992,12056
[report] flash_data,640,316,324
[report] static_bss,119724,110876,8848
[report] flash_text,362744,349992,12752
[report] flash_data,648,316,332
[report] static_bss,147996,110876,37120
[report] heap_used,4440,4440,0
[report] mbedtls_peak,21260,21332,-72
[report] mbedtls_free,11508,11436,72
[report] mbedtls_peak,36108,21332,14776
[report] mbedtls_free,18164,11436,6728
[report] lwip_mem_free,7576,7576,0
[report] lwip_pbufs_free,13,14,-1
[report] stack_log,800,120,680
[report] stack_service,1012,52,960
[report] stack_service,3820,52,3768
[report] stack_harness,2848,2840,8
[report] --- end ---
[device] ready
Expand All @@ -29,7 +29,7 @@

```text
text data bss dec hex filename
362040 648 119724 482412 75c6c /w/build/baseline-cross/baseline.elf
362736 656 147996 511388 7cd9c /w/build/baseline-cross/baseline.elf
```

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

```text
wire <134>1 2026-08-16T07:24:12.430000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T07:24:12+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started
wire <134>1 2026-08-16T09:14:51.440000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="244"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T09:14:51+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="244"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started
```

## Self-check (vs measurements/origin-ip.csv)
## Self-check (vs measurements/tls.csv)

```text
OK flash_text: 362048 (expected 362048, Δ0)
OK flash_data: 640 (expected 640, Δ0)
OK static_bss: 119724 (expected 119724, Δ0)
OK flash_text: 362744 (expected 362744, Δ0)
OK flash_data: 648 (expected 648, Δ0)
OK static_bss: 147996 (expected 147996, Δ0)
OK heap_used: 4440 (expected 4440, Δ0)
OK mbedtls_peak: 21260 (expected 21256, Δ4)
OK mbedtls_free: 11508 (expected 11512, Δ4)
OK mbedtls_peak: 36108 (expected 35992, Δ116)
OK mbedtls_free: 18164 (expected 18280, Δ116)
OK lwip_mem_free: 7576 (expected 7576, Δ0)
OK lwip_pbufs_free: 13 (expected 13, Δ0)
OK lwip_pbufs_free: 13 (expected 14, Δ1)
OK stack_log: 800 (expected 800, Δ0)
OK stack_service: 1012 (expected 1012, Δ0)
OK stack_service: 3820 (expected 3820, Δ0)
OK stack_harness: 2848 (expected 2848, Δ0)
```

Expand Down
Loading