From 880108beb5271b1a0af3234580539f93a3ebec9b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 16 Aug 2026 20:57:28 +0100 Subject: [PATCH] feat: fit the message cap, the pools, the ring and the stacks to this device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every collaborator is in place, so the compile-time sizes come down to what the device uses. Flash +13,788 B (unchanged) RAM +36,048 B (-3,488) Log stack +600 B (-80) Service +3,688 B (-80) The message cap comes first: the ring, the store's record buffer and the formatter frame on both task stacks all follow it. The worst case measured here is 345 octets — four SD-ELEMENTs with both counters at full 32-bit width and both addresses at fifteen characters, plus a short message — and the cap is set to 400 to allow for longer messages on this device. The ring then drops from eight records to four, the store holding the backlog. Three pool tunables come down to one, the library's defaults being sized for a device running several transports at once. The stacks go last, at twice their measured high-water marks rounded up to a whole configMINIMAL_STACK_SIZE. The overrides arrive through SOLIDSYSLOG_USER_TUNABLES_FILE, as an absolute path quoted for the preprocessor, and every group that includes a SolidSyslog header is given it: Core, the platform sources and this application. They change struct sizes, and a build where only some translation units saw them would disagree about how big those structs are. Nothing here changes what the device does or what protection it is under. Co-Authored-By: Claude Opus 5 (1M context) --- Makefile | 2 +- README.md | 53 +++++++++++++++--------------- app/AppConfig.h | 11 +++---- app/config/solid_syslog_tunables.h | 18 ++++++++++ app/syslog/Syslog.c | 6 ++-- make/solidsyslog.mk | 9 ++++- measurements/right-size.csv | 13 ++++++++ measurements/stages.tsv | 1 + run-report.md | 34 +++++++++---------- 9 files changed, 93 insertions(+), 54 deletions(-) create mode 100644 app/config/solid_syslog_tunables.h create mode 100644 measurements/right-size.csv diff --git a/Makefile b/Makefile index e687b2c..ceeb1ce 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ UPSTREAM_INCLUDES := \ # compiles; -MMD tracks them properly from then on. $(APP_OBJS) $(UPSTREAM_OBJS) $(SOLIDSYSLOG_PLATFORM_OBJS): | $(FATFS_STAGED_HEADERS) -$(APP_OBJS) $(SOLIDSYSLOG_PLATFORM_OBJS): CFLAGS := $(COMMON_CFLAGS) $(APP_WARNINGS) $(APP_INCLUDES) $(MBEDTLS_USER_CONFIG) +$(APP_OBJS) $(SOLIDSYSLOG_PLATFORM_OBJS): CFLAGS := $(COMMON_CFLAGS) $(APP_WARNINGS) $(APP_INCLUDES) $(MBEDTLS_USER_CONFIG) $(SOLIDSYSLOG_USER_TUNABLES) $(UPSTREAM_OBJS): CFLAGS := $(COMMON_CFLAGS) $(UPSTREAM_WARNINGS) $(UPSTREAM_INCLUDES) # Our objects, the upstream ones and the platform packs link in loose; mbedTLS diff --git a/README.md b/README.md index 514d071..e35160c 100644 --- a/README.md +++ b/README.md @@ -10,46 +10,46 @@ 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 — AES-GCM at rest +## This stage — Right-sized -Replace the HMAC policy with authenticated encryption. Tamper-evidence establishes that a stored -record was not altered; it does nothing to stop anyone reading it. AES-256-GCM encrypts the body, -authenticates the record header as associated data, and puts the nonce and tag in the trailer. +Fit the compile-time sizes to what this device uses, now that every collaborator is in place. + +The message cap comes first, because the ring, the store's record buffer and the formatter frame on +both task stacks all follow it. ```c -struct SolidSyslogMbedTlsAesGcmPolicyConfig gcmConfig = {.GetKey = SyslogStoreKey, .Rng = rng}; +/* app/config/solid_syslog_tunables.h */ +#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 400U -.SecurityPolicy = SolidSyslogMbedTlsAesGcmPolicy_Create(&gcmConfig), +#define SOLIDSYSLOG_ADDRESS_POOL_SIZE 1U +#define SOLIDSYSLOG_TCP_STREAM_POOL_SIZE 1U +#define SOLIDSYSLOG_STREAM_SENDER_POOL_SIZE 1U ``` -GCM needs a fresh nonce per record and mbedTLS has no context-free RNG, so the policy takes the -device's DRBG as well as the key. That is the only wiring difference from the HMAC policy. +The worst case measured here is 345 octets: the four SD-ELEMENTs with both counters at full 32-bit +width and both addresses at fifteen characters, plus a short message. 400 allows for longer messages +on this device. Anything longer is truncated rather than dropped. -The store key does not change. Its name states what it protects rather than which algorithm protects -it, so escalating the policy needs no new key provisioned. +The pool defaults suit a device running several transports at once. This one runs a single sender +over a single stream to a single destination. -These are separate decisions and the second does not follow from the first. A device that only needs -to prove records were not altered can stop at the HMAC. +The overrides reach the library through `SOLIDSYSLOG_USER_TUNABLES_FILE`, an absolute path quoted +for the preprocessor and given to every group that includes a SolidSyslog header: Core, the platform +sources and this application. They change struct sizes, and a build where only some translation +units saw them would disagree about how big those structs are. -The pipeline element now derives both of its values from what the device holds, and each falls back -to the weakest honest answer when the credential behind it is missing: +The ring drops from eight records to four. The store holds a backlog, so the ring only has to absorb +what can be logged while the service task is sending. -```c -s_sd[3] = SyslogPipelineSd_Init( - ((clientChain != NULL) && (clientKey != NULL)) ? "mtls" : "tls", (rng != NULL) ? "aes-256-gcm" : "none" -); -``` - -```text -... [logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started -``` +The task stacks go last, at twice their measured high-water marks rounded up to a whole +`configMINIMAL_STACK_SIZE`. -**When you need it.** If a disk that leaves the device would give something away — records naming -users, addresses, process values, or anything else you would not publish. +**When you need it.** Once the pipeline is complete. Sizing earlier means sizing against a device +that is still missing collaborators. -**Cost above baseline: Flash +13,788 B, RAM +39,536 B.** +**Cost above baseline: Flash +13,788 B, RAM +36,048 B.** @@ -80,6 +80,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage. | Private SD-ELEMENT | a record that states the protection its own log pipeline was under | +13,556 | +37,472 | | Mutual TLS | a collector that knows which device sent the record, not just that one did | +13,624 | +39,524 | | AES-GCM at rest | spooled records unreadable to anyone holding the disk, not just unforgeable | +13,788 | +39,536 | +| Right-sized | the pools, the ring and the task stacks fitted to what this device uses | +13,788 | +36,048 | *Deltas are bytes above the baseline, which is itself Flash 350,124 B, RAM 111,192 B.* diff --git a/app/AppConfig.h b/app/AppConfig.h index 967bc9e..e996347 100644 --- a/app/AppConfig.h +++ b/app/AppConfig.h @@ -10,12 +10,11 @@ /* CMSDK UART0 on the mps2-an385, surfaced by QEMU over -serial stdio. */ #define DEVICE_UART0_BASE ((uintptr_t) 0x40004000U) -/* Both seams handle a whole record — the log seam formats one, the service seam - * drains one — so each holds SOLIDSYSLOG_MAX_MESSAGE_SIZE and its onward call - * 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 * 16U) +/* Twice the measured high-water mark, rounded up to a whole configMINIMAL_STACK_SIZE, + * or the FreeRTOS floor where that is below it. The reported figure is high-water + * usage, which does not depend on the allocation. */ +#define LOG_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 3U) +#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 15U) #define LOG_TASK_PRIORITY (tskIDLE_PRIORITY + 1U) #define SERVICE_TASK_PRIORITY (tskIDLE_PRIORITY + 1U) diff --git a/app/config/solid_syslog_tunables.h b/app/config/solid_syslog_tunables.h new file mode 100644 index 0000000..f8772f0 --- /dev/null +++ b/app/config/solid_syslog_tunables.h @@ -0,0 +1,18 @@ +/* SolidSyslog compile-time overrides for this device. Reached via + * SOLIDSYSLOG_USER_TUNABLES_FILE; anything not set here keeps the library + * default from SolidSyslogTunablesDefaults.h. */ +#ifndef SOLID_SYSLOG_TUNABLES_H +#define SOLID_SYSLOG_TUNABLES_H + +/* The worst case measured here is 345 octets: the structured data at full width + * with a short message. 400 allows for longer messages on this device. Anything + * longer is truncated rather than dropped. */ +#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 400U + +/* One sender, over one stream, to one destination. The defaults suit a device + * running several transports at once. */ +#define SOLIDSYSLOG_ADDRESS_POOL_SIZE 1U +#define SOLIDSYSLOG_TCP_STREAM_POOL_SIZE 1U +#define SOLIDSYSLOG_STREAM_SENDER_POOL_SIZE 1U + +#endif /* SOLID_SYSLOG_TUNABLES_H */ diff --git a/app/syslog/Syslog.c b/app/syslog/Syslog.c index d525619..4825251 100644 --- a/app/syslog/Syslog.c +++ b/app/syslog/Syslog.c @@ -54,9 +54,9 @@ #define SYSLOG_COLLECTOR_HOST "10.0.2.2" #define SYSLOG_COLLECTOR_PORT ((uint16_t) 6515U) -/* Depth enough to absorb a burst while the sender is busy, without sizing for a - * backlog the store is there to hold. */ -#define SYSLOG_BUFFER_RECORDS 8U +/* Enough to absorb a burst logged while the service task is sending. The store + * holds a backlog, so the ring does not have to. */ +#define SYSLOG_BUFFER_RECORDS 4U /* One "NN.log" per block, on the volume the device already mounts. */ #define SYSLOG_STORE_PREFIX "syslog" diff --git a/make/solidsyslog.mk b/make/solidsyslog.mk index 13d73c7..2c0fcbe 100644 --- a/make/solidsyslog.mk +++ b/make/solidsyslog.mk @@ -11,7 +11,14 @@ SOLIDSYSLOG_LIB := $(BUILD)/libSolidSyslog.a SOLIDSYSLOG_CORE_OBJS := $(SOLIDSYSLOG_CORE_SRCS:%.c=$(OBJ_DIR)/%.o) SOLIDSYSLOG_PLATFORM_OBJS := $(SOLIDSYSLOG_PLATFORM_SRCS:%.c=$(OBJ_DIR)/%.o) -$(SOLIDSYSLOG_CORE_OBJS): CFLAGS := $(COMMON_CFLAGS) $(SOLIDSYSLOG_CORE_INCLUDES) +# Consumed via #include, so the value has to reach the compiler as a quoted C +# string literal — escaped rather than single-quoted, and absolute, because Core +# compiles without our include path on it. +SOLIDSYSLOG_USER_TUNABLES := -DSOLIDSYSLOG_USER_TUNABLES_FILE=\"$(CURDIR)/$(APP_DIR)/config/solid_syslog_tunables.h\" + +# Every object that includes a SolidSyslog header needs it: the tunables change +# struct sizes, so Core, the platforms and our own code must agree on them. +$(SOLIDSYSLOG_CORE_OBJS): CFLAGS := $(COMMON_CFLAGS) $(SOLIDSYSLOG_CORE_INCLUDES) $(SOLIDSYSLOG_USER_TUNABLES) $(SOLIDSYSLOG_LIB): $(SOLIDSYSLOG_CORE_OBJS) @mkdir -p $(@D) diff --git a/measurements/right-size.csv b/measurements/right-size.csv new file mode 100644 index 0000000..a6b3a1a --- /dev/null +++ b/measurements/right-size.csv @@ -0,0 +1,13 @@ +# right-size 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,363256 +flash_data,656 +static_bss,146584 +heap_used,4440 +mbedtls_peak,37244 +mbedtls_free,19076 +lwip_mem_free,7576 +lwip_pbufs_free,13 +stack_log,720 +stack_service,3740 +stack_harness,2848 diff --git a/measurements/stages.tsv b/measurements/stages.tsv index 4b8e4ea..aeeff95 100644 --- a/measurements/stages.tsv +++ b/measurements/stages.tsv @@ -27,3 +27,4 @@ hmac HMAC at rest stored records that cannot be edited undetected, not merely ch pipeline-sd Private SD-ELEMENT a record that states the protection its own log pipeline was under mtls Mutual TLS a collector that knows which device sent the record, not just that one did aes-gcm AES-GCM at rest spooled records unreadable to anyone holding the disk, not just unforgeable +right-size Right-sized the pools, the ring and the task stacks fitted to what this device uses diff --git a/run-report.md b/run-report.md index 8df7ba1..45b2043 100644 --- a/run-report.md +++ b/run-report.md @@ -1,4 +1,4 @@ -# solid-syslog-example — run (aes-gcm) +# solid-syslog-example — run (right-size) ## Device (self-measured) @@ -12,14 +12,14 @@ [report] key,current,baseline,used_above_baseline [report] flash_text,363256,349808,13448 [report] flash_data,656,316,340 -[report] static_bss,150072,110876,39196 +[report] static_bss,146584,110876,35708 [report] heap_used,4440,4440,0 -[report] mbedtls_peak,37136,21328,15808 -[report] mbedtls_free,19184,11440,7744 +[report] mbedtls_peak,37260,21328,15932 +[report] mbedtls_free,19060,11440,7620 [report] lwip_mem_free,7576,7576,0 -[report] lwip_pbufs_free,13,13,0 -[report] stack_log,800,120,680 -[report] stack_service,3820,52,3768 +[report] lwip_pbufs_free,14,13,1 +[report] stack_log,720,120,600 +[report] stack_service,3740,52,3688 [report] stack_harness,2848,2840,8 [report] --- end --- [device] ready @@ -29,7 +29,7 @@ ```text text data bss dec hex filename - 363248 664 150072 513984 7d7c0 /w/build/baseline.elf + 363248 664 146584 510496 7ca20 /w/build/baseline.elf ``` ## Listeners (proved before the device ran) @@ -47,23 +47,23 @@ ## Collector (syslog-ng) received ```text -wire <134>1 2026-08-16T19:53:56.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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started -parsed PRIORITY=134 TIMESTAMP=2026-08-16T19:53:56+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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] MSG=device started +wire <134>1 2026-08-16T19:57:02.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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started +parsed PRIORITY=134 TIMESTAMP=2026-08-16T19:57:02+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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] MSG=device started ``` -## Self-check (vs measurements/aes-gcm.csv) +## Self-check (vs measurements/right-size.csv) ```text OK flash_text: 363256 (expected 363256, Δ0) OK flash_data: 656 (expected 656, Δ0) - OK static_bss: 150072 (expected 150072, Δ0) + OK static_bss: 146584 (expected 146584, Δ0) OK heap_used: 4440 (expected 4440, Δ0) - OK mbedtls_peak: 37136 (expected 37248, Δ112) - OK mbedtls_free: 19184 (expected 19072, Δ112) + OK mbedtls_peak: 37260 (expected 37244, Δ16) + OK mbedtls_free: 19060 (expected 19076, Δ16) OK lwip_mem_free: 7576 (expected 7576, Δ0) - OK lwip_pbufs_free: 13 (expected 13, Δ0) - OK stack_log: 800 (expected 800, Δ0) - OK stack_service: 3820 (expected 3820, Δ0) + OK lwip_pbufs_free: 14 (expected 13, Δ1) + OK stack_log: 720 (expected 720, Δ0) + OK stack_service: 3740 (expected 3740, Δ0) OK stack_harness: 2848 (expected 2848, Δ0) ```