#!/bin/bash
# SPDX-License-Identifier: GPL-2.0

lib_dir=$(dirname $0)/../../../net/forwarding

ALL_TESTS="
	l3_reporting_test
	l3_fail_next_test
	l3_counter_test
	l3_rollback_test
	l3_monitor_test
"

NETDEVSIM_PATH=/sys/bus/netdevsim/
DEV_ADDR_1=1337
DEV_ADDR_2=1057
DEV_ADDR_3=5417
NUM_NETIFS=0
source $lib_dir/lib.sh

DUMMY_IFINDEX=

DEV_ADDR()
{
	local n=$1; shift
	local var=DEV_ADDR_$n

	echo ${!var}
}

DEV()
{
	echo netdevsim$(DEV_ADDR $1)
}

DEVLINK_DEV()
{
	echo netdevsim/$(DEV $1)
}

SYSFS_NET_DIR()
{
	echo /sys/bus/netdevsim/devices/$(DEV $1)/net/
}

DEBUGFS_DIR()
{
	echo /sys/kernel/debug/netdevsim/$(DEV $1)/
}

nsim_add()
{
	local n=$1; shift

	echo "$(DEV_ADDR $n) 1" > ${NETDEVSIM_PATH}/new_device
	while [ ! -d $(SYSFS_NET_DIR $n) ] ; do :; done
}

nsim_reload()
{
	local n=$1; shift
	local ns=$1; shift

	devlink dev reload $(DEVLINK_DEV $n) netns $ns

	if [ $? -ne 0 ]; then
		echo "Failed to reload $(DEV $n) into netns \"testns1\""
		exit 1
	fi

}

nsim_del()
{
	local n=$1; shift

	echo "$(DEV_ADDR $n)" > ${NETDEVSIM_PATH}/del_device
}

nsim_hwstats_toggle()
{
	local action=$1; shift
	local instance=$1; shift
	local netdev=$1; shift
	local type=$1; shift

	local ifindex=$($IP -j link show dev $netdev | jq '.[].ifindex')

	echo $ifindex > $(DEBUGFS_DIR $instance)/hwstats/$type/$action
}

nsim_hwstats_enable()
{
	nsim_hwstats_toggle enable_ifindex "$@"
}

nsim_hwstats_disable()
{
	nsim_hwstats_toggle disable_ifindex "$@"
}

nsim_hwstats_fail_next_enable()
{
	nsim_hwstats_toggle fail_next_enable "$@"
}

setup_prepare()
{
	modprobe netdevsim &> /dev/null
	nsim_add 1
	nsim_add 2
	nsim_add 3

	ip netns add testns1

	if [ $? -ne 0 ]; then
		echo "Failed to add netns \"testns1\""
		exit 1
	fi

	nsim_reload 1 testns1
	nsim_reload 2 testns1
	nsim_reload 3 testns1

	IP="ip -n testns1"

	$IP link add name dummy1 type dummy
	$IP link set dev dummy1 up
	DUMMY_IFINDEX=$($IP -j link show dev dummy1 | jq '.[].ifindex')
}

cleanup()
{
	pre_cleanup

	$IP link del name dummy1
	ip netns del testns1
	nsim_del 3
	nsim_del 2
	nsim_del 1
	modprobe -r netdevsim &> /dev/null
}

netdev_hwstats_used()
{
	local netdev=$1; shift
	local type=$1; shift

	$IP -j stats show dev "$netdev" group offload subgroup hw_stats_info |
	    jq '.[].info.l3_stats.used'
}

netdev_check_used()
{
	local netdev=$1; shift
	local type=$1; shift

	[[ $(netdev_hwstats_used $netdev $type) == "true" ]]
}

netdev_check_unused()
{
	local netdev=$1; shift
	local type=$1; shift

	[[ $(netdev_hwstats_used $netdev $type) == "false" ]]
}

netdev_hwstats_request()
{
	local netdev=$1; shift
	local type=$1; shift

	$IP -j stats show dev "$netdev" group offload subgroup hw_stats_info |
	    jq ".[].info.${type}_stats.request"
}

netdev_check_requested()
{
	local netdev=$1; shift
	local type=$1; shift

	[[ $(netdev_hwstats_request $netdev $type) == "true" ]]
}

netdev_check_unrequested()
{
	local netdev=$1; shift
	local type=$1; shift

	[[ $(netdev_hwstats_request $netdev $type) == "false" ]]
}

reporting_test()
{
	local type=$1; shift
	local instance=1

	RET=0

	[[ -n $(netdev_hwstats_used dummy1 $type) ]]
	check_err $? "$type stats not reported"

	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used before either device or netdevsim request"

	nsim_hwstats_enable $instance dummy1 $type
	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used before device request"
	netdev_check_unrequested dummy1 $type
	check_err $? "$type stats reported as requested before device request"

	$IP stats set dev dummy1 ${type}_stats on
	netdev_check_used dummy1 $type
	check_err $? "$type stats reported as not used after both device and netdevsim request"
	netdev_check_requested dummy1 $type
	check_err $? "$type stats reported as not requested after device request"

	nsim_hwstats_disable $instance dummy1 $type
	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used after netdevsim request withdrawn"

	nsim_hwstats_enable $instance dummy1 $type
	netdev_check_used dummy1 $type
	check_err $? "$type stats reported as not used after netdevsim request reenabled"

	$IP stats set dev dummy1 ${type}_stats off
	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used after device request withdrawn"
	netdev_check_unrequested dummy1 $type
	check_err $? "$type stats reported as requested after device request withdrawn"

	nsim_hwstats_disable $instance dummy1 $type
	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used after both requests withdrawn"

	log_test "Reporting of $type stats usage"
}

