#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#shellcheck disable=SC2034 # SC does not see the global variables
#shellcheck disable=SC2317,SC2329 # unused functions

ALL_TESTS="
	rmon_rx_histogram
	rmon_tx_histogram
"

: "${DRIVER_TEST_CONFORMANT:=yes}"
NUM_NETIFS=2
lib_dir=$(dirname "$0")
source "$lib_dir"/../../../net/forwarding/lib.sh
source "$lib_dir"/../../../kselftest/ktap_helpers.sh

UINT32_MAX=$((2**32 - 1))
ETH_FCS_LEN=4
ETH_HLEN=$((6+6+2))
TEST_NAME=$(basename "$0" .sh)

declare -A netif_mtu

ensure_mtu()
{
	local iface=$1; shift
	local len=$1; shift
	local required=$((len - ETH_HLEN - ETH_FCS_LEN))
	local current

	current=$(run_on "$iface" \
		ip -j link show dev "$iface" | jq -r '.[0].mtu')
	if [ "$current" -lt "$required" ]; then
		run_on "$iface" ip link set dev "$iface" mtu "$required" \
			|| return 1
	fi
}

bucket_test()
{
	local iface=$1; shift
	local neigh=$1; shift
	local set=$1; shift
	local bucket=$1; shift
	local len=$1; shift
	local num_rx=10000
	local num_tx=20000
	local expected=
	local before=
	local after=
	local delta=

	# Mausezahn does not include FCS bytes in its length - but the
	# histogram counters do
	len=$((len - ETH_FCS_LEN))
	len=$((len > 0 ? len : 0))

	before=$(run_on "$iface" ethtool --json -S "$iface" --groups rmon | \
		jq -r ".[0].rmon[\"${set}-pktsNtoM\"][$bucket].val")

	# Send 10k one way and 20k in the other, to detect counters
	# mapped to the wrong direction
	run_on "$neigh" \
		"$MZ" "$neigh" -q -c "$num_rx" -p "$len" -a own -b bcast -d 10us
	run_on "$iface" \
		"$MZ" "$iface" -q -c "$num_tx" -p "$len" -a own -b bcast -d 10us

	hw_stats_settle "$iface"

	after=$(run_on "$iface" ethtool --json -S "$iface" --groups rmon | \
		jq -r ".[0].rmon[\"${set}-pktsNtoM\"][$bucket].val")

	delta=$((after - before))

	expected=$([ "$set" = rx ] && echo "$num_rx" || echo "$num_tx")

	[ "$delta" -ge "$expected" ] && [ "$delta" -le "$UINT32_MAX" ]
}

rmon_histogram()
{
	local iface=$1; shift
	local neigh=$1; shift
	local set=$1; shift
	local nbuckets=0
	local step=

	while read -r -a bucket; do
		step="$set-pkts${bucket[0]}to${bucket[1]}"

		for if in "$iface" "$neigh"; do
			if ! ensure_mtu "$if" "${bucket[0]}"; then
				ktap_print_msg "$if does not support the required MTU for $step"
				ktap_test_xfail "$TEST_NAME.$step"
				return
			fi
		done

		if ! bucket_test "$iface" "$neigh" "$set" "$nbuckets" "${bucket[0]}"; then
			ktap_test_fail "$TEST_NAME.$step"
			return 1
		fi
		ktap_test_pass "$TEST_NAME.$step"
		nbuckets=$((nbuckets + 1))
	done < <(run_on "$iface" ethtool --json -S "$iface" --groups rmon | \
		jq -r ".[0].rmon[\"${set}-pktsNtoM\"][]|[.low, .high]|@tsv" 2>/dev/null)

	if [ "$nbuckets" -eq 0 ]; then
		ktap_print_msg "$iface does not support $set histogram counters"
		return
	fi
}

rmon_rx_histogram()
{
	rmon_histogram "$h1" "$h2" rx
}

rmon_tx_histogram()
{
	rmon_histogram "$h1" "$h2" tx
}

setup_prepare()
{
	h1=${NETIFS[p1]}
	h2=${NETIFS[p2]}

	for iface in "$h1" "$h2"; do
		netif_mtu["$iface"]=$(run_on "$iface" \
			ip -j link show dev "$iface" | jq -r '.[0].mtu')
	done
}

cleanup()
{
	pre_cleanup

	# Do not bring down the interfaces, just configure the initial MTU
	for iface in "$h2" "$h1"; do
		run_on "$iface" ip link set dev "$iface" \
			mtu "${netif_mtu[$iface]}"
	done
}

check_ethtool_counter_group_support
trap cleanup EXIT

bucket_count=$(ethtool --json -S "${NETIFS[p1]}" --groups rmon | \
	jq -r '.[0].rmon |
		"\((."rx-pktsNtoM" | length) +
		   (."tx-pktsNtoM" | length))"')
ktap_print_header
ktap_set_plan "$bucket_count"

setup_prepare
setup_wait

tests_run

ktap_finished