From 7a7c9e134bca8dc89d192238b6ee29bb3762b4b7 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 06:47:03 +0000 Subject: [PATCH 1/2] feat: S14.05 migrate OriginSd onto the SD writer Rewrite OriginSd_Format to emit software / swVersion / enterpriseId via SolidSyslogSdElement + SolidSyslogSdValue_BoundedString (escaped, decoded- length-capped as before) and ip via _Param -> the SdValue handed to GetIpAt (byte-identical to the old raw SolidSyslogFormatter framing; existing OriginSd tests are the safety net), matching the S14.03/S14.04 migrations. Convert SolidSyslogOriginIpAtFunction to void(SolidSyslogSdValue*, void* context, size_t index), with a paired OriginSdConfig.IpContext. Routing the ip value through the SdElement param sink makes the library own the escaping unconditionally, closing the ip SD-injection vector. With the language vector closed in S14.04, S12.32 (#533) is now fully done. Take the #346 eliminate-the-scratch path: the static fields are emitted straight into the element at Format time instead of being pre-formatted into a shared scratch blob at Create. The OriginSd struct now borrows the three config strings directly; FormattedStorage and the ORIGIN_{LITERAL_BYTES,CONTENT_MAX, FORMATTED_MAX} sizing enums are gone. No shared SD scratch buffer remains, so concurrent-Log reentrancy holds by construction. The shared BddTargetIps_At moves to SolidSyslogSdValue_BoundedString. Co-Authored-By: Claude Opus 4.8 (1M context) --- Bdd/Targets/Common/BddTargetIps.c | 7 +- Bdd/Targets/Common/BddTargetIps.h | 4 +- Core/Interface/SolidSyslogOriginSd.h | 5 +- Core/Source/SolidSyslogOriginSd.c | 134 ++++++++--------------- Core/Source/SolidSyslogOriginSdPrivate.h | 14 +-- Tests/Bdd/Targets/BddTargetIpsTest.cpp | 7 +- Tests/SolidSyslogOriginSdTest.cpp | 26 ++++- misra_suppressions.txt | 4 +- 8 files changed, 89 insertions(+), 112 deletions(-) diff --git a/Bdd/Targets/Common/BddTargetIps.c b/Bdd/Targets/Common/BddTargetIps.c index 22b98010..5b29fdfb 100644 --- a/Bdd/Targets/Common/BddTargetIps.c +++ b/Bdd/Targets/Common/BddTargetIps.c @@ -1,5 +1,5 @@ #include "BddTargetIps.h" -#include "SolidSyslogFormatter.h" +#include "SolidSyslogSdValue.h" enum { @@ -23,7 +23,8 @@ size_t BddTargetIps_Count(void) return sizeof(BDD_TARGET_IPS) / sizeof(BDD_TARGET_IPS[0]); } -void BddTargetIps_At(struct SolidSyslogFormatter* formatter, size_t index) +void BddTargetIps_At(struct SolidSyslogSdValue* value, void* context, size_t index) { - SolidSyslogFormatter_EscapedString(formatter, BDD_TARGET_IPS[index], BDD_TARGET_IP_MAX); + (void) context; + SolidSyslogSdValue_BoundedString(value, BDD_TARGET_IPS[index], BDD_TARGET_IP_MAX); } diff --git a/Bdd/Targets/Common/BddTargetIps.h b/Bdd/Targets/Common/BddTargetIps.h index cea771bd..d46ce2e1 100644 --- a/Bdd/Targets/Common/BddTargetIps.h +++ b/Bdd/Targets/Common/BddTargetIps.h @@ -7,10 +7,10 @@ EXTERN_C_BEGIN - struct SolidSyslogFormatter; + struct SolidSyslogSdValue; size_t BddTargetIps_Count(void); - void BddTargetIps_At(struct SolidSyslogFormatter * formatter, size_t index); + void BddTargetIps_At(struct SolidSyslogSdValue * value, void* context, size_t index); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogOriginSd.h b/Core/Interface/SolidSyslogOriginSd.h index 810b4582..21fd8dfe 100644 --- a/Core/Interface/SolidSyslogOriginSd.h +++ b/Core/Interface/SolidSyslogOriginSd.h @@ -7,11 +7,11 @@ EXTERN_C_BEGIN - struct SolidSyslogFormatter; + struct SolidSyslogSdValue; struct SolidSyslogStructuredData; typedef size_t (*SolidSyslogOriginIpCountFunction)(void); - typedef void (*SolidSyslogOriginIpAtFunction)(struct SolidSyslogFormatter* formatter, size_t index); + typedef void (*SolidSyslogOriginIpAtFunction)(struct SolidSyslogSdValue* value, void* context, size_t index); struct SolidSyslogOriginSdConfig { @@ -20,6 +20,7 @@ EXTERN_C_BEGIN const char* EnterpriseId; SolidSyslogOriginIpCountFunction GetIpCount; SolidSyslogOriginIpAtFunction GetIpAt; + void* IpContext; }; struct SolidSyslogStructuredData* SolidSyslogOriginSd_Create(const struct SolidSyslogOriginSdConfig* config); diff --git a/Core/Source/SolidSyslogOriginSd.c b/Core/Source/SolidSyslogOriginSd.c index 75238990..ffd2442f 100644 --- a/Core/Source/SolidSyslogOriginSd.c +++ b/Core/Source/SolidSyslogOriginSd.c @@ -3,69 +3,57 @@ #include #include "SolidSyslogError.h" -#include "SolidSyslogFormatter.h" #include "SolidSyslogNullSd.h" #include "SolidSyslogOriginSdErrors.h" #include "SolidSyslogOriginSdPrivate.h" +#include "SolidSyslogSdElementPrivate.h" +#include "SolidSyslogSdValue.h" #include "SolidSyslogStructuredDataDefinition.h" const struct SolidSyslogErrorSource OriginSdErrorSource = {"OriginSd"}; +struct SolidSyslogFormatter; + static void OriginSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter); static inline struct SolidSyslogOriginSd* OriginSd_SelfFromBase(struct SolidSyslogStructuredData* base); -static inline void OriginSd_PreFormatStaticPrefix( - struct SolidSyslogOriginSd* self, - const struct SolidSyslogOriginSdConfig* config -); -static inline void OriginSd_EmitSoftware( - struct SolidSyslogFormatter* f, - const struct SolidSyslogOriginSdConfig* config -); -static inline void OriginSd_EmitSwVersion( - struct SolidSyslogFormatter* f, - const struct SolidSyslogOriginSdConfig* config -); -static inline void OriginSd_EmitEnterpriseId( - struct SolidSyslogFormatter* f, - const struct SolidSyslogOriginSdConfig* config -); -static inline void OriginSd_EmitIps(struct SolidSyslogFormatter* formatter, const struct SolidSyslogOriginSd* self); -static inline void OriginSd_EmitIp( - struct SolidSyslogFormatter* formatter, - const struct SolidSyslogOriginSd* self, - size_t index -); +static inline void OriginSd_EmitSoftware(struct SolidSyslogSdElement* element, const char* software); +static inline void OriginSd_EmitSwVersion(struct SolidSyslogSdElement* element, const char* swVersion); +static inline void OriginSd_EmitEnterpriseId(struct SolidSyslogSdElement* element, const char* enterpriseId); +static inline void OriginSd_EmitIps(struct SolidSyslogSdElement* element, const struct SolidSyslogOriginSd* self); void OriginSd_Initialise(struct SolidSyslogStructuredData* base, const struct SolidSyslogOriginSdConfig* config) { struct SolidSyslogOriginSd* self = OriginSd_SelfFromBase(base); self->Base.Format = OriginSd_Format; + self->Software = config->Software; + self->SwVersion = config->SwVersion; + self->EnterpriseId = config->EnterpriseId; self->GetIpCount = config->GetIpCount; self->GetIpAt = config->GetIpAt; - OriginSd_PreFormatStaticPrefix(self, config); + self->IpContext = config->IpContext; } void OriginSd_Cleanup(struct SolidSyslogStructuredData* base) { /* Overwrite the abstract base with the shared NullSd vtable so use-after-destroy - * is a safe no-op. Derived fields (incl. the pre-formatted static-prefix Formatter - * storage) are private to this TU; the next _Initialise rebuilds them. */ + * is a safe no-op. Derived fields are private to this TU; the next _Initialise + * overwrites them. */ *base = *SolidSyslogNullSd_Get(); } static void OriginSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogFormatter* formatter) { struct SolidSyslogOriginSd* self = OriginSd_SelfFromBase(base); - struct SolidSyslogFormatter* preformat = SolidSyslogFormatter_FromStorage(self->FormattedStorage); - - SolidSyslogFormatter_BoundedString( - formatter, - SolidSyslogFormatter_AsFormattedBuffer(preformat), - SolidSyslogFormatter_Length(preformat) - ); - OriginSd_EmitIps(formatter, self); - SolidSyslogFormatter_AsciiCharacter(formatter, ']'); + struct SolidSyslogSdElement element; + + SolidSyslogSdElement_FromFormatter(&element, formatter); + SolidSyslogSdElement_Begin(&element, "origin", 0U); + OriginSd_EmitSoftware(&element, self->Software); + OriginSd_EmitSwVersion(&element, self->SwVersion); + OriginSd_EmitEnterpriseId(&element, self->EnterpriseId); + OriginSd_EmitIps(&element, self); + SolidSyslogSdElement_End(&element); } static inline struct SolidSyslogOriginSd* OriginSd_SelfFromBase(struct SolidSyslogStructuredData* base) @@ -73,80 +61,50 @@ static inline struct SolidSyslogOriginSd* OriginSd_SelfFromBase(struct SolidSysl return (struct SolidSyslogOriginSd*) base; } -static inline void OriginSd_PreFormatStaticPrefix( - struct SolidSyslogOriginSd* self, - const struct SolidSyslogOriginSdConfig* config -) +static inline void OriginSd_EmitSoftware(struct SolidSyslogSdElement* element, const char* software) { - static const char sdPrefix[] = "[origin"; - struct SolidSyslogFormatter* f = SolidSyslogFormatter_Create(self->FormattedStorage, ORIGIN_FORMATTED_MAX); - - SolidSyslogFormatter_BoundedString(f, sdPrefix, sizeof(sdPrefix) - 1U); - OriginSd_EmitSoftware(f, config); - OriginSd_EmitSwVersion(f, config); - OriginSd_EmitEnterpriseId(f, config); - /* closing ']' deferred to OriginSd_Format() so per-message ip params can be spliced in */ -} - -static inline void OriginSd_EmitSoftware(struct SolidSyslogFormatter* f, const struct SolidSyslogOriginSdConfig* config) -{ - if (config->Software != NULL) + if (software != NULL) { - static const char softwareSd[] = " software=\""; - SolidSyslogFormatter_BoundedString(f, softwareSd, sizeof(softwareSd) - 1U); - SolidSyslogFormatter_EscapedString(f, config->Software, ORIGIN_SOFTWARE_MAX); - SolidSyslogFormatter_AsciiCharacter(f, '"'); + SolidSyslogSdValue_BoundedString( + SolidSyslogSdElement_Param(element, "software"), + software, + ORIGIN_SOFTWARE_MAX + ); } } -static inline void OriginSd_EmitSwVersion( - struct SolidSyslogFormatter* f, - const struct SolidSyslogOriginSdConfig* config -) +static inline void OriginSd_EmitSwVersion(struct SolidSyslogSdElement* element, const char* swVersion) { - if (config->SwVersion != NULL) + if (swVersion != NULL) { - static const char swVersionSd[] = " swVersion=\""; - SolidSyslogFormatter_BoundedString(f, swVersionSd, sizeof(swVersionSd) - 1U); - SolidSyslogFormatter_EscapedString(f, config->SwVersion, ORIGIN_SWVERSION_MAX); - SolidSyslogFormatter_AsciiCharacter(f, '"'); + SolidSyslogSdValue_BoundedString( + SolidSyslogSdElement_Param(element, "swVersion"), + swVersion, + ORIGIN_SWVERSION_MAX + ); } } -static inline void OriginSd_EmitEnterpriseId( - struct SolidSyslogFormatter* f, - const struct SolidSyslogOriginSdConfig* config -) +static inline void OriginSd_EmitEnterpriseId(struct SolidSyslogSdElement* element, const char* enterpriseId) { - if (config->EnterpriseId != NULL) + if (enterpriseId != NULL) { - static const char enterpriseIdSd[] = " enterpriseId=\""; - SolidSyslogFormatter_BoundedString(f, enterpriseIdSd, sizeof(enterpriseIdSd) - 1U); - SolidSyslogFormatter_EscapedString(f, config->EnterpriseId, ORIGIN_ENTERPRISE_ID_MAX); - SolidSyslogFormatter_AsciiCharacter(f, '"'); + SolidSyslogSdValue_BoundedString( + SolidSyslogSdElement_Param(element, "enterpriseId"), + enterpriseId, + ORIGIN_ENTERPRISE_ID_MAX + ); } } -static inline void OriginSd_EmitIps(struct SolidSyslogFormatter* formatter, const struct SolidSyslogOriginSd* self) +static inline void OriginSd_EmitIps(struct SolidSyslogSdElement* element, const struct SolidSyslogOriginSd* self) { if ((self->GetIpCount != NULL) && (self->GetIpAt != NULL)) { size_t count = self->GetIpCount(); for (size_t i = 0; i < count; i++) { - OriginSd_EmitIp(formatter, self, i); + self->GetIpAt(SolidSyslogSdElement_Param(element, "ip"), self->IpContext, i); } } } - -static inline void OriginSd_EmitIp( - struct SolidSyslogFormatter* formatter, - const struct SolidSyslogOriginSd* self, - size_t index -) -{ - static const char ipSd[] = " ip=\""; - SolidSyslogFormatter_BoundedString(formatter, ipSd, sizeof(ipSd) - 1U); - self->GetIpAt(formatter, index); - SolidSyslogFormatter_AsciiCharacter(formatter, '"'); -} diff --git a/Core/Source/SolidSyslogOriginSdPrivate.h b/Core/Source/SolidSyslogOriginSdPrivate.h index b1835397..f0a1eadc 100644 --- a/Core/Source/SolidSyslogOriginSdPrivate.h +++ b/Core/Source/SolidSyslogOriginSdPrivate.h @@ -4,7 +4,6 @@ #include #include "SolidSyslogError.h" -#include "SolidSyslogFormatter.h" #include "SolidSyslogOriginSd.h" #include "SolidSyslogOriginSdErrors.h" #include "SolidSyslogPrival.h" @@ -15,21 +14,18 @@ enum ORIGIN_SOFTWARE_MAX = 48, ORIGIN_SWVERSION_MAX = 32, ORIGIN_ENTERPRISE_ID_MAX = 64, - ORIGIN_IP_MAX = 64, - ORIGIN_LITERAL_BYTES = - 48, /* [origin software="" swVersion="" enterpriseId="" — closing ']' deferred to per-message OriginSd_Format */ - ORIGIN_CONTENT_MAX = ORIGIN_LITERAL_BYTES + SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_SOFTWARE_MAX) + - SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_SWVERSION_MAX) + - SOLIDSYSLOG_ESCAPED_MAX_SIZE(ORIGIN_ENTERPRISE_ID_MAX), - ORIGIN_FORMATTED_MAX = ORIGIN_CONTENT_MAX + 1 /* null terminator */ + ORIGIN_IP_MAX = 64 }; struct SolidSyslogOriginSd { struct SolidSyslogStructuredData Base; + const char* Software; + const char* SwVersion; + const char* EnterpriseId; SolidSyslogOriginIpCountFunction GetIpCount; SolidSyslogOriginIpAtFunction GetIpAt; - SolidSyslogFormatterStorage FormattedStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(ORIGIN_FORMATTED_MAX)]; + void* IpContext; }; void OriginSd_Initialise(struct SolidSyslogStructuredData* base, const struct SolidSyslogOriginSdConfig* config); diff --git a/Tests/Bdd/Targets/BddTargetIpsTest.cpp b/Tests/Bdd/Targets/BddTargetIpsTest.cpp index d3d7207f..33a4d714 100644 --- a/Tests/Bdd/Targets/BddTargetIpsTest.cpp +++ b/Tests/Bdd/Targets/BddTargetIpsTest.cpp @@ -1,5 +1,7 @@ #include "BddTargetIps.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogSdValue.h" +#include "SolidSyslogSdValuePrivate.h" #include "CppUTest/TestHarness.h" enum @@ -12,10 +14,12 @@ TEST_GROUP(BddTargetIps) { SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(FORMATTER_BUFFER_SIZE)]; struct SolidSyslogFormatter* formatter = nullptr; + struct SolidSyslogSdValue value{}; void setup() override { formatter = SolidSyslogFormatter_Create(storage, FORMATTER_BUFFER_SIZE); + SolidSyslogSdValue_FromFormatter(&value, formatter); } [[nodiscard]] const char* formatted() const @@ -33,6 +37,7 @@ TEST(BddTargetIps, CountIsOne) TEST(BddTargetIps, AtZeroEmitsTheConfiguredIp) { - BddTargetIps_At(formatter, 0); + BddTargetIps_At(&value, nullptr, 0); + SolidSyslogSdValue_Close(&value); STRCMP_EQUAL("192.0.2.1", formatted()); } diff --git a/Tests/SolidSyslogOriginSdTest.cpp b/Tests/SolidSyslogOriginSdTest.cpp index 2ad429e7..383d0d14 100644 --- a/Tests/SolidSyslogOriginSdTest.cpp +++ b/Tests/SolidSyslogOriginSdTest.cpp @@ -9,6 +9,7 @@ #include "SolidSyslogOriginSd.h" #include "SolidSyslogOriginSdErrors.h" #include "SolidSyslogPrival.h" +#include "SolidSyslogSdValue.h" #include "SolidSyslogStructuredData.h" #include "SolidSyslogTunables.h" #include "TestUtils.h" @@ -21,30 +22,34 @@ class TEST_SolidSyslogOriginSd_FormatIncludesDifferentEnterpriseIdFromConfig_Tes class TEST_SolidSyslogOriginSd_FormatIncludesDifferentIpFromCallback_Test; class TEST_SolidSyslogOriginSd_FormatIncludesEnterpriseIdFromConfig_Test; class TEST_SolidSyslogOriginSd_FormatIncludesOneIpFromCallback_Test; +class TEST_SolidSyslogOriginSd_FormatPassesIpContextThrough_Test; class TEST_SolidSyslogOriginSd_IpContainingSpecialsIsEscaped_Test; struct SolidSyslogFormatter; +struct SolidSyslogSdValue; struct SolidSyslogStructuredData; enum { /* Worst-case fully-escaped output is 337 bytes — see - WorstCaseFullyEscapedInputFitsPreFormattedStorage. Pre-message dispatch - widens this further once IP params are spliced in. 512 leaves headroom. */ + WorstCaseFullyEscapedInputFormatsCorrectly. IP params widen this further + once they are appended. 512 leaves headroom. */ TEST_BUFFER_SIZE = 512, MAX_FAKE_IPS = 8 }; static std::array fakeIps; static size_t fakeIpCount; +static void* fakeIpContext; static size_t FakeIpCount() { return fakeIpCount; } -static void FakeIpAt(struct SolidSyslogFormatter* f, size_t index) +static void FakeIpAt(struct SolidSyslogSdValue* value, void* context, size_t index) { - SolidSyslogFormatter_EscapedString(f, fakeIps.at(index), 64); // ORIGIN_IP_MAX + fakeIpContext = context; + SolidSyslogSdValue_BoundedString(value, fakeIps.at(index), 64); // ORIGIN_IP_MAX } #define CHECK_ENTERPRISE_ID(expected) \ @@ -93,6 +98,7 @@ TEST_GROUP(SolidSyslogOriginSd) config.Software = "TestSoftware"; config.SwVersion = "9.8.7"; fakeIpCount = 0; + fakeIpContext = nullptr; sd = SolidSyslogOriginSd_Create(&config); } @@ -361,7 +367,7 @@ TEST(SolidSyslogOriginSd, EnterpriseIdContainingSpecialsIsEscaped) CHECK_ENTERPRISE_ID("a\\\"b\\\\c\\]d"); } -TEST(SolidSyslogOriginSd, WorstCaseFullyEscapedInputFitsPreFormattedStorage) +TEST(SolidSyslogOriginSd, WorstCaseFullyEscapedInputFormatsCorrectly) { const std::string software = repeated(']', 48); /* ORIGIN_SOFTWARE_MAX */ const std::string swVersion = repeated('"', 32); /* ORIGIN_SWVERSION_MAX */ @@ -414,6 +420,16 @@ TEST(SolidSyslogOriginSd, FormatIncludesMultipleIpsFromCallback) ); } +TEST(SolidSyslogOriginSd, FormatPassesIpContextThrough) +{ + int ipContext = 0; + config.IpContext = &ipContext; + useIps({"192.0.2.1"}); + resetFormatter(); + format(); + POINTERS_EQUAL(&ipContext, fakeIpContext); +} + TEST(SolidSyslogOriginSd, IpAtMaxLength) { const std::string maxIp = repeated('a', 64); /* ORIGIN_IP_MAX */ diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 2bb3e825..6ee11e41 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -29,7 +29,7 @@ misra-c2012-11.3:Core/Source/SolidSyslogCircularBuffer.c:91 misra-c2012-11.3:Core/Source/SolidSyslogFileBlockDevice.c:106 misra-c2012-11.3:Core/Source/SolidSyslogFormatter.c:79 misra-c2012-11.3:Core/Source/SolidSyslogMetaSd.c:64 -misra-c2012-11.3:Core/Source/SolidSyslogOriginSd.c:73 +misra-c2012-11.3:Core/Source/SolidSyslogOriginSd.c:61 misra-c2012-11.3:Core/Source/SolidSyslogPassthroughBuffer.c:53 misra-c2012-11.3:Core/Source/SolidSyslogStreamSender.c:166 misra-c2012-11.3:Core/Source/SolidSyslogSwitchingSender.c:63 @@ -175,7 +175,7 @@ misra-c2012-5.7:Core/Source/SolidSyslogMessageFormatter.c:19 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 -misra-c2012-5.7:Core/Source/SolidSyslogOriginSdPrivate.h:14 +misra-c2012-5.7:Core/Source/SolidSyslogOriginSdPrivate.h:13 misra-c2012-5.7:Platform/FreeRtos/Source/SolidSyslogFreeRtosSysUpTime.c:7 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixHostname.c:10 misra-c2012-5.7:Platform/Posix/Source/SolidSyslogPosixMessageQueueBuffer.c:25 From cce316d88040a57366eced0447af436e7fd8e251 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sat, 6 Jun 2026 06:49:00 +0000 Subject: [PATCH 2/2] docs: update DEVLOG for S14.05 OriginSd SD-writer migration Co-Authored-By: Claude Opus 4.8 (1M context) --- DEVLOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index 47150c02..48e1a961 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -14486,3 +14486,37 @@ MISRA rule — different category, doesn't set precedent. ### Open questions - None outstanding. + +## 2026-06-06 — S14.05: OriginSd onto the SD writer + #346 scratch removal + +### Decisions +- **OriginSd migrated onto the E14 SD writer**, byte-identical (existing OriginSd tests + are the net), same shape as S14.03/S14.04: `SdElement_FromFormatter` + + `_Begin("origin",0U)` / `_Param` → `SolidSyslogSdValue_*` / `_End`. Static fields use + `SolidSyslogSdValue_BoundedString` with the existing `ORIGIN_*_MAX` decoded caps. +- **`GetIpAt` signature changed** to `void(struct SolidSyslogSdValue*, void* context, + size_t index)` with a paired `OriginSdConfig.IpContext`. It keeps its own typedef name + `SolidSyslogOriginIpAtFunction` (not the shared `SolidSyslogSdValueFunction` — it + carries the extra `index`). Routing `ip` through the param sink closes the `ip` + injection vector; with `language` (S14.04) that completes **#533**. New behaviour + (`void* context`) driven RED-first by `FormatPassesIpContextThrough`. +- **Took the #346 eliminate-the-scratch path.** The static fields emit straight into the + element at Format time; the `OriginSd` struct now **borrows** the three config strings + directly. `FormattedStorage` and the `ORIGIN_{LITERAL_BYTES,CONTENT_MAX,FORMATTED_MAX}` + sizing enums are deleted. No shared SD scratch buffer remains → concurrent-`Log` + reentrancy holds by construction. Behaviour change: the config strings are borrowed for + the SD's lifetime (were copied at `Create`) — fine for the literal/long-lived config + integrators pass. +- The shared `BddTargetIps_At` moved to `SolidSyslogSdValue_BoundedString`; renamed the + now-misnamed `WorstCaseFullyEscapedInputFitsPreFormattedStorage` test. +- Checks: debug green (1459 tests), OriginSd 38/38, BddTargetIps 2/2, BddTargetTests + 66/66; clang-format applied; cppcheck-MISRA exit 0 (anchors moved: OriginSd.c 11.3 + 73→61, OriginSdPrivate.h 5.7 14→13). PR #555. + +### Deferred +- SD vtable flip (S14.06), config header fields (S14.07). CLAUDE.md OriginSd + header-table row (still describes the removed pre-formatted storage) left for the + S14.08 wholesale table revision. + +### Open questions +- None outstanding.