l3_reporting_test()
{
	reporting_test l3
}

__fail_next_test()
{
	local instance=$1; shift
	local type=$1; shift

	RET=0

	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used before either device or netdevsim request"

	nsim_hwstats_enable $instance dummy1 $type
	nsim_hwstats_fail_next_enable $instance dummy1 $type
	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used before device request"
	netdev_check_unrequested dummy1 $type
	check_err $? "$type stats reported as requested before device request"

	$IP stats set dev dummy1 ${type}_stats on 2>/dev/null
	check_fail $? "$type stats request not bounced as it should have been"
	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used after bounce"
	netdev_check_unrequested dummy1 $type
	check_err $? "$type stats reported as requested after bounce"

	$IP stats set dev dummy1 ${type}_stats on
	check_err $? "$type stats request failed when it shouldn't have"
	netdev_check_used dummy1 $type
	check_err $? "$type stats reported as not used after both device and netdevsim request"
	netdev_check_requested dummy1 $type
	check_err $? "$type stats reported as not requested after device request"

	$IP stats set dev dummy1 ${type}_stats off
	nsim_hwstats_disable $instance dummy1 $type

	log_test "Injected failure of $type stats enablement (netdevsim #$instance)"
}

fail_next_test()
{
	__fail_next_test 1 "$@"
	__fail_next_test 2 "$@"
	__fail_next_test 3 "$@"
}

l3_fail_next_test()
{
	fail_next_test l3
}

get_hwstat()
{
	local netdev=$1; shift
	local type=$1; shift
	local selector=$1; shift

	$IP -j stats show dev $netdev group offload subgroup ${type}_stats |
		  jq ".[0].stats64.${selector}"
}

counter_test()
{
	local type=$1; shift
	local instance=1

	RET=0

	nsim_hwstats_enable $instance dummy1 $type
	$IP stats set dev dummy1 ${type}_stats on
	netdev_check_used dummy1 $type
	check_err $? "$type stats reported as not used after both device and netdevsim request"

	# Netdevsim counts 10pps on ingress. We should see maybe a couple
	# packets, unless things take a reeealy long time.
	local pkts=$(get_hwstat dummy1 l3 rx.packets)
	((pkts < 10))
	check_err $? "$type stats show >= 10 packets after first enablement"

	sleep 2.5

	local pkts=$(get_hwstat dummy1 l3 rx.packets)
	((pkts >= 20))
	check_err $? "$type stats show < 20 packets after 2.5s passed"

	$IP stats set dev dummy1 ${type}_stats off

	sleep 2

	$IP stats set dev dummy1 ${type}_stats on
	local pkts=$(get_hwstat dummy1 l3 rx.packets)
	((pkts < 10))
	check_err $? "$type stats show >= 10 packets after second enablement"

	$IP stats set dev dummy1 ${type}_stats off
	nsim_hwstats_fail_next_enable $instance dummy1 $type
	$IP stats set dev dummy1 ${type}_stats on 2>/dev/null
	check_fail $? "$type stats request not bounced as it should have been"

	sleep 2

	$IP stats set dev dummy1 ${type}_stats on
	local pkts=$(get_hwstat dummy1 l3 rx.packets)
	((pkts < 10))
	check_err $? "$type stats show >= 10 packets after post-fail enablement"

	$IP stats set dev dummy1 ${type}_stats off

	log_test "Counter values in $type stats"
}

l3_counter_test()
{
	counter_test l3
}

rollback_test()
{
	local type=$1; shift

	RET=0

	nsim_hwstats_enable 1 dummy1 l3
	nsim_hwstats_enable 2 dummy1 l3
	nsim_hwstats_enable 3 dummy1 l3

	# The three netdevsim instances are registered in order of their number
	# one after another. It is reasonable to expect that whatever
	# notifications take place hit no. 2 in between hitting nos. 1 and 3,
	# whatever the actual order. This allows us to test that a fail caused
	# by no. 2 does not leave the system in a partial state, and rolls
	# everything back.

	nsim_hwstats_fail_next_enable 2 dummy1 l3
	$IP stats set dev dummy1 ${type}_stats on 2>/dev/null
	check_fail $? "$type stats request not bounced as it should have been"

	netdev_check_unused dummy1 $type
	check_err $? "$type stats reported as used after bounce"
	netdev_check_unrequested dummy1 $type
	check_err $? "$type stats reported as requested after bounce"

	sleep 2

	$IP stats set dev dummy1 ${type}_stats on
	check_err $? "$type stats request not upheld as it should have been"

	local pkts=$(get_hwstat dummy1 l3 rx.packets)
	((pkts < 10))
	check_err $? "$type stats show $pkts packets after post-fail enablement"

	$IP stats set dev dummy1 ${type}_stats off

	nsim_hwstats_disable 3 dummy1 l3
	nsim_hwstats_disable 2 dummy1 l3
	nsim_hwstats_disable 1 dummy1 l3

	log_test "Failure in $type stats enablement rolled back"
}

l3_rollback_test()
{
	rollback_test l3
}

l3_monitor_test()
{
	hw_stats_monitor_test dummy1 l3		   \
		"nsim_hwstats_enable 1 dummy1 l3"  \
		"nsim_hwstats_disable 1 dummy1 l3" \
		"$IP"
}

trap cleanup EXIT

setup_prepare
tests_run

exit $EXIT_STATUS