#!/bin/sh

# @stanv: It is necessary to turn off service before doing any changes to
# configuration file. Otherwise it is high risk to get uncleaned `tc' rules.

module_name=alterator-shapercontrol
po_domain="$module_name"
alterator_api_version=1

. alterator-sh-functions
. shell-config
. alterator-hw-functions
. alterator-net-functions
. alterator-service-functions

SQLITE3=/usr/bin/sqlite3
SQLITE3_DB=/etc/sc/sc.db
SHAPERCONTROL=/usr/sbin/shapercontrol
SHAPERCONTROL_CONF=/etc/sc/sc.conf

_num_to_ipv4addr()
{
	num="$1"; shift

	printf '%s.%s.%s.%s\n' \
		"$(($num >> 24 & 0xff))" \
		"$(($num >> 16 & 0xff))" \
		"$(($num >> 8 & 0xff))" \
		"$(($num & 0xff))"
}

_ipv4addr_to_num()
{
	IFS=. read -r a b c d <<< "$1"
	printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
}

init_config()
{
	if [ ! -e "$SHAPERCONTROL_CONF" ]; then
		local i= int_if= ext_if= int_net= int_addr=

		# find internal interface with IPv4 address
		for i in $(iptables_helper show -i | head -1); do
			int_addr="$(read_iface_current_addr "$i")"
			if [ -n "$int_addr" ]; then
				int_net="$(netname "$int_addr" | cut -f1)"
				int_if="$i"
				break
			fi
		done
		ext_if="$(iptables_helper show -e | head -1)"

		cp "${SHAPERCONTROL_CONF}.default" "$SHAPERCONTROL_CONF"

		# Set default values
		_set_value "in_if" "$int_if"
		_set_value "out_if" "$out_if"
		_set_value network ''
		# stanv@: let admin think # _add_value "bypass_int" "$int_net"
	fi

	[ -s "$SQLITE3_DB" ] || "$SHAPERCONTROL" dbcreate
}

check_config()
{
	if [ -e "$SHAPERCONTROL_CONF" ];
	then
		write_bool_param check_label false
	else
		write_bool_param check_label true
	fi
}

get_state()
{
	if service_control sc is-active;
	then
		write_bool_param state true
	else
		write_bool_param state false
	fi
}

get_out_if_list()
{
	[ -r "$SHAPERCONTROL_CONF" ] || return

	local selected_if="$(get_value "out_if")"

	# stanv@: allow shaper to control traffic flow between any interfaces,
	# not just between in and out in terms of firewall.
	#all_ifaces="$(iptables_helper show -i)"
	#all_ifaces="${all_ifaces}$(iptables_helper show -e)"
	all_ifaces="$(list_iface)"

	# stanv@: Obliged, due bogus concept design
	if [ -z "$selected_if" ]; then
		write_enum_item "" "`_ 'Not defined (bad configuration)'`"
	fi

	write_enum_item "disable" "`_ 'Disable (Recommended for NAT)'`"

	# Configuration has an interface that is absent in system
	if [ -n "$selected_if" ] && [ "$selected_if" != "disable" ]; then
		echo "$all_ifaces" | grep -q -s "^$selected_if$" || write_enum_item "$selected_if" "$selected_if `_ '(absent in system)'`"
	fi

	for iface in $all_ifaces;
	do
		iface_hw="$(netdev_read_mac "$iface")"
		iface_inet="$(read_iface_current_addr "$iface")"
		iface_desc="$iface"
		[ -n "$iface_hw" ] && iface_desc="$iface_desc - $iface_hw"
		[ -n "$iface_inet" ] && iface_desc="$iface_desc ($iface_inet)"

		write_enum_item "$iface" "$iface_desc"
	done
}

get_in_if_list()
{
	[ -r "$SHAPERCONTROL_CONF" ] || return

	local selected_if="$(get_value "in_if")"

	# stanv@: allow shaper to control traffic flow between any interfaces,
	# not just between in and out in terms of firewall.
	# all_ifaces="$(iptables_helper show -i)"
	# all_ifaces="${all_ifaces}$(iptables_helper show -e)"
	all_ifaces="$(list_iface)"

	# stanv@: Obliged, due bogus concept design
	if [ -z "$selected_if" ]; then
		write_enum_item "" "`_ 'Not defined (bad configuration)'`"
	fi

	# Configuration has an interface that is absent in system
	if [ -n "$selected_if" ] && [ "$selected_if" != "disable" ]; then
		echo "$all_ifaces" | grep -q -s "^$selected_if$" || write_enum_item "$selected_if" "$selected_if `_ '(absent in system)'`"
	fi

	for iface in $all_ifaces;
	do
		iface_hw="$(netdev_read_mac "$iface")"
		iface_inet="$(read_iface_current_addr "$iface")"
		iface_desc="$iface"
		[ -n "$iface_hw" ] && iface_desc="$iface_desc - $iface_hw"
		[ -n "$iface_inet" ] && iface_desc="$iface_desc ($iface_inet)"

		write_enum_item "$iface" "$iface_desc"
	done
}

