Wireshark's built-in HSMS dissector — written by network-protocol
authors who don't know us, didn't talk to us, and don't share
implementation details with secsgem-py — is a third independent codec
for our framing. If they parse our pcap without warnings, our HSMS
framing is wire-correct independently of both our internal tests and
the secsgem-py interop path.
interop/tshark_validate.sh:
- Boots secs_server on 127.0.0.1:5099 (away from the demo port)
- Captures the loopback wire traffic with tcpdump
- Runs secs_client through ~24 transactions plus Separate.req +
TCP FIN
- Parses the pcap with tshark -V using the HSMS dissector
- Asserts: no "Malformed Packet", no "Dissector bug", at least one
HSMS frame, expected tokens present (Select.req/rsp, Separate.req,
Data message), reports histogram (count by control type + distinct
S/F pairs)
Result against the demo: 69 HSMS frames dissected, 49 distinct
S/F pairs (S01F01..S16F28), all clean.
Dockerfile gains tshark + tcpdump. .gitea/workflows/ci.yml gains a
`tshark-dissector` job that runs this validator as part of every
push to main. README proof table grows to 6 commands.
VERIFICATION.md §1a documents a follow-up: round-trip the KAT
fixtures through secsgem-py to corroborate that the format codes
we used match an independent implementation. Strengthens the KAT
proof from "internally consistent" to "confirmed by a second
implementer who read the spec without talking to us."
Plan: VERIFICATION.md §2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a -DSECSGEM_TSAN=ON CMake option that builds every target with
-fsanitize=thread + debug symbols + -O1 + frame pointers. Wires a
dedicated thread-sanitizer job into .gitea/workflows/ci.yml that
builds and runs the full test suite under TSan with
TSAN_OPTIONS=halt_on_error=1 (any flagged race fails the job, not
just warns).
Result against the full 426-case / 2557-assertion suite: 0 warnings,
all green. That converts the existing test_thread_safety.cpp (which
exercised the asio::post-onto-strand pattern) and test_concurrency
(in-flight transaction interleaving) and test_robustness_fuzz (28
random action types × thousands of ticks) from "pattern smoke-tests"
into actual race detection.
The first TSan run caught a real bug in test_robustness_fuzz's
act_exception_complete: it held a pointer to an ExceptionStore
entry across fire_internal(RecoveryComplete), which deletes the
entry. The subsequent state() read was a use-after-free. TSan
flagged it 8 times (4 reads × 2 stack-frame variants). Fix is
scoped lookup + re-check via has() after the mutation; matches the
contract any reasonable caller would follow.
The asio std_fenced_block atomic_thread_fence path generates TSan
"not supported" warnings during compile — those are asio's, not
ours, and don't affect runtime detection.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`actions/checkout@v4` is a JavaScript action — it expects `node` on
PATH in the runner image. Gitea Actions (and local `act`) running
against `ubuntu:24.04` had neither node nor git pre-installed, so
checkout failed with:
❌ Failure - Main actions/checkout@v4
exitcode '127': command not found
The pre-step now installs nodejs + git + ca-certificates from apt
before checkout runs. The rest of the C++ toolchain installs in a
second step after the source tree is on disk.
Doesn't affect GitHub-hosted runners (their images already have node);
doesn't change build behaviour either.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Gitea Actions workflow at .gitea/workflows/ci.yml. Spins up an
ubuntu:24.04 container, installs the same apt deps the Dockerfile uses
(build-essential, cmake, ninja-build, libasio-dev, libyaml-cpp-dev,
python3, python3-yaml), checks out, runs cmake + ninja, and executes
the doctest binary.
Runs the same toolchain as the local Docker setup; no docker-in-docker
required.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>