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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Bdd/Targets/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ if(SOLIDSYSLOG_POSIX)
Linux/BddTargetUdpConfig.c
Common/BddTargetAppName.c
Common/BddTargetErrorText.c
Common/BddTargetCustomSd.c
Common/BddTargetInteractive.c
Common/BddTargetIps.c
Common/BddTargetLanguage.c
Expand Down Expand Up @@ -54,6 +55,7 @@ elseif(SOLIDSYSLOG_WINSOCK AND HAVE_WINDOWS_PLATFORM)
Windows/BddTargetWindowsCommandLine.c
Common/BddTargetAppName.c
Common/BddTargetErrorText.c
Common/BddTargetCustomSd.c
Common/BddTargetInteractive.c
Common/BddTargetIps.c
Common/BddTargetLanguage.c
Expand Down
27 changes: 27 additions & 0 deletions Bdd/Targets/Common/BddTargetCustomSd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "BddTargetCustomSd.h"

#include "SolidSyslogSdElement.h"
#include "SolidSyslogSdValue.h"
#include "SolidSyslogStructuredDataDefinition.h"

/* IANA-reserved "example" Private Enterprise Number — safe for documentation. */
enum
{
EXAMPLE_ENTERPRISE_NUMBER = 32473U
};

static void CustomSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogSdElement* element)
{
(void) base; /* Stateless: this example emits a fixed element. */

SolidSyslogSdElement_Begin(element, "example", EXAMPLE_ENTERPRISE_NUMBER);
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "detail"), "Hello World");
SolidSyslogSdElement_End(element);
}

static struct SolidSyslogStructuredData customSd = {CustomSd_Format};

struct SolidSyslogStructuredData* BddTargetCustomSd_Get(void)
{
return &customSd;
}
18 changes: 18 additions & 0 deletions Bdd/Targets/Common/BddTargetCustomSd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef BDDTARGETCUSTOMSD_H
#define BDDTARGETCUSTOMSD_H

#include "ExternC.h"

EXTERN_C_BEGIN

struct SolidSyslogStructuredData;

/* The worked custom SD-ELEMENT for the integrator guide
* (docs/structured-data.md). Emits [example@32473 detail="Hello World"].
* Stateless singleton — handed to SolidSyslog_LogWithSd by the
* `send-custom` interactive command. */
struct SolidSyslogStructuredData* BddTargetCustomSd_Get(void);

EXTERN_C_END

#endif /* BDDTARGETCUSTOMSD_H */
20 changes: 19 additions & 1 deletion Bdd/Targets/Common/BddTargetInteractive.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>

#include "BddTargetCustomSd.h"
#include "SolidSyslog.h"
#include "SolidSyslogTunables.h"

Expand Down Expand Up @@ -74,6 +75,19 @@ static void HandleSend(struct SolidSyslog* handle, const char* args, const struc
printf("Sent %d message%s\n", count, (count == 1) ? "" : "s");
}

static void HandleSendCustom(struct SolidSyslog* handle, const char* args, const struct SolidSyslogMessage* message)
{
int count = ParseCount(args);
struct SolidSyslogStructuredData* sd[] = {BddTargetCustomSd_Get()};

for (int i = 0; i < count; i++)
{
SolidSyslog_LogWithSd(handle, message, sd, 1);
}

printf("Sent %d custom message%s\n", count, (count == 1) ? "" : "s");
}

