#!/bin/sh

. /lib/functions.sh

URL=""
PROV_CODE=""
RETRY_MIN_INTERVAL="5"
RETRY_INTERVAL_MUL="2000"
ENDPOINT_ID=""
CONTROLLER_DISCOVERED=0
OBUSPA_BOOT_MARKER="/etc/obuspa/.boot"

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

get_oui_from_db() {
	db -q get device.deviceinfo.ManufacturerOUI
}

get_serial_from_db() {
	db -q get device.deviceinfo.SerialNumber
}

get_vivsoi() {
	# opt125 environment variable has data in below format
	# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	# |      enterprise-number1       |
	# |                               |
	# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	# |   data-len1   |               |
	# +-+-+-+-+-+-+-+-+ option-data1  |
	# /                               /
	# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -----
	# |      enterprise-number2       |   ^
	# |                               |   |
	# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+   |
	# |   data-len2   |               | optional
	# +-+-+-+-+-+-+-+-+ option-data2  |   |
	# /                               /   |
	# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+   |
	# ~            ...                ~   V
	# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -----

	#  Enterprise Id  Len  Sub Op  SLen  Data Sub Op  SLen Data       Sub Op  SLen Data
	# +-------------+-----+------+------+----+------+-----+----+-----+------+-----+----+
	# |     id      |  n  |   1  |  n1  | D1 |   2  |  n2 | D2 | ... |   6  |  n6 | D6 |
	# +-------------+-----+------+------+----+------+-----+----+-----+------+-----+----+

	local opt125="$1"
	local len="$2"
	local ent_id

	#hex-string 2 character=1 Byte
	# length in hex string will be twice of actual Byte length
	[ "$len" -gt "8" ] || return

	data="${opt125}"
	rem_len="${len}"
	while [ "${rem_len}" -gt 0 ]; do
		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 ))
			# 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 ))

		opt_len=$(printf "%d\n" "0x$len_val")
		[ "${opt_len}" -eq 0 ] && return

		# 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")

			# 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 ))

			# get the value of sub option starting 4 means starting after length
			sub_opt_val=${sub_data:4:"${sub_opt_len}"}

			# assign the value found in sub option
			case "${sub_opt_id}" in
				"25")
					URL=$(echo -n "${sub_opt_val}" | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf && echo '')
					CONTROLLER_DISCOVERED=1
				;;
				"26")
					PROV_CODE=$(echo -n "${sub_opt_val}" | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf && echo '')
					CONTROLLER_DISCOVERED=1
				;;
				"27")
					RETRY_MIN_INTERVAL=$(echo -n "${sub_opt_val}" | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf && echo '')
					CONTROLLER_DISCOVERED=1
				;;
				"28")
					RETRY_INTERVAL_MUL=$(echo -n "${sub_opt_val}" | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf && echo '')
					CONTROLLER_DISCOVERED=1
				;;
				"29")
					ENDPOINT_ID=$(echo -n "${sub_opt_val}" | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf && echo '')
					CONTROLLER_DISCOVERED=1
				;;
			esac

			# 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

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

get_access_role()
{
	local mode lan_proto

	lan_proto="$(uci -q get network.lan.proto)"

	if [ "${lan_proto}" = "dhcp" ]; then
		mode="extender"
	else
		mode="full_access"
	fi

	echo "$mode"
}

get_agent_topic()
{
	AgentEndpointID="$(uci -q get obuspa.localagent.EndpointID)"
	if [ -z "${AgentEndpointID}" ]; then
		serial=$(get_serial_from_db)
		oui=$(get_oui_from_db)
		AgentEndpointID=$(echo "${oui}-${serial//+/%2B}")
	fi

	topic_base=$(echo "${AgentEndpointID}" | sed -E 's/[^[:alnum:]]/_/g')
	agent_topic="/usp/${topic_base}/endpoint"
	echo "${agent_topic}"
}

config_load obuspa
config_get_bool enable_obuspa global enabled 1
config_get wan_intf global interface
config_get_bool dhcp_discovery global dhcp_discovery 1

