#
# Copyright 2018 devolo AG
#

set_delos_peer_isolation() { (
	#local EBLOG=--log
	action="$1"

	delos_determine_prefixes

	# throw away current user-defined chains
	ebtables -D FORWARD -i ${INTERFACE_PREFIX}0+ -o ${INTERFACE_PREFIX}1+ -j ssid-isolation $EBLOG 2>/dev/null
	ebtables -D FORWARD -i ${INTERFACE_PREFIX}1+ -o ${INTERFACE_PREFIX}0+ -j ssid-isolation $EBLOG 2>/dev/null
	ebtables -X ssid-isolation 2>/dev/null

	[ "$action" = "disable" ] && return 0

	# enabled radios
	radios=$(ubus call uci get '{"config":"wireless","type":"wifi-device"}' | jsonfilter -e '$.*[!(@.disabled="1")][".name"]')
	radios=$(echo $radios)   	# sic! DO NOT ADD QUOTES!
	# if at most 1 radio is enabled no rules are required
	[ $(echo $radios | wc -w) -lt 2 ] && return

	. /usr/share/libubox/jshn.sh
	json_load "$(ubus call uci get '{"config":"wireless","type":"wifi-iface"}')"

	json_select values

        # Note: result is returned in devs (radios), aps_<radio> (for each radio)
	delos_enumerate_${DRIVER}

	# if SSIDs for at most 1 radio are enabled no rules are required
	[ $(echo "$devs" | wc -w) -lt 2 ] && return

	# add the specific peer isolation rules to chain ssid-isolation
	# problem: must use same wlan iface enumeration as netifd/mac80211.sh
	#   ebtables -A ssid-isolation -i <wlan0...> -o <wlan1...> -j DROP
	#   ebtables -A ssid-isolation -i <wlan1...> -o <wlan0...> -j DROP

	# match paired APs ifnames (conditions as in delos-webui: same ssid+encr+key)
	# for APs with client isolation/guest wifi restrictions enabled!
	## works for 2 radios only. match first device SSIDs with those of 2nd
	echo $devs | {
		read dev1 dev2 _
		eval "ifs1=\${aps_$dev1}"
		eval "ifs2=\${aps_$dev2}"
		pairs=$(for if1 in $ifs1; do
			json_select $if1
			json_get_vars ifname ssid encryption key isolate:0 dvl_guest:0 dvl_unrestricted_access:0
			# isolate too if guest wifi internet is not unrestricted
			[ "$isolate" = "1" ] || ([ "$dvl_guest" = "1" ] && [ "$dvl_unrestricted_access" = "0" ]) && (
				# subshell! for separate json context
				ifname1=$ifname
				ssid1=$ssid
				encryption1=$encryption
				key1=$key
				json_get_var dynamic_vlan dynamic_vlan 0
				for if2 in $ifs2; do
					json_select ..
					json_select $if2
					json_get_vars ifname ssid encryption key
					[ "$ssid1" = "$ssid" ] &&
					[ "$encryption1" = "$encryption" ] &&
					[ "$key1" = "$key" ] && {
						echo "$if1;$if2"
						break
					}
				done
			)
			json_select ..
		done)
		# make rules
		chains_installed=
		for pair in $pairs; do
			json_select ${pair%;*}
			json_get_var ifname1 ifname
			json_get_vars dynamic_vlan
			json_select ..
			json_select ${pair#*;}
			json_get_var ifname2 ifname
			json_select ..
			# Once install user-defined chains to group rules for easier deletion
			[ -z "$chains_installed" ] && {
				chains_installed=1
				ebtables -N ssid-isolation -P RETURN
				ebtables -A FORWARD -i ${INTERFACE_PREFIX}0+ -o ${INTERFACE_PREFIX}1+ -j ssid-isolation $EBLOG
				ebtables -A FORWARD -i ${INTERFACE_PREFIX}1+ -o ${INTERFACE_PREFIX}0+ -j ssid-isolation $EBLOG
			}
			# rule for plain vlan
			ebtables -A ssid-isolation -i $ifname1 -o $ifname2 -j DROP $EBLOG
			ebtables -A ssid-isolation -i $ifname2 -o $ifname1 -j DROP $EBLOG
			[ "$dynamic_vlan" -gt 0 ] &&  {
				# rule for dynamic vlans
				ebtables -A ssid-isolation -i $ifname1.+ -o $ifname2.+ -j DROP $EBLOG
				ebtables -A ssid-isolation -i $ifname2.+ -o $ifname1.+ -j DROP $EBLOG
			}
		done
	}
	json_select ..
	json_cleanup
) }

delos_determine_prefixes() {
	if [ -d /sys/class/net/wifi0 ]; then
		DRIVER=qcawifi
		RADIO_PREFIX="wifi"
		INTERFACE_PREFIX="ath"
	else
		DRIVER=mac80211
		RADIO_PREFIX="radio"
		INTERFACE_PREFIX="wlan"
	fi
}

delos_enumerate_mac80211() {
	# APs' ifnames are generated after those of non AP-interfaces (idx covers this)
	# find enabled APs of enabled radios, save new radios list in devs
	devs=
	json_get_keys ifs
	for iface in $ifs; do
		json_select $iface
		json_get_vars disabled mode device
		list_contains radios $device && [ "$disabled" != "1" ] && {
			list_contains devs $device || {
				append devs $device
				eval "non_aps_$device="
				eval "aps_$device="
			}
			[ "$mode" != "ap" ] && {
				eval ": \$((non_aps_$device++))"
			}
			[ "$mode" = "ap" ] && {
				eval "append aps_$device $iface"
			}
		}
		json_select ..
	done
	for i in $devs; do
		eval "logger \"$i: \${non_aps_$i:-0} non-APs. APs:\${aps_$i}\""
	done

	for i in $devs; do
		eval "idx=\${non_aps_$i}"
		eval "ifs=\${aps_$i}"
		[ -z "$ifs" ] && return
		for iface in $ifs; do
			json_select $iface
			json_get_vars ifname mode device
			[ "$mode" = "ap" ] && [ -z "$ifname" ] && {
				json_add_string ifname "${INTERFACE_PREFIX}${i#${RADIO_PREFIX}}${idx:+-${idx}}"
				: $((idx++))
			}
			json_select ..
		done
	done
}

delos_enumerate_qcawifi() {
	# enumerate APs ifnames as in qcawifi, add to json-config
	# qcawifi script just takes one interface after another regardless of its type -> skip non-aps
	devs=
	json_get_keys ifs
	for radio in $radios; do
		eval idx_$radio=
	done
	for iface in $ifs; do
		json_select $iface
		json_get_vars disabled mode device
		list_contains radios $device && [ "$disabled" != "1" ] && {
			list_contains devs $device || {
				append devs $device
			}
			[ "$mode" = "ap" ] && {
				eval "append aps_$device $iface"
			}
			eval "json_add_string ifname \"${INTERFACE_PREFIX}\${device#\${RADIO_PREFIX}}\${idx_$device:+\$idx_$device}\""
			eval ": \$((idx_$device++))"
		}
		json_select ..
	done
}

delos_wifi_peer_isolation_post() {
	set_delos_peer_isolation "$@"
}

# is DELOS_WIFI_DRIVERS defined at all (empty or non-empty)? (force exit code 0 if empty,
# don't exit current script if not defined)
if (: ${DELOS_WIFI_DRIVERS?}) 2>/dev/null; then
	# we were sourced to register as a plugin
	delos_wifi_driver_peer_isolation() {
		type=$1; shift
		type delos_wifi_peer_isolation_$type >/dev/null 2>/dev/null && delos_wifi_peer_isolation_$type "$@"
	}

	append DELOS_WIFI_DRIVERS delos_wifi_driver_peer_isolation
fi