static void HandleSet(const char* args, BddTargetInteractiveSetHandler onSet)
{
char name[MAX_LINE_LENGTH];
Expand Down Expand Up @@ -120,7 +134,11 @@ void BddTargetInteractive_Run(
}

const char* args = NULL;
if (MatchCommand(line, "send", &args))
if (MatchCommand(line, "send-custom", &args))
{
HandleSendCustom(handle, args, message);
}
else if (MatchCommand(line, "send", &args))
{
HandleSend(handle, args, message);
}
Expand Down
1 change: 1 addition & 0 deletions Bdd/Targets/FreeRtos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ add_executable(SolidSyslogBddTarget
${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicyStatic.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetErrorText.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetCustomSd.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetInteractive.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetIps.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetLanguage.c
Expand Down
1 change: 1 addition & 0 deletions Bdd/Targets/FreeRtosLwip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ add_executable(SolidSyslogBddTargetLwip
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetErrorText.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetFatFsMount.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetFreeRtosPipeline.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetCustomSd.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetInteractive.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetIps.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetLanguage.c
Expand Down
10 changes: 10 additions & 0 deletions Bdd/features/custom_sd.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@udp
Feature: Custom structured data — caller-supplied SD-ELEMENT
An integrator can attach a custom SD-ELEMENT to a single log call via
SolidSyslog_LogWithSd. The library frames it per RFC 5424 §6.3 and the
element reaches the receiver alongside the per-instance SDs.

Scenario: A custom SD-ELEMENT reaches the oracle
Given the syslog oracle is running
When the BDD target sends a custom syslog message
Then the structured data contains detail "Hello World"
24 changes: 22 additions & 2 deletions Bdd/features/steps/syslog_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def wait_for_messages(context, expected_messages):
context.message_count = len(context.all_lines)


def run_example(context, extra_args=None, expected_messages=1):
def run_example(context, extra_args=None, expected_messages=1, command="send"):
"""Run the BDD target via the prompt protocol.

Every supported runner ships a single buffered binary now — Linux
Expand Down Expand Up @@ -433,7 +433,7 @@ def run_example(context, extra_args=None, expected_messages=1):
try:
wait_for_prompt(process)
apply_extra_args(context, process, extra_args)
send_command(process, f"send {expected_messages}")
send_command(process, f"{command} {expected_messages}")
wait_for_messages(context, expected_messages)

returncode = stop_example_process(process, context.target)
Expand Down Expand Up @@ -804,6 +804,11 @@ def step_bdd_target_sends_message(context):
run_example(context)


@when("the BDD target sends a custom syslog message")
def step_bdd_target_sends_custom_message(context):
run_example(context, command="send-custom")


@when("the BDD target sends a syslog message with transport {transport}")
def step_bdd_target_sends_with_transport(context, transport):
run_example(context, ["--transport", transport])
Expand Down Expand Up @@ -1116,6 +1121,21 @@ def step_check_language(context, value):
)


@then('the structured data contains detail "{value}"')
def step_check_detail(context, value):
sd = context.fields.get("STRUCTURED_DATA", "")
# Escape-aware like the language matcher (RFC 5424 §6.3.3), so the assertion
# holds whether the oracle re-renders the value escaped or decoded.
match = re.search(r'detail="((?:\\.|[^"])*)"', sd)
assert match, (
f"No detail found in structured data: {sd}"
)
actual = re.sub(r"\\(.)", r"\1", match.group(1))
assert actual == value, (
f"Expected detail {value}, got {actual}"
)


@then("the structured data contains sysUpTime as a decimal integer")
def step_check_sys_up_time_shape(context):
sd = context.fields.get("STRUCTURED_DATA", "")
Expand Down
38 changes: 38 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14712,3 +14712,41 @@ SolidSyslogMessage"; the per-message Open questions); it was the never-decompose

### Open questions
- None outstanding.

## 2026-06-06 — S14.10: custom-SD integrator guide + worked example (E14 close)

The user-facing payoff of E14: document and demonstrate authoring a custom SD with the new
safe API.

### Decisions
- **`docs/structured-data.md`** — the custom-SD integrator guide: two-type writer model
(SdElement frames + names, SdValue escapes values), implementing a `Format` vtable with the
embed-base downcast for stateful data, SD-IDs / enterprise numbers (`@32473`), registering
per-instance (`Config.Sd[]`) **and** per-message (`SolidSyslog_LogWithSd`), the reentrancy
note, and a library-owns-vs-you-own table. Written around the real worked example.
- **Worked custom SD is cross-platform, not Linux-only** (David's steer): `BddTargetCustomSd`
+ a `send-custom` command live in `Bdd/Targets/Common`, so all four targets
(Linux/Windows/FreeRtos/FreeRtosLwip) expose it from the one shared `BddTargetInteractive`
runner. `send-custom` emits the element via `SolidSyslog_LogWithSd` — exercising S14.09's
new API end-to-end.
- **Kept the example simple** (David's steer): `detail="Hello World"`, not a value full of
escapables. A clear example beats one muddied by escaped data.
- **Escape round-trip oracle BDD dropped** (David's call): escaping is already covered by the
SdValue unit tests, and syslog-ng vs OTEL re-render an *escaped* value differently — so an
oracle assertion there would be testing the oracle's quirks, not the library. (`Hello World`
round-trips identically on both, so the one `custom_sd.feature` scenario is clean and
cross-platform.) Updates #549's acceptance criteria.
- **Doc accuracy:** `_Begin`/`_Param` only suppress on a NULL name and bound length to 32;
they do not validate the name charset at format time (`=`/`]`/`"` pass through). The guide
says valid names are the integrator's responsibility — not an overstated "library skips
invalid names".

### Checks
- BddTargetTests 68/68 (custom-SD escaped... → simple element; send-custom dispatch). Linux
target links; `send-custom` smoke-tested (prints "Sent 1 custom message"). clang-format clean.
- BDD target code is Tier-3 (outside the cppcheck-misra file set), so no suppression work.

### Pending → WSL
- The `custom_sd.feature` oracle round-trip is **authored but not yet run** — `behave` needs
the docker/syslog-ng oracle, which runs from WSL, not the devcontainer. Next step: pull the
branch in WSL, run the `@udp` `custom_sd` scenario, confirm `detail "Hello World"` round-trips.
Comment on lines +14749 to +14752

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update validation status to avoid stale release notes.

This “not yet run” note conflicts with the PR objective/status context indicating custom_sd.feature already passed. Please align this section with the final executed state (or add explicit run date/context if it was pending at entry time).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DEVLOG.md` around lines 14749 - 14752, Update the DEVLOG entry for custom_sd
by replacing the “authored but not yet run” status with the actual executed
state (or append an explicit runtime context): locate the paragraph referencing
custom_sd.feature, behave, and the `@udp` custom_sd scenario and either change the
phrase to indicate it "passed" (e.g., "custom_sd.feature — executed and passed
on <date>") or add a parenthetical note with the run date, environment (WSL),
and confirmation of the detail "Hello World" round-trip so the release notes
aren’t stale.

39 changes: 39 additions & 0 deletions Tests/Bdd/Targets/BddTargetCustomSdTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "BddTargetCustomSd.h"
#include "SolidSyslogFormatter.h"
#include "SolidSyslogSdElement.h"
#include "SolidSyslogSdElementPrivate.h"
#include "SolidSyslogStructuredData.h"

#include "CppUTest/TestHarness.h"

enum
{
FORMATTER_BUFFER_SIZE = 128
};

// clang-format off
TEST_GROUP(BddTargetCustomSd)
{
SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(FORMATTER_BUFFER_SIZE)];
struct SolidSyslogFormatter* formatter = nullptr;
struct SolidSyslogSdElement element{};

void setup() override
{
formatter = SolidSyslogFormatter_Create(storage, FORMATTER_BUFFER_SIZE);
SolidSyslogSdElement_FromFormatter(&element, formatter);
}

[[nodiscard]] const char* formatted() const
{
return SolidSyslogFormatter_AsFormattedBuffer(formatter);
}
};

// clang-format on

TEST(BddTargetCustomSd, EmitsTheExampleElement)
{
SolidSyslogStructuredData_Format(BddTargetCustomSd_Get(), &element);
STRCMP_EQUAL("[example@32473 detail=\"Hello World\"]", formatted());
}
7 changes: 7 additions & 0 deletions Tests/Bdd/Targets/BddTargetInteractiveTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ TEST(BddTargetInteractive, SetCommandPrintsInvalidOnHandlerFailure)
CHECK(captured.find("set: invalid") != std::string::npos);
}

TEST(BddTargetInteractive, SendCustomCommandIsDispatchedAndCounted)
{
std::string captured = RunCapturingStdout("send-custom 2\nquit\n", RecordSet);

CHECK(captured.find("Sent 2 custom messages") != std::string::npos);
}

/* Test list (ZOMBIES order):
* Z [x] SetHandlerNotCalledWithQuitOnly
* O [x] SetCommandCallsHandlerOnce
Expand Down
2 changes: 2 additions & 0 deletions Tests/Bdd/Targets/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Platform-agnostic tests always included.
set(BDD_TARGET_TEST_SOURCES
BddTargetAppNameTest.cpp
BddTargetCustomSdTest.cpp
BddTargetErrorTextTest.cpp
BddTargetIpsTest.cpp
BddTargetLanguageTest.cpp
Expand All @@ -9,6 +10,7 @@ set(BDD_TARGET_TEST_SOURCES

set(BDD_TARGET_PRODUCTION_SOURCES
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetAppName.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetCustomSd.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetErrorText.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetIps.c
${CMAKE_SOURCE_DIR}/Bdd/Targets/Common/BddTargetLanguage.c
Expand Down
Loading
Loading