Skip to content

Add an opt-in plaintext message trace for conformance tooling - #205

Merged
kahrendt merged 4 commits into
mainfrom
message-trace
Oct 14, 2026
Merged

kahrendt merged 4 commits into
mainfrom
message-trace

Conversation

@kahrendt

@kahrendt kahrendt commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Many sendspin-cpp client cases in the conformance matrix fail because the adapter cannot see the messages the library exchanges: received audio hashes, raw chunk frames, and the protocol-baseline trace. This adds an opt-in plaintext trace, mirroring sendspin-server-cpp's ServerConfig::trace.

Change

  • SENDSPIN_ENABLE_MESSAGE_TRACE: a host-only CMake option, off by default, with no Kconfig entry. With it off, the build is unchanged, ESP included.
  • SendspinClientConfig::message_trace receives a SendspinTracedMessage for every message on the protocol task. Each one carries:
    • connection id, direction and stamp
    • the plaintext: inbound after decryption and reassembly, outbound before encryption (each fragment on its own), plus the cleartext handshake
    • audio_bytes: for a player audio chunk, the encoded audio it carries
  • The pairing secrets a client sends (long_term_psk, wrapped_psk, wrapped_nonce_B) read "<redacted>".
  • MessageTracer (src/message_trace.h) is owned per connection. The trace points are gated inline in connection.cpp and in NoiseTransport::encrypt_in_place().
  • The gate is listed in CLAUDE.md, conventions.md, internals.md and the house-patterns skill. integration-guide.md documents the option.
  • CI's test and TSan jobs build with the trace on; the build and no-opus jobs leave it off.

Notes

  • Once this lands, the conformance adapter (adapters/sendspin-cpp/client/main.cpp in Sendspin/conformance) needs updating to turn the option on and read the trace.

Tests

  • End-to-end against the fake encrypted server, read only through the trace. It asserts:
    • the exact opening sequence from client/init to server/activate, with the transport JSON readable
    • an audio chunk's audio_bytes
    • a single connection id, and stamps on the client clock
  • A redaction table whose secret rows are built by the real pairing formatters, with control rows: a message without secrets and a binary message reach the trace unchanged and uncopied.
  • Not covered by any test: inbound reassembly and outbound fragmentation.

Adds SendspinClientConfig::message_trace behind SENDSPIN_ENABLE_MESSAGE_TRACE,
a host-only CMake option that is off by default and has no Kconfig entry.
The callback receives every protocol message's plaintext on the protocol
task: inbound after decryption and reassembly, outbound before encryption,
and the cleartext handshake. It mirrors sendspin-server-cpp's
ServerConfig::trace, so the conformance harness can observe sendspin-cpp's
messages and audio chunks.
@kahrendt
kahrendt marked this pull request as ready for review October 14, 2026 13:46
@kahrendt
kahrendt requested a balanced review from Copilot October 14, 2026 13:46

Copilot AI left a comment

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.

🟡 Changes recommended

Redaction tests bypass the real serializers, and normative threading documentation still contradicts the callback contract.

2 open findings
What changed in this PR

Adds a host-only plaintext protocol trace for conformance tooling.

Changes:

  • Introduces per-connection tracing with pairing-secret redaction.
  • Traces cleartext and encrypted transport messages.
  • Adds tests, CI coverage, build gates, and documentation.
File Description
include/​sendspin/​config.h Defines the public trace API and config callback.
src/​message_trace.h Declares the per-connection tracer.
src/​message_trace.cpp Implements tracing, timestamps, audio sizing, and redaction.
src/​connection.h Owns and configures each connection’s tracer.
src/​connection.cpp Traces inbound and cleartext outbound messages.
src/​connection_manager.cpp Installs configured tracing on new connections.
src/​noise_transport.h Adds tracer integration to encrypted transport.
src/​noise_transport.cpp Traces frames before encryption.
CMakeLists.txt Adds the host-only build option.
cmake/​sources.cmake Defines the optional trace source set.
tests/​test_message_trace.cpp Tests tracing, audio metadata, and redaction.
tests/​CMakeLists.txt Conditionally includes trace tests.
.github/​workflows/​ci.yml Enables tracing in sanitizer test jobs.
docs/​integration-guide.md Documents usage, fields, and threading.
docs/​internals.md Documents trace architecture and gating.
docs/​conventions.md Records allowed compile-gate locations.
CLAUDE.md Updates architecture and build guidance.
.claude/​skills/​house-patterns/​SKILL.md Updates repository review guidance.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/test_message_trace.cpp Outdated
Comment thread include/sendspin/config.h
clang-tidy analyzes every src/ file against the default build's compile
database, where the trace is off, so message_trace.cpp was analyzed
without its macro. Generate the database with the trace on.
The secret-bearing rows of the redaction test are now built by the client's
own formatters, so a change to how they spell a field fails the test. The
conventions threading rule names message_trace as the protocol task's one
consumer callback. clang-tidy.sh configures its own build-tidy directory with
the trace on every run, so it never reuses a trace-off build or changes the
main one.

Copilot AI left a comment

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.

🔵 Needs a closer look

The pass-through controls neither fully assert unchanged bytes nor follow the required control-row labeling convention.

0 open findings

2 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Verify unchanged secret bytes, not just pointer and length

tests/​test_message_trace.cpp:244

The no-secret controls only verify that the pointer was not copied; they do not verify the claimed unchanged bytes. A regression that reports the original pointer with a truncated plaintext_len would still pass, so also compare the captured value with row.sent.

Low severity Label redaction table accepting rows as Control

tests/​test_message_trace.cpp:221

These are the accepting controls for the redaction table, but they are not labeled as such. The repository’s testing convention requires accepting rows to be visibly marked Control: so a failure cannot be mistaken for a rejection case; rename both control rows accordingly.

🧠 Review effort: Balanced

The pass-through rows now compare the traced bytes with what was sent, so a
wrong plaintext_len fails them, and carry the Control: label the test
standards require.
@kahrendt
kahrendt enabled auto-merge (squash) October 14, 2026 14:07
@kahrendt
kahrendt disabled auto-merge October 14, 2026 14:13
@kahrendt
kahrendt merged commit b95cf85 into main Oct 14, 2026
16 checks passed
@kahrendt
kahrendt deleted the message-trace branch October 14, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants