#!/bin/sh
# speedtest.sh - speed-test engine: Ookla CLI preferred, curl fallback.
#
# Config (override via environment or daemon config):
#   CELL_METERED=1           skip auto-tests on cellular (default: 0)
#   CELLULAR_IFACE           cellular real iface name (default: rmnet_mhi0)
#   SPEED_INTERVAL           min seconds between auto-tests (default: 300)
#   SPEED_STATE_DIR          state file directory (default: /tmp/busrouter)
#
# Public API:
#   speed_test_iface <iface>   run speed test; echo "dl_mbps ul_mbps"
#   speed_test_due <iface>     exit 0 if test is due, 1 if too recent

LIB_DIR="${LIB_DIR:-/usr/lib/busrouter}"
: "${CELL_METERED:=0}"
: "${CELLULAR_IFACE:=rmnet_mhi0}"
: "${SPEED_INTERVAL:=300}"
: "${SPEED_STATE_DIR:=/tmp/busrouter}"

# Ookla CLI speed test bound to interface.
# Outputs "dl_mbps ul_mbps" on success; returns 1 if CLI unavailable or test fails.
_speed_ookla() {
	local iface="$1"
	local bin
	bin=$(command -v speedtest 2>/dev/null) || return 1
	$bin --interface "$iface" -f json --accept-license --accept-gdpr 2>/dev/null | grep -o '"bandwidth":[0-9]*' | awk -F: 'NR==1{dl=$2} NR==2{ul=$2} END{if(dl>0) printf "%.1f %.1f\n", dl/125000, ul/125000}'
}

# Curl-based throughput test bound to interface.
# Download: fetch 10 MB from Cloudflare; upload: POST 5 MB of /dev/urandom.
# Outputs "dl_mbps ul_mbps"; returns 1 on failure.
_speed_curl() {
	local iface="$1" dl ul
	dl=$(curl -sf --interface "$iface" --max-time 20 -o /dev/null -w "%{speed_download}" "http://speed.cloudflare.com/__down?bytes=10485760" 2>/dev/null)
	[ -n "$dl" ] && [ "${dl%.*}" -gt 0 ] 2>/dev/null || return 1
	ul=$(dd if=/dev/urandom bs=1048576 count=5 2>/dev/null | curl -sf --interface "$iface" --max-time 20 -X POST --data-binary @- -o /dev/null -w "%{speed_upload}" "http://speed.cloudflare.com/__up" 2>/dev/null)
	: "${ul:=0}"
	echo "$dl $ul" | awk '{printf "%.1f %.1f\n", $1/125000, $2/125000}'
}

# Run a speed test on interface and emit "dl_mbps ul_mbps".
# Skips auto-test on metered cellular (CELL_METERED=1) and emits "0 0".
speed_test_iface() {
	local iface="$1"
	[ -n "$iface" ] || { logger -t busrouter "speed_test_iface: missing iface arg"; return 1; }

	if [ "$CELL_METERED" = "1" ] && [ "$iface" = "$CELLULAR_IFACE" ]; then
		logger -t busrouter "speed_test: skipped (CELL_METERED=1) on $iface"
		echo "0 0"
		return 0
	fi

	local result
	result=$(_speed_ookla "$iface" 2>/dev/null)
	[ -n "$result" ] || result=$(_speed_curl "$iface")
	if [ -z "$result" ]; then
		logger -t busrouter "speed_test: FAILED for $iface"
		return 1
	fi
	logger -t busrouter "speed_test[$iface]: $result"
	echo "$result"
}

# Returns exit 0 if a speed test is due, exit 1 if tested too recently.
# Usage: speed_test_due <iface>
# State persisted in SPEED_STATE_DIR/speed_last_<iface>.
speed_test_due() {
	local iface="$1"
	local state_file="${SPEED_STATE_DIR}/speed_last_${iface}"
	local now
	mkdir -p "$SPEED_STATE_DIR"
	now=$(date +%s)
	if [ ! -f "$state_file" ]; then
		echo "$now" > "$state_file"
		return 0
	fi
	local last
	last=$(cat "$state_file"); last="${last%%[!0-9]*}"
	if [ "$(( now - last ))" -ge "${SPEED_INTERVAL:-300}" ]; then
		echo "$now" > "$state_file"
		return 0
	fi
	return 1
}
