#!/bin/sh

set -eu

# A stateful mock of brctl(8) for the ikvswitch unit tests: successful
# mutations are appended to ${IKV_NET_MOCK_LOG} (queries and failures
# are not), and the state of bridges and their ports is derived from
# that log, mimicking the kernel: creating an existing bridge fails,
# deleting a missing bridge or a bridge which still has ports fails,
# enslaving a port which is already a member of a bridge fails, and
# "brctl show <bridge>" lists its ports (which is what the re-entrancy
# guards of ikvswitch-host-networking query). Requires IKV_NET_MOCK_LOG
# to be set.

: "${IKV_NET_MOCK_LOG:?IKV_NET_MOCK_LOG must be set}"

bridge_exists () {
	awk -v NAME="${1}" '
		$1 == "addbr" && $2 == NAME { c++ }
		$1 == "delbr" && $2 == NAME { d++ }
		END { exit !(c > d) }
	' "${IKV_NET_MOCK_LOG}"
}

link_created () {
	awk -v NAME="${1}" '
		$1 == "tuntap" && $2 == "add" && $4 == NAME { c++ }
		$1 == "tuntap" && $2 == "delete" && $5 == NAME { d++ }
		$1 == "link" && $2 == "add" && $3 == NAME { c++ }
		$1 == "link" && $2 == "delete" && $3 == NAME { d++ }
		$1 == "addbr" && $2 == NAME { c++ }
		$1 == "delbr" && $2 == NAME { d++ }
		END { exit !(c > d) }
	' "${IKV_NET_MOCK_LOG}"
}

port_bridge () {
	awk -v PORT="${1}" '
		$1 == "addif" && $3 == PORT { b = $2 }
		$1 == "delif" && $3 == PORT { b = "" }
		END { if (b != "") print b ; else exit 1 }
	' "${IKV_NET_MOCK_LOG}"
}

ports_of () {
	awk -v BR="${1}" '
		$1 == "addif" && $2 == BR { m[$3] = BR }
		$1 == "delif" && $3 { delete m[$3] }
		END { for (p in m) print p }
	' "${IKV_NET_MOCK_LOG}"
}

case "${1}" in
addbr)
	if bridge_exists "${2}" ; then
		echo "device ${2} already exists" >&2
		exit 1
	fi
	;;
delbr)
	if ! bridge_exists "${2}" ; then
		echo "bridge ${2} doesn't exist" >&2
		exit 1
	fi
	if [ -n "$(ports_of "${2}")" ] ; then
		echo "bridge ${2} still has ports" >&2
		exit 1
	fi
	;;
addif)
	if ! bridge_exists "${2}" ; then
		echo "bridge ${2} doesn't exist" >&2
		exit 1
	fi
	if ! link_created "${3}" ; then
		echo "can't add ${3} to bridge ${2}: No such device" >&2
		exit 1
	fi
	if [ -n "$(port_bridge "${3}" || true)" ] ; then
		echo "device ${3} is already a member of a bridge; can't enslave it to master ${2}." >&2
		exit 1
	fi
	;;
delif)
	if [ "$(port_bridge "${3}" || true)" != "${2}" ] ; then
		echo "can't remove port ${3} from bridge ${2}: Invalid argument" >&2
		exit 1
	fi
	;;
show)
	# Pure query: not logged.
	if ! bridge_exists "${2}" ; then
		echo "bridge ${2} doesn't exist" >&2
		exit 1
	fi
	echo "bridge name	bridge id		STP enabled	interfaces"
	for PORT in $(ports_of "${2}") ; do
		printf "\t\t\t\t\t%s\n" "${PORT}"
	done
	exit 0
	;;
esac

echo "$@" >>"${IKV_NET_MOCK_LOG}"
exit 0
