#!/bin/bash
# Build all test binaries for hyperswarm-peer.
# C files compiled with gcc, C++ files with g++, then linked.
set -e
cd "$(dirname "$0")"
SRCDIR=../src
UDX_INC=../thirdparty/libudx/include
UDX_SRC=../thirdparty/libudx/src
# src/compact_encoding.h forwards to libcompact, holepunch's own C port of
# compact-encoding, so every binary that touches the wire links it. Symlinked
# into thirdparty/ the same way libudx is.
COMPACT_SRC=../thirdparty/libcompact/src
COMPACT_INC="-I../thirdparty/libcompact/include -I../thirdparty/libutf/include"
CFLAGS="-std=c11 -D_GNU_SOURCE -I$SRCDIR -I$UDX_INC $COMPACT_INC -c"
CXXFLAGS="-std=c++17 -D_GNU_SOURCE -I$SRCDIR -I$UDX_INC $COMPACT_INC -c"
LDFLAGS="-luv -lsodium -lpthread"
# ---- Optional sanitizer build: SAN=address ./build_tests.sh ----------------
# Godot loads GDExtensions with RTLD_DEEPBIND, which ASan refuses outright, so a
# memory bug in the transport CANNOT be caught through the game. These binaries
# drive the same SwarmManager/DhtConnector without Godot, which makes them the
# only place a sanitizer can see that code.
#
# Sanitized and normal objects MUST NOT share a cache: the udx objects below are
# reused when newer than their source, so one directory holding both would link
# instrumented and uninstrumented code together and fail in ways worse than the
# bug being chased. Hence the per-mode object dir.
OBJ="/tmp/hsp-obj${SAN:+-$SAN}"
mkdir -p "$OBJ"
if [ -n "${SAN:-}" ]; then
echo "=== SANITIZER BUILD (-fsanitize=$SAN), objects in $OBJ ==="
CFLAGS="-fsanitize=$SAN -fno-omit-frame-pointer -g $CFLAGS"
CXXFLAGS="-fsanitize=$SAN -fno-omit-frame-pointer -g $CXXFLAGS"
LDFLAGS="-fsanitize=$SAN $LDFLAGS"
fi
# Build libcompact objects (shared across all tests: every wire path uses them).
COMPACT_OBJS=""
for f in "$COMPACT_SRC"/*.c; do
b=$(basename "$f" .c)
obj="$OBJ/compact_$b.o"
if [ ! -f "$obj" ] || [ "$f" -nt "$obj" ]; then
gcc -std=c99 -O2 -D_GNU_SOURCE $COMPACT_INC -c -o "$obj" "$f"
fi
COMPACT_OBJS="$COMPACT_OBJS $obj"
done
LDFLAGS="$COMPACT_OBJS $LDFLAGS"
echo "=== libcompact objects built ==="
# Build libudx object files (shared across all tests).
UDX_OBJS=""
for f in udx.c udx_bbr.c udx_rate.c cirbuf.c endian.c io_posix.c queue.c win_filter.c win_filter_f64.c; do
obj="$OBJ/udx_${f%.c}.o"
if [ ! -f "$obj" ] || [ "$UDX_SRC/$f" -nt "$obj" ]; then
gcc $CFLAGS -o "$obj" "$UDX_SRC/$f"
fi
UDX_OBJS="$UDX_OBJS $obj"
done
echo "=== libudx objects built ==="
# ---- test_dht_bootstrap ----
echo "Building test_dht_bootstrap..."
g++ $CXXFLAGS -o $OBJ/test_dht_bootstrap.o test_dht_bootstrap.cpp
g++ $CXXFLAGS -o $OBJ/dht_rpc.o $SRCDIR/dht_rpc.cpp
g++ $CXXFLAGS -o $OBJ/noise_ik.o $SRCDIR/noise_ik.cpp
g++ -o test_dht_bootstrap \
$OBJ/test_dht_bootstrap.o $OBJ/dht_rpc.o $OBJ/noise_ik.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_dht_bootstrap"
# ---- test_phase4 ----
echo "Building test_phase4..."
g++ $CXXFLAGS -o $OBJ/test_phase4.o test_phase4.cpp
g++ $CXXFLAGS -o $OBJ/holepuncher.o $SRCDIR/holepuncher.cpp
g++ $CXXFLAGS -o $OBJ/hyperdht.o $SRCDIR/hyperdht.cpp
g++ $CXXFLAGS -o $OBJ/dht_connector.o $SRCDIR/dht_connector.cpp
g++ -o test_phase4 \
$OBJ/test_phase4.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_phase4"
# ---- test_regression ----
echo "Building test_regression..."
g++ $CXXFLAGS -o $OBJ/test_regression.o test_regression.cpp
g++ $CXXFLAGS -o $OBJ/noise_handshake.o $SRCDIR/noise_handshake.cpp
g++ $CXXFLAGS -o $OBJ/noise_stream.o $SRCDIR/noise_stream.cpp
g++ $CXXFLAGS -o $OBJ/udx_transport.o $SRCDIR/udx_transport.cpp
g++ $CXXFLAGS -o $OBJ/swarm_manager.o $SRCDIR/swarm_manager.cpp
# protomux.o is NOT optional: udx_transport.cpp calls Protomux::recv and the
# Protomux destructor, so omitting it fails the link. Because this script runs
# under `set -e`, that failure aborted EVERY target below this one — which is why
# the interop/phase binaries on disk had been stale since February while the
# script still looked like it covered them (hyper-games #17 §5).
g++ $CXXFLAGS -o $OBJ/protomux.o $SRCDIR/protomux.cpp
g++ -o test_regression \
$OBJ/test_regression.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o \
$OBJ/hyperdht.o $OBJ/dht_connector.o $OBJ/udx_transport.o \
$OBJ/swarm_manager.o $OBJ/protomux.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_regression"
# ---- test_stream_consumer: the opt-in seam an embedder (keetd) uses to run its own
# protocol stack over the secret-stream, and proof the default path is unchanged ----
echo "Building test_stream_consumer..."
g++ $CXXFLAGS -o $OBJ/test_stream_consumer.o test_stream_consumer.cpp
g++ -o test_stream_consumer \
$OBJ/test_stream_consumer.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o \
$OBJ/hyperdht.o $OBJ/dht_connector.o $OBJ/udx_transport.o \
$OBJ/swarm_manager.o $OBJ/protomux.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_stream_consumer"
# ---- test_peer_lifecycle: a PeerConnection outlives its udx stream's libuv handles ----
echo "Building test_peer_lifecycle..."
g++ $CXXFLAGS -o $OBJ/test_peer_lifecycle.o test_peer_lifecycle.cpp
g++ -o test_peer_lifecycle \
$OBJ/test_peer_lifecycle.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o \
$OBJ/hyperdht.o $OBJ/dht_connector.o $OBJ/udx_transport.o \
$OBJ/swarm_manager.o $OBJ/protomux.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_peer_lifecycle"
# ---- test_interop_client ----
echo "Building test_interop_client..."
g++ $CXXFLAGS -o $OBJ/test_interop_client.o test_interop_client.cpp
g++ -o test_interop_client \
$OBJ/test_interop_client.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_interop_client"
# ---- test_phase5 ----
echo "Building test_phase5..."
g++ $CXXFLAGS -o $OBJ/test_phase5.o test_phase5.cpp
g++ -o test_phase5 \
$OBJ/test_phase5.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_phase5"
# ---- test_phase6 ----
echo "Building test_phase6..."
g++ $CXXFLAGS -o $OBJ/test_phase6.o test_phase6.cpp
g++ -o test_phase6 \
$OBJ/test_phase6.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_phase6"
# ---- test_phase7 ----
echo "Building test_phase7..."
g++ $CXXFLAGS -o $OBJ/test_phase7.o test_phase7.cpp
g++ -o test_phase7 \
$OBJ/test_phase7.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_phase7"
# ---- test_integration ----
echo "Building test_integration..."
g++ $CXXFLAGS -o $OBJ/test_integration.o test_integration.cpp
g++ -o test_integration \
$OBJ/test_integration.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_integration"
# ---- test_crossystem_client ----
echo "Building test_crossystem_client..."
g++ $CXXFLAGS -o $OBJ/test_crossystem_client.o test_crossystem_client.cpp
g++ -o test_crossystem_client \
$OBJ/test_crossystem_client.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_crossystem_client"
# ---- test_interop_server_bin ----
echo "Building test_interop_server_bin..."
g++ $CXXFLAGS -o $OBJ/test_interop_server_bin.o test_interop_server_bin.cpp
g++ -o test_interop_server_bin \
$OBJ/test_interop_server_bin.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_interop_server_bin"
# ---- test_verify_vectors ----
echo "Building test_verify_vectors..."
g++ $CXXFLAGS -o $OBJ/test_verify_vectors.o test_verify_vectors.cpp
g++ -o test_verify_vectors \
$OBJ/test_verify_vectors.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_verify_vectors"
# ---- test_three_peer_swarm ----
# Same object set as test_regression: this drives UdxTransport, which owns the
# HyperDht/DhtConnector/SwarmManager trio, so it needs protomux too.
echo "Building test_three_peer_swarm..."
# -I liboverlay/include: this one attaches a real overlay (Protocol +
# OverlayTransport), which is the ingredient the swarm-only version lacked.
g++ $CXXFLAGS -I../liboverlay/include -o $OBJ/test_three_peer_swarm.o test_three_peer_swarm.cpp
g++ -o test_three_peer_swarm \
$OBJ/test_three_peer_swarm.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o \
$OBJ/hyperdht.o $OBJ/dht_connector.o $OBJ/udx_transport.o \
$OBJ/swarm_manager.o $OBJ/protomux.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_three_peer_swarm"
# ---- test_session_channel ----
# The blender-bridge session channel on a real swarm, Godot-free. Same object set
# as test_three_peer_swarm minus liboverlay: this attaches a plain Protomux
# channel rather than an overlay protocol, because the session channel carries
# opaque frames and holds no logic of its own.
echo "Building test_session_channel..."
g++ $CXXFLAGS -o $OBJ/test_session_channel.o test_session_channel.cpp
g++ -o test_session_channel \
$OBJ/test_session_channel.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o \
$OBJ/hyperdht.o $OBJ/dht_connector.o $OBJ/udx_transport.o \
$OBJ/swarm_manager.o $OBJ/protomux.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_session_channel"
# ---- test_protomux_gc ----
# Protomux only: no sockets, no threads, no DHT. The reject path is provoked by
# two muxes talking to each other, which is all it takes.
echo "Building test_protomux_gc..."
g++ $CXXFLAGS -o $OBJ/test_protomux_gc.o test_protomux_gc.cpp
g++ $CXXFLAGS -o $OBJ/protomux.o $SRCDIR/protomux.cpp
g++ -o test_protomux_gc $OBJ/test_protomux_gc.o $OBJ/protomux.o $LDFLAGS
echo " OK: test_protomux_gc"
# ---- test_bootstrap_ownership ----
# Drives UdxTransport::start_dht directly, so it needs the same object set as
# test_regression.
echo "Building test_bootstrap_ownership..."
g++ $CXXFLAGS -o $OBJ/test_bootstrap_ownership.o test_bootstrap_ownership.cpp
g++ -o test_bootstrap_ownership \
$OBJ/test_bootstrap_ownership.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o \
$OBJ/hyperdht.o $OBJ/dht_connector.o $OBJ/udx_transport.o \
$OBJ/swarm_manager.o $OBJ/protomux.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_bootstrap_ownership"
# ---- test_overlay_late_attach ----
# Protomux + the overlay's Protomux adapter, nothing else: liboverlay is
# header-only, so the ordering this covers needs no library and no socket.
echo "Building test_overlay_late_attach..."
g++ $CXXFLAGS -I../liboverlay/include -o $OBJ/test_overlay_late_attach.o test_overlay_late_attach.cpp
g++ -o test_overlay_late_attach $OBJ/test_overlay_late_attach.o $OBJ/protomux.o $LDFLAGS
echo " OK: test_overlay_late_attach"
echo ""
# ---- test_ann_signable ----
# The (un)announce signable against hyperdht's OWN bytes. See
# docs/PARITY-hyperswarm.md §7. Vector: gen_unannounce_vector.js.
echo "Building test_ann_signable..."
g++ $CXXFLAGS -o $OBJ/test_ann_signable.o test_ann_signable.cpp
g++ -o test_ann_signable \
$OBJ/test_ann_signable.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_ann_signable"
# ---- test_handshake_pool ----
# hyper-games #72's wall: a responder must still answer after it has already
# handled many handshakes. See docs/PARITY-hyperswarm.md §8.
echo "Building test_handshake_pool..."
g++ $CXXFLAGS -o $OBJ/test_handshake_pool.o test_handshake_pool.cpp
g++ -o test_handshake_pool \
$OBJ/test_handshake_pool.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_handshake_pool"
# ---- test_handshake_dedup: a repeated inbound handshake gets the SAME reply ----
echo "Building test_handshake_dedup..."
g++ $CXXFLAGS -o $OBJ/test_handshake_dedup.o test_handshake_dedup.cpp
g++ -o test_handshake_dedup \
$OBJ/test_handshake_dedup.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_handshake_dedup"
# ---- test_find_peer_exhausted ----
# A connect attempt must fail when find_peer EXHAUSTS, not 30s later. See
# docs/PARITY-hyperswarm.md §11.2.
echo "Building test_find_peer_exhausted..."
g++ $CXXFLAGS -o $OBJ/test_find_peer_exhausted.o test_find_peer_exhausted.cpp
g++ -o test_find_peer_exhausted \
$OBJ/test_find_peer_exhausted.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_find_peer_exhausted"
# ---- test_self_announce ----
# A listening peer must keep re-announcing its OWN key. See
# docs/PARITY-hyperswarm.md §9.
echo "Building test_self_announce..."
g++ $CXXFLAGS -o $OBJ/test_self_announce.o test_self_announce.cpp
g++ -o test_self_announce \
$OBJ/test_self_announce.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_self_announce"
# ---- test_peer_topics ----
# A peer discovered on a SECOND topic must record it even while connected. See
# docs/PARITY-hyperswarm.md §10.
echo "Building test_peer_topics..."
g++ $CXXFLAGS -o $OBJ/test_peer_topics.o test_peer_topics.cpp
g++ -o test_peer_topics \
$OBJ/test_peer_topics.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$OBJ/dht_connector.o $OBJ/swarm_manager.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_peer_topics"
# ---- test_self_announce_live ----
# Needs a real testnet: run via `node run_self_announce_live.js`, not directly.
echo "Building test_self_announce_live..."
g++ $CXXFLAGS -o $OBJ/test_self_announce_live.o test_self_announce_live.cpp
g++ -o test_self_announce_live \
$OBJ/test_self_announce_live.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_self_announce_live"
# ---- test_compact_encoding ----
# ⛔ THESE FOUR WERE NOT IN THIS SCRIPT. Their binaries sat on disk from
# February while the sources moved under them, so running ./test_compact_encoding
# measured a build from another era — the exact trap §5 of hyper-games #17
# describes, reached a second way: not a half-updated object cache, but a target
# the script never had. A gate the build does not rebuild is not a gate.
echo "Building test_compact_encoding..."
g++ $CXXFLAGS -o $OBJ/test_compact_encoding.o test_compact_encoding.cpp
g++ -o test_compact_encoding $OBJ/test_compact_encoding.o $UDX_OBJS $LDFLAGS
echo " OK: test_compact_encoding"
# ---- test_phase3 ----
echo "Building test_phase3..."
g++ $CXXFLAGS -o $OBJ/test_phase3.o test_phase3.cpp
g++ -o test_phase3 $OBJ/test_phase3.o $OBJ/noise_ik.o $UDX_OBJS $LDFLAGS
echo " OK: test_phase3"
# ---- test_dht_godot_style ----
echo "Building test_dht_godot_style..."
g++ $CXXFLAGS -o $OBJ/test_dht_godot_style.o test_dht_godot_style.cpp
g++ -o test_dht_godot_style \
$OBJ/test_dht_godot_style.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_dht_godot_style"
# ---- test_live_lookup ----
# Needs a real DHT: it looks a key up for real. Built here so it cannot rot.
echo "Building test_live_lookup..."
g++ $CXXFLAGS -o $OBJ/test_live_lookup.o test_live_lookup.cpp
g++ -o test_live_lookup \
$OBJ/test_live_lookup.o $OBJ/noise_ik.o $OBJ/dht_rpc.o \
$OBJ/holepuncher.o $OBJ/hyperdht.o $OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_live_lookup"
# ---- test_relay_error_abort ----
# An unreadable holepunch reply must fail the attempt now, not at the 30s wall
# clock. See docs/PARITY-hyperswarm.md §11.1.
echo "Building test_relay_error_abort..."
g++ $CXXFLAGS -o $OBJ/test_relay_error_abort.o test_relay_error_abort.cpp
g++ -o test_relay_error_abort \
$OBJ/test_relay_error_abort.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$OBJ/dht_connector.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_relay_error_abort"
# ---- test_unannounce_live ----
# Needs a real testnet: run via `node run_unannounce_live.js`, not directly.
# docs/PARITY-hyperswarm.md A2.
echo "Building test_unannounce_live..."
g++ $CXXFLAGS -o $OBJ/test_unannounce_live.o test_unannounce_live.cpp
g++ -o test_unannounce_live \
$OBJ/test_unannounce_live.o $OBJ/noise_ik.o $OBJ/noise_handshake.o \
$OBJ/noise_stream.o $OBJ/dht_rpc.o $OBJ/holepuncher.o $OBJ/hyperdht.o \
$UDX_OBJS $LDFLAGS
echo " OK: test_unannounce_live"
echo "=== All test binaries built successfully ==="