Files
comet/run_interop_tests.sh
Ken Johnson da732a10bd Version 1.2.1: full BinkP/Argus parity, Comet augmentation, WebUI
Version scheme: Major.Minor.Build-Revision.

BinkP gains every major Argus/binkd extension:

- PLZ (zlib) compression with adaptive block sizing (4KB→16KB)
- NR mode inbound resume via .bkp-part partials (FSP-1029)
- ND/NDA deferred cleanup: mid-session abort preserves outbound (FSP-1038)
- MBT multi-batch: FREQ response rides same session via second EOB
- M_NUL TRF traffic advisory and M_NUL FREQ (FRL-1026)
- M_NUL NDL/PHN info strings (new Phone, NodelistFlags config)
- RFC 2822 date format for M_NUL TIME
- Strict M_GET validation and duplicate-file pre-check
- TBinkpPostAuthCallback: host can route InboundDir before transfer
  (models binkd select_inbound / complete_login)
- TCometBinkpResult: Authenticated / AuthMethod fields

Comet native extensions keep the protocol ahead of BinkP:

- INIT payload adds Location/Time/Phone/NodelistFlags (trailing
  strings, backward-compatible)
- LST file listing: NPKT_LSTREQ/LSTITEM/LSTEND + COPT_LST
- Transactional file cleanup: destructive actions deferred until
  successful session close (matches ND semantics)
- Shared CometRFCDateStr across protocols — no drift between
  BinkP TIME and Comet INIT.Time

Daemon:
- BinkP inbound now starts unsecure and promotes to secure only
  after auth (fixes pre-1.2.1 bug where SecInbound was selected
  unconditionally).

TCometFileProvider: GetPartialSize and OpenForReceiveNamed for
NR partials; defaults preserve the random-temp scheme for
providers that don't track partials (Fastway plugin safe).

WebUI: /src/web/ + /src/webui/ backend, modeled after the Argus
GUI. Live session activity, outbound polls, FREQ requests,
nodelist, config editor, scheduler, SSE event stream.
2026-04-21 09:37:03 -07:00

