#!/usr/bin/env bash
trap 'kill $RELAY_PID 2>/dev/null; killm f' EXIT
#
# Regression test: an LTP sender must not cancel an export session on the
# strength of a reception report that a later report has superseded.
#
# When a report describes gaps in the red part and the session has issued
# its full complement of checkpoints, the sender can no longer repair
# those gaps, and it used to cancel the session then and there.  But
# reception claims accumulate across reports, so the report in hand may
# already be out of date: one claiming the whole red part may be right
# behind it.  Cancelling on the older report throws away a transfer that
# actually succeeded, and because the cancellation reaches BP as a
# convergence-layer failure, BP re-forwards -- handing the receiver every
# bundle in the block a second time.
#
# runtest.py relays both directions and stages the sequence.  The block is
# delivered untouched, so the receiver takes delivery and reports complete
# reception; that report is held.  Reports of gaps are then injected until
# the sender stops issuing new checkpoint serial numbers, which is how its
# checkpoint budget being spent is observed rather than assumed -- the size
# of that budget is derived inside ION and is not something a test should
# hard-code.  The completing report is delivered last.  See that file.
#
# bprecvfile names successive received files testfile1, testfile2 and so
# on, so a duplicate delivery is plainly visible.

# Find a Python 3 version >= 3.10 with bespokebpv7 installed.
PYTHON_CMD="python3"
if command -v "$PYTHON_CMD" &> /dev/null; then
	py_maj=$("$PYTHON_CMD" -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
	py_min=$("$PYTHON_CMD" -c 'import sys; print(sys.version_info[1])' 2>/dev/null)
	if [ "$py_maj" = "3" ] && [ "$py_min" -ge 10 ]; then
		:
	else
		PYTHON_CMD=""
	fi
else
	PYTHON_CMD=""
fi

if [ -z "$PYTHON_CMD" ]; then
	echo "SKIP: Python 3.10 or higher is required but not found."
	exit 2
fi

if ! "$PYTHON_CMD" -c 'import bespokebpv7' 2>/dev/null; then
	echo "SKIP: bespokebpv7 module not found. Please install bespokebpv7."
	exit 2
fi

# Reading and crafting LTP reports needs ReportSegment, which older
# releases of bespokebpv7 do not have; importing it would fail outright
# rather than skip.
if ! "$PYTHON_CMD" -c 'from bespokebpv7.segments import ReportSegment' 2>/dev/null
then
	echo "SKIP: this bespokebpv7 has no ReportSegment; please update it."
	exit 2
fi

./cleanup
sleep 1

# Setting LTP_SUPERSEDED_CONTROL relays faithfully instead, which must
# deliver the file exactly once.  It separates "the staged sequence did
# not happen" from "the topology is not carrying traffic at all", and is
# the first thing to try if this test reports nothing arriving.
RELAY_ARGS=""
if [ -n "$LTP_SUPERSEDED_CONTROL" ]; then
	RELAY_ARGS="--passthrough"
	echo "Relays in CONTROL mode: no segment rules."
else
	echo "Relays staging a superseded report."
fi

echo "Starting relays..."
$PYTHON_CMD runtest.py --duration 90 $RELAY_ARGS > relay.stdout 2>&1 &
RELAY_PID=$!
sleep 2

if ! kill -0 $RELAY_PID 2>/dev/null; then
	echo "ERROR: relays did not start."
	cat relay.stdout
	exit 1
fi

echo "Starting ION..."
export ION_NODE_LIST_DIR=$PWD
rm -f ./ion_nodes

cd 3.ipn.ltp
./ionstart > node3.stdout
../../../system_up -i "p 30" -l "p 30" -b "p 30"

if [ $? -ne 3 ]; then
	echo "Node 3 not started: Aborting Test"
	exit 1
fi

cd ../2.ipn.ltp
./ionstart > node2.stdout
../../../system_up -i "p 30" -l "p 30" -b "p 30"

if [ $? -ne 3 ]; then
	echo "Node 2 not started: Aborting Test"
	exit 1
fi

echo "Starting bprecvfile on node 3..."
cd ../3.ipn.ltp
bprecvfile ipn:3.1 &
sleep 2

echo "Sending the file from node 2, reliably..."
cd ../2.ipn.ltp
bpsendfile ipn:2.2 ipn:3.1 testfile 0.1.0.0.0

# The relay driver reports when it has watched the sender run out of
# checkpoints.  Wait for that rather than for a fixed interval.
echo "Waiting for the sender's checkpoint budget to be spent..."
cd ..
spent=0
i=0
while [ $i -lt 80 ]; do
	if grep -q "CHECKPOINT BUDGET OBSERVED EXHAUSTED" relay.stdout 2>/dev/null; then
		spent=1
		break
	fi
	sleep 1
	i=$((i + 1))
done

grep -E "receiver holds the block|injection |CHECKPOINT BUDGET|completing report delivered" relay.stdout 2>/dev/null

# Let the sender act on the completing report and BP deliver.
sleep 10

echo ""
echo "What the relays did:"
$PYTHON_CMD - <<'PY'
import json
import os

for name, label in (("mitm-data.jsonl", "data"), ("mitm-report.jsonl", "report")):
    if not os.path.exists(name):
        print(f"  {label}: no trace")
        continue

    records = [json.loads(line) for line in open(name) if line.strip()]
    acts: dict[str, int] = {}
    for record in records:
        acts[record["act"]] = acts.get(record["act"], 0) + 1

    print(f"  {label}: {len(records)} datagrams, {acts}")
    for record in records:
        if record["act"] != "pass":
            claims = record.get("claims")
            print(
                f"    {record['act']}: {record.get('seg_type')}"
                + (f" claims={claims}" if claims else "")
                + (f"  ({record['note']})" if record.get("note") else "")
            )
PY

cd 3.ipn.ltp
echo ""
echo "Files received by node 3:"
ls -l testfile[0-9]* 2>/dev/null || echo "  (none)"
echo ""

if [ $spent -eq 0 ]; then
	echo "TEST INCONCLUSIVE: the sender was never seen to run out of"
	echo "                   checkpoints, so the condition this test"
	echo "                   exercises did not arise.  Neither result"
	echo "                   would mean anything; see relay.stdout."
	RETVAL=1
elif [ ! -f testfile1 ]; then
	echo "TEST INCONCLUSIVE: nothing arrived, so the staged sequence did"
	echo "                   not play out.  Try LTP_SUPERSEDED_CONTROL=1."
	RETVAL=1
elif [ -f testfile2 ]; then
	echo "TEST FAILED: the file arrived more than once.  The sender"
	echo "             cancelled a session that had succeeded, and BP"
	echo "             re-forwarded a block the receiver already held."
	RETVAL=1
elif ! diff ../2.ipn.ltp/testfile testfile1 > /dev/null; then
	echo "TEST INCONCLUSIVE: the file arrived once but is incomplete."
	RETVAL=1
else
	echo "TEST PASSED: the file arrived exactly once, intact."
	RETVAL=0
fi

echo "Stopping ION..."
exit $RETVAL
