From 42b97158bb8026219a150a69750b29d8488b1e17 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 09:06:51 +0000 Subject: [PATCH 1/3] feat: S14.07 move header-field callbacks off the formatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce SolidSyslogHeaderField — an opaque, stack-transient US-ASCII writer for the RFC 5424 HOSTNAME / APP-NAME / PROCID header fields. It wraps the message-buffer formatter and exposes only a PRINTUSASCII append (printable US-ASCII '!'..'~', everything else — space included — substituted) bounded to the field width, plus a Uint32 for PROCID. The PRINTUSASCII charset is a different rule from SD escaping, hence a dedicated writer rather than reusing SolidSyslogSdValue. Change the three header-field callbacks from SolidSyslogStringFunction to the new SolidSyslogHeaderFieldFunction (void(SolidSyslogHeaderField*, void* context)) with paired context fields on SolidSyslogConfig and the MessageFormatter context. MessageFormatter now builds one HeaderField per field (capped at the field width) and passes it; the scratch-field formatter + re-filter is gone. The Posix/Windows hostname & processId helpers and every BDD header-field callback move to the new shape. SolidSyslogStringFunction.h is retired — it was the last public consumer of SolidSyslogFormatter, clearing the way for S14.08. PosixMessageQueueBuffer, which reused the PROCID helper to stamp its queue name with the pid, now formats getpid() directly rather than depend on the header-field helper. Output is byte-for-byte identical; existing tests stand. Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/Common/BddTargetAppName.c | 7 +- Bdd/Targets/Common/BddTargetAppName.h | 4 +- .../Common/BddTargetFreeRtosPipeline.c | 8 +- .../Common/BddTargetFreeRtosPipeline.h | 4 +- Bdd/Targets/FreeRtos/main.c | 9 +- Bdd/Targets/FreeRtosLwip/main.c | 9 +- Core/Interface/SolidSyslogConfig.h | 13 ++- Core/Interface/SolidSyslogHeaderField.h | 34 ++++++ .../SolidSyslogHeaderFieldFunction.h | 19 ++++ Core/Interface/SolidSyslogStringFunction.h | 14 --- Core/Source/CMakeLists.txt | 1 + Core/Source/SolidSyslog.c | 63 ++++++++--- Core/Source/SolidSyslogHeaderField.c | 39 +++++++ Core/Source/SolidSyslogHeaderFieldPrivate.h | 35 ++++++ Core/Source/SolidSyslogMessageFormatter.c | 48 +++++--- Core/Source/SolidSyslogMessageFormatter.h | 11 +- Core/Source/SolidSyslogPrivate.h | 4 +- Core/Source/SolidSyslogStatic.c | 9 +- .../Interface/SolidSyslogPosixHostname.h | 4 +- .../Interface/SolidSyslogPosixProcessId.h | 4 +- .../Posix/Source/SolidSyslogPosixHostname.c | 10 +- .../SolidSyslogPosixMessageQueueBuffer.c | 4 +- .../Posix/Source/SolidSyslogPosixProcessId.c | 9 +- .../Interface/SolidSyslogWindowsHostname.h | 2 +- .../Interface/SolidSyslogWindowsProcessId.h | 2 +- .../Source/SolidSyslogWindowsHostname.c | 8 +- .../Source/SolidSyslogWindowsProcessId.c | 7 +- Tests/Bdd/Targets/BddTargetAppNameTest.cpp | 19 ++-- .../Targets/BddTargetServiceThreadTest.cpp | 3 +- Tests/CMakeLists.txt | 1 + Tests/SolidSyslogHeaderFieldTest.cpp | 104 ++++++++++++++++++ Tests/SolidSyslogMessageFormatterTest.cpp | 3 + Tests/SolidSyslogPoolTest.cpp | 2 +- ...SolidSyslogPosixMessageQueueBufferTest.cpp | 3 +- Tests/SolidSyslogTest.cpp | 30 ++--- Tests/SolidSyslogWindowsHostnameTest.cpp | 11 +- Tests/SolidSyslogWindowsProcessIdTest.cpp | 9 +- Tests/StringFake.c | 19 ++-- Tests/StringFake.h | 8 +- Tests/StringFakeTest.cpp | 7 +- docs/rfc-compliance.md | 4 +- misra_suppressions.txt | 24 ++-- 42 files changed, 468 insertions(+), 160 deletions(-) create mode 100644 Core/Interface/SolidSyslogHeaderField.h create mode 100644 Core/Interface/SolidSyslogHeaderFieldFunction.h delete mode 100644 Core/Interface/SolidSyslogStringFunction.h create mode 100644 Core/Source/SolidSyslogHeaderField.c create mode 100644 Core/Source/SolidSyslogHeaderFieldPrivate.h create mode 100644 Tests/SolidSyslogHeaderFieldTest.cpp diff --git a/Bdd/Targets/Common/BddTargetAppName.c b/Bdd/Targets/Common/BddTargetAppName.c index 5505e9d1..06ec56c9 100644 --- a/Bdd/Targets/Common/BddTargetAppName.c +++ b/Bdd/Targets/Common/BddTargetAppName.c @@ -1,5 +1,5 @@ #include "BddTargetAppName.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" #include #include @@ -57,7 +57,8 @@ void BddTargetAppName_Set(const char* argv0) } } -void BddTargetAppName_Get(struct SolidSyslogFormatter* formatter) +void BddTargetAppName_Get(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_BoundedString(formatter, appName, appNameLength); + (void) context; + SolidSyslogHeaderField_PrintUsAscii(field, appName, appNameLength); } diff --git a/Bdd/Targets/Common/BddTargetAppName.h b/Bdd/Targets/Common/BddTargetAppName.h index 1da4b2af..75d312da 100644 --- a/Bdd/Targets/Common/BddTargetAppName.h +++ b/Bdd/Targets/Common/BddTargetAppName.h @@ -5,10 +5,10 @@ EXTERN_C_BEGIN - struct SolidSyslogFormatter; + struct SolidSyslogHeaderField; void BddTargetAppName_Set(const char* argv0); - void BddTargetAppName_Get(struct SolidSyslogFormatter * formatter); + void BddTargetAppName_Get(struct SolidSyslogHeaderField * field, void* context); EXTERN_C_END diff --git a/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c b/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c index a45485ec..14003347 100644 --- a/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c +++ b/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c @@ -27,6 +27,7 @@ #include "SolidSyslogError.h" #include "SolidSyslogFileBlockDevice.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" #include "SolidSyslogFreeRtosMutex.h" #include "SolidSyslogFreeRtosSysUpTime.h" #include "SolidSyslogMbedTlsAesGcmPolicy.h" @@ -175,7 +176,7 @@ static void OnStoreFull(void* context); static size_t GetCapacityThreshold(void* context); static void OnThresholdCrossed(void* context); static void TeardownAll(void); -static void GetAppName(struct SolidSyslogFormatter* formatter); +static void GetAppName(struct SolidSyslogHeaderField* field, void* context); static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality); static void ErrorHandlerEx(void* context, const struct SolidSyslogErrorEvent* event); @@ -220,9 +221,10 @@ void BddTargetFreeRtosPipeline_SetConfig(const struct BddTargetFreeRtosPipelineC /* ---- SolidSyslog config callbacks ------------------------------------------ */ -static void GetAppName(struct SolidSyslogFormatter* formatter) +static void GetAppName(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_BoundedString(formatter, appName, strlen(appName)); + (void) context; + SolidSyslogHeaderField_PrintUsAscii(field, appName, strlen(appName)); } /* No RTC and no time-sync on these reference targets — RFC 5424 §6.2.3.1 diff --git a/Bdd/Targets/Common/BddTargetFreeRtosPipeline.h b/Bdd/Targets/Common/BddTargetFreeRtosPipeline.h index ef3f08f3..c3865983 100644 --- a/Bdd/Targets/Common/BddTargetFreeRtosPipeline.h +++ b/Bdd/Targets/Common/BddTargetFreeRtosPipeline.h @@ -2,8 +2,8 @@ #define BDD_TARGET_FREE_RTOS_PIPELINE_H #include "SolidSyslogEndpoint.h" +#include "SolidSyslogHeaderFieldFunction.h" #include "SolidSyslogSender.h" -#include "SolidSyslogStringFunction.h" #include #include @@ -37,7 +37,7 @@ struct BddTargetFreeRtosPipelineConfig struct SolidSyslogSender* (*BuildSender)(void); /* Emit the RFC 5424 HOSTNAME by reading the platform IP stack. Wired into * SolidSyslogConfig.GetHostname. */ - SolidSyslogStringFunction GetHostname; + SolidSyslogHeaderFieldFunction GetHostname; /* Tear down the sender + platform adapters. Runs on the interactive task * after the shared pipeline teardown (SolidSyslog / SD / store / buffer). */ void (*TeardownNetwork)(void); diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index fbdd8d61..1eede1c4 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -23,7 +23,7 @@ #include "BddTargetTlsConfig.h" #include "BddTargetTlsSender.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" #include "SolidSyslogPlusTcpAddress.h" #include "SolidSyslogPlusTcpDatagram.h" #include "SolidSyslogPlusTcpResolver.h" @@ -96,7 +96,7 @@ static BaseType_t interactiveTaskCreated = pdFALSE; extern NetworkInterface_t* pxMPS2_FillInterfaceDescriptor(BaseType_t xEMACIndex, NetworkInterface_t* pxInterface); static void SetEthernetIrqPriority(void); -static void GetHostname(struct SolidSyslogFormatter* formatter); +static void GetHostname(struct SolidSyslogHeaderField* field, void* context); static struct SolidSyslogSender* BuildSender(void); static void TeardownNetwork(void); @@ -229,16 +229,17 @@ static void SetEthernetIrqPriority(void) *ipr = ETHERNET_IRQ_PRIORITY; } -static void GetHostname(struct SolidSyslogFormatter* formatter) +static void GetHostname(struct SolidSyslogHeaderField* field, void* context) { /* RFC 5424 §6.2.4 rung 2 (static IP address). Read back from the IP stack so * a future DHCP / hostname slice satisfies the same rung without re-touching * this callback. */ uint32_t ipAddress = 0U; char ipBuffer[16]; + (void) context; FreeRTOS_GetEndPointConfiguration(&ipAddress, NULL, NULL, NULL, &networkEndPoint); FreeRTOS_inet_ntoa(ipAddress, ipBuffer); - SolidSyslogFormatter_BoundedString(formatter, ipBuffer, strlen(ipBuffer)); + SolidSyslogHeaderField_PrintUsAscii(field, ipBuffer, strlen(ipBuffer)); } /* Build the PlusTcp SwitchingSender: UDP datagram, octet-framed TCP, and a diff --git a/Bdd/Targets/FreeRtosLwip/main.c b/Bdd/Targets/FreeRtosLwip/main.c index 1ecca1a2..12692eb1 100644 --- a/Bdd/Targets/FreeRtosLwip/main.c +++ b/Bdd/Targets/FreeRtosLwip/main.c @@ -24,7 +24,7 @@ #include "BddTargetSwitchConfig.h" #include "BddTargetTlsSender.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" #include "SolidSyslogLwipRawAddress.h" #include "SolidSyslogLwipRawDatagram.h" #include "SolidSyslogLwipRawDnsResolver.h" @@ -70,7 +70,7 @@ static void LwipTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context) static void NetworkBringUp(void* context); static void WarmUpGatewayArp(void); static void GatewayResolvedQuery(void* context); -static void GetHostname(struct SolidSyslogFormatter* formatter); +static void GetHostname(struct SolidSyslogHeaderField* field, void* context); static struct SolidSyslogSender* BuildSender(void); static void TeardownNetwork(void); @@ -233,12 +233,13 @@ void vApplicationStackOverflowHook(TaskHandle_t task, char* taskName) } } -static void GetHostname(struct SolidSyslogFormatter* formatter) +static void GetHostname(struct SolidSyslogHeaderField* field, void* context) { /* RFC 5424 §6.2.4 rung 2 (static IP) — read back from the netif so a future * DHCP slice satisfies the same rung without touching this callback. */ const char* address = ip4addr_ntoa(netif_ip4_addr(&networkInterface)); - SolidSyslogFormatter_BoundedString(formatter, address, strlen(address)); + (void) context; + SolidSyslogHeaderField_PrintUsAscii(field, address, strlen(address)); } /* Bring up the netif on the tcpip thread, warm the gateway ARP, then build the diff --git a/Core/Interface/SolidSyslogConfig.h b/Core/Interface/SolidSyslogConfig.h index a9cd3ffa..e579cdca 100644 --- a/Core/Interface/SolidSyslogConfig.h +++ b/Core/Interface/SolidSyslogConfig.h @@ -3,7 +3,7 @@ #include -#include "SolidSyslogStringFunction.h" +#include "SolidSyslogHeaderFieldFunction.h" #include "SolidSyslogTimestamp.h" #include "ExternC.h" @@ -11,7 +11,7 @@ EXTERN_C_BEGIN struct SolidSyslog; struct SolidSyslogBuffer; - struct SolidSyslogFormatter; + struct SolidSyslogHeaderField; struct SolidSyslogSender; struct SolidSyslogStore; struct SolidSyslogStructuredData; @@ -21,9 +21,12 @@ EXTERN_C_BEGIN struct SolidSyslogBuffer* Buffer; struct SolidSyslogSender* Sender; SolidSyslogClockFunction Clock; - SolidSyslogStringFunction GetHostname; - SolidSyslogStringFunction GetAppName; - SolidSyslogStringFunction GetProcessId; + SolidSyslogHeaderFieldFunction GetHostname; + void* GetHostnameContext; + SolidSyslogHeaderFieldFunction GetAppName; + void* GetAppNameContext; + SolidSyslogHeaderFieldFunction GetProcessId; + void* GetProcessIdContext; struct SolidSyslogStore* Store; struct SolidSyslogStructuredData** Sd; size_t SdCount; diff --git a/Core/Interface/SolidSyslogHeaderField.h b/Core/Interface/SolidSyslogHeaderField.h new file mode 100644 index 00000000..a2942a15 --- /dev/null +++ b/Core/Interface/SolidSyslogHeaderField.h @@ -0,0 +1,34 @@ +#ifndef SOLIDSYSLOGHEADERFIELD_H +#define SOLIDSYSLOGHEADERFIELD_H + +#include "ExternC.h" + +#include +#include + +EXTERN_C_BEGIN + + /* The value sink for an RFC 5424 header field (HOSTNAME / APP-NAME / + * PROCID). A field producer is handed only a SolidSyslogHeaderField* — it + * can append PRINTUSASCII content (printable US-ASCII; any other byte, + * space included, is substituted) bounded to the field width, but cannot + * reach the raw formatter. Stack-transient, no pool (D.002). */ + struct SolidSyslogHeaderField; + + /* Appends up to maxLength bytes of source (stopping at a NUL terminator), + * substituting any byte outside printable US-ASCII ('!'..'~') — space + * included — with the library's substitute character. Output is further + * bounded by the field width the writer was created with. */ + void SolidSyslogHeaderField_PrintUsAscii( + struct SolidSyslogHeaderField * field, + const char* source, + size_t maxLength + ); + + /* Appends the decimal digits of value (always printable US-ASCII), bounded + * by the field width. */ + void SolidSyslogHeaderField_Uint32(struct SolidSyslogHeaderField * field, uint32_t value); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGHEADERFIELD_H */ diff --git a/Core/Interface/SolidSyslogHeaderFieldFunction.h b/Core/Interface/SolidSyslogHeaderFieldFunction.h new file mode 100644 index 00000000..26c87d71 --- /dev/null +++ b/Core/Interface/SolidSyslogHeaderFieldFunction.h @@ -0,0 +1,19 @@ +#ifndef SOLIDSYSLOGHEADERFIELDFUNCTION_H +#define SOLIDSYSLOGHEADERFIELDFUNCTION_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogHeaderField; + + /* Appends an RFC 5424 header-field value (HOSTNAME / APP-NAME / PROCID) into + * the field sink it is handed. The library owns the PRINTUSASCII charset + * (the sink applies it), so a callback cannot break the header framing. + * context is passed through unchanged from the config the callback was + * registered in. */ + typedef void (*SolidSyslogHeaderFieldFunction)(struct SolidSyslogHeaderField* field, void* context); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGHEADERFIELDFUNCTION_H */ diff --git a/Core/Interface/SolidSyslogStringFunction.h b/Core/Interface/SolidSyslogStringFunction.h deleted file mode 100644 index 597a1644..00000000 --- a/Core/Interface/SolidSyslogStringFunction.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SOLIDSYSLOGSTRINGFUNCTION_H -#define SOLIDSYSLOGSTRINGFUNCTION_H - -#include "ExternC.h" - -EXTERN_C_BEGIN - - struct SolidSyslogFormatter; - - typedef void (*SolidSyslogStringFunction)(struct SolidSyslogFormatter* formatter); - -EXTERN_C_END - -#endif /* SOLIDSYSLOGSTRINGFUNCTION_H */ diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 235f9460..2a84eb12 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCES SolidSyslogStructuredData.c SolidSyslogSdValue.c SolidSyslogSdElement.c + SolidSyslogHeaderField.c SolidSyslogTimeQualitySd.c SolidSyslogTimeQualitySdStatic.c SolidSyslogOriginSd.c diff --git a/Core/Source/SolidSyslog.c b/Core/Source/SolidSyslog.c index 6e70e8b5..b239eed6 100644 --- a/Core/Source/SolidSyslog.c +++ b/Core/Source/SolidSyslog.c @@ -15,27 +15,39 @@ #include "SolidSyslogNullSender.h" #include "SolidSyslogNullStore.h" #include "SolidSyslogPrivate.h" +#include "SolidSyslogHeaderFieldFunction.h" #include "SolidSyslogSender.h" #include "SolidSyslogStore.h" -#include "SolidSyslogStringFunction.h" #include "SolidSyslogTimestamp.h" #include "SolidSyslogTunables.h" const struct SolidSyslogErrorSource SolidSyslogErrorSource = {"SolidSyslog"}; struct SolidSyslogBuffer; -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; struct SolidSyslogSender; struct SolidSyslogStore; struct SolidSyslogStructuredData; static inline void SolidSyslog_DrainBufferIntoStore(struct SolidSyslog* self); static inline void SolidSyslog_SendOneFromStore(struct SolidSyslog* self); -static void SolidSyslog_InstallAppName(struct SolidSyslog* self, SolidSyslogStringFunction configured); +static void SolidSyslog_InstallAppName( + struct SolidSyslog* self, + SolidSyslogHeaderFieldFunction configured, + void* context +); static void SolidSyslog_InstallBuffer(struct SolidSyslog* self, struct SolidSyslogBuffer* configured); static void SolidSyslog_InstallClock(struct SolidSyslog* self, SolidSyslogClockFunction configured); -static void SolidSyslog_InstallHostname(struct SolidSyslog* self, SolidSyslogStringFunction configured); -static void SolidSyslog_InstallProcessId(struct SolidSyslog* self, SolidSyslogStringFunction configured); +static void SolidSyslog_InstallHostname( + struct SolidSyslog* self, + SolidSyslogHeaderFieldFunction configured, + void* context +); +static void SolidSyslog_InstallProcessId( + struct SolidSyslog* self, + SolidSyslogHeaderFieldFunction configured, + void* context +); static void SolidSyslog_InstallSender(struct SolidSyslog* self, struct SolidSyslogSender* configured); static void SolidSyslog_InstallStore(struct SolidSyslog* self, struct SolidSyslogStore* configured); static void SolidSyslog_InstallStructuredData( @@ -54,9 +66,9 @@ void SolidSyslog_Initialise(struct SolidSyslog* self, const struct SolidSyslogCo SolidSyslog_InstallSender(self, config->Sender); SolidSyslog_InstallStore(self, config->Store); SolidSyslog_InstallClock(self, config->Clock); - SolidSyslog_InstallHostname(self, config->GetHostname); - SolidSyslog_InstallAppName(self, config->GetAppName); - SolidSyslog_InstallProcessId(self, config->GetProcessId); + SolidSyslog_InstallHostname(self, config->GetHostname, config->GetHostnameContext); + SolidSyslog_InstallAppName(self, config->GetAppName, config->GetAppNameContext); + SolidSyslog_InstallProcessId(self, config->GetProcessId, config->GetProcessIdContext); SolidSyslog_InstallStructuredData(self, config->Sd, config->SdCount); } @@ -74,9 +86,12 @@ static void SolidSyslog_ResetToDefaults(struct SolidSyslog* self) self->Sender = SolidSyslogNullSender_Get(); self->Store = SolidSyslogNullStore_Get(); self->Format.Clock = SolidSyslog_NullClock; - self->Format.GetHostname = SolidSyslog_NullStringFunction; - self->Format.GetAppName = SolidSyslog_NullStringFunction; - self->Format.GetProcessId = SolidSyslog_NullStringFunction; + self->Format.GetHostname = SolidSyslog_NullHeaderField; + self->Format.GetHostnameContext = NULL; + self->Format.GetAppName = SolidSyslog_NullHeaderField; + self->Format.GetAppNameContext = NULL; + self->Format.GetProcessId = SolidSyslog_NullHeaderField; + self->Format.GetProcessIdContext = NULL; self->Format.Sd = NULL; self->Format.SdCount = 0; } @@ -137,27 +152,42 @@ static void SolidSyslog_InstallClock(struct SolidSyslog* self, SolidSyslogClockF } } -static void SolidSyslog_InstallHostname(struct SolidSyslog* self, SolidSyslogStringFunction configured) +static void SolidSyslog_InstallHostname( + struct SolidSyslog* self, + SolidSyslogHeaderFieldFunction configured, + void* context +) { if (configured != NULL) { self->Format.GetHostname = configured; + self->Format.GetHostnameContext = context; } } -static void SolidSyslog_InstallAppName(struct SolidSyslog* self, SolidSyslogStringFunction configured) +static void SolidSyslog_InstallAppName( + struct SolidSyslog* self, + SolidSyslogHeaderFieldFunction configured, + void* context +) { if (configured != NULL) { self->Format.GetAppName = configured; + self->Format.GetAppNameContext = context; } } -static void SolidSyslog_InstallProcessId(struct SolidSyslog* self, SolidSyslogStringFunction configured) +static void SolidSyslog_InstallProcessId( + struct SolidSyslog* self, + SolidSyslogHeaderFieldFunction configured, + void* context +) { if (configured != NULL) { self->Format.GetProcessId = configured; + self->Format.GetProcessIdContext = context; } } @@ -288,7 +318,8 @@ void SolidSyslog_NullClock(struct SolidSyslogTimestamp* ts) (void) ts; } -void SolidSyslog_NullStringFunction(struct SolidSyslogFormatter* formatter) +void SolidSyslog_NullHeaderField(struct SolidSyslogHeaderField* field, void* context) { - (void) formatter; + (void) field; + (void) context; } diff --git a/Core/Source/SolidSyslogHeaderField.c b/Core/Source/SolidSyslogHeaderField.c new file mode 100644 index 00000000..2e87ef0b --- /dev/null +++ b/Core/Source/SolidSyslogHeaderField.c @@ -0,0 +1,39 @@ +#include "SolidSyslogHeaderFieldPrivate.h" + +#include + +#include "SolidSyslogFormatter.h" + +static inline size_t HeaderField_Consumed(const struct SolidSyslogHeaderField* field, size_t before); + +void SolidSyslogHeaderField_FromFormatter( + struct SolidSyslogHeaderField* field, + struct SolidSyslogFormatter* formatter, + size_t maxLength +) +{ + field->Formatter = formatter; + field->Remaining = maxLength; +} + +void SolidSyslogHeaderField_PrintUsAscii(struct SolidSyslogHeaderField* field, const char* source, size_t maxLength) +{ + size_t limit = (maxLength < field->Remaining) ? maxLength : field->Remaining; + size_t before = SolidSyslogFormatter_Length(field->Formatter); + + SolidSyslogFormatter_PrintUsAsciiString(field->Formatter, source, limit); + field->Remaining -= HeaderField_Consumed(field, before); +} + +void SolidSyslogHeaderField_Uint32(struct SolidSyslogHeaderField* field, uint32_t value) +{ + size_t before = SolidSyslogFormatter_Length(field->Formatter); + + SolidSyslogFormatter_Uint32(field->Formatter, value); + field->Remaining -= HeaderField_Consumed(field, before); +} + +static inline size_t HeaderField_Consumed(const struct SolidSyslogHeaderField* field, size_t before) +{ + return SolidSyslogFormatter_Length(field->Formatter) - before; +} diff --git a/Core/Source/SolidSyslogHeaderFieldPrivate.h b/Core/Source/SolidSyslogHeaderFieldPrivate.h new file mode 100644 index 00000000..df5ef3a3 --- /dev/null +++ b/Core/Source/SolidSyslogHeaderFieldPrivate.h @@ -0,0 +1,35 @@ +#ifndef SOLIDSYSLOGHEADERFIELDPRIVATE_H +#define SOLIDSYSLOGHEADERFIELDPRIVATE_H + +#include "ExternC.h" + +#include + +#include "SolidSyslogHeaderField.h" + +EXTERN_C_BEGIN + + struct SolidSyslogFormatter; + + /* Definition lives here (not the public header) so a field producer handed + * a SolidSyslogHeaderField* cannot reach the wrapped formatter. Remaining + * is the field-width budget still available across this field's appends. */ + struct SolidSyslogHeaderField + { + struct SolidSyslogFormatter* Formatter; + size_t Remaining; + }; + + /* Internal constructor — wraps a message-buffer formatter and caps this + * field at maxLength bytes. MessageFormatter builds one per header field + * and passes it to the configured callback. Stack-transient: the caller + * owns the storage. */ + void SolidSyslogHeaderField_FromFormatter( + struct SolidSyslogHeaderField * field, + struct SolidSyslogFormatter * formatter, + size_t maxLength + ); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGHEADERFIELDPRIVATE_H */ diff --git a/Core/Source/SolidSyslogMessageFormatter.c b/Core/Source/SolidSyslogMessageFormatter.c index a6bd67d3..d71bff8e 100644 --- a/Core/Source/SolidSyslogMessageFormatter.c +++ b/Core/Source/SolidSyslogMessageFormatter.c @@ -6,9 +6,10 @@ #include "SolidSyslog.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderFieldFunction.h" +#include "SolidSyslogHeaderFieldPrivate.h" #include "SolidSyslogPrival.h" #include "SolidSyslogSdElementPrivate.h" -#include "SolidSyslogStringFunction.h" #include "SolidSyslogStructuredData.h" #include "SolidSyslogTimestamp.h" #include "SolidSyslogTimestampFormatter.h" @@ -33,7 +34,8 @@ static inline bool MessageFormatter_SeverityIsValid(uint8_t severity); static inline void MessageFormatter_FormatTimestamp(struct SolidSyslogFormatter* f, SolidSyslogClockFunction clock); static inline void MessageFormatter_FormatStringField( struct SolidSyslogFormatter* f, - SolidSyslogStringFunction fn, + SolidSyslogHeaderFieldFunction fn, + void* context, size_t maxSize ); static inline void MessageFormatter_FormatMsgId(struct SolidSyslogFormatter* f, const char* messageId); @@ -57,11 +59,26 @@ void SolidSyslogMessageFormatter_Format( SolidSyslogFormatter_AsciiCharacter(f, ' '); MessageFormatter_FormatTimestamp(f, context->Clock); SolidSyslogFormatter_AsciiCharacter(f, ' '); - MessageFormatter_FormatStringField(f, context->GetHostname, SOLIDSYSLOG_MAX_HOSTNAME_SIZE); + MessageFormatter_FormatStringField( + f, + context->GetHostname, + context->GetHostnameContext, + SOLIDSYSLOG_MAX_HOSTNAME_SIZE + ); SolidSyslogFormatter_AsciiCharacter(f, ' '); - MessageFormatter_FormatStringField(f, context->GetAppName, SOLIDSYSLOG_MAX_APP_NAME_SIZE); + MessageFormatter_FormatStringField( + f, + context->GetAppName, + context->GetAppNameContext, + SOLIDSYSLOG_MAX_APP_NAME_SIZE + ); SolidSyslogFormatter_AsciiCharacter(f, ' '); - MessageFormatter_FormatStringField(f, context->GetProcessId, SOLIDSYSLOG_MAX_PROCESS_ID_SIZE); + MessageFormatter_FormatStringField( + f, + context->GetProcessId, + context->GetProcessIdContext, + SOLIDSYSLOG_MAX_PROCESS_ID_SIZE + ); SolidSyslogFormatter_AsciiCharacter(f, ' '); MessageFormatter_FormatMsgId(f, message->MessageId); SolidSyslogFormatter_AsciiCharacter(f, ' '); @@ -121,22 +138,21 @@ static inline void MessageFormatter_FormatTimestamp(struct SolidSyslogFormatter* static inline void MessageFormatter_FormatStringField( struct SolidSyslogFormatter* f, - SolidSyslogStringFunction fn, + SolidSyslogHeaderFieldFunction fn, + void* context, size_t maxSize ) { - SolidSyslogFormatterStorage fieldStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(SOLIDSYSLOG_MAX_HOSTNAME_SIZE)]; - struct SolidSyslogFormatter* field = SolidSyslogFormatter_Create(fieldStorage, maxSize); - - fn(field); + size_t lengthBefore = SolidSyslogFormatter_Length(f); + struct SolidSyslogHeaderField field; - size_t fieldLength = SolidSyslogFormatter_Length(field); + /* maxSize is the field's storage size (carries a NUL slot); the usable + * field width is one less — matching the RFC HOSTNAME / APP-NAME / PROCID + * caps the scratch-field formatter enforced before this writer existed. */ + SolidSyslogHeaderField_FromFormatter(&field, f, maxSize - 1U); + fn(&field, context); - if (fieldLength > 0U) - { - SolidSyslogFormatter_PrintUsAsciiString(f, SolidSyslogFormatter_AsFormattedBuffer(field), fieldLength); - } - else + if (SolidSyslogFormatter_Length(f) == lengthBefore) { SolidSyslogFormatter_NilValue(f); } diff --git a/Core/Source/SolidSyslogMessageFormatter.h b/Core/Source/SolidSyslogMessageFormatter.h index 5da2f004..a1ff6e09 100644 --- a/Core/Source/SolidSyslogMessageFormatter.h +++ b/Core/Source/SolidSyslogMessageFormatter.h @@ -5,7 +5,7 @@ #include -#include "SolidSyslogStringFunction.h" +#include "SolidSyslogHeaderFieldFunction.h" #include "SolidSyslogTimestamp.h" EXTERN_C_BEGIN @@ -20,9 +20,12 @@ EXTERN_C_BEGIN struct SolidSyslogMessageFormatterContext { SolidSyslogClockFunction Clock; - SolidSyslogStringFunction GetHostname; - SolidSyslogStringFunction GetAppName; - SolidSyslogStringFunction GetProcessId; + SolidSyslogHeaderFieldFunction GetHostname; + void* GetHostnameContext; + SolidSyslogHeaderFieldFunction GetAppName; + void* GetAppNameContext; + SolidSyslogHeaderFieldFunction GetProcessId; + void* GetProcessIdContext; struct SolidSyslogStructuredData** Sd; size_t SdCount; }; diff --git a/Core/Source/SolidSyslogPrivate.h b/Core/Source/SolidSyslogPrivate.h index 66f56653..ce95d061 100644 --- a/Core/Source/SolidSyslogPrivate.h +++ b/Core/Source/SolidSyslogPrivate.h @@ -9,7 +9,7 @@ struct SolidSyslogBuffer; struct SolidSyslogConfig; -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; struct SolidSyslogSender; struct SolidSyslogStore; @@ -29,7 +29,7 @@ void SolidSyslog_Cleanup(struct SolidSyslog* self); * NullInstance). No public Null equivalent exists for the function-pointer * typedefs, so they stay TU-internal across this class. */ void SolidSyslog_NullClock(struct SolidSyslogTimestamp* ts); -void SolidSyslog_NullStringFunction(struct SolidSyslogFormatter* formatter); +void SolidSyslog_NullHeaderField(struct SolidSyslogHeaderField* field, void* context); static inline void SolidSyslog_Report(enum SolidSyslogSeverity severity, uint16_t category, enum SolidSyslogErrors code) { diff --git a/Core/Source/SolidSyslogStatic.c b/Core/Source/SolidSyslogStatic.c index 89b7abb7..e16a777d 100644 --- a/Core/Source/SolidSyslogStatic.c +++ b/Core/Source/SolidSyslogStatic.c @@ -72,9 +72,12 @@ static void SolidSyslog_EnsureNullInstancePopulated(void) SolidSyslog_NullInstance.Sender = SolidSyslogNullSender_Get(); SolidSyslog_NullInstance.Store = SolidSyslogNullStore_Get(); SolidSyslog_NullInstance.Format.Clock = SolidSyslog_NullClock; - SolidSyslog_NullInstance.Format.GetHostname = SolidSyslog_NullStringFunction; - SolidSyslog_NullInstance.Format.GetAppName = SolidSyslog_NullStringFunction; - SolidSyslog_NullInstance.Format.GetProcessId = SolidSyslog_NullStringFunction; + SolidSyslog_NullInstance.Format.GetHostname = SolidSyslog_NullHeaderField; + SolidSyslog_NullInstance.Format.GetHostnameContext = NULL; + SolidSyslog_NullInstance.Format.GetAppName = SolidSyslog_NullHeaderField; + SolidSyslog_NullInstance.Format.GetAppNameContext = NULL; + SolidSyslog_NullInstance.Format.GetProcessId = SolidSyslog_NullHeaderField; + SolidSyslog_NullInstance.Format.GetProcessIdContext = NULL; SolidSyslog_NullInstance.Format.Sd = NULL; SolidSyslog_NullInstance.Format.SdCount = 0; populated = true; diff --git a/Platform/Posix/Interface/SolidSyslogPosixHostname.h b/Platform/Posix/Interface/SolidSyslogPosixHostname.h index 44028cb1..3e0f4c68 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixHostname.h +++ b/Platform/Posix/Interface/SolidSyslogPosixHostname.h @@ -3,11 +3,11 @@ #include "ExternC.h" -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; EXTERN_C_BEGIN - void SolidSyslogPosixHostname_Get(struct SolidSyslogFormatter * formatter); + void SolidSyslogPosixHostname_Get(struct SolidSyslogHeaderField * field, void* context); EXTERN_C_END diff --git a/Platform/Posix/Interface/SolidSyslogPosixProcessId.h b/Platform/Posix/Interface/SolidSyslogPosixProcessId.h index f557982a..deb53145 100644 --- a/Platform/Posix/Interface/SolidSyslogPosixProcessId.h +++ b/Platform/Posix/Interface/SolidSyslogPosixProcessId.h @@ -3,11 +3,11 @@ #include "ExternC.h" -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; EXTERN_C_BEGIN - void SolidSyslogPosixProcessId_Get(struct SolidSyslogFormatter * formatter); + void SolidSyslogPosixProcessId_Get(struct SolidSyslogHeaderField * field, void* context); EXTERN_C_END diff --git a/Platform/Posix/Source/SolidSyslogPosixHostname.c b/Platform/Posix/Source/SolidSyslogPosixHostname.c index 3a7357d1..93a49138 100644 --- a/Platform/Posix/Source/SolidSyslogPosixHostname.c +++ b/Platform/Posix/Source/SolidSyslogPosixHostname.c @@ -2,22 +2,24 @@ #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; enum { MAX_HOSTNAME_SIZE = 256U }; -void SolidSyslogPosixHostname_Get(struct SolidSyslogFormatter* formatter) +void SolidSyslogPosixHostname_Get(struct SolidSyslogHeaderField* field, void* context) { char hostname[MAX_HOSTNAME_SIZE]; + (void) context; + if (gethostname(hostname, sizeof(hostname)) == 0) { hostname[sizeof(hostname) - 1U] = '\0'; - SolidSyslogFormatter_PrintUsAsciiString(formatter, hostname, sizeof(hostname)); + SolidSyslogHeaderField_PrintUsAscii(field, hostname, sizeof(hostname)); } } diff --git a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c index f7bc5dda..0d365bbe 100644 --- a/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c +++ b/Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "SolidSyslogBufferCategories.h" #include "SolidSyslogBufferDefinition.h" @@ -16,7 +17,6 @@ #include "SolidSyslogNullBuffer.h" #include "SolidSyslogPosixMessageQueueBufferErrors.h" #include "SolidSyslogPosixMessageQueueBufferPrivate.h" -#include "SolidSyslogPosixProcessId.h" #include "SolidSyslogPrival.h" const struct SolidSyslogErrorSource PosixMessageQueueBufferErrorSource = {"PosixMessageQueueBuffer"}; @@ -54,7 +54,7 @@ bool PosixMessageQueueBuffer_Initialise( struct SolidSyslogFormatter* name = SolidSyslogFormatter_Create(self->NameStorage, POSIX_MESSAGE_QUEUE_BUFFER_MAX_NAME_SIZE); SolidSyslogFormatter_BoundedString(name, queueNamePrefix, sizeof(queueNamePrefix) - 1U); - SolidSyslogPosixProcessId_Get(name); + SolidSyslogFormatter_Uint32(name, (uint32_t) getpid()); SolidSyslogFormatter_AsciiCharacter(name, '_'); SolidSyslogFormatter_Uint32(name, (uint32_t) slotIndex); diff --git a/Platform/Posix/Source/SolidSyslogPosixProcessId.c b/Platform/Posix/Source/SolidSyslogPosixProcessId.c index 6d9a1803..0e437309 100644 --- a/Platform/Posix/Source/SolidSyslogPosixProcessId.c +++ b/Platform/Posix/Source/SolidSyslogPosixProcessId.c @@ -3,11 +3,12 @@ #include #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; -void SolidSyslogPosixProcessId_Get(struct SolidSyslogFormatter* formatter) +void SolidSyslogPosixProcessId_Get(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_Uint32(formatter, (uint32_t) getpid()); + (void) context; + SolidSyslogHeaderField_Uint32(field, (uint32_t) getpid()); } diff --git a/Platform/Windows/Interface/SolidSyslogWindowsHostname.h b/Platform/Windows/Interface/SolidSyslogWindowsHostname.h index 8174ed8f..13ad44cd 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsHostname.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsHostname.h @@ -5,7 +5,7 @@ EXTERN_C_BEGIN - void SolidSyslogWindowsHostname_Get(struct SolidSyslogFormatter * formatter); + void SolidSyslogWindowsHostname_Get(struct SolidSyslogHeaderField * field, void* context); EXTERN_C_END diff --git a/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h b/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h index 81bee13b..58b5f444 100644 --- a/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h +++ b/Platform/Windows/Interface/SolidSyslogWindowsProcessId.h @@ -5,7 +5,7 @@ EXTERN_C_BEGIN - void SolidSyslogWindowsProcessId_Get(struct SolidSyslogFormatter * formatter); + void SolidSyslogWindowsProcessId_Get(struct SolidSyslogHeaderField * field, void* context); EXTERN_C_END diff --git a/Platform/Windows/Source/SolidSyslogWindowsHostname.c b/Platform/Windows/Source/SolidSyslogWindowsHostname.c index ac4af1c4..e933b122 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsHostname.c +++ b/Platform/Windows/Source/SolidSyslogWindowsHostname.c @@ -1,5 +1,5 @@ #include "SolidSyslogWindowsHostname.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" #include "SolidSyslogWindowsHostnameInternal.h" /* File-local forwarder. Taking the address of an imported Windows API @@ -20,14 +20,16 @@ enum MAX_HOSTNAME_SIZE = 256U }; -void SolidSyslogWindowsHostname_Get(struct SolidSyslogFormatter* formatter) +void SolidSyslogWindowsHostname_Get(struct SolidSyslogHeaderField* field, void* context) { char hostname[MAX_HOSTNAME_SIZE]; DWORD size = sizeof(hostname); + (void) context; + if (WindowsHostname_GetComputerNameExA(ComputerNamePhysicalDnsHostname, hostname, &size) != FALSE) { hostname[sizeof(hostname) - 1U] = '\0'; - SolidSyslogFormatter_PrintUsAsciiString(formatter, hostname, sizeof(hostname)); + SolidSyslogHeaderField_PrintUsAscii(field, hostname, sizeof(hostname)); } } diff --git a/Platform/Windows/Source/SolidSyslogWindowsProcessId.c b/Platform/Windows/Source/SolidSyslogWindowsProcessId.c index d9a5d8f7..8e29d37b 100644 --- a/Platform/Windows/Source/SolidSyslogWindowsProcessId.c +++ b/Platform/Windows/Source/SolidSyslogWindowsProcessId.c @@ -1,5 +1,5 @@ #include "SolidSyslogWindowsProcessId.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" #include "SolidSyslogWindowsProcessIdInternal.h" #include @@ -17,7 +17,8 @@ static DWORD WINAPI WindowsProcessId_CallGetCurrentProcessId(void) return GetCurrentProcessId(); } -void SolidSyslogWindowsProcessId_Get(struct SolidSyslogFormatter* formatter) +void SolidSyslogWindowsProcessId_Get(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_Uint32(formatter, (uint32_t) WindowsProcessId_GetCurrentProcessId()); + (void) context; + SolidSyslogHeaderField_Uint32(field, (uint32_t) WindowsProcessId_GetCurrentProcessId()); } diff --git a/Tests/Bdd/Targets/BddTargetAppNameTest.cpp b/Tests/Bdd/Targets/BddTargetAppNameTest.cpp index 94ad9a00..6c7aeebf 100644 --- a/Tests/Bdd/Targets/BddTargetAppNameTest.cpp +++ b/Tests/Bdd/Targets/BddTargetAppNameTest.cpp @@ -1,5 +1,6 @@ #include "BddTargetAppName.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderFieldPrivate.h" #include "CppUTest/TestHarness.h" enum @@ -12,10 +13,12 @@ TEST_GROUP(BddTargetAppName) { SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(FORMATTER_BUFFER_SIZE)]; struct SolidSyslogFormatter* formatter = nullptr; + struct SolidSyslogHeaderField field{}; void setup() override { formatter = SolidSyslogFormatter_Create(storage, FORMATTER_BUFFER_SIZE); + SolidSyslogHeaderField_FromFormatter(&field, formatter, FORMATTER_BUFFER_SIZE); } [[nodiscard]] const char* formatted() const @@ -29,55 +32,55 @@ TEST_GROUP(BddTargetAppName) TEST(BddTargetAppName, BackslashSeparatorExtractsBaseName) { BddTargetAppName_Set("dir\\binary"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("binary", formatted()); } TEST(BddTargetAppName, ForwardSlashSeparatorExtractsBaseName) { BddTargetAppName_Set("/usr/local/bin/example"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("example", formatted()); } TEST(BddTargetAppName, NoSeparatorReturnsWholeArgument) { BddTargetAppName_Set("example"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("example", formatted()); } TEST(BddTargetAppName, MixedSeparatorsUseRightmost) { BddTargetAppName_Set("C:\\msys64\\home/user/example"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("example", formatted()); } TEST(BddTargetAppName, ExeExtensionIsStripped) { BddTargetAppName_Set("app.exe"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("app", formatted()); } TEST(BddTargetAppName, UpperCaseExeExtensionIsStripped) { BddTargetAppName_Set("APP.EXE"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("APP", formatted()); } TEST(BddTargetAppName, ExeWithPathSeparatorIsStripped) { BddTargetAppName_Set("C:\\bin\\SolidSyslogBddTarget.exe"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("SolidSyslogBddTarget", formatted()); } TEST(BddTargetAppName, NonExeExtensionIsKept) { BddTargetAppName_Set("data.txt"); - BddTargetAppName_Get(formatter); + BddTargetAppName_Get(&field, nullptr); STRCMP_EQUAL("data.txt", formatted()); } diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index 1a1ebf2d..dc645efa 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -76,7 +76,8 @@ TEST_GROUP(BddTargetServiceThread) buffer = SolidSyslogPosixMessageQueueBuffer_Create(SOLIDSYSLOG_MAX_MESSAGE_SIZE, 10); store = SolidSyslogNullStore_Get(); - SolidSyslogConfig config = {buffer, sender, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; + SolidSyslogConfig config = + {buffer, sender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; solidSyslog = SolidSyslog_Create(&config); } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index a7c01ceb..5f38639a 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -51,6 +51,7 @@ set(TEST_SOURCES SolidSyslogNullSdTest.cpp SolidSyslogSdValueTest.cpp SolidSyslogSdElementTest.cpp + SolidSyslogHeaderFieldTest.cpp SolidSyslogNullSenderTest.cpp SolidSyslogNullStoreTest.cpp SolidSyslogNullSecurityPolicyTest.cpp diff --git a/Tests/SolidSyslogHeaderFieldTest.cpp b/Tests/SolidSyslogHeaderFieldTest.cpp new file mode 100644 index 00000000..b7945203 --- /dev/null +++ b/Tests/SolidSyslogHeaderFieldTest.cpp @@ -0,0 +1,104 @@ +#include + +#include "CppUTest/TestHarness.h" +#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" +#include "SolidSyslogHeaderFieldPrivate.h" + +enum +{ + TEST_BUFFER_SIZE = 64 +}; + +#define CHECK_FIELD(expected) STRCMP_EQUAL(expected, SolidSyslogFormatter_AsFormattedBuffer(formatter)) + +// clang-format off +TEST_GROUP(SolidSyslogHeaderField) +{ + SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(TEST_BUFFER_SIZE)]; + struct SolidSyslogFormatter* formatter = nullptr; + struct SolidSyslogHeaderField field{}; + + void setup() override + { + formatter = SolidSyslogFormatter_Create(storage, TEST_BUFFER_SIZE); + } + + void fromFormatter(size_t maxLength) + { + SolidSyslogHeaderField_FromFormatter(&field, formatter, maxLength); + } +}; + +// clang-format on + +TEST(SolidSyslogHeaderField, PrintUsAsciiOfEmptyStringWritesNothing) +{ + fromFormatter(32); + SolidSyslogHeaderField_PrintUsAscii(&field, "", 32); + CHECK_FIELD(""); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiAppendsPrintableAscii) +{ + fromFormatter(32); + SolidSyslogHeaderField_PrintUsAscii(&field, "host01", 32); + CHECK_FIELD("host01"); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiSubstitutesControlCharacter) +{ + fromFormatter(32); + SolidSyslogHeaderField_PrintUsAscii(&field, "a\tb", 32); + CHECK_FIELD("a?b"); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiSubstitutesSpace) +{ + fromFormatter(32); + SolidSyslogHeaderField_PrintUsAscii(&field, "a b", 32); + CHECK_FIELD("a?b"); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiStopsAtNul) +{ + fromFormatter(32); + SolidSyslogHeaderField_PrintUsAscii(&field, "ab\0cd", 5); + CHECK_FIELD("ab"); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiTruncatesAtMaxLength) +{ + fromFormatter(32); + SolidSyslogHeaderField_PrintUsAscii(&field, "abcdef", 3); + CHECK_FIELD("abc"); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiTruncatesAtFieldWidth) +{ + fromFormatter(3); + SolidSyslogHeaderField_PrintUsAscii(&field, "abcdef", 32); + CHECK_FIELD("abc"); +} + +TEST(SolidSyslogHeaderField, PrintUsAsciiBudgetIsSharedAcrossCalls) +{ + fromFormatter(4); + SolidSyslogHeaderField_PrintUsAscii(&field, "ab", 32); + SolidSyslogHeaderField_PrintUsAscii(&field, "cdef", 32); + CHECK_FIELD("abcd"); +} + +TEST(SolidSyslogHeaderField, Uint32AppendsDigits) +{ + fromFormatter(32); + SolidSyslogHeaderField_Uint32(&field, 1234); + CHECK_FIELD("1234"); +} + +TEST(SolidSyslogHeaderField, Uint32OfZeroAppendsZero) +{ + fromFormatter(32); + SolidSyslogHeaderField_Uint32(&field, 0); + CHECK_FIELD("0"); +} diff --git a/Tests/SolidSyslogMessageFormatterTest.cpp b/Tests/SolidSyslogMessageFormatterTest.cpp index af010cec..b40411fd 100644 --- a/Tests/SolidSyslogMessageFormatterTest.cpp +++ b/Tests/SolidSyslogMessageFormatterTest.cpp @@ -43,9 +43,12 @@ TEST_GROUP(SolidSyslogMessageFormatter) context = { MessageFormatterTestClock, StringFake_GetHostname, + nullptr, StringFake_GetAppName, + nullptr, StringFake_GetProcessId, nullptr, + nullptr, 0 }; message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; diff --git a/Tests/SolidSyslogPoolTest.cpp b/Tests/SolidSyslogPoolTest.cpp index 95b023f7..5135520a 100644 --- a/Tests/SolidSyslogPoolTest.cpp +++ b/Tests/SolidSyslogPoolTest.cpp @@ -40,7 +40,7 @@ TEST_GROUP(SolidSyslogPool) { fakeSender = SenderFake_Create(); buffer = SolidSyslogPassthroughBuffer_Create(fakeSender); - config = {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, + config = {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, SolidSyslogNullStore_Get(), nullptr, 0}; } diff --git a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp index 26c3109d..0b49000f 100644 --- a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp +++ b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp @@ -188,7 +188,8 @@ TEST(SolidSyslogPosixMessageQueueBuffer, ServiceSendsMessageWrittenViaLog) { struct SolidSyslogSender* fakeSender = SenderFake_Create(); SolidSyslogStore* nullStore = SolidSyslogNullStore_Get(); - SolidSyslogConfig config = {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; + SolidSyslogConfig config = + {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; struct SolidSyslog* solidSyslog = SolidSyslog_Create(&config); SolidSyslogMessage message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index b20533ed..499163c1 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -112,7 +112,7 @@ TEST_GROUP(SolidSyslog) metaSdCounter = nullptr; metaSd = nullptr; timeQualitySd = nullptr; - config = {buffer, nullptr, nullptr, StringFake_GetHostname, StringFake_GetAppName, StringFake_GetProcessId, store, nullptr, 0}; + config = {buffer, nullptr, nullptr, StringFake_GetHostname, nullptr, StringFake_GetAppName, nullptr, StringFake_GetProcessId, nullptr, store, nullptr, 0}; solidSyslog = SolidSyslog_Create(&config); message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; } @@ -478,7 +478,8 @@ TEST(SolidSyslogTimestamp, TimestampAppearsInCorrectMessageFieldPosition) TEST(SolidSyslog, ServiceSendsMessageReadFromBuffer) { SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); - SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; + SolidSyslogConfig serviceConfig = + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -500,7 +501,7 @@ TEST(SolidSyslog, ServiceSendsBufferedMessageWithNullStore) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* nullStore = SolidSyslogNullStore_Get(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -522,7 +523,7 @@ TEST(SolidSyslog, ServiceSendsFromStoreWhenHasUnsent) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -545,7 +546,7 @@ TEST(SolidSyslog, ServiceMarksSentAfterSuccessfulSend) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -566,7 +567,7 @@ TEST(SolidSyslog, ServiceDoesNotMarkSentOnSendFailure) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -588,7 +589,7 @@ TEST(SolidSyslog, ServiceWritesBufferMessageToStore) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -614,7 +615,7 @@ TEST(SolidSyslog, ServiceSendsStoreMessageNotBufferMessage) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -641,7 +642,7 @@ TEST(SolidSyslog, ServiceDoesNotBypassToSenderWhenNonTransientStoreRejectsWrite) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -664,7 +665,7 @@ TEST(SolidSyslog, ServiceDoesNotSendWhenStoreReadFails) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -687,7 +688,7 @@ TEST(SolidSyslog, ServiceDoesNotMarkSentWhenSendingFromBuffer) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -780,7 +781,7 @@ TEST(SolidSyslog, ServiceDoesNothingWhenStoreIsHalted) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogStore* fakeStore = StoreFake_Create(); SolidSyslogConfig serviceConfig = - {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; + {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; SolidSyslog_Destroy(solidSyslog); solidSyslog = SolidSyslog_Create(&serviceConfig); @@ -801,7 +802,8 @@ TEST(SolidSyslog, ServiceDoesNothingWhenStoreIsHalted) TEST(SolidSyslog, LogAfterDestroyAndRecreateWithNullFunctionsProducesNilvalues) { SolidSyslog_Destroy(solidSyslog); - SolidSyslogConfig nilConfig = {buffer, nullptr, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; + SolidSyslogConfig nilConfig = + {buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; solidSyslog = SolidSyslog_Create(&nilConfig); Log(); CHECK_TIMESTAMP_IS_NILVALUE(); @@ -840,7 +842,7 @@ TEST_GROUP(SolidSyslogLifecycle) [[nodiscard]] SolidSyslogConfig validConfig() const { - return {buffer, sender, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; + return {buffer, sender, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; } }; diff --git a/Tests/SolidSyslogWindowsHostnameTest.cpp b/Tests/SolidSyslogWindowsHostnameTest.cpp index 3e38c86d..6677b745 100644 --- a/Tests/SolidSyslogWindowsHostnameTest.cpp +++ b/Tests/SolidSyslogWindowsHostnameTest.cpp @@ -1,5 +1,6 @@ #include "CppUTest/TestHarness.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderFieldPrivate.h" #include "SolidSyslogWindowsHostname.h" #include "SolidSyslogWindowsHostnameInternal.h" @@ -38,6 +39,7 @@ TEST_GROUP(SolidSyslogWindowsHostname) { SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(FORMATTER_BUFFER_SIZE)]; struct SolidSyslogFormatter* formatter = nullptr; + struct SolidSyslogHeaderField field{}; void setup() override { @@ -45,6 +47,7 @@ TEST_GROUP(SolidSyslogWindowsHostname) fakeReturnValue = TRUE; UT_PTR_SET(WindowsHostname_GetComputerNameExA, FakeGetComputerNameExA); formatter = SolidSyslogFormatter_Create(storage, FORMATTER_BUFFER_SIZE); + SolidSyslogHeaderField_FromFormatter(&field, formatter, FORMATTER_BUFFER_SIZE); } const char* formatted() const @@ -57,21 +60,21 @@ TEST_GROUP(SolidSyslogWindowsHostname) TEST(SolidSyslogWindowsHostname, WritesFakeHostnameIntoFormatter) { - SolidSyslogWindowsHostname_Get(formatter); + SolidSyslogWindowsHostname_Get(&field, nullptr); STRCMP_EQUAL("winhost", formatted()); } TEST(SolidSyslogWindowsHostname, WritesNothingWhenApiFails) { fakeReturnValue = FALSE; - SolidSyslogWindowsHostname_Get(formatter); + SolidSyslogWindowsHostname_Get(&field, nullptr); STRCMP_EQUAL("", formatted()); } TEST(SolidSyslogWindowsHostname, EmptyHostnameProducesEmptyString) { fakeHostname = ""; - SolidSyslogWindowsHostname_Get(formatter); + SolidSyslogWindowsHostname_Get(&field, nullptr); STRCMP_EQUAL("", formatted()); } @@ -83,6 +86,6 @@ TEST(SolidSyslogWindowsHostname, HostnameTooLongForBufferProducesEmptyString) memset(longName, 'x', 260); longName[260] = '\0'; fakeHostname = longName; - SolidSyslogWindowsHostname_Get(formatter); + SolidSyslogWindowsHostname_Get(&field, nullptr); STRCMP_EQUAL("", formatted()); } diff --git a/Tests/SolidSyslogWindowsProcessIdTest.cpp b/Tests/SolidSyslogWindowsProcessIdTest.cpp index 8f9b4787..7327de45 100644 --- a/Tests/SolidSyslogWindowsProcessIdTest.cpp +++ b/Tests/SolidSyslogWindowsProcessIdTest.cpp @@ -1,5 +1,6 @@ #include "CppUTest/TestHarness.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderFieldPrivate.h" #include "SolidSyslogWindowsProcessId.h" #include "SolidSyslogWindowsProcessIdInternal.h" @@ -22,12 +23,14 @@ TEST_GROUP(SolidSyslogWindowsProcessId) { SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(FORMATTER_BUFFER_SIZE)]; struct SolidSyslogFormatter* formatter = nullptr; + struct SolidSyslogHeaderField field{}; void setup() override { fakePid = 4321; UT_PTR_SET(WindowsProcessId_GetCurrentProcessId, FakeGetCurrentProcessId); formatter = SolidSyslogFormatter_Create(storage, FORMATTER_BUFFER_SIZE); + SolidSyslogHeaderField_FromFormatter(&field, formatter, FORMATTER_BUFFER_SIZE); } const char* formatted() const @@ -40,20 +43,20 @@ TEST_GROUP(SolidSyslogWindowsProcessId) TEST(SolidSyslogWindowsProcessId, WritesFakePidAsDecimal) { - SolidSyslogWindowsProcessId_Get(formatter); + SolidSyslogWindowsProcessId_Get(&field, nullptr); STRCMP_EQUAL("4321", formatted()); } TEST(SolidSyslogWindowsProcessId, WritesZeroWhenPidIsZero) { fakePid = 0; - SolidSyslogWindowsProcessId_Get(formatter); + SolidSyslogWindowsProcessId_Get(&field, nullptr); STRCMP_EQUAL("0", formatted()); } TEST(SolidSyslogWindowsProcessId, WritesMaxDwordValueAsDecimal) { fakePid = 0xFFFFFFFFU; - SolidSyslogWindowsProcessId_Get(formatter); + SolidSyslogWindowsProcessId_Get(&field, nullptr); STRCMP_EQUAL("4294967295", formatted()); } diff --git a/Tests/StringFake.c b/Tests/StringFake.c index 85fd6bb1..a6a89236 100644 --- a/Tests/StringFake.c +++ b/Tests/StringFake.c @@ -2,9 +2,9 @@ #include -#include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderField.h" -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; static const char* fakeHostname; static const char* fakeAppName; @@ -22,9 +22,10 @@ void StringFake_SetHostname(const char* hostname) fakeHostname = hostname; } -void StringFake_GetHostname(struct SolidSyslogFormatter* formatter) +void StringFake_GetHostname(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_PrintUsAsciiString(formatter, fakeHostname, strlen(fakeHostname)); + (void) context; + SolidSyslogHeaderField_PrintUsAscii(field, fakeHostname, strlen(fakeHostname)); } void StringFake_SetAppName(const char* appName) @@ -32,9 +33,10 @@ void StringFake_SetAppName(const char* appName) fakeAppName = appName; } -void StringFake_GetAppName(struct SolidSyslogFormatter* formatter) +void StringFake_GetAppName(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_PrintUsAsciiString(formatter, fakeAppName, strlen(fakeAppName)); + (void) context; + SolidSyslogHeaderField_PrintUsAscii(field, fakeAppName, strlen(fakeAppName)); } void StringFake_SetProcessId(const char* procId) @@ -42,7 +44,8 @@ void StringFake_SetProcessId(const char* procId) fakeProcessId = procId; } -void StringFake_GetProcessId(struct SolidSyslogFormatter* formatter) +void StringFake_GetProcessId(struct SolidSyslogHeaderField* field, void* context) { - SolidSyslogFormatter_PrintUsAsciiString(formatter, fakeProcessId, strlen(fakeProcessId)); + (void) context; + SolidSyslogHeaderField_PrintUsAscii(field, fakeProcessId, strlen(fakeProcessId)); } diff --git a/Tests/StringFake.h b/Tests/StringFake.h index 90b8b2e5..5b862483 100644 --- a/Tests/StringFake.h +++ b/Tests/StringFake.h @@ -3,17 +3,17 @@ #include "ExternC.h" -struct SolidSyslogFormatter; +struct SolidSyslogHeaderField; EXTERN_C_BEGIN void StringFake_Reset(void); void StringFake_SetHostname(const char* hostname); - void StringFake_GetHostname(struct SolidSyslogFormatter * formatter); + void StringFake_GetHostname(struct SolidSyslogHeaderField * field, void* context); void StringFake_SetAppName(const char* appName); - void StringFake_GetAppName(struct SolidSyslogFormatter * formatter); + void StringFake_GetAppName(struct SolidSyslogHeaderField * field, void* context); void StringFake_SetProcessId(const char* procId); - void StringFake_GetProcessId(struct SolidSyslogFormatter * formatter); + void StringFake_GetProcessId(struct SolidSyslogHeaderField * field, void* context); EXTERN_C_END diff --git a/Tests/StringFakeTest.cpp b/Tests/StringFakeTest.cpp index 2dd61d2f..386070ac 100644 --- a/Tests/StringFakeTest.cpp +++ b/Tests/StringFakeTest.cpp @@ -1,4 +1,5 @@ #include "SolidSyslogFormatter.h" +#include "SolidSyslogHeaderFieldPrivate.h" #include "StringFake.h" #include "CppUTest/TestHarness.h" @@ -14,10 +15,12 @@ TEST_GROUP(StringFake) { SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(TEST_BUFFER_SIZE)]; SolidSyslogFormatter* formatter; + struct SolidSyslogHeaderField field{}; void setup() override { formatter = SolidSyslogFormatter_Create(storage, TEST_BUFFER_SIZE); + SolidSyslogHeaderField_FromFormatter(&field, formatter, TEST_BUFFER_SIZE); StringFake_Reset(); } }; @@ -26,7 +29,7 @@ TEST_GROUP(StringFake) TEST(StringFake, ReturnsEmptyStringAfterReset) { - StringFake_GetHostname(formatter); + StringFake_GetHostname(&field, nullptr); STRCMP_EQUAL("", SolidSyslogFormatter_AsFormattedBuffer(formatter)); LONGS_EQUAL(0, SolidSyslogFormatter_Length(formatter)); } @@ -34,7 +37,7 @@ TEST(StringFake, ReturnsEmptyStringAfterReset) TEST(StringFake, ReturnsConfiguredHostname) { StringFake_SetHostname("MyHost"); - StringFake_GetHostname(formatter); + StringFake_GetHostname(&field, nullptr); STRCMP_EQUAL("MyHost", SolidSyslogFormatter_AsFormattedBuffer(formatter)); LONGS_EQUAL(6, SolidSyslogFormatter_Length(formatter)); } diff --git a/docs/rfc-compliance.md b/docs/rfc-compliance.md index 00eb2d94..c9c0ea78 100644 --- a/docs/rfc-compliance.md +++ b/docs/rfc-compliance.md @@ -23,10 +23,10 @@ Status key: | 6.2.5 | PROCID — max 128 chars, PRINTUSASCII | Supported | Truncated to 128. Non-PRINTUSASCII bytes substituted with `?` | | 6.2.6 | MSGID — max 32 chars, PRINTUSASCII | Supported | Truncated to 32. Non-PRINTUSASCII bytes substituted with `?` | | 6.3 | STRUCTURED-DATA — SD-ELEMENTs or NILVALUE | Supported | Extensible via `SolidSyslogStructuredData` vtable | -| 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogFormatter_EscapedString` — RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9); `OriginSd` escapes software, swVersion, enterpriseId, and each ip via this primitive; `MetaSd` escapes language via the integrator's `SolidSyslogStringFunction` callback that wraps the same primitive. SD-NAME / SD-ID syntax validation only matters once callers can supply names — landed under [E14](https://github.com/DavidCozens/solid-syslog/issues/64) (Custom Structured Data); the standard SDs (meta / timeQuality / origin) use compile-time-constant names | +| 6.3.3 | SD-PARAM value escaping (`]`, `\`, `"`) | Supported | `SolidSyslogFormatter_EscapedString` — RFC 3629 UTF-8 validated, ill-formed input substituted per-byte with U+FFFD (Unicode §3.9); `OriginSd` escapes software, swVersion, enterpriseId, and each ip via this primitive; `MetaSd` escapes language via the integrator's `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies the same escaping. SD-NAME / SD-ID syntax validation only matters once callers can supply names — landed under [E14](https://github.com/DavidCozens/solid-syslog/issues/64) (Custom Structured Data); the standard SDs (meta / timeQuality / origin) use compile-time-constant names | | 6.3.3 | timeQuality SD — tzKnown, isSynced, syncAccuracy | Supported | `SolidSyslogTimeQualitySd` | | 6.3.4, 7.2 | origin SD — software, swVersion, enterpriseId, ip | Supported | `SolidSyslogOriginSd` covers all four §7.2 parameters. `software`, `swVersion`, and `enterpriseId` are static strings supplied via `SolidSyslogOriginSdConfig`; each is escaped per §6.3.3 via `SolidSyslogFormatter_EscapedString` and pre-formatted at Create time into an internal storage buffer. `ip` is repeatable per RFC 5424 §7.2 and sourced via two callbacks (`SolidSyslogOriginIpCountFunction`, `SolidSyslogOriginIpAtFunction`) so multi-homed hosts can reflect runtime address changes; the library asks for a count then loops 0..N-1 framing each `ip="…"` token (with a leading space) while the integrator's at-callback writes one escaped IP value per call (this avoids a formatter rewind primitive). All four parameters are independently optional — a NULL field or NULL callback omits the corresponding parameter from the SD-ELEMENT. Per-IP value capped at 64 chars via `_EscapedString`; no library-side cap on the IP count (bounded by `SOLIDSYSLOG_MAX_MESSAGE_SIZE`). Bare `[origin]` with no parameters is RFC-legal (§7.2 marks all params OPTIONAL, no SHOULD enforcement) and is what the library emits when the integrator wires nothing | -| 6.3.5, 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogStringFunction` callback that writes through `SolidSyslogFormatter_EscapedString` to satisfy SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | +| 6.3.5, 7.3 | meta SD — sequenceId, sysUpTime, language | Supported | `SolidSyslogMetaSd` covers all three IANA-registered parameters. `sequenceId` (§7.3.1) sourced via an injected `SolidSyslogAtomicCounter`. `sysUpTime` (§7.3.2 / RFC 3418 `TimeTicks`) sourced via a `SolidSyslogSysUpTimeFunction` callback returning `uint32_t` hundredths; reference platform integrations are `SolidSyslogPosixSysUpTime` (`clock_gettime(CLOCK_BOOTTIME)`) and `SolidSyslogWindowsSysUpTime` (`GetTickCount64`), with the cast to `uint32_t` providing RFC 3418's natural wrap. `language` (§7.3.3 / BCP 47) sourced via a `SolidSyslogSdValueFunction` callback streaming into a `SolidSyslogSdValue`, which applies SD-PARAM-VALUE escaping per §6.3.3. All three independently optional — a NULL field in `SolidSyslogMetaSdConfig` omits that parameter from the SD-ELEMENT | | 6.3.5, 7.3.1 | meta SD — sequenceId wraps at 2147483647 to 1 | Supported | `SolidSyslogAtomicCounter` wraps via CAS-loop in [1, 2³¹ - 1]; never returns 0; never above max. AtomicCounter is a vtable abstraction — concrete impls are `SolidSyslogStdAtomicCounter` (C11 `` + `atomic_compare_exchange_strong_explicit`) on POSIX/clang/gcc/modern MSVC, and `SolidSyslogWindowsAtomicCounter` (`volatile LONG` + `InterlockedCompareExchange`) on legacy MSVC. The integrator picks one at setup time by calling the relevant platform's `_Create`; CMake's `HAVE_STDATOMIC_H` / `HAVE_WINDOWS_INTERLOCKED` checks gate which platform sources are compiled. sequenceId is assigned at the point of message raise (application-layer originator), preserving end-to-end loss-detection across the internal buffer / store-and-forward / transport pipeline. Trade-off: under concurrent raise from multiple threads, a small reorder window may occur in transmitted IDs (adjacent IDs may invert, since buffer/transport scheduling between raise and wire is not under library control). All IDs remain unique and non-zero — SIEMs performing gap detection identify message loss correctly; SIEMs requiring strict monotonic ordering should sort by timestamp | | 6.4 | MSG — UTF-8 preferred | Supported | RFC 3629 UTF-8 validated at the formatter primitives (`SolidSyslogFormatter_BoundedString`), with ill-formed input substituted per-byte with U+FFFD (Unicode §3.9). MSG is prefixed with the §6.4 UTF-8 BOM (`%xEF.BB.BF`) unconditionally; if the caller's body already begins with a BOM it is stripped so the wire frame contains exactly one ([S12.13](https://github.com/DavidCozens/solid-syslog/issues/219)). Truncation preserves codepoint boundaries at both layers: the formatter clips at `SOLIDSYSLOG_MAX_MESSAGE_SIZE` without splitting a codepoint ([S12.10](https://github.com/DavidCozens/solid-syslog/issues/121)), and on UDP the sender walks back over any partial codepoint when the kernel reports `EMSGSIZE` for the path MTU ([S12.12](https://github.com/DavidCozens/solid-syslog/issues/210)). TCP/TLS streams fragment transparently at the transport layer and so do not need a path-MTU trim | | 8.1 | Message size — max 2048 recommended | Supported | Default `SOLIDSYSLOG_MAX_MESSAGE_SIZE` = 2048, matching the §8.1 SHOULD value. Per-target override via a CMake variable is planned in [E21 #217](https://github.com/DavidCozens/solid-syslog/issues/217) for memory-constrained MCUs | diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 3a942a3a..c8b486a6 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -103,19 +103,19 @@ misra-c2012-18.7:Core/Source/SolidSyslogFormatter.c:12 # D.006 — Rule 11.8: const-strip false-positives + platform-API (Winsock, lwIP) # See docs/misra-deviations.md#d006 misra-c2012-11.8:Core/Source/BlockSequence.c:465 -misra-c2012-11.8:Core/Source/SolidSyslog.c:53 -misra-c2012-11.8:Core/Source/SolidSyslog.c:54 -misra-c2012-11.8:Core/Source/SolidSyslog.c:55 -misra-c2012-11.8:Core/Source/SolidSyslog.c:56 -misra-c2012-11.8:Core/Source/SolidSyslog.c:57 -misra-c2012-11.8:Core/Source/SolidSyslog.c:58 -misra-c2012-11.8:Core/Source/SolidSyslog.c:59 -misra-c2012-11.8:Core/Source/SolidSyslog.c:60 -misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:58 +misra-c2012-11.8:Core/Source/SolidSyslog.c:65 +misra-c2012-11.8:Core/Source/SolidSyslog.c:66 +misra-c2012-11.8:Core/Source/SolidSyslog.c:67 +misra-c2012-11.8:Core/Source/SolidSyslog.c:68 +misra-c2012-11.8:Core/Source/SolidSyslog.c:69 +misra-c2012-11.8:Core/Source/SolidSyslog.c:70 +misra-c2012-11.8:Core/Source/SolidSyslog.c:71 +misra-c2012-11.8:Core/Source/SolidSyslog.c:72 misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:60 misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:62 -misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:64 -misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:68 +misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:69 +misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:76 +misra-c2012-11.8:Core/Source/SolidSyslogMessageFormatter.c:85 misra-c2012-11.8:Core/Source/SolidSyslogBlockStoreStatic.c:46 misra-c2012-11.8:Core/Source/SolidSyslogBlockStoreStatic.c:117 misra-c2012-11.8:Core/Source/SolidSyslogBlockStoreStatic.c:143 @@ -171,7 +171,7 @@ misra-c2012-5.7:Core/Interface/SolidSyslogFormatter.h:14 misra-c2012-5.7:Core/Interface/SolidSyslogSecurityPolicyDefinition.h:13 misra-c2012-5.7:Core/Interface/SolidSyslogTimeQuality.h:12 misra-c2012-5.7:Core/Interface/SolidSyslogTransport.h:5 -misra-c2012-5.7:Core/Source/SolidSyslogMessageFormatter.c:20 +misra-c2012-5.7:Core/Source/SolidSyslogMessageFormatter.c:21 misra-c2012-5.7:Core/Source/SolidSyslogCircularBuffer.c:19 misra-c2012-5.7:Core/Source/SolidSyslogCrc16.c:12 misra-c2012-5.7:Core/Source/SolidSyslogCrc16Policy.c:10 From 2fdb69e89887e49d673395d321232b493eef2db1 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 09:07:07 +0000 Subject: [PATCH 2/3] docs: update DEVLOG for S14.07 header-field writer Co-Authored-By: Claude Opus 4.8 (1M context) --- DEVLOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index 275ca80c..a44a71b8 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -14551,3 +14551,45 @@ MISRA rule — different category, doesn't set precedent. ### Open questions - None outstanding. + +## 2026-06-06 — S14.07: move header-field callbacks off the formatter + +### Decisions +- **New `SolidSyslogHeaderField` writer** (TDD'd first, 10 tests): opaque, + stack-transient US-ASCII sink for the RFC 5424 HOSTNAME / APP-NAME / PROCID + fields. Wraps the message-buffer formatter; exposes `_PrintUsAscii(source, + maxLength)` (printable-US-ASCII filter, space substituted, NUL-stopped, bounded + by maxLength AND the field width) and `_Uint32` (PROCID). A different charset + rule from SD escaping → its own type, not `SolidSyslogSdValue`. Name chosen by + David over `SolidSyslogToken` / `SolidSyslogField`. +- **Callbacks flipped** `SolidSyslogStringFunction` → `SolidSyslogHeaderFieldFunction` + (`void(SolidSyslogHeaderField*, void* context)`) in its own header, with three + paired context fields on `SolidSyslogConfig` + the MessageFormatter context. + MessageFormatter builds one HeaderField per field (cap = field-storage size − 1, + preserving the old scratch-formatter's null-slot width) and passes it; the + scratch-field + re-filter is gone. Byte-identical (existing tests stand). +- **`SolidSyslogStringFunction.h` retired** — it was the last public consumer of + `SolidSyslogFormatter`, clearing the way for S14.08 to make the formatter + fully private. +- **Surprises:** (1) `PosixMessageQueueBuffer` coincidentally reused the PROCID + helper to stamp its queue name with the pid — decoupled to a direct `getpid()` + + `Formatter_Uint32` rather than route a queue name through the header-field + helper (which would have forced a Platform source to include a Core/Source + private header). (2) ~16 positional `SolidSyslogConfig{}` test literals + silently misaligned once three context fields were interleaved (`store` landing + in a `void*` context slot) — fixed by inserting the context nullptrs. +- Migrated the Posix/Windows hostname & processId helpers + all BDD header-field + callbacks (BddTargetAppName, FreeRtos/FreeRtosLwip GetHostname, pipeline + GetAppName). Updated the two stale `docs/rfc-compliance.md` `language` + references (already wrong since S14.04) to `SolidSyslogSdValueFunction`. +- Checks: debug green (1469 tests), BddTargetTests 66/66; clang-format applied; + cppcheck-MISRA exit 0 (anchors moved: SolidSyslog.c 11.8 ×8, MessageFormatter.c + 11.8 ×5 + 5.7 20→21; renumber clean). + +### Deferred +- Moving `SolidSyslogFormatter.h` out of the public interface is S14.08, along + with the wholesale CLAUDE.md public-header-table revision (the new HeaderField / + HeaderFieldFunction rows land there). + +### Open questions +- None outstanding. From 044eff19793d1f308f05464135dd583fc391f004 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 11:02:05 +0000 Subject: [PATCH 3/3] fix: guard SolidSyslogHeaderField_Uint32 against field-budget underflow A header-field callback is arbitrary user code that may make any number of mixed _PrintUsAscii / _Uint32 appends on one field, so the field-width budget must hold across calls. _Uint32 subtracted the consumed bytes from Remaining unconditionally, underflowing size_t when a number overran the remainder. Skip the write once the field is full and clamp Remaining to zero instead of underflowing. Two tests cover the mixed-append cap. Co-Authored-By: Claude Opus 4.8 (1M context) --- Core/Source/SolidSyslogHeaderField.c | 17 +++++++++++++---- DEVLOG.md | 11 +++++++++++ Tests/SolidSyslogHeaderFieldTest.cpp | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/Core/Source/SolidSyslogHeaderField.c b/Core/Source/SolidSyslogHeaderField.c index 2e87ef0b..3ab2aaba 100644 --- a/Core/Source/SolidSyslogHeaderField.c +++ b/Core/Source/SolidSyslogHeaderField.c @@ -27,10 +27,19 @@ void SolidSyslogHeaderField_PrintUsAscii(struct SolidSyslogHeaderField* field, c void SolidSyslogHeaderField_Uint32(struct SolidSyslogHeaderField* field, uint32_t value) { - size_t before = SolidSyslogFormatter_Length(field->Formatter); - - SolidSyslogFormatter_Uint32(field->Formatter, value); - field->Remaining -= HeaderField_Consumed(field, before); + /* Unlike _PrintUsAscii, _Uint32 cannot pre-clamp its output to the field + * budget (the formatter writes the whole number), so guard the budget + * directly: skip once the field is full, and clamp to zero rather than + * letting the size_t subtraction underflow when this number alone overruns + * the remainder. A header-field callback may mix any number of appends. */ + if (field->Remaining > 0U) + { + size_t before = SolidSyslogFormatter_Length(field->Formatter); + + SolidSyslogFormatter_Uint32(field->Formatter, value); + size_t consumed = HeaderField_Consumed(field, before); + field->Remaining = (consumed >= field->Remaining) ? 0U : (field->Remaining - consumed); + } } static inline size_t HeaderField_Consumed(const struct SolidSyslogHeaderField* field, size_t before) diff --git a/DEVLOG.md b/DEVLOG.md index a44a71b8..b4680980 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -14593,3 +14593,14 @@ MISRA rule — different category, doesn't set precedent. ### Open questions - None outstanding. + +### S14.07 follow-up (CodeRabbit review) +- `SolidSyslogHeaderField_Uint32` could underflow `Remaining` (size_t) if a number + overran the field budget. Initially looked unreachable (PROCID = a single ≤10-digit + uint32 ≤ 128 cap), but the header-field callbacks are arbitrary user code that may make + any number of mixed `_PrintUsAscii` / `_Uint32` appends on one field — so the budget must + hold across calls and `_Uint32` must respect it. Added the guard (skip when the field is + full; clamp to zero instead of underflowing) + two tests for the mixed-append cap. RFC + 5424 §6 ABNF is the source of the per-field caps (HOSTNAME 255 / APP-NAME 48 / PROCID 128 + / MSGID 32), and streaming straight to the message buffer is exactly why the writer must + carry the cap explicitly now (the old scratch formatter enforced it via its capacity). diff --git a/Tests/SolidSyslogHeaderFieldTest.cpp b/Tests/SolidSyslogHeaderFieldTest.cpp index b7945203..cc69d73f 100644 --- a/Tests/SolidSyslogHeaderFieldTest.cpp +++ b/Tests/SolidSyslogHeaderFieldTest.cpp @@ -102,3 +102,19 @@ TEST(SolidSyslogHeaderField, Uint32OfZeroAppendsZero) SolidSyslogHeaderField_Uint32(&field, 0); CHECK_FIELD("0"); } + +TEST(SolidSyslogHeaderField, Uint32IsDroppedOnceFieldWidthIsExhausted) +{ + fromFormatter(2); + SolidSyslogHeaderField_PrintUsAscii(&field, "ab", 32); + SolidSyslogHeaderField_Uint32(&field, 9); + CHECK_FIELD("ab"); +} + +TEST(SolidSyslogHeaderField, Uint32ExhaustsBudgetSoLaterAppendsAreDropped) +{ + fromFormatter(3); + SolidSyslogHeaderField_Uint32(&field, 123); + SolidSyslogHeaderField_PrintUsAscii(&field, "x", 32); + CHECK_FIELD("123"); +}