__get_list()
{
	local name="$1"

	[ -n "$name" ] || return
	[ -r "$SHAPERCONTROL_CONF" ] || return

	grep -m 1 "^[ \t]*$name[ \t]*=.*$" "$SHAPERCONTROL_CONF" | sed "s/^[ \t]*$name[ \t]*=[ \t]*//" | sed 's/[ \t]*$//'
}

_get_list()
{
	local values="$(__get_list "$1")"
	local value=

	for value in $values;
	do
		[ -z "$value" ] && continue
		write_enum_item "$value"
	done
}

get_network_list()
{
	_get_list "network"
}

get_bypass_int_list()
{
	_get_list "bypass_int"
}

get_bypass_ext_list()
{
	_get_list "bypass_ext"
}

_get_value()
{
	local name="$1"
	local value="$(get_value "$name")"

	if [ -z "$value" ]; then
		return
	fi

	write_string_param "$name" "$value"
}

get_value()
{
	local name="$1"

	[ -n "$name" ] || return
	[ -r "$SHAPERCONTROL_CONF" ] || return

	value="$(grep -m 1 "^[ \t]*$name[ \t]*=.*$" "$SHAPERCONTROL_CONF" | sed "s/^[ \t]*$name[ \t]*=[ \t]*//" | sed 's/[ \t]*$//')"
	echo "$value"
}

get_out_if()
{
	_get_value "out_if"
}

get_in_if()
{
	_get_value "in_if"
}

get_rate_ratio()
{
	_get_value "rate_ratio"
}

set_state() {
	[ -n "$in_state" ] || return

	if [ "$in_state" = '#f' ]; then
		set_state_off
	elif [ "$in_state" = '#t' ]; then
		set_state_on
	fi
}

set_state_off()
{
	service_control sc off
	if service_control sc is-active;
	then
		service_control sc stop
	fi
}

set_state_on() {
	service_control sc on
	if service_control sc is-active;
	then
		service_control sc reload
	else
		service_control sc start
	fi
}

_set_value()
{
	local name="$1"
	local value="$2"

	[ -n "$name" ] || return
	[ -w "$SHAPERCONTROL_CONF" ] || return

	set_state_off

	if grep "^[ \t]*$name[ \t]*=.*$" "$SHAPERCONTROL_CONF" >/dev/null 2>&1;
	then
		sed -i "s/^[ \t]*$name[ \t]*=.*$/$name = $value/" "$SHAPERCONTROL_CONF"
	else
		echo "$name = $value" >> "$SHAPERCONTROL_CONF"
	fi
}

set_out_if()
{
	_set_value "out_if" "$in_interface"
}

set_in_if()
{
	_set_value "in_if" "$in_interface"
}

set_rate_ratio()
{
	_set_value "rate_ratio" "$in_rate_ratio"
}

_add_value()
{
	local name="$1"
	local value="$2"

	[ -n "$name" ] || return
	[ -n "$value" ] || return
	[ -w "$SHAPERCONTROL_CONF" ] || return

	set_state_off

	if grep "^[ \t]*$name[ \t]*=.*$" "$SHAPERCONTROL_CONF" >/dev/null 2>&1;
	then
		sed -i "/^[ \t]*$name[ \t]*=/{s|$|\ $value|}" "$SHAPERCONTROL_CONF"
	else
		echo "$name = $value" >> "$SHAPERCONTROL_CONF"
	fi
}

add_network()
{
	_add_value "network" "$in_network"

	# 'filter_network' will be after 'network'
	[ -w "$SHAPERCONTROL_CONF" ] || return

	set_state_off

	sed -i "/^[ \t]*filter_network[ \t]*=/d" "$SHAPERCONTROL_CONF"
	echo 'filter_network = $network' >> "$SHAPERCONTROL_CONF"
}

add_bypass_int()
{
	_add_value "bypass_int" "$in_network"
}

add_bypass_ext()
{
	_add_value "bypass_ext" "$in_network"
}

_del_values()
{
	local name="$1"
	local values="$2"

	[ -n "$name" ] || return
	[ -n "$values" ] || return
	[ -w "$SHAPERCONTROL_CONF" ] || return

	set_state_off

	local IFS=';'

	for value in $values;
	do
		[ -z "$value" ] && continue
		sed -i "/^[ \t]*$name[ \t]*=/{s|[ \t]*$value[ \t]*|\ |}" "$SHAPERCONTROL_CONF"
	done
}

del_network()
{
	_del_values "network" "$in_networks"
}

del_bypass_int()
{
	_del_values "bypass_int" "$in_networks"
	[ -n "$(__get_list bypass_int)" ] ||
		sed -i '/^bypass_int[[:blank:]]*=/d' "$SHAPERCONTROL_CONF"
}

del_bypass_ext()
{
	_del_values "bypass_ext" "$in_networks"
	[ -n "$(__get_list bypass_ext)" ] ||
		sed -i '/^bypass_ext[[:blank:]]*=/d' "$SHAPERCONTROL_CONF"
}

