# IMDS network address helpers
# vim:set filetype=sh:
# shellcheck shell=sh

_imds_ipv4_network() {
	local data
	case "$2" in
		*.*) data=$(ipcalc -n -p "$1" "$2") || return 1;;
		*) data=$(ipcalc -n -p "$1/$2") || return 1;;
	esac
	printf '%s\n' "$data" | awk -F= '
		$1 == "NETWORK" { network = $2 }
		$1 == "PREFIX" { prefix = $2 }
		END { print network "/" prefix }'
}

# Print an IPv6 network in expanded notation, using ipcalc when supported.
_imds_ipv6_network() {
	local data
	data=$(ipcalc -n -p "$1/$2" 2>/dev/null) || data=
	printf '%s\n' "$data" | awk -F= -v address="$1" -v prefix="$2" '
		$1 == "NETWORK" && $2 != "" { address = $2; calculated = 1 }
		END {
			if (prefix !~ /^[0-9]+$/ || prefix > 128 ||
			    address ~ /[^0-9a-fA-F:]/ || address == "") exit 1
			n = split(address, sides, "::")
			if (n > 2) exit 1
			left = sides[1] == "" ? 0 : split(sides[1], words, ":")
			if (n == 2) {
				right = sides[2] == "" ? 0 : split(sides[2], tail, ":")
				if (left + right >= 8) exit 1
				for (i = left + 1; i <= 8 - right; i++) words[i] = "0"
				for (i = 1; i <= right; i++) words[8 - right + i] = tail[i]
			} else if (left != 8) exit 1
			for (i = 1; i <= 8; i++) {
				if (length(words[i]) < 1 || length(words[i]) > 4) exit 1
				value = 0
				for (j = 1; j <= length(words[i]); j++)
					value = value * 16 + index("0123456789abcdef",
					    tolower(substr(words[i], j, 1))) - 1
				if (!calculated) {
					bits = prefix - (i - 1) * 16
					if (bits < 0) bits = 0
					if (bits > 16) bits = 16
					block = 2 ^ (16 - bits)
					value = int(value / block) * block
				}
				result = result sprintf("%s%x", i == 1 ? "" : ":",
				    value)
			}
			printf "%s/%d\n", result, prefix
		}'
}

# Convert an IPv6 netmask or numeric prefix to a prefix length.
_imds_ipv6_netmask_prefix() {
	local mask="$1" word value bit prefix=0 zero_seen= IFS=:
	case "$mask" in
		*[!0-9]*) ;;
		'') return 1;;
		*) [ "$mask" -le 128 ] || return 1; echo "$mask"; return 0;;
	esac
	mask=$(_imds_ipv6_network "$mask" 128) || return 1
	mask="${mask%/*}"
	for word in $mask; do
		value=$((0x$word))
		bit=32768
		while [ "$bit" -gt 0 ]; do
			if [ "$((value & bit))" -ne 0 ]; then
				[ -z "$zero_seen" ] || return 1
				prefix=$((prefix + 1))
			else
				zero_seen=1
			fi
			bit=$((bit / 2))
		done
	done
	echo "$prefix"
}
