#!/bin/sh /etc/rc.common

START=99
STOP=01
USE_PROCD=1

PROG=/usr/sbin/uspc
CONFIGURATION=uspc

RESET_FILE="/tmp/usp/uspc_param_reset.txt"
SQL_DB_FILE="/tmp/usp/uspc.db"

BASEPATH=""
INSTANCE_COUNT=0

. /usr/share/libubox/jshn.sh
. /lib/functions.sh

global_init()
{
	BASEPATH=""
	INSTANCE_COUNT=0
}

log()
{
	echo "$*"|logger -t uspc.init -p debug
}

db_set_reset_file()
{
	local param value

	param="${1}"
	shift
	value="$*"

	if [ -n "${param}" ] && [ -n "${value}" ]; then
		echo "${param} \"${value}\"">>${RESET_FILE}
	else
		echo >>${RESET_FILE}
	fi
}

db_set()
{
	db_set_reset_file "$@"
}

# if db present then check if it matches with existing instances
# fallback to max instance present + 1
# In case of no db get the count
get_base_path()
{
	local refpath value path count

	refpath="${1}"
	value="${2}"
	path=""
	count=0

	if [ -z "${path}" ]; then
		INSTANCE_COUNT=$(( INSTANCE_COUNT + 1 ))
		path="${refpath}${INSTANCE_COUNT}"
	fi
	BASEPATH="${path}"
}

get_refrence_path()
{
	local dmref value path

	dmref="${1}"
	value="${2}"
	path=""

	path=$(grep "${dmref}\d.Alias " ${RESET_FILE}|grep -w "${value}")
	path=${path%.*}
	echo "${path}"
}

convert_to_hex() {
	local val=""
	local optval="${1}"
	OPTIND=1
	while getopts ":" opt "-$optval"
	do
		temp=$(printf "%02X" "'${OPTARG:-:}")
		val="${val}:${temp}"
	done

	echo "${val}"
}

check_for_suboptions() {
	new_opt=""
	# Check if option 25 and 29 present inside enterprise id 3561
	data=$(echo "${1}" | sed 's/://g')
	len=$(printf "${data}"|wc -c)

	rem_len="${len}"
	while [ $rem_len -gt 8 ]; do
		subopt_present=0

		ent_id="${data:0:8}"
		ent_id=$(printf "%d\n" "0x$ent_id")
		if [ $ent_id -ne 3561 ]; then
			len_val=${data:8:2}
			data_len=$(printf "%d\n" "0x$len_val")
			# add 4 byte for ent_id and 1 byte for len
			data_len=$(( data_len * 2 + 10 ))

			opt=${data:0:"${data_len}"}
			if [ -n "${new_opt}" ]; then
				new_opt="${new_opt}:$(echo ${opt} | sed 's/../&:/g;s/:$//')"
			else
				new_opt="$(echo ${opt} | sed 's/../&:/g;s/:$//')"
			fi

			# move ahead data to next enterprise id
			data=${data:"${data_len}":"${rem_len}"}
			rem_len=$(( rem_len - data_len ))
			continue
		fi

		# read the length of enterprise data
		len_val=${data:8:2}
		data_len=$(printf "%d\n" "0x$len_val")
		# add 4 byte for ent_id and 1 byte for len
		data_len=$(( data_len * 2 + 10 ))

		len_val=${data:8:2}
		opt_len=$(printf "%d\n" "0x$len_val")
		if [ $opt_len -eq 0 ]; then
			echo "${new_opt}"
			return 0
		fi

		# populate the option data of enterprise id
		sub_data_len=$(( opt_len * 2))
		# starting 10 means ahead of length field
		sub_data=${data:10:"${sub_data_len}"}

		# parsing of suboption of option 125
		while [ $sub_data_len -gt 0 ]; do
			# get the suboption id
			sub_opt_id=${sub_data:0:2}
			sub_opt_id=$(printf "%d\n" "0x$sub_opt_id")
			case "${sub_opt_id}" in
			"25") subopt_present=1
			;;
			"26") subopt_present=1
			;;
			"27") subopt_present=1
			;;
			"28") subopt_present=1
			;;
			"29") subopt_present=1
			;;
			esac

			if [ $subopt_present -eq 1 ]; then
				break;
			fi

			# get the length of suboption
			sub_opt_len=${sub_data:2:2}
			sub_opt_len=$(printf "%d\n" "0x$sub_opt_len")
			sub_opt_len=$(( sub_opt_len * 2 ))

			# add 2 bytes for sub_opt id and sub_opt len field
			sub_opt_end=$(( sub_opt_len + 4 ))

			# update the remaining sub option hex string length
			sub_data_len=$((sub_data_len - sub_opt_end))

			# fetch next sub option hex string
			sub_data=${sub_data:${sub_opt_end}:${sub_data_len}}
		done

		if [ $subopt_present -eq 1 ]; then
			# move ahead data to next enterprise id
			rem_len=$(( rem_len - $data_len ))
			data=${data:"${data_len}":"${rem_len}"}
		else
			opt=${data:0:"${data_len}"}
			if [ -n "${new_opt}" ]; then
				new_opt="${new_opt}:$(echo ${opt} | sed 's/../&:/g;s/:$//')"
			else
				new_opt="$(echo ${opt} | sed 's/../&:/g;s/:$//')"
			fi

			# move ahead data to next enterprise id
			rem_len=$(( rem_len - $data_len ))
			data=${data:"${data_len}":"${rem_len}"}
		fi
	done

	echo "${new_opt}"
}