590 lines
19 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Comet Protocol Interop Test Suite
# Tests: Standalone Comet 1.1-1 vs Fastway BBS Comet implementations
#
# Topology:
# Local (1:213/725) 192.168.10.175:26638 Comet 1.1-1 (standalone)
# fimail (1:213/726) fastway.server.lan:26638 Fastway/0.1.0
# bbsnode1(1:213/720) 192.168.10.176:24554 Comet/1.01.01 (Fastway TC)
#
# Date: 2026-04-07
# ============================================================================
set -euo pipefail
COMET="./build/linux/comet"
CFG="comet.cfg"
BASEDIR="/home/ken/Source Code/comet"
OUTBOUND="/home/ken/var/lib/fidonet/outbound"
LOCAL_INBOUND="/home/ken/var/lib/fidonet/inbound"
LOCAL_SECURE="/home/ken/var/lib/fidonet/secure"
RESULTS="$BASEDIR/INTEROP_TESTS.txt"
TESTDATA="/tmp/comet-testdata"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Remote systems
FIMAIL_HOST="fastway.server.lan"
FIMAIL_INBOUND="/var/lib/fastway/inbound/unsecure"
FIMAIL_SECURE="/var/lib/fastway/inbound/secure"
FIMAIL_TESTDATA="/tmp/comet-testdata"
BBSNODE1_HOST="bbsnode1"
BBSNODE1_INBOUND="/var/lib/fidonet/inbound/unsecure"
BBSNODE1_SECURE="/var/lib/fidonet/inbound/secure"
BBSNODE1_OUTBOUND="/var/lib/fidonet/outbound"
BBSNODE1_TESTDATA="/tmp/comet-testdata"
# BSO hex addresses (net<<16 | node, 8-digit lowercase hex)
# 1:213/720 -> net=213=0x00D5, node=720=0x02D0 -> 00d502d0
# 1:213/726 -> net=213=0x00D5, node=726=0x02D6 -> 00d502d6
# 1:213/725 -> net=213=0x00D5, node=725=0x02D5 -> 00d502d5
FLO_BBSNODE1="$OUTBOUND/00d502d0.flo"
FLO_FIMAIL="$OUTBOUND/00d502d6.flo"
# Counters
PASS=0
FAIL=0
SKIP=0
TEST_NUM=0
# ---- Helpers ---------------------------------------------------------------
log() {
echo "$@" | tee -a "$RESULTS"
}
log_header() {
log ""
log "=== $1 ==="
log ""
}
clean_local_inbound() {
rm -f "$LOCAL_INBOUND"/test_* "$LOCAL_SECURE"/test_* 2>/dev/null || true
}
clean_remote() {
local host="$1"
local inbound="$2"
local secure="$3"
ssh "$host" "rm -f $inbound/test_* $secure/test_* 2>/dev/null" 2>/dev/null || true
}
# Get file size on remote host
remote_filesize() {
local host="$1"
local path="$2"
if [ "$host" = "$FIMAIL_HOST" ]; then
ssh "$host" "stat -f%z '$path' 2>/dev/null || echo 0" 2>/dev/null
else
ssh "$host" "stat -c%s '$path' 2>/dev/null || echo 0" 2>/dev/null
fi
}
local_filesize() {
stat -c%s "$1" 2>/dev/null || echo 0
}
# SHA256 on remote
remote_sha256() {
local host="$1"
local path="$2"
if [ "$host" = "$FIMAIL_HOST" ]; then
ssh "$host" "sha256 -q '$path' 2>/dev/null || sha256sum '$path' 2>/dev/null | cut -d' ' -f1" 2>/dev/null
else
ssh "$host" "sha256sum '$path' 2>/dev/null | cut -d' ' -f1" 2>/dev/null
fi
}
local_sha256() {
sha256sum "$1" 2>/dev/null | cut -d' ' -f1
}
# Run a single outbound test from local
run_test() {
local desc="$1"
local target_addr="$2"
local flo_file="$3"
local files_to_send="$4" # space-separated list of full paths
local verify_host="$5"
local verify_dir="$6"
local extra_args="${7:-}"
local timeout_sec="${8:-60}"
TEST_NUM=$((TEST_NUM + 1))
log "--- TEST $TEST_NUM: $desc ---"
# Clean remote inbound
local verify_secure
if [ "$verify_host" = "$FIMAIL_HOST" ]; then
verify_secure="$FIMAIL_SECURE"
else
verify_secure="$BBSNODE1_SECURE"
fi
clean_remote "$verify_host" "$verify_dir" "$verify_secure"
# Queue files in .flo
> "$flo_file"
local file_count=0
local total_size=0
for f in $files_to_send; do
echo "$f" >> "$flo_file"
file_count=$((file_count + 1))
total_size=$((total_size + $(local_filesize "$f")))
done
# Run comet
local start_ns=$(date +%s%N)
local output
local exit_code=0
output=$(timeout "$timeout_sec" $COMET -c "$CFG" call "$target_addr" $extra_args 2>&1) || exit_code=$?
local end_ns=$(date +%s%N)
local elapsed_ms=$(( (end_ns - start_ns) / 1000000 ))
# Parse output
local protocol=$(echo "$output" | grep -oP 'Comet detected|BinkP detected' | head -1)
local auth=$(echo "$output" | grep -E 'ED25519 signature verified|ED25519 not verified|passwordless|NOPWD|CRAM-MD5' | head -1 | sed 's/^+ //')
local encrypt=$(echo "$output" | grep -E 'encryption active|Encryption disabled|encryption' | head -1 | sed 's/^+ //')
local session=$(echo "$output" | grep -E 'Session complete:|BinkP session' | head -1 | sed 's/^+ //')
local speed=$(echo "$output" | grep -oP 'Event: sent .* @ \K[^ ]+' | tail -1)
local remote_mailer=$(echo "$output" | grep -oP 'Rcvd INIT: .* \[\K[^\]]+' | head -1)
log " Target: $target_addr ($verify_host)"
log " Files: $file_count ($((total_size / 1024)) KB)"
[ -n "$remote_mailer" ] && log " Remote: $remote_mailer"
[ -n "$protocol" ] && log " Protocol: $protocol"
[ -n "$auth" ] && log " Auth: $auth"
[ -n "$encrypt" ] && log " Encrypt: $encrypt"
[ -n "$speed" ] && log " Speed: ${speed}/s"
log " Time: ${elapsed_ms}ms"
log " Exit: $exit_code"
if [ "$exit_code" -ne 0 ]; then
log " Status: FAIL (exit code $exit_code)"
local err=$(echo "$output" | grep -iE 'error|fail|timeout|cannot|refused' | head -3)
[ -n "$err" ] && log " Error: $err"
FAIL=$((FAIL + 1))
log ""
return 1
fi
# Verify files arrived
local all_ok=true
for f in $files_to_send; do
local fname=$(basename "$f")
local local_size=$(local_filesize "$f")
local local_hash=$(local_sha256 "$f")
# Check unsecure first, then secure
local remote_size=$(remote_filesize "$verify_host" "$verify_dir/$fname")
local remote_path="$verify_dir/$fname"
if [ "$remote_size" = "0" ] && [ "$verify_dir" != "$verify_secure" ]; then
remote_size=$(remote_filesize "$verify_host" "$verify_secure/$fname")
remote_path="$verify_secure/$fname"
fi
if [ "$remote_size" = "$local_size" ]; then
local remote_hash=$(remote_sha256 "$verify_host" "$remote_path")
if [ "$remote_hash" = "$local_hash" ]; then
log " Verify $fname: OK ($local_size bytes, SHA256 match)"
else
log " Verify $fname: HASH MISMATCH (local=$local_hash remote=$remote_hash)"
all_ok=false
fi
else
log " Verify $fname: SIZE MISMATCH (local=$local_size remote=$remote_size)"
all_ok=false
fi
done
if $all_ok; then
log " Status: PASS"
PASS=$((PASS + 1))
else
log " Status: FAIL (verification)"
FAIL=$((FAIL + 1))
fi
log ""
[ -n "$session" ] && log " $session"
log ""
}
# Run an inbound test (remote calls us)
run_inbound_test() {
local desc="$1"
local remote_host="$2"
local remote_addr="$3"
local remote_outbound="$4"
local remote_testdata="$5"
local bso_hex="$6"
local test_file="$7"
local timeout_sec="${8:-60}"
TEST_NUM=$((TEST_NUM + 1))
log "--- TEST $TEST_NUM: $desc ---"
clean_local_inbound
# Queue file on remote side using BSO
ssh "$remote_host" "echo '$remote_testdata/$test_file' > '$remote_outbound/$bso_hex.flo'" 2>/dev/null
# Start local comet as listener, wait for connection
local start_ns=$(date +%s%N)
local output
local exit_code=0
# Run comet in daemon mode briefly, accepting one inbound session
# We'll poll for the file to arrive
$COMET -c "$CFG" -d 2>/dev/null &
local daemon_pid=$!
sleep 1
# Trigger remote to call us
# For bbsnode1: use the Fastway API or direct call
# For fimail: use standalone comet or API
if [ "$remote_host" = "$BBSNODE1_HOST" ]; then
# Trigger via mailer link poll - write a command to the database
ssh "$remote_host" "sqlite3 /opt/fastway/data/fastway.db \"UPDATE p_mailer_link_props SET str_value = 'pending' WHERE address = '1:213/725' AND key = 'poll_status';\"" 2>/dev/null
# Also try direct outbound trigger
ssh "$remote_host" "sqlite3 /opt/fastway/data/fastway.db \"INSERT OR REPLACE INTO p_mailer_link_props (address, key, str_value) VALUES ('1:213/725', 'poll_status', 'pending');\"" 2>/dev/null
elif [ "$remote_host" = "$FIMAIL_HOST" ]; then
# Use standalone comet on fimail
ssh "$remote_host" "cd /home/ken && timeout 30 /usr/local/bin/comet -c comet-test.cfg call 1:213/725 2>&1" &
fi
# Wait for file to arrive (poll for up to timeout)
local waited=0
local fname=$(basename "$test_file")
while [ $waited -lt $timeout_sec ]; do
if [ -f "$LOCAL_INBOUND/$fname" ] || [ -f "$LOCAL_SECURE/$fname" ]; then
break
fi
sleep 2
waited=$((waited + 2))
done
local end_ns=$(date +%s%N)
local elapsed_ms=$(( (end_ns - start_ns) / 1000000 ))
# Kill daemon
kill $daemon_pid 2>/dev/null || true
wait $daemon_pid 2>/dev/null || true
# Check result
local received_path=""
if [ -f "$LOCAL_INBOUND/$fname" ]; then
received_path="$LOCAL_INBOUND/$fname"
elif [ -f "$LOCAL_SECURE/$fname" ]; then
received_path="$LOCAL_SECURE/$fname"
fi
log " Source: $remote_addr ($remote_host)"
log " File: $test_file"
log " Time: ${elapsed_ms}ms"
if [ -n "$received_path" ]; then
local local_size=$(local_filesize "$received_path")
local remote_size=$(remote_filesize "$remote_host" "$remote_testdata/$test_file")
local local_hash=$(local_sha256 "$received_path")
local remote_hash=$(remote_sha256 "$remote_host" "$remote_testdata/$test_file")
if [ "$local_size" = "$remote_size" ] && [ "$local_hash" = "$remote_hash" ]; then
log " Verify: OK ($local_size bytes, SHA256 match)"
log " Status: PASS"
PASS=$((PASS + 1))
else
log " Verify: MISMATCH (local=$local_size/$local_hash remote=$remote_size/$remote_hash)"
log " Status: FAIL"
FAIL=$((FAIL + 1))
fi
else
log " Verify: File not received after ${timeout_sec}s"
log " Status: FAIL"
FAIL=$((FAIL + 1))
fi
# Clean up remote .flo
ssh "$remote_host" "rm -f '$remote_outbound/$bso_hex.flo'" 2>/dev/null
log ""
}
# Run bidirectional test
run_bidir_test() {
local desc="$1"
local target_addr="$2"
local flo_file="$3"
local send_file="$4"
local verify_host="$5"
local verify_dir="$6"
local remote_outbound="$7"
local remote_testdata="$8"
local bso_hex="$9"
local recv_file="${10}"
local timeout_sec="${11:-60}"
TEST_NUM=$((TEST_NUM + 1))
log "--- TEST $TEST_NUM: $desc ---"
# Clean both sides
local verify_secure
if [ "$verify_host" = "$FIMAIL_HOST" ]; then
verify_secure="$FIMAIL_SECURE"
else
verify_secure="$BBSNODE1_SECURE"
fi
clean_remote "$verify_host" "$verify_dir" "$verify_secure"
clean_local_inbound
# Queue file on our side
echo "$TESTDATA/$send_file" > "$flo_file"
# Queue file on remote side (BSO: 1:213/725 = 00d502d5)
ssh "$verify_host" "echo '$remote_testdata/$recv_file' > '$remote_outbound/00d502d5.flo'" 2>/dev/null
# Small delay to let remote detect the outbound
sleep 1
# Run comet call (it handles both TX and RX in single session)
local start_ns=$(date +%s%N)
local output
local exit_code=0
output=$(timeout "$timeout_sec" $COMET -c "$CFG" call "$target_addr" 2>&1) || exit_code=$?
local end_ns=$(date +%s%N)
local elapsed_ms=$(( (end_ns - start_ns) / 1000000 ))
local session=$(echo "$output" | grep -E 'Session complete:' | head -1 | sed 's/^+ //')
local remote_mailer=$(echo "$output" | grep -oP 'Rcvd INIT: .* \[\K[^\]]+' | head -1)
log " Send: $send_file -> $target_addr"
log " Recv: $recv_file <- $target_addr"
[ -n "$remote_mailer" ] && log " Remote: $remote_mailer"
log " Time: ${elapsed_ms}ms"
log " Exit: $exit_code"
[ -n "$session" ] && log " $session"
if [ "$exit_code" -ne 0 ]; then
log " Status: FAIL (exit code $exit_code)"
FAIL=$((FAIL + 1))
log ""
return 1
fi
local all_ok=true
# Verify sent file arrived on remote
local send_fname=$(basename "$send_file")
local send_local_size=$(local_filesize "$TESTDATA/$send_file")
local send_remote_size=$(remote_filesize "$verify_host" "$verify_dir/$send_fname")
if [ "$send_remote_size" = "0" ]; then
send_remote_size=$(remote_filesize "$verify_host" "$verify_secure/$send_fname")
fi
if [ "$send_remote_size" = "$send_local_size" ]; then
log " TX verify $send_fname: OK ($send_local_size bytes)"
else
log " TX verify $send_fname: MISMATCH (local=$send_local_size remote=$send_remote_size)"
all_ok=false
fi
# Verify received file arrived locally
local recv_fname=$(basename "$recv_file")
local recv_remote_size=$(remote_filesize "$verify_host" "$remote_testdata/$recv_file")
local recv_local_path=""
if [ -f "$LOCAL_INBOUND/$recv_fname" ]; then
recv_local_path="$LOCAL_INBOUND/$recv_fname"
elif [ -f "$LOCAL_SECURE/$recv_fname" ]; then
recv_local_path="$LOCAL_SECURE/$recv_fname"
fi
if [ -n "$recv_local_path" ]; then
local recv_local_size=$(local_filesize "$recv_local_path")
if [ "$recv_local_size" = "$recv_remote_size" ]; then
log " RX verify $recv_fname: OK ($recv_local_size bytes)"
else
log " RX verify $recv_fname: MISMATCH (remote=$recv_remote_size local=$recv_local_size)"
all_ok=false
fi
else
log " RX verify $recv_fname: NOT RECEIVED"
all_ok=false
fi
if $all_ok; then
log " Status: PASS"
PASS=$((PASS + 1))
else
log " Status: FAIL (verification)"
FAIL=$((FAIL + 1))
fi
log ""
# Clean remote outbound
ssh "$verify_host" "rm -f '$remote_outbound/00d502d5.flo'" 2>/dev/null
}
# ============================================================================
# MAIN
# ============================================================================
cd "$BASEDIR"
cat > "$RESULTS" << EOF
============================================================
Comet Protocol Interop Test Suite
$TIMESTAMP
Local: 1:213/725 Comet 1.1-1 (192.168.10.175:26638)
fimail: 1:213/726 Fastway/0.1.0 (fastway.server.lan:26638)
bbsnode1: 1:213/720 Comet/1.01.01 (FW) (192.168.10.176:24554)
============================================================
EOF
echo ""
echo "Comet Protocol Interop Test Suite"
echo "================================="
echo ""
# ---- PHASE 1: Single-file Comet NOPWD to bbsnode1 -------------------------
log_header "PHASE 1a: Comet NOPWD — Local -> bbsnode1 (1:213/720)"
for size in 1kb 100kb 1mb 10mb 25mb; do
run_test "Comet NOPWD ${size} -> bbsnode1" \
"1:213/720" "$FLO_BBSNODE1" \
"$TESTDATA/test_${size}.bin" \
"$BBSNODE1_HOST" "$BBSNODE1_INBOUND" \
"" 120
done
# ---- PHASE 1b: Single-file Comet NOPWD to fimail --------------------------
log_header "PHASE 1b: Comet NOPWD — Local -> fimail (1:213/726)"
for size in 1kb 100kb 1mb 10mb 25mb; do
run_test "Comet NOPWD ${size} -> fimail" \
"1:213/726" "$FLO_FIMAIL" \
"$TESTDATA/test_${size}.bin" \
"$FIMAIL_HOST" "$FIMAIL_INBOUND" \
"" 120
done
# ---- PHASE 2: BinkP fallback ----------------------------------------------
log_header "PHASE 2: BinkP Fallback"
# Temporarily add NoComet to force BinkP for bbsnode1
sed -i '/^\[Node:1:213\/720\]/a NoComet = yes' "$CFG"
run_test "BinkP 1MB -> bbsnode1" \
"1:213/720" "$FLO_BBSNODE1" \
"$TESTDATA/test_1mb.bin" \
"$BBSNODE1_HOST" "$BBSNODE1_INBOUND" \
"" 60
run_test "BinkP 10MB -> bbsnode1" \
"1:213/720" "$FLO_BBSNODE1" \
"$TESTDATA/test_10mb.bin" \
"$BBSNODE1_HOST" "$BBSNODE1_INBOUND" \
"" 120
# Remove NoComet
sed -i '/^NoComet = yes$/d' "$CFG"
# BinkP to fimail
sed -i '/^\[Node:1:213\/726\]/a NoComet = yes' "$CFG"
run_test "BinkP 1MB -> fimail" \
"1:213/726" "$FLO_FIMAIL" \
"$TESTDATA/test_1mb.bin" \
"$FIMAIL_HOST" "$FIMAIL_INBOUND" \
"" 60
run_test "BinkP 10MB -> fimail" \
"1:213/726" "$FLO_FIMAIL" \
"$TESTDATA/test_10mb.bin" \
"$FIMAIL_HOST" "$FIMAIL_INBOUND" \
"" 120
sed -i '/^NoComet = yes$/d' "$CFG"
# ---- PHASE 3: Multi-file --------------------------------------------------
log_header "PHASE 3: Multi-file (5 files in single session)"
MULTI_FILES="$TESTDATA/test_1kb.bin $TESTDATA/test_100kb.bin $TESTDATA/test_1mb.bin $TESTDATA/test_10mb.bin $TESTDATA/test_25mb.bin"
run_test "Multi-file (all sizes) -> bbsnode1" \
"1:213/720" "$FLO_BBSNODE1" \
"$MULTI_FILES" \
"$BBSNODE1_HOST" "$BBSNODE1_INBOUND" \
"" 180
run_test "Multi-file (all sizes) -> fimail" \
"1:213/726" "$FLO_FIMAIL" \
"$MULTI_FILES" \
"$FIMAIL_HOST" "$FIMAIL_INBOUND" \
"" 180
# ---- PHASE 4: Bidirectional -----------------------------------------------
log_header "PHASE 4: Bidirectional Transfers"
run_bidir_test "Bidir 1MB <-> bbsnode1" \
"1:213/720" "$FLO_BBSNODE1" \
"test_1mb.bin" \
"$BBSNODE1_HOST" "$BBSNODE1_INBOUND" \
"$BBSNODE1_OUTBOUND" "$BBSNODE1_TESTDATA" \
"00d502d0" "test_1mb.bin" 60
run_bidir_test "Bidir 10MB <-> bbsnode1" \
"1:213/720" "$FLO_BBSNODE1" \
"test_10mb.bin" \
"$BBSNODE1_HOST" "$BBSNODE1_INBOUND" \
"$BBSNODE1_OUTBOUND" "$BBSNODE1_TESTDATA" \
"00d502d0" "test_10mb.bin" 120
run_bidir_test "Bidir 1MB <-> fimail" \
"1:213/726" "$FLO_FIMAIL" \
"test_1mb.bin" \
"$FIMAIL_HOST" "$FIMAIL_INBOUND" \
"/var/lib/fastway/outbound" "$FIMAIL_TESTDATA" \
"00d502d6" "test_1mb.bin" 60
run_bidir_test "Bidir 10MB <-> fimail" \
"1:213/726" "$FLO_FIMAIL" \
"test_10mb.bin" \
"$FIMAIL_HOST" "$FIMAIL_INBOUND" \
"/var/lib/fastway/outbound" "$FIMAIL_TESTDATA" \
"00d502d6" "test_10mb.bin" 120
# ---- PHASE 5: Inbound (remote calls us) -----------------------------------
log_header "PHASE 5: Inbound Tests (remote calls local)"
# fimail calling local using standalone comet
run_inbound_test "Inbound from fimail (1:213/726)" \
"$FIMAIL_HOST" "1:213/726" \
"/var/lib/fastway/outbound" "$FIMAIL_TESTDATA" \
"00d502d5" "test_1mb.bin" 45
# ---- SUMMARY ---------------------------------------------------------------
log ""
log "============================================================"
log " RESULTS SUMMARY"
log " Completed: $(date '+%Y-%m-%d %H:%M:%S')"
log ""
log " PASS: $PASS"
log " FAIL: $FAIL"
log " SKIP: $SKIP"
log " TOTAL: $((PASS + FAIL + SKIP))"
log ""
if [ $FAIL -eq 0 ]; then
log " ALL TESTS PASSED"
else
log " ** $FAIL TEST(S) FAILED **"
fi
log "============================================================"
echo ""
echo "Results written to: $RESULTS"