#!/bin/sh
# Copyright (C) 2025 iopsys Software Solutions AB
# Author: Suvendhu Hansa <suvendhu.hansa@iopsys.eu>
# Purpose: This script is used in airoha platform to test Download speed
#          using fast path via speed_test module
# Limitations:
#    1. speed_test module does not provide error codes when fails, so fallback
#       to curl command to get the exact error code
#    2. speed_test module does not work with IPv6, so fallback to curl for
#       testing with v6 network
#    3. Configuration of speed_test module for FTP protocol is unknown, so
#       rely on curl for download test with ftp

. /usr/share/libubox/jshn.sh

ROOT="$(dirname "${0}")"
. "${ROOT}"/bbf_api

HTTP_DOWNLOAD_TIMEOUT=110
FTP_DOWNLOAD_TIMEOUT=1800
TEST_RESULT_PATH=/proc/tc3162/speedtest_result_tr143
TEST_RESULT_PATH_PER_CON=/proc/tc3162/speedtest_result_tr143_ng

download_error() {
	json_init
	json_add_string "Status" "$1"
	json_dump

	# Store data in dmmap_diagnostics for both protocols (cwmp/usp)
	[ "$2" = "both_proto" ] && {
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.DiagnosticState="$1"
	}

	$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.Status="complete"
	$UCI_COMMIT_BBF_DMMAP
}

get_error_reason() {
	if [ "${1}" = "IPv4" ]; then
		curl_proto="--ipv4"
	else
		curl_proto="--ipv6"
	fi

	curl ${curl_proto} --fail --silent --max-time 3 "${2}://${3}:${4}${5}" --output /dev/null
	code="$?"

	if [ "${code}" -eq 7 ]; then
		echo "Error_InitConnectionFailed"
		return
	elif [ "${code}" -eq 22 ]; then
		echo "Error_NoResponse"
		return
	elif [ "${code}" -eq 27 ]; then
		echo "Error_IncorrectSize"
		return
	else
		echo "${6}"
		return
	fi
}

install_speedtest_modules() {
	if ! lsmod | grep -qe "^arht_timer "; then
		if ! insmod arht_timer; then
			download_error "Error_Internal" "${1}"
			exit 0
		fi
	fi

	if ! lsmod | grep -qe "^speedtest "; then
		if ! insmod speedtest; then
			rmmod arht_timer
			download_error "Error_Internal" "${1}"
			exit 0
		fi
	fi

	if ! lsmod | grep -qe "^lro_wan "; then
		if ! insmod lro_wan; then
			rmmod speedtest
			rmmod arht_timer
			download_error "Error_Internal" "${1}"
			exit 0
		fi
	fi

	if ! lsmod | grep -qe "^lro_lan "; then
		if ! insmod lro_lan; then
			rmmod speedtest
			rmmod arht_timer
			rmmod lro_wan
			download_error "Error_Internal" "${1}"
			exit 0
		fi
	fi
}

remove_speedtest_modules() {
	rmmod speedtest
	rmmod lro_wan
	rmmod lro_lan
	rmmod arht_timer
}

