Files
waste-go/test-network.sh

483 lines
24 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# test-network.sh — spin up anchor + 3 peers, connect them, exchange messages + DMs.
# Usage: ./test-network.sh
# Requires: go, nc, jq, sqlite3
set -euo pipefail
# ── colours ───────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; BLUE='\033[0;34m'
BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m'
ANCHOR_COLOR=$YELLOW
ALICE_COLOR=$CYAN
BOB_COLOR=$MAGENTA
CHARLIE_COLOR=$GREEN
# ── config ────────────────────────────────────────────────────────────────────
ANCHOR_PORT=19339
ALICE_IPC=19337
BOB_IPC=19340
CHARLIE_IPC=19343
NETWORK_NAME="test-$(date +%s)"
DATA_ROOT="/tmp/waste-test"
rm -rf "$DATA_ROOT"
mkdir -p "$DATA_ROOT/bin"
# Seed per-peer share directories with dummy files.
mkdir -p "$DATA_ROOT/alice/share" "$DATA_ROOT/bob/share" "$DATA_ROOT/charlie/share"
echo "alice's notes" > "$DATA_ROOT/alice/share/notes.txt"
dd if=/dev/urandom bs=1K count=64 2>/dev/null | base64 > "$DATA_ROOT/alice/share/photo.jpg.b64"
dd if=/dev/urandom bs=1K count=128 2>/dev/null | base64 > "$DATA_ROOT/bob/share/archive.tar.b64"
echo -e "file1.mp3\nfile2.mp3" > "$DATA_ROOT/bob/share/playlist.m3u"
echo "charlie's doc" > "$DATA_ROOT/charlie/share/document.txt"
echo "#!/bin/sh" > "$DATA_ROOT/charlie/share/script.sh"
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
# Kill any leftover processes from a previous run holding our fixed ports.
for port in "$ANCHOR_PORT" "$ALICE_IPC" "$BOB_IPC" "$CHARLIE_IPC"; do
pid=$(lsof -ti tcp:"$port" 2>/dev/null || true)
[ -n "$pid" ] && kill -9 $pid 2>/dev/null || true
done
# Wait until all ports are actually free before proceeding.
for port in "$ANCHOR_PORT" "$ALICE_IPC" "$BOB_IPC" "$CHARLIE_IPC"; do
n=0
while lsof -ti tcp:"$port" >/dev/null 2>&1; do
sleep 0.1
n=$(( n + 1 ))
[ "$n" -gt 30 ] && echo -e "${RED}port ${port} still in use after 3s${RESET}" >&2 && break
done
done
# ── cleanup ───────────────────────────────────────────────────────────────────
PIDS=()
cleanup() {
echo ""
echo -e "${DIM}── cleaning up ──────────────────────────────────────────${RESET}"
for pid in "${PIDS[@]:-}"; do
kill "$pid" 2>/dev/null || true
done
wait 2>/dev/null || true
echo -e "${DIM}data left at: ${DATA_ROOT}${RESET}"
echo -e "${DIM}done.${RESET}"
}
trap cleanup EXIT INT TERM
# ── helpers ───────────────────────────────────────────────────────────────────
log() {
local color="$1"; shift
local label="$1"; shift
echo -e "${color}${BOLD}[${label}]${RESET} $*"
}
# Pretty-print a raw IPC JSON line
pretty() {
local color="$1"
local label="$2"
local line="$3"
local type
type=$(echo "$line" | jq -r '.type // "?"' 2>/dev/null)
case "$type" in
state_snapshot)
local id nick
id=$(echo "$line" | jq -r '.local_peer.id[:16]' 2>/dev/null | sed 's/.\{4\}/& /g')
nick=$(echo "$line" | jq -r '.local_peer.alias' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${DIM}state_snapshot${RESET} id=${CYAN}${id}${RESET} alias=${BOLD}${nick}${RESET}"
;;
session_ready)
local pid nick
pid=$(echo "$line" | jq -r 'if .peer_id then .peer_id[:16] else "?" end' 2>/dev/null | sed 's/.\{4\}/& /g')
nick=$(echo "$line" | jq -r '.nick // "?"' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${GREEN}✓ session_ready${RESET} peer=${BOLD}${nick}${RESET} (${DIM}${pid}${RESET})"
;;
peer_connected)
local nick
nick=$(echo "$line" | jq -r '.peer.alias // "?"' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${GREEN}→ peer_connected${RESET} ${BOLD}${nick}${RESET}"
;;
peer_disconnected)
local pid
pid=$(echo "$line" | jq -r 'if .peer_id then .peer_id[:8] else "?" end' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${RED}← peer_disconnected${RESET} ${DIM}${pid}${RESET}"
;;
message_received)
local from body room to_field
from=$(echo "$line" | jq -r '.message.from[:8] // "?"' 2>/dev/null)
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
body=$(echo "$line" | jq -r '.message.text // ""' 2>/dev/null)
room=$(echo "$line" | jq -r '.message.room // ""' 2>/dev/null)
to_field=$(echo "$line" | jq -r '.message.to // ""' 2>/dev/null)
if [ -n "$to_field" ]; then
local to_short
to_short=$(echo "$to_field" | cut -c1-8)
echo -e "${color}${BOLD}[${label}]${RESET} ${BLUE}📨 DM${RESET} ${DIM}<${from}…→${to_short}…>${RESET} ${body}"
else
echo -e "${color}${BOLD}[${label}]${RESET} ${BOLD}💬 #${room}${RESET} ${DIM}<${from}…>${RESET} ${body}"
fi
;;
incoming_file)
local name size xid
name=$(echo "$line" | jq -r '.offer.name // "?"' 2>/dev/null)
size=$(echo "$line" | jq -r '.offer.size // 0' 2>/dev/null)
xid=$(echo "$line" | jq -r '.offer.xid[:8] // "?"' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${YELLOW}↓ incoming_file${RESET} ${BOLD}${name}${RESET} (${size}B) xid=${DIM}${xid}${RESET}"
;;
file_progress)
local xid rx total
xid=$(echo "$line" | jq -r '.transfer_id[:8] // "?"' 2>/dev/null)
rx=$(echo "$line" | jq -r '.bytes_received // 0' 2>/dev/null)
total=$(echo "$line" | jq -r '.total_bytes // 0' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${DIM}file_progress${RESET} xid=${xid}${rx}/${total}B"
;;
file_complete)
local xid path
xid=$(echo "$line" | jq -r '.transfer_id[:8] // "?"' 2>/dev/null)
path=$(echo "$line" | jq -r '.path // "?"' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${GREEN}✓ file_complete${RESET} xid=${DIM}${xid}${RESET}${BOLD}${path}${RESET}"
;;
error)
local msg
msg=$(echo "$line" | jq -r '.error_message // .' 2>/dev/null)
echo -e "${color}${BOLD}[${label}]${RESET} ${RED}✗ error:${RESET} ${msg}"
;;
*)
echo -e "${color}${BOLD}[${label}]${RESET} ${DIM}${line}${RESET}"
;;
esac
}
# Subscribe to a peer's IPC events in the background, pretty-printing each line.
subscribe() {
local color="$1"
local label="$2"
local port="$3"
(
set +e
nc 127.0.0.1 "$port" 2>/dev/null | while IFS= read -r line; do
pretty "$color" "$label" "$line"
done
) &
PIDS+=($!)
}
# Send a single JSON command to a peer's IPC port (fire-and-forget).
ipc() {
local port="$1"
local json="$2"
echo "$json" | nc -q 0 127.0.0.1 "$port" >/dev/null 2>&1 || true
}
# Query get_state and return a single field via jq from the state_snapshot line.
# Retries for up to 3 seconds in case the daemon is still initialising.
# Usage: peer_field <ipc_port> <jq_expr>
peer_field() {
local port="$1"
local expr="$2"
local result=""
local n=0
while [ -z "$result" ] || [ "$result" = "null" ]; do
result=$(echo '{"type":"get_state"}' \
| timeout 2 nc 127.0.0.1 "$port" 2>/dev/null \
| grep '"type":"state_snapshot"' \
| head -1 \
| jq -r "$expr" 2>/dev/null || true)
n=$(( n + 1 ))
[ "$n" -ge 10 ] && break
[ -z "$result" ] || [ "$result" = "null" ] && sleep 0.3
done
echo "$result"
}
# Wait until a TCP port accepts connections (up to N seconds).
wait_port() {
local port="$1"
local label="$2"
local n=0
while ! nc -z 127.0.0.1 "$port" 2>/dev/null; do
sleep 0.1
n=$(( n + 1 ))
if [ "$n" -gt 80 ]; then
echo -e "${RED}timeout waiting for ${label} on :${port}${RESET}" >&2
exit 1
fi
done
return 0
}
# ── build ─────────────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}waste-go network test${RESET}"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "${DIM}network : ${BOLD}${NETWORK_NAME}${RESET}"
echo -e "${DIM}data : ${DATA_ROOT}${RESET}"
echo ""
echo -e "${DIM}building binaries…${RESET}"
go build -o "$DATA_ROOT/bin/waste-anchor" ./cmd/anchor
go build -o "$DATA_ROOT/bin/waste-daemon" ./cmd/daemon
echo -e "${GREEN}✓ built${RESET}"
echo ""
# ── anchor ────────────────────────────────────────────────────────────────────
log "$ANCHOR_COLOR" "anchor" "starting on :${ANCHOR_PORT}"
"$DATA_ROOT/bin/waste-anchor" -bind "127.0.0.1:${ANCHOR_PORT}" \
2> >(while IFS= read -r l; do echo -e "${ANCHOR_COLOR}${DIM}[anchor] ${l}${RESET}"; done) &
PIDS+=($!)
wait_port "$ANCHOR_PORT" "anchor"
log "$ANCHOR_COLOR" "anchor" "ready"
echo ""
# ── daemons ───────────────────────────────────────────────────────────────────
ANCHOR_URL="ws://127.0.0.1:${ANCHOR_PORT}/ws"
log "$ALICE_COLOR" "alice" "starting daemon (ipc :${ALICE_IPC})"
"$DATA_ROOT/bin/waste-daemon" \
-alias alice \
-data-dir "$DATA_ROOT/alice" \
-ipc-port "$ALICE_IPC" \
-anchor "$ANCHOR_URL" \
-share-dir "$DATA_ROOT/alice/share" \
2026-06-22 14:45:15 +02:00
2> >(tee "$DATA_ROOT/alice/daemon.log" | while IFS= read -r l; do echo -e "${ALICE_COLOR}${DIM}[alice] ${l}${RESET}"; done) &
PIDS+=($!)
log "$BOB_COLOR" "bob" "starting daemon (ipc :${BOB_IPC})"
"$DATA_ROOT/bin/waste-daemon" \
-alias bob \
-data-dir "$DATA_ROOT/bob" \
-ipc-port "$BOB_IPC" \
-anchor "$ANCHOR_URL" \
-share-dir "$DATA_ROOT/bob/share" \
2026-06-22 14:45:15 +02:00
2> >(tee "$DATA_ROOT/bob/daemon.log" | while IFS= read -r l; do echo -e "${BOB_COLOR}${DIM}[bob] ${l}${RESET}"; done) &
PIDS+=($!)
log "$CHARLIE_COLOR" "charlie" "starting daemon (ipc :${CHARLIE_IPC})"
"$DATA_ROOT/bin/waste-daemon" \
-alias charlie \
-data-dir "$DATA_ROOT/charlie" \
-ipc-port "$CHARLIE_IPC" \
-anchor "$ANCHOR_URL" \
-share-dir "$DATA_ROOT/charlie/share" \
2026-06-22 14:45:15 +02:00
2> >(tee "$DATA_ROOT/charlie/daemon.log" | while IFS= read -r l; do echo -e "${CHARLIE_COLOR}${DIM}[charlie]${l}${RESET}"; done) &
PIDS+=($!)
wait_port "$ALICE_IPC" "alice"
wait_port "$BOB_IPC" "bob"
wait_port "$CHARLIE_IPC" "charlie"
echo ""
# ── join network ──────────────────────────────────────────────────────────────
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "joining all peers to network: ${BOLD}${NETWORK_NAME}${RESET}"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
JOIN=$(printf '{"type":"join_network","network_name":"%s"}' "$NETWORK_NAME")
ipc "$ALICE_IPC" "$JOIN"
sleep 0.2
ipc "$BOB_IPC" "$JOIN"
sleep 0.2
ipc "$CHARLIE_IPC" "$JOIN"
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
sleep 0.3
# ── resolve peer IDs ──────────────────────────────────────────────────────────
# Peer IDs are network-scoped (derived identity), so we query AFTER joining.
ALICE_ID=$(peer_field "$ALICE_IPC" '.local_peer.id')
BOB_ID=$(peer_field "$BOB_IPC" '.local_peer.id')
CHARLIE_ID=$(peer_field "$CHARLIE_IPC" '.local_peer.id')
log "$ANCHOR_COLOR" "ids" "alice = ${ALICE_ID:0:16}"
log "$ANCHOR_COLOR" "ids" "bob = ${BOB_ID:0:16}"
log "$ANCHOR_COLOR" "ids" "charlie = ${CHARLIE_ID:0:16}"
echo ""
# ── subscribe ─────────────────────────────────────────────────────────────────
echo -e "${DIM}subscribing to IPC event streams…${RESET}"
subscribe "$ALICE_COLOR" "alice " "$ALICE_IPC"
subscribe "$BOB_COLOR" "bob " "$BOB_IPC"
subscribe "$CHARLIE_COLOR" "charlie" "$CHARLIE_IPC"
echo -e "${DIM}waiting for ICE / DataChannel setup (up to 10s)…${RESET}"
sleep 6
2026-06-22 14:45:15 +02:00
# ── YAW/2.1 forward-secrecy check ────────────────────────────────────────────
# The daemon logs "2.1 FS offer" when forward-secret signaling was negotiated,
# or "2.0 fallback offer" if the peer didn't respond to the ekey in time.
# We verify by counting FS vs fallback lines in the daemon log files.
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "YAW/2.1 forward-secret signaling check"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
fs_ok=0
fs_fail=0
for peer_data in "$DATA_ROOT/alice" "$DATA_ROOT/bob" "$DATA_ROOT/charlie"; do
logfile="$peer_data/daemon.log"
if [ -f "$logfile" ]; then
ok=$(grep -c "2\.1 FS offer" "$logfile" 2>/dev/null || true)
fail=$(grep -c "2\.0 fallback" "$logfile" 2>/dev/null || true)
fs_ok=$(( fs_ok + ok ))
fs_fail=$(( fs_fail + fail ))
fi
done
# Also check stderr captured above (it was piped to terminal; count from the
# variable output buffer if possible — or just note the result from logs).
# Simpler: re-check via a short daemon log we write below.
# For now just print what we know from the test run output.
if [ "$fs_fail" -eq 0 ]; then
echo -e " ${GREEN}✓ all sessions negotiated YAW/2.1 (forward-secret)${RESET}"
else
echo -e " ${YELLOW}${fs_fail} session(s) fell back to YAW/2.0 (non-FS)${RESET}"
fi
# ── group chat ────────────────────────────────────────────────────────────────
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "group chat (#general)"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"hey everyone, alice here"}'
sleep 0.4
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"hi alice! bob checking in"}'
sleep 0.4
ipc "$CHARLIE_IPC" '{"type":"send_message","room":"general","body":"charlie online. the mesh works!"}'
sleep 0.4
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"nice — three-way chat over WebRTC 🎉"}'
sleep 0.4
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"no central server, fully peer-to-peer"}'
sleep 0.4
# ── direct messages ───────────────────────────────────────────────────────────
# DMs set the "to" field to the recipient's full hex peer ID.
# The room field is used as "dm:<recipient_id>" by convention so both sides
# can query their DM history for the same conversation.
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "direct messages"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
# Alice → Bob (privately)
ipc "$ALICE_IPC" "$(jq -cn \
--arg to "$BOB_ID" \
--arg room "dm:$BOB_ID" \
--arg body "hey bob, this is just between us" \
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
sleep 0.4
# Bob replies to Alice
ipc "$BOB_IPC" "$(jq -cn \
--arg to "$ALICE_ID" \
--arg room "dm:$ALICE_ID" \
--arg body "got it alice, loud and clear 🤫" \
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
sleep 0.4
# Charlie → Alice (privately)
ipc "$CHARLIE_IPC" "$(jq -cn \
--arg to "$ALICE_ID" \
--arg room "dm:$ALICE_ID" \
--arg body "alice, can you hear me? charlie dm test" \
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
sleep 0.4
# Alice replies to Charlie
ipc "$ALICE_IPC" "$(jq -cn \
--arg to "$CHARLIE_ID" \
--arg room "dm:$CHARLIE_ID" \
--arg body "yes charlie, DMs working perfectly!" \
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
sleep 0.4
# ── file list check ───────────────────────────────────────────────────────────
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "file listing"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
alice_nets=$(echo '{"type":"get_state"}' | timeout 2 nc 127.0.0.1 "$ALICE_IPC" 2>/dev/null \
| grep '"type":"state_snapshot"' | head -1 \
| jq -r '[.networks[].network_name] | join(", ")' 2>/dev/null || echo "?")
echo -e "${DIM} alice networks: [${alice_nets:-none}]${RESET}"
for peer_ipc in "$ALICE_IPC" "$BOB_IPC" "$CHARLIE_IPC"; do
Multi-network foundation: netmgr, derived identities, additive IPC protocol YAW/2 peer wire protocol is unchanged. Changes are local only. internal/crypto: - DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash; same master + same network always produces the same Ed25519 keypair (stable peer ID) internal/proto: - NetworkInfo type (network_id, network_name, local_peer) - NetworkID field on IpcMessage (optional; commands default to first network when absent) - Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer) - EvtNetworkJoined / EvtNetworkLeft events internal/netmgr (new): - Manager holds N independent Network contexts (derived identity, mesh, store, anchor) - Join(name) creates context, derives identity, opens per-network DB, starts anchor client - Leave(id) / LeaveAll() cancel contexts and close stores - Resolve(netID) returns named network, or Default() when netID is empty (backward compat) - Fan-out: Manager.Subscribe() receives tagged events from all networks - Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short internal/ipc: - Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn) - Commands without network_id route to mgr.Default() (backward compat) - state_snapshot includes Networks array; local_peer/connected_peers still populated from first network - generate_invite, get_file_list, send_message all respect network_id routing cmd/daemon: - Creates netmgr.Manager instead of mesh.Mesh directly - --join and --share-dir pass through Config - Auto-join via mgr.Join() before IPC starts test-network.sh: - Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:13:54 +02:00
if [ "$peer_ipc" = "$ALICE_IPC" ]; then peer_name="alice"
elif [ "$peer_ipc" = "$BOB_IPC" ]; then peer_name="bob"
else peer_name="charlie"; fi
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
raw=$(echo '{"type":"get_file_list"}' \
| nc -q 2 127.0.0.1 "$peer_ipc" 2>/dev/null || true)
result=$(echo "$raw" | grep '"type":"file_list"' | head -1 || true)
if [ -n "$result" ]; then
count=$(echo "$result" | jq '.files | length' 2>/dev/null || echo "?")
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
files=$(echo "$result" | jq -r '[.files[].name] | join(" ")' 2>/dev/null || true)
echo -e " ${BOLD}${peer_name}${RESET}: ${count} file(s) — ${files}"
else
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
types=$(echo "$raw" | jq -r '.type' 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
echo -e " ${RED}${peer_name}: no file_list response${RESET} ${DIM}(got: ${types:-nothing})${RESET}"
fi
done
sleep 0.5
# ── file transfer ────────────────────────────────────────────────────────────
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "file transfer (alice → bob: notes.txt)"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
ipc "$ALICE_IPC" "$(jq -cn --arg peer "$BOB_ID" --arg path "notes.txt" \
'{"type":"send_file","peer_id":$peer,"path":$path}')"
# Wait up to 10s for bob to receive the file (glob over downloads-* dir)
received=0
for i in $(seq 1 50); do
sleep 0.2
if ls "$DATA_ROOT/bob/downloads-"*/notes.txt 2>/dev/null | grep -q .; then
received=1; break
fi
done
if [ "$received" = "1" ]; then
rx_file=$(ls "$DATA_ROOT/bob/downloads-"*/notes.txt 2>/dev/null | head -1)
orig_sha=$(sha256sum "$DATA_ROOT/alice/share/notes.txt" | awk '{print $1}')
rx_sha=$(sha256sum "$rx_file" | awk '{print $1}')
if [ "$orig_sha" = "$rx_sha" ]; then
echo -e " ${GREEN}✓ notes.txt received by bob, sha256 matches${RESET}"
else
echo -e " ${RED}✗ notes.txt received but sha256 mismatch!${RESET}"
echo -e " orig: $orig_sha"
echo -e " got: $rx_sha"
fi
else
echo -e " ${RED}✗ notes.txt not received by bob within 10s${RESET}"
fi
sleep 0.5
# ── leave ─────────────────────────────────────────────────────────────────────
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "charlie leaves the network"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
ipc "$CHARLIE_IPC" '{"type":"leave_network"}'
sleep 1
# ── persistence check ─────────────────────────────────────────────────────────
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "verifying persistence (SQLite)"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
for peer in alice bob charlie; do
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
db=$(ls "$DATA_ROOT/$peer/messages-"*.db 2>/dev/null | head -1 || true)
if [ -n "$db" ] && [ -f "$db" ]; then
total=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages;" 2>/dev/null || echo "?")
group=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages WHERE room='general';" 2>/dev/null || echo "?")
dms=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages WHERE room LIKE 'dm:%';" 2>/dev/null || echo "?")
peers_count=$(sqlite3 "$db" "SELECT COUNT(*) FROM peers;" 2>/dev/null || echo "?")
echo -e " ${BOLD}${peer}${RESET}: ${total} total (${group} group, ${dms} DM), ${peers_count} known peers"
else
Bring wire protocol into full YAW/2 spec compliance Five interop issues fixed against PROTOCOL.md: - Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex string) as specified in §5.1, not net_raw_bytes. Both anchor server and client updated to match. - Chat wire fields renamed to spec names: text/ts (Unix ms int64) replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage matches §8 exactly; store and TUI updated accordingly. - Direct messages now use the spec "pm" type (flat {type,mid,text,ts}) instead of chat+to. Receiver reconstructs a ChatMessage with dm:<short-id> room for IPC/storage. §8 compliant. - File transfer message types changed to spec hyphenated names: file-offer, file-accept, file-cancel, file-done with spec field names (name/size not filename/size_bytes). §9 compliant. - DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen fires on OnOpen callback or immediately if the channel is already open when WireDataChannel is called (answerer race). Also fixes two bugs found during testing: - mid was missing from outgoing wire messages, causing all received messages to arrive with mid="" and collide on the UNIQUE DB constraint. mid is now included on all sent chat/pm messages; a random mid is generated for any received message that omits it. - Test scripts hardened: kill -9 + active lsof polling replaces blind sleep for port cleanup; join_network sent before peer_field queries (local_peer is now network-scoped and nil until joined). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:38:01 +02:00
echo -e " ${RED}${peer}: no messages-*.db found${RESET}"
fi
done
echo ""
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
echo -e "${GREEN}${BOLD}test complete${RESET}"
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
sleep 0.5