configure_dnsmasq_op125() {
	intf="${1}"

	endpoint=""
	proto=""
	address=""
	port=""
	topic=""
	prov_code="obusp-client"
	interval="5"
	multiplier="2000"

	config_load ${CONFIGURATION}
	config_get endpoint controller EndpointID
	config_get proto controller Protocol
	if [ -z "${endpoint}" ] || [ -z "${proto}" ]; then
		return 0
	fi

	if [ "${proto}" = "MQTT" ]; then
		config_get port mqtt BrokerPort "1883"
		config_get topic controller ResponseTopicConfigured
		proto="mqtt://"
	else
		return 0
	fi

	address=$(ifstatus "${intf}" | jsonfilter -q -e '@["ipv4-address"][0].address')
	if [ -z "${address}" ] || [ -z "${topic}" ]; then
		return 0
	fi

	subop_present=0
	opt125="125,"
	base_opt=""

	service="$(uci -q get dhcp."${intf}".dhcpv4)"
	if [ "${service}" = "server" ]; then
		opt_list="$(uci -q get dhcp."${intf}".dhcp_option)"
		for sopt in $opt_list; do
			if [[ "$sopt" == "$opt125"* ]]; then
				base_opt=$(check_for_suboptions "${sopt:4}")
				uci -q del_list dhcp."${intf}".dhcp_option="$sopt"
				uci -q commit dhcp
				break
			fi
		done
	else
		return 0
	fi

	if [ -z "${base_opt}" ]; then
		opt125="125,00:00:0D:E9"
	else
		opt125="125,${base_opt}:00:00:0D:E9"
	fi

	url="${proto}${address}:${port}${topic}"
	url_len=$(echo -n "${url}" | wc -m)
	prov_code_len=$(echo -n "${prov_code}" | wc -m)
	endpoint_len=$(echo -n "${endpoint}" | wc -m)
	interval_len=$(echo -n "${interval}" | wc -m)
	multiplier_len=$(echo -n "${multiplier}" | wc -m)

	([ ${url_len} -gt 255 ] || [ ${prov_code_len} -gt 255 ] || [ ${endpoint_len} -gt 255 ]) && return 0
	([ ${interval_len} -gt 255 ] || [ ${multiplier_len} -gt 255 ]) && return 0

	opt125_len=$((url_len + prov_code_len + endpoint_len + interval_len + multiplier_len))
	opt125_len=$((opt125_len + 10))

	[ $opt125_len -gt 255 ] && return 0

        hex_opt125_len=$(printf "%02X" "${opt125_len}")
        opt125="${opt125}:${hex_opt125_len}"
        hex_url=$(convert_to_hex "${url}")
        if [ -z "${hex_url}" ]; then
                return 0
        fi

        hex_url_len=$(printf "%02X" "${url_len}")
        opt125="${opt125}:19:${hex_url_len}${hex_url}"

        hex_prov_code=$(convert_to_hex "${prov_code}")
        if [ -z "${hex_prov_code}" ]; then
                return 0
        fi

        hex_prov_len=$(printf "%02X" "${prov_code_len}")
	opt125="${opt125}:1A:${hex_prov_len}${hex_prov_code}"

	hex_interval=$(convert_to_hex "${interval}")
	if [ -z "${hex_interval}" ]; then
		return 0
	fi

	hex_interval_len=$(printf "%02X" "${interval_len}")
	opt125="${opt125}:1B:${hex_interval_len}${hex_interval}"

	hex_multiplier=$(convert_to_hex "${multiplier}")
	if [ -z "${hex_multiplier}" ]; then
		return 0
	fi

	hex_multiplier_len=$(printf "%02X" "${multiplier_len}")
	opt125="${opt125}:1C:${hex_multiplier_len}${hex_multiplier}"

	hex_endpoint=$(convert_to_hex "${endpoint}")
	if [ -z "${hex_endpoint}" ]; then
		return 0
	fi

	hex_endpoint_len=$(printf "%02X" "${endpoint_len}")
	opt125="${opt125}:1D:${hex_endpoint_len}${hex_endpoint}"

	uci -q add_list dhcp."${intf}".dhcp_option="$opt125"
	ubus call uci commit '{"config":"dhcp"}'
}