if [ "${enable_obuspa}" -eq 0 ] || [ "${dhcp_discovery}" -eq 0 ]; then
	return 0
fi

if [ -z "${wan_intf}" ]; then
	role="$(get_access_role)"

	if [ "${role}" = "extender" ]; then
		wan_intf="lan"
		uci -q set obuspa.global.interface="lan"
		uci commit obuspa
	else
		wan_intf="wan"
	fi
fi

if [ "${wan_intf}" = "${INTERFACE}" ]; then
	if [ -n "$opt125" ]; then
		len=$(echo -n "${opt125}"|wc -c)
		get_vivsoi "$opt125" "$len"
	fi

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

	proto=""
	ip=""
	port=""
	topic=""
	is_fqdn=1
	offered_proto=""
	mtp_encrypt=""
	dhcp_controller=""
	dhcp_mtp=""
	dhcp_mqtt=""

	if [ -n "$URL" ]; then
		case $URL in
		ws://*) is_fqdn=0;;
		wss://*) is_fqdn=0;;
		mqtt://*) is_fqdn=0;;
		mqtts://*) is_fqdn=0;;
		*://*) return 0;;
		*) is_fqdn=1
		esac;

		if [ ${is_fqdn} -eq 1 ]; then
			case $URL in
			*.local*)
				# TODO extend for mdns handling
				;;
			*)
				# This is an FQDN, perform DNS query
				nslookup "${URL}" > /tmp/fqdn_ip
				nslookup -type=ptr "${URL}" > /tmp/fqdn_ptr
				nslookup -type=srv "${URL}" > /tmp/fqdn_srv
				nslookup -type=txt "${URL}" > /tmp/fqdn_srv

				# TODO extend to collect information from dns-sd records
				;;
			esac

			return 0
		else
			proto=$(echo "${URL}" | awk -F: '{print $1}')
			dest=$(echo "${URL}" | awk -F/ '{print $3}')
			ip=$(echo "${dest}" | awk -F: '{print $1}')
			port=$(echo "${dest}" | awk -F: '{print $2}')
			topic=$(echo "${URL}" | sed  's/^.*:'"${port}"'/\1/g')
		fi
	fi

	if [ "${proto}" = "mqtt" ] || [ "${proto}" = "mqtts" ]; then
		offered_proto="MQTT"
		if [ "${proto}" = "mqtt" ]; then
			mtp_encrypt="TCP/IP"
		else
			mtp_encrypt="TLS"
		fi
	elif [ "${proto}" = "ws" ] || [ "${proto}" = "wss" ]; then
		offered_proto="WebSocket"
		if [ "${proto}" = "wss" ]; then
			mtp_encrypt="1"
		else
			mtp_encrypt="0"
		fi
	fi

	controllers=$(uci -q show obuspa | grep "=controller" | cut -d'=' -f1 | cut -d'.' -f2)
	for controller in $controllers; do
		dhcp_disc=$(uci -q get obuspa."${controller}".dhcp_discovered)
		if [ "${dhcp_disc}" -eq 1 ]; then
			dhcp_controller="${controller}"
			break
		fi
	done

	# Check if any of the existing controller section matches with the endpointid
	if [ -z "${dhcp_controller}" ] && [ -n "${ENDPOINT_ID}" ]; then
		for controller in $controllers; do
			endpointid=$(uci -q get obuspa."${controller}".EndpointID)
			if [ "${endpointid}" = "${ENDPOINT_ID}" ]; then
				dhcp_controller="${controller}"
				break
			fi
		done
	fi

	if [ -n "${dhcp_controller}" ]; then
		cont_proto=$(uci -q get obuspa."${dhcp_controller}".Protocol)
		if [ "${cont_proto}" = "MQTT" ]; then
			dhcp_mqtt=$(uci -q get obuspa."${dhcp_controller}".mqtt)

			mtps=$(uci -q show obuspa | grep "=mtp" | cut -d'=' -f1 | cut -d'.' -f2)
			for mtp in $mtps; do
				mtp_mqtt=$(uci -q get obuspa."${mtp}".mqtt)
				if [ "${mtp_mqtt}" = "${dhcp_mqtt}" ]; then
					dhcp_mtp="${mtp}"
					break
				fi
			done
		elif [ "${cont_proto}" = "WebSocket" ]; then
			cont_port=$(uci -q get obuspa."${dhcp_controller}".Port)
			cont_encr=$(uci -q get obuspa."${dhcp_controller}".EnableEncryption)

			mtps=$(uci -q show obuspa | grep "=mtp" | cut -d'=' -f1 | cut -d'.' -f2)
			for mtp in $mtps; do
				mtp_port=$(uci -q get obuspa."${mtp}".Port)
				mtp_encr=$(uci -q get obuspa."${mtp}".EnableEncryption)
				if [ "${mtp_port}" = "${cont_port}" ] && [ "${mtp_encr}" = "${cont_encr}" ]; then
					dhcp_mtp="${mtp}"
					break
				fi
			done
		fi
	fi

	uci_change=0
	proto_changed="0"

	if [ -n "${dhcp_controller}" ]; then
		## Handling of controller section
		ct_endpoint=$(uci -q get obuspa."${dhcp_controller}".EndpointID)
		ct_proto=$(uci -q get obuspa."${dhcp_controller}".Protocol)
		ct_prov=$(uci -q get obuspa."${dhcp_controller}".ProvisioningCode)

		if [ "${ct_proto}" = "MQTT" ]; then
			ct_topic=$(uci -q get obuspa."${dhcp_controller}".Topic)
		else
			ct_topic=$(uci -q get obuspa."${dhcp_controller}".Path)
		fi

		if [ -n "${ENDPOINT_ID}" ] && [ "${ct_endpoint}" != "${ENDPOINT_ID}" ]; then
			uci -q set obuspa."${dhcp_controller}".EndpointID="${ENDPOINT_ID}"
			uci_change=1
		fi

		if [ -n "${offered_proto}" ] && [ "${ct_proto}" != "${offered_proto}" ]; then
			uci -q set obuspa."${dhcp_controller}".Protocol="${offered_proto}"
			if [ "${offered_proto}" != "MQTT" ]; then
				uci -q delete obuspa."${dhcp_controller}".mqtt
				uci -q delete obuspa."${dhcp_controller}".Topic
				uci -q set obuspa."${dhcp_controller}".Host="${ip}"
				uci -q set obuspa."${dhcp_controller}".Port="${port}"
				uci -q set obuspa."${dhcp_controller}".Path="${ct_topic}"
				uci -q set obuspa."${dhcp_controller}".EnableEncryption="${mtp_encrypt}"
			else
				uci -q delete obuspa."${dhcp_controller}".EnableEncryption
				uci -q delete obuspa."${dhcp_controller}".Path
				uci -q delete obuspa."${dhcp_controller}".Host
				uci -q delete obuspa."${dhcp_controller}".Port

				if [ -z "${dhcp_mqtt}" ]; then
					uci -q set obuspa."${dhcp_controller}".mqtt='dhcpmqtt'
				else
					uci -q set obuspa."${dhcp_controller}".mqtt="${dhcp_mqtt}"
				fi

				uci -q set obuspa."${dhcp_controller}".Topic="${ct_topic}"
			fi

			proto_changed=1
			uci_change=1
		fi

		if [ -n "${topic}" ] && [ "${ct_topic}" != "${topic}" ]; then
			protocol="${ct_proto}"
			if [ -n "${offered_proto}" ]; then
				protocol="${offered_proto}"
			fi

			if [ "${protocol}" = "MQTT" ]; then
				uci -q set obuspa."${dhcp_controller}".Topic="${topic}"
			else
				uci -q set obuspa."${dhcp_controller}".Path="${topic}"
			fi

			uci_change=1
		fi

		if [ -n "${PROV_CODE}" ] && [ "${ct_prov}" != "${PROV_CODE}" ]; then
			uci -q set obuspa."${dhcp_controller}".ProvisioningCode="${PROV_CODE}"
			uci_change=1
		fi

		if [ "${proto_changed}" -eq 1 ]; then
			if [ "${offered_proto}" = "WebSocket" ]; then
				if [ -n "${dhcp_mqtt}" ]; then
					uci -q delete obuspa."${dhcp_mqtt}"
				fi

				if [ -z "${dhcp_mtp}" ]; then
					uci -q set obuspa.dhcpmtp="mtp"
					dhcp_mtp="dhcpmtp"
					uci -q set obuspa."${dhcp_mtp}".Enable='1'
				fi

				uci -q set obuspa."${dhcp_mtp}".mqtt=''
				uci -q set obuspa."${dhcp_mtp}".ResponseTopicConfigured=''
				uci -q set obuspa."${dhcp_mtp}".Protocol='WebSocket'
				uci -q set obuspa."${dhcp_mtp}".Port="${port}"
				uci -q set obuspa."${dhcp_mtp}".EnableEncryption="${mtp_encrypt}"

				uci_change=1
			else
				if [ -z "${dhcp_mqtt}" ]; then
					user="$(uci -q get obuspa.global.username)"
					pass="$(uci -q get obuspa.global.password)"

					uci -q set obuspa.dhcpmqtt="mqtt"
					dhcp_mqtt="dhcpmqtt"
					uci -q set obuspa."${dhcp_mqtt}".Enable='1'
					uci -q set obuspa."${dhcp_mqtt}".Username="${user}"
					uci -q set obuspa."${dhcp_mqtt}".Password="${pass}"
				fi

				uci -q set obuspa."${dhcp_mqtt}".BrokerAddress="${ip}"
				uci -q set obuspa."${dhcp_mqtt}".BrokerPort="${port}"
				uci -q set obuspa."${dhcp_mqtt}".TransportProtocol="${mtp_encrypt}"
				uci -q set obuspa."${dhcp_mqtt}".ProtocolVersion='5.0'

				if [ -z "${dhcp_mtp}" ]; then
					uci -q set obuspa.dhcpmtp="mtp"
					dhcp_mtp="dhcpmtp"
					uci -q set obuspa."${dhcp_mtp}".Enable='1'
				fi

				agent_topic=$(get_agent_topic)
				uci -q delete obuspa."${dhcp_mtp}".Port
				uci -q delete obuspa."${dhcp_mtp}".EnableEncryption
				uci -q set obuspa."${dhcp_mtp}".Protocol='MQTT'
				uci -q set obuspa."${dhcp_mtp}".ResponseTopicConfigured="${agent_topic}"
				uci -q set obuspa."${dhcp_mtp}".mqtt="${dhcp_mqtt}"

				uci_change=1
			fi
		else
			if [ "${ct_proto}" = "WebSocket" ]; then
				conf_ip="$(uci -q get obuspa."${dhcp_controller}".Host)"
				conf_port="$(uci -q get obuspa."${dhcp_mtp}".Port)"
				conf_encr="$(uci -q get obuspa."${dhcp_mtp}".EnableEncryption)"

				if [ -n "${ip}" ] && [ "${conf_ip}" != "${ip}" ]; then
					uci -q set obuspa."${dhcp_controller}".Host="${ip}"
					uci_change=1
				fi

				if [ -n "${port}" ] && [ "${conf_port}" != "${port}" ]; then
					uci -q set obuspa."${dhcp_mtp}".Port="${port}"
					uci -q set obuspa."${dhcp_controller}".Port="${port}"
					uci_change=1
				fi

				if [ -n "${mtp_encrypt}" ] && [ "${conf_encr}" != "${mtp_encrypt}" ]; then
					uci -q set obuspa."${dhcp_mtp}".EnableEncryption="${mtp_encrypt}"
					uci -q set obuspa."${dhcp_controller}".EnableEncryption="${mtp_encrypt}"
					uci_change=1
				fi
			else
				conf_ip=$(uci -q get obuspa."${dhcp_mqtt}".BrokerAddress)
				conf_port=$(uci -q get obuspa."${dhcp_mqtt}".BrokerPort)
				conf_encr=$(uci -q get obuspa."${dhcp_mqtt}".TransportProtocol)

				if [ -n "${port}" ] && [ "${conf_port}" != "${port}" ]; then
					uci -q set obuspa."${dhcp_mqtt}".BrokerPort="${port}"
					uci_change=1
				fi

				if [ -n "${mtp_encrypt}" ] && [ "${conf_encr}" != "${mtp_encrypt}" ]; then
					uci -q set obuspa."${dhcp_mqtt}".TransportProtocol="${mtp_encrypt}"
					uci_change=1
				fi

				if [ -n "${ip}" ] && [ "${conf_ip}" != "${ip}" ]; then
					uci -q set obuspa."${dhcp_mqtt}".BrokerAddress="${ip}"
					uci_change=1
				fi
			fi
		fi
	else
		# Only setup a new controller, only if mandatory param present
		if [ -n "${ENDPOINT_ID}" ] && [ -n "${URL}" ]; then
			uci -q delete obuspa.dhcpmtp
			uci -q delete obuspa.dhcpmqtt

			uci -q set obuspa.dhcpcontroller="controller"
			uci -q set obuspa.dhcpcontroller.dhcp_discovered="1"
			uci -q set obuspa.dhcpcontroller.EndpointID="${ENDPOINT_ID}"
			uci -q set obuspa.dhcpcontroller.ProvisioningCode="${PROV_CODE}"
			uci -q set obuspa.dhcpcontroller.Protocol="${offered_proto}"
			uci -q set obuspa.dhcpcontroller.assigned_role_name="$(get_access_role)"
			uci -q set obuspa.dhcpcontroller.Enable='1'

			if [ -n "${offered_proto}" ]; then
				if [ "${offered_proto}" = "MQTT" ]; then
					user="$(uci -q get obuspa.global.username)"
					pass="$(uci -q get obuspa.global.password)"

					uci -q set obuspa.dhcpcontroller.Topic="${topic}"
					uci -q set obuspa.dhcpcontroller.mqtt='dhcpmqtt'

					uci -q set obuspa.dhcpmqtt="mqtt"
					uci -q set obuspa.dhcpmqtt.BrokerAddress="${ip}"
					uci -q set obuspa.dhcpmqtt.BrokerPort="${port}"
					uci -q set obuspa.dhcpmqtt.TransportProtocol="${mtp_encrypt}"
					uci -q set obuspa.dhcpmqtt.Enable='1'
					uci -q set obuspa.dhcpmqtt.ProtocolVersion='5.0'
					uci -q set obuspa.dhcpmqtt.Username="${user}"
					uci -q set obuspa.dhcpmqtt.Password="${pass}"


					agent_topic=$(get_agent_topic)
					uci -q set obuspa.dhcpmtp="mtp"
					uci -q set obuspa.dhcpmtp.Protocol='MQTT'
					uci -q set obuspa.dhcpmtp.ResponseTopicConfigured="${agent_topic}"
					uci -q set obuspa.dhcpmtp.Enable='1'
					uci -q set obuspa.dhcpmtp.mqtt='dhcpmqtt'
				else
					uci -q set obuspa.dhcpcontroller.Path="${topic}"
					uci -q set obuspa.dhcpcontroller.Host="${ip}"
					uci -q set obuspa.dhcpcontroller.Port="${port}"
					uci -q set obuspa.dhcpcontroller.EnableEncryption="${mtp_encrypt}"

					uci -q set obuspa.dhcpmtp="mtp"
					uci -q set obuspa.dhcpmtp.Protocol='WebSocket'
					uci -q set obuspa.dhcpmtp.Port="${port}"
					uci -q set obuspa.dhcpmtp.Enable='1'
					uci -q set obuspa.dhcpmtp.EnableEncryption="${mtp_encrypt}"
				fi
			fi
			uci_change=1
		fi
	fi

	if [ ${uci_change} -eq 1 ]; then
		if [ -f "${OBUSPA_BOOT_MARKER}" ]; then
			rm -f "${OBUSPA_BOOT_MARKER}"
		fi
		log "# Reloading obuspa as dhcp config changed"
		ubus call uci commit '{"config":"obuspa"}'
	fi
fi