get_dstip_port_path() {
	url="${1}"
	ip_proto="${2}"

	tmp_ip=""
	tmp_port=""
	tmp_path=""
	tmp_proto=""

	dst=$(echo "${url}" | cut -d'/' -f 3)
	if [ -z "${dst}" ]; then
		echo "Error_Other"
		return
	fi

	OCTET_REGEX="(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])"
	PORT_REGEX="([1-9]|[1-9][0-9]|[1-9][0-9]{2}|[1-9][0-9]{3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"
	FULL_REGEX="^${OCTET_REGEX}\\.${OCTET_REGEX}\\.${OCTET_REGEX}\\.${OCTET_REGEX}(:${PORT_REGEX})?$"

	if [[ "${dst}" =~ ${FULL_REGEX} ]]; then
		# ipv4 ip
		if [ "${ip_proto}" = "IPv6" ]; then
			echo "Error_Other"
			return
		fi

		colons="${dst//[^:]}"
		num_colon="${#colons}"
		if [ "${num_colon}" -eq 1 ]; then
			tmp_ip=$(echo "${dst}" | cut -d':' -f 1)
			tmp_port=$(echo "${dst}" | cut -d':' -f 2)
		else
			tmp_ip="${dst}"
			tmp_port="80"
		fi

		tmp_proto="IPv4"
	else
		colons="${dst//[^:]}"
		num_colon="${#colons}"

		if [ "${num_colon}" -gt 1 ]; then
			# ipv6 ip, possible formats:
			# [xx::xx]:22
			# [xx::xx]
			# xx::xx
			if [ "${ip_proto}" = "IPv4" ]; then
				echo "Error_Other"
				return
			fi

			if [ "${dst:0:1}" != "[" ] && [[ "${dst}" =~ .\] ]]; then
				echo "Error_Other"
				return
			elif [ "${dst:0:1}" = "[" ] && [[ "${dst}" != *\]* ]]; then
				echo "Error_Other"
				return
			elif [ "${dst:0:1}" = "[" ] && [[ "${dst}" =~ .\]: ]]; then
				tmp="${dst:1}"
				tmp_ip=$(echo "${tmp}" | cut -d']' -f 1)
				tmp_port=$(echo "${tmp}" | cut -d']' -f 2 | cut -d':' -f 2)
			elif [ "${dst:0:1}" = "[" ] && [[ "${dst}" =~ .\]$ ]]; then
				len="${#dst}"
				len=$(( len - 2 ))
				tmp_ip="${dst:1:$len}"
				tmp_port="80"
			else
				tmp_ip="${dst}"
				tmp_port="80"
			fi

			tmp_proto="IPv6"
		else
			# FQDN
			colons="${dst//[^:]}"
			num_colon="${#colons}"
			fqdn="${dst}"
			resolved=""
			tmp_port="80"

			if [ "${num_colon}" -eq 1 ]; then
				fqdn=$(echo "${dst}" | cut -d':' -f 1)
				tmp_port=$(echo "${dst}" | cut -d':' -f 2)
			fi

			if [ "${ip_proto}" = "IPv4" ]; then
				resolved=$(nslookup -type=a "${fqdn}" | grep Address: | tail -n +2 | head -n 1 | awk '{ print $NF }')
				tmp_proto="IPv4"
			elif [ "${ip_proto}" = "IPv6" ]; then
				resolved=$(nslookup -type=aaaa "${fqdn}" | grep Address: | tail -n +2 | head -n 1 | awk '{ print $NF }')
				tmp_proto="IPv6"
			else
				tmp_proto="IPv4"
				resolved=$(nslookup -type=a "${fqdn}" | grep Address: | tail -n +2 | head -n 1 | awk '{ print $NF }')
				if [ -z "${resolved}" ]; then
					resolved=$(nslookup -type=aaaa "${fqdn}" | grep Address: | tail -n +2 | head -n 1 | awk '{ print $NF }')
					tmp_proto="IPv6"
				fi
			fi

			if [ -z "${resolved}" ]; then
				echo "Error_CannotResolveHostName"
				return
			fi

			tmp_ip="${resolved}"
		fi
	fi

	path=$(echo "${url}" | cut -d'/' -f4-)
	if [ -z "${path}" ]; then
		echo "Error_Other"
		return
	fi

	tmp_path="/${path}"

	echo "${tmp_ip} ${tmp_port} ${tmp_path} ${tmp_proto}"
}

itoa() {
	#returns the dotted-decimal ascii form of an IP arg passed in integer format
	echo -n $(($(($(($((${1}/256))/256))/256))%256)).
	echo -n $(($(($((${1}/256))/256))%256)).
	echo -n $(($((${1}/256))%256)).
	echo $((${1}%256))
}

get_subnet_mask() {
	mask=$(ifstatus "${1}" | jsonfilter -e '@["ipv4-address"][0].mask')
	if [ -z "${mask}" ]; then
		mask=$(ip addr show "${2}" | grep -w inet | awk '{print $2}' | cut -d'/' -f 2)
		if [ -z "${mask}" ]; then
			echo ""
			return
		fi
	fi

	pow=$(( 32 - mask ))
	sub=$(( 2 ** pow - 1 ))
	mask_int=$(( 4294967295 - sub ))

	itoa $mask_int
}