boot() {
	local enabled
	local interface

	config_load ${CONFIGURATION}
	config_get_bool enabled global enabled 0
	config_get interface global interface "lan"

	if [ "${enabled}" -eq 0 ]; then
		return 0;
	fi

	# Check if device is an extender nothing to do
	lan_proto="$(uci -q get network.lan.proto)"
	if [ "${lan_proto}" == "dhcp" ]; then
		# extender so return
		return 0;
	fi

	configure_dnsmasq_op125 "${interface}"

	start
}

validate_global_section()
{
	uci_validate_section ${CONFIGURATION} uspc "${1}" \
		'enabled:bool:1' \
		'debug:bool:0' \
		'prototrace:bool:0' \
		'log_level:uinteger' \
		'log_dest:string' \
		'db_file:string'
}

validate_mqtt_client_section()
{
	uci_validate_section ${CONFIGURATION} mqtt "${1}" \
		'Enable:bool:1' \
		'BrokerAddress:string' \
		'BrokerPort:port:1883' \
		'Username:string' \
		'Password:string' \
		'ProtocolVersion:or("3.1", "3.1.1","5.0"):5.0' \
		'TransportProtocol:or("TCP/IP","TLS"):TCP/IP' \
		'ClientID:string'
}

validate_controller_section()
{
	uci_validate_section ${CONFIGURATION} mtp "${1}" \
		'EndpointID:string' \
		'Protocol:or("MQTT", "WebSocket")' \
		'ResponseTopicConfigured:string' \
		'Destination:string' \
		'Port:port' \
		'Path:string' \
		'mqtt:string' \
		'stomp:string' \
		'Reference:string' \
		'EnableEncryption:bool:0'
}

configure_controller() {
	local EndpointID Protocol ResponseTopicConfigured
	local Destination Path Port EnableEncryption Reference
	local stomp mqtt dm_ref sec

	sec="${1}"
	validate_controller_section "${1}" || {
		log "Validation of mtp section failed"
		return 1;
	}

	if [ -z "${EndpointID}" ]; then
		log "EndpointID not defined for controller"
		return 1;
	fi

	db_set Device.LocalAgent.EndpointID "${EndpointID}"

	sec="${sec/mtp_/cpe-}"
	get_base_path "Device.LocalAgent.MTP." "${sec}"
	if [ -z "${BASEPATH}" ]; then
		log "Failed to get path [$BASEPATH]"
		return 1;
	fi

	if [ -z "${Protocol}" ]; then
		log "Protocol not defined for the mtp[${1}] section"
		return 1;
	fi

	dm_ref=""
	if [ -z "${Reference}" ]; then
		if [ "${Protocol}" = "STOMP" ]; then
			stomp="${stomp/stomp_/cpe-}"
			dm_ref=$(get_refrence_path "Device.STOMP.Connection." "${stomp}")
		elif [ "${Protocol}" = "MQTT" ]; then
			mqtt="${mqtt/mqtt_/cpe-}"
			dm_ref=$(get_refrence_path "Device.MQTT.Client." "${mqtt}")
		fi
	else
		dm_ref="${Reference}"
	fi

	db_set "${BASEPATH}.Alias" "${sec}"
	db_set "${BASEPATH}.Enable" "1"
	db_set "${BASEPATH}.Protocol" "${Protocol}"
	if [ "${Protocol}" = "MQTT" ]; then
		db_set "${BASEPATH}.MQTT.Reference" "${dm_ref}"
		db_set "${BASEPATH}.MQTT.ResponseTopicConfigured" "${ResponseTopicConfigured}"
	elif [ "${Protocol}" = "STOMP" ]; then
		db_set "${BASEPATH}.STOMP.Reference" "${dm_ref}"
		db_set "${BASEPATH}.STOMP.Destination" "${Destination}"
	elif [ "${Protocol}" = "CoAP" ]; then
		db_set "${BASEPATH}.CoAP.Path" "${Path}"
		db_set "${BASEPATH}.CoAP.Port" "${Port}"
	elif [ "${Protocol}" = "WebSocket" ]; then
		db_set "${BASEPATH}.WebSocket.Path" "${Path}"
		db_set "${BASEPATH}.WebSocket.Port" "${Port}"
		db_set "${BASEPATH}.WebSocket.EnableEncryption" "${EnableEncryption}"
	fi
	db_set
}

validate_agent_section()
{
	uci_validate_section ${CONFIGURATION} agent "${1}" \
		'name:string' \
		'EndpointID:string' \
		'Topic:string'
}