db_avail_size()
{
	write_enum_item "10" "`_ "10 lines"`"
	write_enum_item "20" "`_ "20 lines"`"
	write_enum_item "50" "`_ "50 lines"`"
	write_enum_item "100" "`_ "100 lines"`"
}

db_add()
{
	[ -n "$in_ip" ] || return
	[ -n "$in_rate" ] || return

	"$SHAPERCONTROL" dbadd "$in_ip" "$in_rate"
	"$SHAPERCONTROL" sync
}

db_get_list()
{
	limit="$in_size"
	[ -z "$limit" -o "$limit" = "#f" ] && limit="20"

	offset="$in_start"
	[ -z "$offset" -o "$offset" = "#f" ] && offset="0"

	search=""
	[ -n "$in_find" -a "$in_find" != "#f" ] && search=" WHERE ip='$(_ipv4addr_to_num "$in_find")' OR rate='$in_find' "

	total="$(echo "SELECT count(ip) FROM rates $search ;" | "$SQLITE3" "$SQLITE3_DB")"

	if [ "$in_action" = "first" ];
	then
		offset="0"
	elif [ "$in_action" = "back" ];
	then
		if [ $(($offset - $limit)) -lt 0 ];
		then
			offset="0"
		else
			offset="$(($offset - $limit))"
		fi
	elif [ "$in_action" = "next" ];
	then
		if [ $(($offset + $limit)) -le $total ];
		then
			offset="$(($offset + $limit))"
		fi
	elif [ "$in_action" = "last" ];
	then
		offset="$(($total - ($total % $limit)))"
	fi

	local IFS=$'\n'

	for line in $(echo "SELECT ip, rate FROM rates $search LIMIT $limit OFFSET $offset;" | "$SQLITE3" "$SQLITE3_DB");
	do
		ip_num="$(echo "$line" | cut -d'|' -f1)"
		rate="$(echo "$line" | cut -d'|' -f2)"
		ip_str="$(_num_to_ipv4addr "$ip_num")"
		write_table_item \
			name		"$ip_str" \
			list_ip		"$ip_str" \
			list_rate	"$rate"
	done
}

db_get_ssr()
{
	limit="$in_size"
	[ -z "$limit" -o "$limit" = "#f" ] && limit="20"

	offset="$in_start"
	[ -z "$offset" -o "$offset" = "#f" ] && offset="0"

	search=""
	[ -n "$in_find" -a "$in_find" != "#f" ] && search=" WHERE ip='$(_ipv4addr_to_num "$in_find")' OR rate='$in_find' "

	total="$(echo "SELECT count(ip) FROM rates $search ;" | "$SQLITE3" "$SQLITE3_DB")"

	if [ "$in_action" = "first" ];
	then
		offset="0"
	elif [ "$in_action" = "back" ];
	then
		if [ $(($offset - $limit)) -lt 0 ];
		then
			offset="0"
		else
			offset="$(($offset - $limit))"
		fi
	elif [ "$in_action" = "next" ];
	then
		if [ $(($offset + $limit)) -le $total ];
		then
			offset="$(($offset + $limit))"
		fi
	elif [ "$in_action" = "last" ];
	then
		offset="$(($total - ($total % $limit)))"
	fi

	write_string_param start "$offset"
	write_string_param size "$limit"

	if [ $(($offset + 1)) -gt $total ];
	then
		start="$total"
	else
		start="$(($offset + 1))"
	fi
	if [ $(($offset + $limit + 1)) -gt $total ];
	then
		stop="$total"
	else
		stop="$(($offset + $limit + 1))"
	fi
	local format="`_ "Lines %s-%s of %s"`"
	write_string_param range "$(printf "$format" "$start" "$stop" "$total")"
}

db_del()
{
	[ -n "$in_list" ] || return
	[ "$in_list" != '#f' ] || return

	local IFS=$'\n'
	local IFS="$IFS;"

	for ip in $in_list;
	do
		"$SHAPERCONTROL" dbdel "$ip"
	done

	"$SHAPERCONTROL" sync
}

alterator_export_proc init_config
alterator_export_proc check_config
alterator_export_proc get_state
alterator_export_proc get_out_if_list
alterator_export_proc get_in_if_list
alterator_export_proc get_network_list
alterator_export_proc get_bypass_int_list
alterator_export_proc get_bypass_ext_list
alterator_export_proc get_out_if
alterator_export_proc get_in_if
alterator_export_proc get_rate_ratio
alterator_export_proc set_state
alterator_export_proc set_out_if
alterator_export_proc set_in_if
alterator_export_proc set_rate_ratio
alterator_export_proc add_network
alterator_export_proc add_bypass_int
alterator_export_proc add_bypass_ext
alterator_export_proc del_network
alterator_export_proc del_bypass_int
alterator_export_proc del_bypass_ext

alterator_export_proc db_avail_size
alterator_export_proc db_add
alterator_export_proc db_get_list
alterator_export_proc db_get_ssr
alterator_export_proc db_del

message_loop

# vim: autoindent tabstop=2 shiftwidth=2 noexpandtab softtabstop=2 filetype=sh