extend_v6address() {
	address="${1}"
	str_tmp=":"
	account=$(echo "${address}" | grep -o ':' | wc -l)

	if [ "${account}" -lt 7 ];then
		for i in $(seq 1 $((8-account)))
		do
			str_tmp=${str_tmp}0:
		done
		address=$(echo "${address}" | sed 's/::/'"${str_tmp}"'/')
	fi

	p1=$(echo "${address}" | awk -F ':' '{print $1}')
	p1=$((0x$p1))

	p2=$(echo "${address}" | awk -F ':' '{print $2}')
	p2=$((0x$p2))

	p3=$(echo "${address}" | awk -F ':' '{print $3}')
	p3=$((0x$p3))

	p4=$(echo "${address}" | awk -F ':' '{print $4}')
	p4=$((0x$p4))

	p5=$(echo "${address}" | awk -F ':' '{print $5}')
	p5=$((0x$p5))

	p6=$(echo "${address}" | awk -F ':' '{print $6}')
	p6=$((0x$p6))

	p7=$(echo "${address}" | awk -F ':' '{print $7}')
	p7=$((0x$p7))

	p8=$(echo "${address}" | awk -F ':' '{print $8}')
	p8=$((0x$p8))

	address=$(printf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" $p1 $p2 $p3 $p4 $p5 $p6 $p7 $p8)
	echo "${address}"
}

get_usec_from_nsec_ceil() {
	nsec="${1}"

	msec_tmp=$(printf "%.2f" $(( 10**2 * nsec / 1000 ))e-2)
	x=${msec_tmp%%[.]*}
	separator_idx=$((${#x}+1))
	msec_frac=${msec_tmp:$separator_idx}
	msec=${msec_tmp:0:$((separator_idx-1))}

	if [ "${msec_frac}" -ne 0 ]; then
		msec=$(( msec + 1 ))
	fi

	printf "%06d" "${msec}"
}

get_usec_from_nsec_floor() {
	nsec="${1}"

	msec_tmp=$(printf "%.2f" $(( 10**2 * nsec / 1000 ))e-2)
	x=${msec_tmp%%[.]*}
	separator_idx=$((${#x}+1))
	msec=${msec_tmp:0:$((separator_idx-1))}

	printf "%06d" "${msec}"
}

publish_result() {
	local count num_con

	json_init
	json_add_string "Status" "Complete"
	json_add_string "IPAddressUsed" "${1}"
	json_add_string "ROMTime" "${2}"
	json_add_string "BOMTime" "${3}"
	json_add_string "EOMTime" "${4}"
	json_add_int "TestBytesReceived" "${5}"
	json_add_int "TotalBytesReceived" "${6}"
	json_add_int "TotalBytesSent" "${7}"
	json_add_int "PeriodOfFullLoading" "${8}"
	json_add_string "TCPOpenRequestTime" "${9}"
	json_add_string "TCPOpenResponseTime" "${10}"

	num_con="${12}"
	if [ "${11}" -eq "1" ] || [ "${11}" = "true" ]; then
		count=0
		json_add_array "DownloadPerConnection"
		if [ "${14}" = "legacy" ]; then
			while [ "${count}" -lt "${num_con}" ]; do
				json_add_object ""
				json_add_string "ROMTime" "${2}"
				json_add_string "BOMTime" "${3}"
				json_add_string "EOMTime" "${4}"
				json_add_int "TestBytesReceived" "${5}"
				json_add_int "TotalBytesReceived" "${6}"
				json_add_int "TotalBytesSent" "${7}"
				json_add_string "TCPOpenRequestTime" "${9}"
				json_add_string "TCPOpenResponseTime" "${10}"
				json_close_object
				count="$(( count + 1 ))"
			done
		else
			while [ "${count}" -lt "${num_con}" ]; do
				# Read result
				tmp=$(grep -e "^${count}_ROMTime:" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 10))
				rom_time="${tmp:n:-1}"
				x=${rom_time%%[:]*}
				separator_idx=$((${#x}+1))
				rom_frac=${rom_time:$separator_idx}
				rom_msec=$(get_usec_from_nsec_floor "${rom_frac}")
				rom_sec=${rom_time:0:$((separator_idx-1))}

				tmp=$(grep -e "^${count}_BOMTime:" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 10))
				bom_time="${tmp:n:-1}"
				x=${bom_time%%[:]*}
				separator_idx=$((${#x}+1))
				bom_frac=${bom_time:$separator_idx}
				bom_msec=$(get_usec_from_nsec_floor "${bom_frac}")
				bom_sec=${bom_time:0:$((separator_idx-1))}

				tmp=$(grep -e "^${count}_EOMTime:" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 10))
				eom_time="${tmp:n:-1}"
				x=${eom_time%%[:]*}
				separator_idx=$((${#x}+1))
				eom_frac=${eom_time:$separator_idx}
				eom_msec=$(get_usec_from_nsec_ceil "${eom_frac}")
				eom_sec=${eom_time:0:$((separator_idx-1))}

				tmp=$(grep -e "^${count}_TCPRequesetTime:" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 18))
				tcpreq_time="${tmp:n:-1}"
				x=${tcpreq_time%%[:]*}
				separator_idx=$((${#x}+1))
				tcpreq_frac=${tcpreq_time:$separator_idx}
				tcpreq_msec=$(get_usec_from_nsec_floor "${tcpreq_frac}")
				tcpreq_sec=${tcpreq_time:0:$((separator_idx-1))}

				tmp=$(grep -e "^${count}_TCPResponseTime:" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 18))
				tcpres_time="${tmp:n:-1}"
				x=${tcpres_time%%[:]*}
				separator_idx=$((${#x}+1))
				tcpres_frac=${tcpres_time:$separator_idx}
				tcpres_msec=$(get_usec_from_nsec_ceil "${tcpres_frac}")
				tcpres_sec=${tcpres_time:0:$((separator_idx-1))}

				tmp=$(grep -e "^${count}_TestByteReceived(Byte):" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 25))
				test_recv="${tmp:n:-1}"
				if [ "${test_recv}" -lt "${15}" ]; then
					test_recv="${15}"
				fi

				tmp=$(grep -e "^${count}_TotalByteReceived(Byte):" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 26))
				total_recv="${tmp:n:-1}"
				if [ "${total_recv}" -lt "${15}" ]; then
					total_recv="${15}"
				fi

				tmp=$(grep -e "^${count}_TotalByteSend(Byte):" "${TEST_RESULT_PATH_PER_CON}")
				n=$((${#count} + 22))
				total_send="${tmp:n:-1}"

				ROMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${rom_msec}Z" -d @"${rom_sec}")
				BOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${bom_msec}Z" -d @"${bom_sec}")
				EOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${eom_msec}Z" -d @"${eom_sec}")
				TCPOpenRequestTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${tcpreq_msec}Z" -d @"${tcpreq_sec}")
				TCPOpenResponseTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${tcpres_msec}Z" -d @"${tcpres_sec}")

				#publish result
				json_add_object ""
				json_add_string "ROMTime" "${ROMTime}"
				json_add_string "BOMTime" "${BOMTime}"
				json_add_string "EOMTime" "${EOMTime}"
				json_add_int "TestBytesReceived" "${test_recv}"
				json_add_int "TotalBytesReceived" "${total_recv}"
				json_add_int "TotalBytesSent" "${total_send}"
				json_add_string "TCPOpenRequestTime" "${TCPOpenRequestTime}"
				json_add_string "TCPOpenResponseTime" "${TCPOpenResponseTime}"
				json_close_object

				count="$(( count + 1 ))"
			done
		fi
		json_close_array
	fi
	json_dump

	# Store data in dmmap_diagnostics for both protocols (cwmp/usp)
	[ "${13}" = "both_proto" ] && {
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.DiagnosticState="Complete"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.IPAddressUsed="${1}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.ROMTime="${2}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.BOMTime="${3}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.EOMTime="${4}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.TestBytesReceived="${5}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.TotalBytesReceived="${6}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.TotalBytesSent="${7}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.PeriodOfFullLoading="${8}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.TCPOpenRequestTime="${9}"
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.TCPOpenResponseTime="${10}"
		if [ "${11}" = "true" ] || [ "${11}" = "1" ]; then
			count=0
			if [ "${14}" = "legacy" ]; then
				while [ "${count}" -lt "${num_con}" ]; do
					$UCI_ADD_BBF_DMMAP dmmap_diagnostics DownloadPerConnection
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].ROMTime="${2}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].BOMTime="${3}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].EOMTime="${4}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TestBytesReceived="${5}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TotalBytesReceived="${6}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TotalBytesSent="${7}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TCPOpenRequestTime="${9}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TCPOpenResponseTime="${10}"
					count="$(( count + 1 ))"
				done
			else
				while [ "${count}" -lt "${num_con}" ]; do
					# Read result
					tmp=$(grep "${count}_ROMTime:" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 10))
					rom_time="${tmp:n:-1}"
					x=${rom_time%%[:]*}
					separator_idx=$((${#x}+1))
					rom_frac=${rom_time:$separator_idx}
					rom_msec=$(get_usec_from_nsec_floor "${rom_frac}")
					rom_sec=${rom_time:0:$((separator_idx-1))}

					tmp=$(grep -e "^${count}_BOMTime:" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 10))
					bom_time="${tmp:n:-1}"
					x=${bom_time%%[:]*}
					separator_idx=$((${#x}+1))
					bom_frac=${bom_time:$separator_idx}
					bom_msec=$(get_usec_from_nsec_floor "${bom_frac}")
					bom_sec=${bom_time:0:$((separator_idx-1))}

					tmp=$(grep -e "^${count}_EOMTime:" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 10))
					eom_time="${tmp:n:-1}"
					x=${eom_time%%[:]*}
					separator_idx=$((${#x}+1))
					eom_frac=${eom_time:$separator_idx}
					eom_msec=$(get_usec_from_nsec_ceil "${eom_frac}")
					eom_sec=${eom_time:0:$((separator_idx-1))}

					tmp=$(grep -e "^${count}_TCPRequesetTime:" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 18))
					tcpreq_time="${tmp:n:-1}"
					x=${tcpreq_time%%[:]*}
					separator_idx=$((${#x}+1))
					tcpreq_frac=${tcpreq_time:$separator_idx}
					tcpreq_msec=$(get_usec_from_nsec_floor "${tcpreq_frac}")
					tcpreq_sec=${tcpreq_time:0:$((separator_idx-1))}

					tmp=$(grep -e "^${count}_TCPResponseTime:" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 18))
					tcpres_time="${tmp:n:-1}"
					x=${tcpres_time%%[:]*}
					separator_idx=$((${#x}+1))
					tcpres_frac=${tcpres_time:$separator_idx}
					tcpres_msec=$(get_usec_from_nsec_ceil "${tcpres_frac}")
					tcpres_sec=${tcpres_time:0:$((separator_idx-1))}

					tmp=$(grep -e "^${count}_TestByteReceived(Byte):" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 25))
					test_recv="${tmp:n:-1}"
					if [ "${test_recv}" -lt "${15}" ]; then
						test_recv="${15}"
					fi

					tmp=$(grep -e "^${count}_TotalByteReceived(Byte):" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 26))
					total_recv="${tmp:n:-1}"
					if [ "${total_recv}" -lt "${15}" ]; then
						total_recv="${15}"
					fi

					tmp=$(grep -e "^${count}_TotalByteSend(Byte):" "${TEST_RESULT_PATH_PER_CON}")
					n=$((${#count} + 22))
					total_send="${tmp:n:-1}"

					ROMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${rom_msec}Z" -d @"${rom_sec}")
					BOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${bom_msec}Z" -d @"${bom_sec}")
					EOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${eom_msec}Z" -d @"${eom_sec}")
					TCPOpenRequestTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${tcpreq_msec}Z" -d @"${tcpreq_sec}")
					TCPOpenResponseTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${tcpres_msec}Z" -d @"${tcpres_sec}")

					#publish result
					$UCI_ADD_BBF_DMMAP dmmap_diagnostics DownloadPerConnection
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].ROMTime="${ROMTime}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].BOMTime="${BOMTime}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].EOMTime="${EOMTime}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TestBytesReceived="${test_recv}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TotalBytesReceived="${total_recv}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TotalBytesSent="${total_send}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TCPOpenRequestTime="${TCPOpenRequestTime}"
					$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[${count}].TCPOpenResponseTime="${TCPOpenResponseTime}"

					count="$(( count + 1 ))"
				done
			fi
		fi
	}

	$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.Status="complete"
	$UCI_COMMIT_BBF_DMMAP
}

execute_http_download_test() {
	url="${1}"
	iface="${2}"
	dscp="${3}"
	eth_prio="${4}"
	ip_proto="${5}"
	num_of_con="${6}"
	enable_per_con="${7}"
	proto="${8}"

	if [ -z "${iface}" ]; then
		iface="wan"
	fi

	device=$(ifstatus "${iface}" | jsonfilter -e @.l3_device)
	if [ -z "${device}" ]; then
		download_error "Error_Other" "${proto}"
		return
	fi

	wan_proto=$(ifstatus "${iface}" | jsonfilter -e @.proto)
	if [ -z "${wan_proto}" ]; then
		download_error "Error_Other" "${proto}"
		return
	fi

	ipalloc_type="ip"
	if [ "${wan_proto}" = "pppoe" ]; then
		ipalloc_type="ppp"
	fi

	get_res=$(get_dstip_port_path "${url}" "${ip_proto}")
	if [ "${get_res:0:6}" = "Error_" ]; then
		download_error "${get_res}" "${proto}"
		return
	fi

	dst_ip=$(echo "${get_res}" | awk '{print $1}')
	dst_port=$(echo "${get_res}" | awk '{print $2}')
	file_path=$(echo "${get_res}" | awk '{print $3}')
	dst_proto=$(echo "${get_res}" | awk '{print $4}')

	if [ -z "${dst_ip}" ] || [ -z "${dst_port}" ] || [ -z "${file_path}" ] || [ -z "${dst_proto}" ]; then
		download_error "Error_Other" "${proto}"
		return
	fi

	dut_ip=$(get_ip_addr_used "${url}" "${dst_proto}" "${iface}")

	netmask=""
	nexthop=""
	if [ "${dst_proto}" = "IPv4" ]; then
		netmask=$(get_subnet_mask "${iface}" "${device}")
		nexthop=$(ifstatus "${iface}" | jsonfilter -e @.route[0].nexthop)
		if [ -z "${netmask}" ] || [ -z "${nexthop}" ]; then
			download_error "Error_Other" "${proto}"
			return
		fi
	elif [ "${dst_proto}" = "IPv6" ]; then
		dst_ip=$(extend_v6address "${dst_ip}")
	else
		download_error "Error_Other" "${proto}"
		return
	fi

	if [ ! -f /proc/tc3162/speed_test ]; then
		download_error "Error_Other" "${proto}"
		return
	fi

	# Airoha fast path does not work with 1 connection, workaround to use 3 streams
	if [ "${num_of_con}" -lt "3" ]; then
		conns=$(( num_of_con + 2 ))
	else
		conns=$(( num_of_con + 1 )) # +1 since speed_test opens 1 less connection than what is configured
	fi

	# now start the speedtest tool
	echo "fin" > /proc/tc3162/speed_test
	echo "reset" > /proc/tc3162/speed_test
	sleep 1
	echo "${dst_proto}" > /proc/tc3162/speed_test
	echo "${ipalloc_type}" > /proc/tc3162/speed_test
	echo "wandev=${device}" > /proc/tc3162/speed_test
	echo "offset=1" > /proc/tc3162/speed_test
	echo "duration=${HTTP_DOWNLOAD_TIMEOUT}" > /proc/tc3162/speed_test
	echo "duration_end=1" > /proc/tc3162/speed_test
	echo "lro=1" > /proc/tc3162/speed_test
	echo "conns=${conns}" > /proc/tc3162/speed_test
	echo "clear_data=1" > /proc/tc3162/speed_test
	if [ -n "${dscp}" ]; then
		echo "dscp=${dscp}" > /proc/tc3162/speed_test
	fi

	if [ -n "${eth_prio}" ]; then
		echo "ethpri=${eth_prio}" > /proc/tc3162/speed_test
	fi

	echo "destip=${dst_ip}" > /proc/tc3162/speed_test
	echo "destport=${dst_port}" > /proc/tc3162/speed_test
	if [ "${dst_proto}" = "IPv4" ]; then
		echo "mask=${netmask}" > /proc/tc3162/speed_test
		echo "gateway=${nexthop}" > /proc/tc3162/speed_test
		echo "host=${dst_ip}" > /proc/tc3162/speed_test
	else
		ping -6 -I "${device}" -c 2 "${dst_ip}" >/dev/null
		echo "host=[${dst_ip}]" > /proc/tc3162/speed_test
	fi
	echo "action=GET ${file_path} ${dst_ip} ${dst_port}" > /proc/tc3162/speed_test

	timeout=${HTTP_DOWNLOAD_TIMEOUT}
	state=""
	eom="0:0"
	while [ "${timeout}" -gt 0 ]; do
		sleep 5

		state=$(grep "download state" /proc/tc3162/speedtest_state| awk '{ print $NF }')

		tmp=$(grep "EOMTime:" /proc/tc3162/speedtest_result)
		if [ -n "${tmp}" ]; then
			eom="${tmp:9:-1}"
		fi

		if [ "${state}" = "idle" ] || [ "${eom}" != "0:0" ]; then
			break
		fi

		timeout=$(( timeout - 5 ))
	done

	if [ "${timeout}" -eq 0 ]; then
		err=$(get_error_reason "${dst_proto}" "http" "${dst_ip}" "${dst_port}" "${file_path}" "Error_Timeout")
		download_error "${err}" "${proto}"
		return
	fi

	err=$(grep "err" /proc/tc3162/speedtest_result| cut -d: -f 2)
	if [ "${err}" -ne 0 ]; then
		err=$(get_error_reason "${dst_proto}" "http" "${dst_ip}" "${dst_port}" "${file_path}" "Error_Other")
		download_error "${err}" "${proto}"
		return
	fi

	if [ ! -f "${TEST_RESULT_PATH}" ]; then
		download_error "Error_Other" "${proto}"
		return
	fi

	# Read result
	tmp=$(grep "ROMTime:" "${TEST_RESULT_PATH}")
	rom_time="${tmp:9:-1}"
	x=${rom_time%%[:]*}
	separator_idx=$((${#x}+1))
	rom_frac=${rom_time:$separator_idx}
	rom_msec=$(get_usec_from_nsec_floor "${rom_frac}")
	rom_sec=${rom_time:0:$((separator_idx-1))}

	tmp=$(grep "BOMTime:" "${TEST_RESULT_PATH}")
	bom_time="${tmp:9:-1}"
	x=${bom_time%%[:]*}
	separator_idx=$((${#x}+1))
	bom_frac=${bom_time:$separator_idx}
	bom_msec=$(get_usec_from_nsec_floor "${bom_frac}")
	bom_sec=${bom_time:0:$((separator_idx-1))}

	tmp=$(grep "EOMTime:" "${TEST_RESULT_PATH}")
	eom_time="${tmp:9:-1}"
	x=${eom_time%%[:]*}
	separator_idx=$((${#x}+1))
	eom_frac=${eom_time:$separator_idx}
	eom_msec=$(get_usec_from_nsec_ceil "${eom_frac}")
	eom_sec=${eom_time:0:$((separator_idx-1))}

	tmp=$(grep "TCPRequesetTime:" "${TEST_RESULT_PATH}")
	tcpreq_time="${tmp:17:-1}"
	x=${tcpreq_time%%[:]*}
	separator_idx=$((${#x}+1))
	tcpreq_frac=${tcpreq_time:$separator_idx}
	tcpreq_msec=$(get_usec_from_nsec_floor "${tcpreq_frac}")
	tcpreq_sec=${tcpreq_time:0:$((separator_idx-1))}

	tmp=$(grep "TCPResponseTime:" "${TEST_RESULT_PATH}")
	tcpres_time="${tmp:17:-1}"
	x=${tcpres_time%%[:]*}
	separator_idx=$((${#x}+1))
	tcpres_frac=${tcpres_time:$separator_idx}
	tcpres_msec=$(get_usec_from_nsec_ceil "${tcpres_frac}")
	tcpres_sec=${tcpres_time:0:$((separator_idx-1))}

	tmp=$(grep "TestByteReceived(Byte):" "${TEST_RESULT_PATH}")
	test_recv="${tmp:24:-1}"

	tmp=$(grep "TotalByteReceived(Byte):" "${TEST_RESULT_PATH}")
	total_recv="${tmp:25:-1}"

	tmp=$(grep "TotalByteSend(Byte):" "${TEST_RESULT_PATH}")
	total_send="${tmp:21:-1}"

	tmp=$(grep "downloadRealLen file size(Byte):" "${TEST_RESULT_PATH}")
	file_len="${tmp:33:-1}"

	exp_recv=$(( file_len * num_of_con ))
	if [ $(( total_recv - exp_recv )) -lt 0 ]; then
		total_recv="${exp_recv}"
	fi

	if [ $(( test_recv - exp_recv )) -lt 0 ]; then
		test_recv="${exp_recv}"
	fi

	ROMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${rom_msec}Z" -d @"${rom_sec}")
	BOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${bom_msec}Z" -d @"${bom_sec}")
	EOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${eom_msec}Z" -d @"${eom_sec}")
	TCPOpenRequestTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${tcpreq_msec}Z" -d @"${tcpreq_sec}")
	TCPOpenResponseTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${tcpres_msec}Z" -d @"${tcpres_sec}")

	period_time=$(echo "${eom_sec}" "${bom_sec}" "${eom_msec}" "${bom_msec}" | awk '{printf ($1 - $2) * 1000000 + ($3 - $4)}')

	publish_result "${dut_ip}" "${ROMTime}" "${BOMTime}" "${EOMTime}" "${test_recv}" "${total_recv}" \
		"${total_send}" "${period_time}" "${TCPOpenRequestTime}" "${TCPOpenResponseTime}" "${enable_per_con}" "${num_of_con}" "${proto}" "speed_test" "${file_len}"
}

execute_legacy_download_test() {
	url="${1}"
	iface="${2}"
	dscp="${3}"
	eth_prio="${4}"
	ip_proto="${5}"
	num_of_con="${6}"
	enable_per_con="${7}"
	proto="${8}"

	# Fail if url is empty
	[ -z "${url}" ] && {
		download_error "Error_InitConnectionFailed" "${proto}"
		return
	}

	[ "${url:0:7}" != "http://" ] && [ "${url:0:6}" != "ftp://" ] && {
		download_error "Error_Other" "${proto}"
		return
	}

	if [ -n "${iface}" ]; then
		device=$(ifstatus "${iface}" | jsonfilter -e '@.l3_device')

		# If no device was found, return error
		[ -z "${device}" ] && {
			download_error "Error_NoRouteToHost" "${proto}"
			return
		}
	else
		device=$(route -n | grep 'UG[ \t]' | awk '{print $8}')
	fi

	dut_ip=$(get_ip_addr_used "${url}" "${ip_proto}" "${iface}")

	# Assign default value
	if [ "$ip_proto" = "IPv4" ]; then ip_proto="--ipv4"; elif [ "$ip_proto" = "IPv6" ]; then ip_proto="--ipv6"; else ip_proto=""; fi

	format='{ "size_download": "%{size_download}",
			  "size_header": "%{size_header}",
			  "time_appconnect": "%{time_appconnect}",
			  "time_connect": "%{time_connect}",
			  "time_pretransfer": "%{time_pretransfer}",
			  "time_starttransfer": "%{time_starttransfer}",
			  "time_total": "%{time_total}",
			  "exitcode": "%{exitcode}" }'

	tx_bytes_start=$(ubus call network.device status "{'name':'$device'}" | jsonfilter -e @.statistics.tx_bytes)
	rx_bytes_start=$(ubus call network.device status "{'name':'$device'}" | jsonfilter -e @.statistics.rx_bytes)

	time_start=$(date +"%s.282646") # It should be like that time_start=$(date +"%s.%6N") but since OpenWrt busybox has limitations and doesn't support nonoseconds so keep it hardcoded
	if [ -z "${ip_proto}" ]; then
		res=$(curl --fail --silent --max-time "${FTP_DOWNLOAD_TIMEOUT}" -w "${format}" "${url}" --output /dev/null)
	else
		res=$(curl "${ip_proto}" --fail --silent --max-time "${FTP_DOWNLOAD_TIMEOUT}" -w "${format}" "${url}" --output /dev/null)
	fi
	time_end=$(date +"%s.282646") # It should be like that time_end=$(date +"%s.%6N") but since OpenWrt busybox has limitations and doesn't support nonoseconds so keep it hardcoded

	tx_bytes_end=$(ubus call network.device status "{'name':'$device'}" | jsonfilter -e @.statistics.tx_bytes)
	rx_bytes_end=$(ubus call network.device status "{'name':'$device'}" | jsonfilter -e @.statistics.rx_bytes)
	
	logger -t "tr143_download" "########### ${url} ==> ${res} ###########"
	json_load "${res}"
	json_get_var size_download size_download
	json_get_var size_header size_header
	json_get_var time_appconnect time_appconnect
	json_get_var time_connect time_connect
	json_get_var time_pretransfer time_pretransfer
	json_get_var time_starttransfer time_starttransfer
	json_get_var time_total time_total
	json_get_var exitcode exitcode

	[ -z "${exitcode}" ] && exitcode=-1
	[ "$exitcode" = "6" ] && {
		download_error "Error_CannotResolveHostName" "${proto}"
		return
	}

	[ "$exitcode" = "7" ] && {
		download_error "Error_InitConnectionFailed" "${proto}"
		return
	}

	[ "$exitcode" = "22" ] && {
		download_error "Error_NoResponse" "${proto}"
		return
	}

	[ "$exitcode" = "27" ] && {
		download_error "Error_IncorrectSize" "${proto}"
		return
	}

	[ "$exitcode" = "28" ] && {
		download_error "Error_Timeout" "${proto}"
		return
	}

	[ "$exitcode" != "0" ] && {
		download_error "Error_Other" "${proto}"
		return
	}
	
	[ -z "${time_appconnect}" ] && time_appconnect=0
	tcp_open_request_time=$(echo "${time_start}" "${time_appconnect}" | awk '{printf "%.6f", $1 + $2}')
	[ -z "${time_connect}" ] && time_connect=0
	tcp_open_response_time=$(echo "${time_start}" "${time_connect}" | awk '{printf "%.6f", $1 + $2}')
	[ -z "${time_pretransfer}" ] && time_pretransfer=0
	rom_time=$(echo "${time_start}" "${time_pretransfer}" | awk '{printf "%.6f", $1 + $2}')
	[ -z "${time_starttransfer}" ] && time_starttransfer=0
	bom_time=$(echo "${time_start}" "${time_starttransfer}" | awk '{printf "%.6f", $1 + $2}')
	[ -z "${time_total}" ] && time_total=0
	eom_time=$(echo "${time_start}" "${time_total}" | awk '{printf "%.6f", $1 + $2}')

	x=${tcp_open_request_time%%[.]*}
	separator_idx=$((${#x}+1))
	TCPOpenRequestTime_MicroSec=${tcp_open_request_time:$separator_idx}
	TCPOpenRequestTime_Sec=${tcp_open_request_time:0:$((separator_idx-1))}

	x=${tcp_open_response_time%%[.]*}
	separator_idx=$((${#x}+1))
	TCPOpenResponseTime_MicroSec=${tcp_open_response_time:$separator_idx}
	TCPOpenResponseTime_Sec=${tcp_open_response_time:0:$((separator_idx-1))}

	x=${rom_time%%[.]*}
	separator_idx=$((${#x}+1))
	ROMTime_MicroSec=${rom_time:$separator_idx}
	ROMTime_Sec=${rom_time:0:$((separator_idx-1))}

	x=${bom_time%%[.]*}
	separator_idx=$((${#x}+1))
	BOMTime_MicroSec=${bom_time:$separator_idx}
	BOMTime_Sec=${bom_time:0:$((separator_idx-1))}

	x=${eom_time%%[.]*}
	separator_idx=$((${#x}+1))
	EOMTime_MicroSec=${eom_time:$separator_idx}
	EOMTime_Sec=${eom_time:0:$((separator_idx-1))}

	TCPOpenRequestTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${TCPOpenRequestTime_MicroSec}Z" -d @"${TCPOpenRequestTime_Sec}")
	TCPOpenResponseTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${TCPOpenResponseTime_MicroSec}Z" -d @"${TCPOpenResponseTime_Sec}")
	ROMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${ROMTime_MicroSec}Z" -d @"${ROMTime_Sec}")
	BOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${BOMTime_MicroSec}Z" -d @"${BOMTime_Sec}")
	EOMTime=$(date -u +"%Y-%m-%dT%H:%M:%S.${EOMTime_MicroSec}Z" -d @"${EOMTime_Sec}")

	total_send=$((tx_bytes_end-tx_bytes_start))
	total_recv=$((rx_bytes_end-rx_bytes_start))

	[ -z "${size_header}" ] && size_header=0
	[ -z "${size_download}" ] && size_download=0
	test_recv=$((size_download+size_header))
	period_time=$(echo "${time_end}" "${time_start}" | awk '{printf ($1 - $2) * 1000000}')

	publish_result "${dut_ip}" "${ROMTime}" "${BOMTime}" "${EOMTime}" "${test_recv}" "${total_recv}" \
		"${total_send}" "${period_time}" "${TCPOpenRequestTime}" "${TCPOpenResponseTime}" "${enable_per_con}" "${num_of_con}" "${proto}" "legacy" "0"
}

download_launch() {
	input="$1"

	json_load "${input}"
	
	json_get_var url url
	json_get_var iface iface
	json_get_var dscp dscp
	json_get_var eth_prio eth_prio
	json_get_var ip_proto ip_proto
	json_get_var num_of_con num_of_con
	json_get_var enable_per_con enable_per_con
	json_get_var proto proto

	[ -z "${num_of_con}" ] && num_of_con=1
	# Check if a download process is already running
	download_s=$(uci_get_bbf_dmmap dmmap_diagnostics.download)
	if [ -z "${download_s}" ]; then
		[ ! -f /etc/bbfdm/dmmap/dmmap_diagnostics ] && touch /etc/bbfdm/dmmap/dmmap_diagnostics
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download='download'
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.Status="running"
	else
		Status=$(uci_get_bbf_dmmap dmmap_diagnostics.download.Status)
		if [ "${Status}" = "running" ]; then
			return
		fi

		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.Status="running"
	fi
	# Cleanup per-connection results
	sections="$(${UCI_SHOW_BBF_DMMAP} dmmap_diagnostics |awk -F'=' '/=DownloadPerConnection$/ {print $1}' |sort -r)"
	for sec in ${sections}; do
		[ -z "${sec}" ] && continue
		$UCI_DELETE_BBF_DMMAP "${sec}"
	done

	$UCI_COMMIT_BBF_DMMAP

	# Fail if url is empty
	if [ -z "${url}" ]; then
		download_error "Error_InitConnectionFailed" "${proto}"
		return
	fi

	if [ "${url:0:7}" = "http://" ]; then
		install_speedtest_modules "${proto}"
		execute_http_download_test "${url}" "${iface}" "${dscp}" "${eth_prio}" "${ip_proto}" \
			"${num_of_con}" "${enable_per_con}" "${proto}"
		remove_speedtest_modules
	elif [ "${url:0:6}" = "ftp://" ]; then
		execute_legacy_download_test "${url}" "${iface}" "${dscp}" "${eth_prio}" "${ip_proto}" \
			"${num_of_con}" "${enable_per_con}" "${proto}"
	else
		download_error "Error_Other" "${proto}"
	fi

	return
}

if [ -n "$1" ]; then
	download_launch "$1"
else
	download_error "Error_Internal"
fi