configure_agent() {
	local EndpointID Topic name
	local stomp mqtt dm_ref sec

	sec="${1}"
	validate_agent_section "${1}" || {
		log "Validation of agent section failed"
		return 1;
	}

	if [ -z "${EndpointID}" ]; then
		log "EndpointID not defined for agent"
		return 1;
	fi

	if [ -z "${Topic}" ]; then
		log "Topic not defined for agent"
		return 1;
	fi


	sec="${sec/mtp_/cpe-}"
	get_base_path "Device.LocalAgent.Controller." "${sec}"
	if [ -z "${BASEPATH}" ]; then
		log "Failed to get path [$BASEPATH]"
		return 1;
	fi

	db_set "${BASEPATH}.Alias" "${sec}"
	db_set "${BASEPATH}.Enable" "1"
	db_set "${BASEPATH}.EndpointID" "${EndpointID}"
	db_set "${BASEPATH}.MTP.1.Enable" "1"
	db_set "${BASEPATH}.MTP.1.Protocol" "MQTT"
	db_set "${BASEPATH}.MTP.1.MQTT.Reference" "Device.MQTT.Client.1"
	db_set "${BASEPATH}.MTP.1.MQTT.Topic" "${Topic}"
	db_set
}

configure_mqtt_client() {
	local BrokerAddress BrokerPort Enable Username Password ProtocolVersion
	local TransportProtocol ClientID
	local sec

	sec="${1}"
	validate_mqtt_client_section "${1}" || {
		log "Validation of mqtt section failed"
		return 1;
	}

	sec="${sec/mqtt_/cpe-}"
	get_base_path "Device.MQTT.Client." "${sec}"
	if [ -z "${BASEPATH}" ]; then
		log "Failed to get path [$BASEPATH]"
		return 1;
	fi

	db_set "${BASEPATH}.Alias" "${sec}"
	db_set "${BASEPATH}.Enable" "${Enable}"
	db_set "${BASEPATH}.BrokerAddress" "${BrokerAddress}"
	db_set "${BASEPATH}.BrokerPort" "${BrokerPort}"
	db_set "${BASEPATH}.Username" "${Username}"
	db_set "${BASEPATH}.Password" "${Password}"
	db_set "${BASEPATH}.ProtocolVersion" "${ProtocolVersion}"
	db_set "${BASEPATH}.TransportProtocol" "${TransportProtocol}"
	db_set "${BASEPATH}.ClientID" "${ClientID}"

	db_set
}

configure_uspc() {
	local enabled trust_cert ifname interface debug prototrace log_level db_file log_dest
	local client_cert

	validate_global_section "global"

	if [ "${debug}" -ne "0" ]; then
		# Forward stdout of the command to logd
		procd_set_param stdout 1
		# Same for stderr
		procd_set_param stderr 1
	fi

	procd_append_param command -u
	if [ "${prototrace}" -eq 1 ]; then
		procd_append_param command -p
	fi

	if [ -n "${log_level}" ]; then
		procd_append_param command -v "${log_level}"
	fi

	if [ -n "${log_dest}" ]; then
		procd_append_param command -l "${log_dest}"
	fi

	if [ -f "${RESET_FILE}" ]; then
		procd_append_param command -r ${RESET_FILE}
	fi
}

# Create factory reset file
db_init()
{
	# Load configuration
	config_load $CONFIGURATION
	config_get SQL_DB_FILE global db_file "/tmp/usp/uspc.db"

	# Remove DB and generate from uci
	[ -f "${SQL_DB_FILE}" ] && rm -f "${SQL_DB_FILE}"

	# Remove reset file if present
	[ -f "${RESET_FILE}" ] && rm -f ${RESET_FILE}

	config_load $CONFIGURATION
	global_init
	config_foreach configure_mqtt_client mqtt
	global_init
	config_foreach configure_controller controller
	global_init
	config_foreach configure_agent agent

	return 0;
}

register_service()
{
	procd_open_instance ${CONFIGURATION}
	procd_set_param command ${PROG}

	configure_uspc
	procd_set_param respawn \
			"${respawn_threshold:-5}" \
			"${respawn_timeout:-10}" "${respawn_retry:-3}"

	procd_close_instance
}

start_service() {
	local enabled mode

	config_load ${CONFIGURATION}
	config_get_bool enabled global enabled 0

	if [ "${enabled}" -eq 0 ]; then
		return 0;
	fi

	# Check if device is an extender then do not start the service
	lan_proto="$(uci -q get network.lan.proto)"
	if [ "${lan_proto}" == "dhcp" ]; then
		# extender so return
		return 0;
	fi

	mkdir -p /tmp/usp/
	db_init
	register_service
}

stop_service() {
	${PROG} -c stop >/dev/null 2>&1
}

reload_service() {
	stop
	start
}

service_triggers() {
	procd_add_reload_trigger "uspc"
}
