#!/usr/bin/env bash # Validate our HSMS framing against Wireshark/tshark's built-in HSMS # dissector — an independent third codec. # # Wireshark's HSMS dissector is written by network-protocol authors # who haven't read our code, didn't talk to us, and don't share # implementation details with secsgem-py. 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. # # Usage: tshark_validate.sh [--port PORT] # Run inside the builder image (which has tshark + tcpdump). # # Exit codes: # 0 — tshark dissected every HSMS frame without errors # 1 — malformed packet or dissector bug # 2 — script error (binary missing, capture failed, etc.) set -euo pipefail PORT=${PORT:-5099} SERVER=/app/build/secs_server CLIENT=/app/build/secs_client [ -x "$SERVER" ] || { echo "FAIL: $SERVER missing — rebuild first"; exit 2; } [ -x "$CLIENT" ] || { echo "FAIL: $CLIENT missing — rebuild first"; exit 2; } TMPDIR=$(mktemp -d) PCAP="$TMPDIR/secsgem.pcap" SERVER_LOG="$TMPDIR/server.log" CLIENT_LOG="$TMPDIR/client.log" TSHARK_LOG="$TMPDIR/tshark.txt" cleanup() { [ -n "${TCPDUMP_PID:-}" ] && kill "$TCPDUMP_PID" 2>/dev/null || true [ -n "${SERVER_PID:-}" ] && kill "$SERVER_PID" 2>/dev/null || true wait 2>/dev/null || true } trap cleanup EXIT echo "starting tcpdump capture on lo:$PORT → $PCAP" tcpdump -i lo -w "$PCAP" "tcp port $PORT" >/dev/null 2>&1 & TCPDUMP_PID=$! # Give tcpdump a moment to attach to the interface before the server binds. sleep 0.5 echo "starting secs_server on 127.0.0.1:$PORT" $SERVER --port "$PORT" --device 0 \ --config /app/data/equipment.yaml \ --state-table /app/data/control_state.yaml \ --pj-state-table /app/data/process_job_state.yaml \ --cj-state-table /app/data/control_job_state.yaml \ > "$SERVER_LOG" 2>&1 & SERVER_PID=$! sleep 0.5 echo "running secs_client (~24 transactions)" $CLIENT --host 127.0.0.1 --port "$PORT" --device 0 \ > "$CLIENT_LOG" 2>&1 || true # Let the final separate / TCP teardown land in the pcap. The client # sends Separate.req then exits; we need to give tcpdump enough time # to flush both the Separate frame and the trailing FIN/ACK pair to # the pcap file (tcpdump buffers internally). sleep 1.5 cleanup trap - EXIT # Make sure tcpdump flushed. sleep 0.5 if [ ! -s "$PCAP" ]; then echo "FAIL: empty pcap (capture didn't work?)" echo "--- server log ---"; cat "$SERVER_LOG" echo "--- client log ---"; cat "$CLIENT_LOG" exit 2 fi echo "parsing $PCAP with tshark + HSMS dissector" # -d tcp.port==$PORT,hsms forces the dissector for our chosen port # (Wireshark's default is 5000, so a non-default port needs the hint). tshark -r "$PCAP" -d "tcp.port==$PORT,hsms" -V > "$TSHARK_LOG" 2>&1 if grep -qi 'Malformed Packet' "$TSHARK_LOG"; then echo "FAIL: tshark reported Malformed Packet — dissector failed on our frame" grep -B2 -A6 'Malformed Packet' "$TSHARK_LOG" exit 1 fi if grep -qi 'Dissector bug' "$TSHARK_LOG"; then echo "FAIL: Dissector bug reported" grep -B2 -A6 'Dissector bug' "$TSHARK_LOG" exit 1 fi frames=$(grep -c 'High-speed SECS Message Service' "$TSHARK_LOG" || true) if [ "$frames" -lt 1 ]; then echo "FAIL: no HSMS frames dissected (capture may not include traffic)" echo "--- tshark output (last 60 lines) ---"; tail -60 "$TSHARK_LOG" exit 1 fi # Spot-check for expected wire elements: each MUST appear at least once # in a healthy demo run. Linktest is intentionally disabled in the # demo client config (Timers::linktest = 0ms) so it's not expected. # Tokens here are what the tshark -V protocol tree actually emits: # "Header (Select.req)" for control frames # "SType (Session type):" on every HSMS frame # "Data message" for SECS data frames # "Header (S" + SxxFyy form for SECS data frames expected=("Header (Select.req)" "Header (Select.rsp)" "Header (Separate.req)" "Data message" "SType (Session type):") missing=() for token in "${expected[@]}"; do if ! grep -q -F "$token" "$TSHARK_LOG"; then missing+=("$token") fi done if [ ${#missing[@]} -gt 0 ]; then echo "FAIL: expected tokens missing from tshark output:" for m in "${missing[@]}"; do echo " - $m"; done echo "--- tshark output (first 200 lines) ---"; head -200 "$TSHARK_LOG" exit 1 fi # Histogram of distinct S/F pairs seen (proves we exercise breadth, not # just one message type). distinct_sf=$(grep -oE 'Header \(S[0-9]+F[0-9]+\)' "$TSHARK_LOG" | sort -u | wc -l) echo "" echo "tshark dissection summary:" echo " HSMS frames dissected: $frames" echo " Select.req frames: $(grep -c -F 'Header (Select.req)' "$TSHARK_LOG" || echo 0)" echo " Select.rsp frames: $(grep -c -F 'Header (Select.rsp)' "$TSHARK_LOG" || echo 0)" echo " Separate.req frames: $(grep -c -F 'Header (Separate.req)' "$TSHARK_LOG" || echo 0)" echo " SECS data frames: $(grep -c -F 'Data message' "$TSHARK_LOG" || echo 0)" echo " distinct S/F pairs: $distinct_sf" echo "" echo "PASS: tshark dissected $frames HSMS frame(s) with no malformed-packet warnings" rm -rf "$TMPDIR"