#!/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

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"
			exit 0
		fi
	fi

	if ! lsmod | grep -qe "^speedtest "; then
		if ! insmod speedtest; then
			rmmod arht_timer
			download_error "Error_Internal"
			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"
			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"
			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}"
	proto="${3}"

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

	if [[ $dst =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
		# ipv4 ip
		if [ "${ip_proto}" = "IPv6" ]; then
			download_error "Error_Other" "${proto}"
			return
		fi

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

		eval $7="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
				download_error "Error_Other" "${proto}"
				return
			fi

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

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

			if [ "${num_colon}" -eq 1 ]; then
				fqdn=$(echo $dst | cut -d':' -f 1)
				eval $5=$(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 }')"
				eval $7="IPv4"
			elif [ "${ip_proto}" = "IPv6" ]; then
				resolved="$(nslookup -type=aaaa $fqdn | grep Address: | tail -n +2 | head -n 1 | awk '{ print $NF }')"
				eval $7="IPv6"
			else
				eval $7="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 }')"
					eval $7="IPv6"
				fi
			fi

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

			eval $4="${resolved}"
		fi
	fi

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

	eval $6="/${path}"
}

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 ))
	echo $(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
}

publish_result() {
	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}"
	if [ "${11}" = "true" ] || [ "${11}" = "1" ]; then
		json_add_array "DownloadPerConnection"
		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
	fi
	json_dump

	# Store data in dmmap_diagnostics for both protocols (cwmp/usp)
	[ "${12}" == "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
			$UCI_ADD_BBF_DMMAP dmmap_diagnostics DownloadPerConnection
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].ROMTime="${2}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].BOMTime="${3}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].EOMTime="${4}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].TestBytesReceived="${5}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].TotalBytesReceived="${6}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].TotalBytesSent="${7}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].TCPOpenRequestTime="${9}"
			$UCI_SET_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0].TCPOpenResponseTime="${10}"
		else
			$UCI_DELETE_BBF_DMMAP dmmap_diagnostics.@DownloadPerConnection[0]
		fi
	}

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

reset_test_run() {
	echo "fin" > /proc/tc3162/speed_test
	echo "reset" > /proc/tc3162/speed_test
}

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

	msec_tmp=$(printf "%.2f" $(( 10**2 * nsec / 1000 ))e-2)
	separator_idx=$(expr index "${msec_tmp}" .)
	msec_frac=${msec_tmp:$separator_idx}
	msec=${msec_tmp:0:$((separator_idx-1))}

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

	echo $(printf "%06d" "${msec}")
}

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

	msec_tmp=$(printf "%.2f" $(( 10**2 * nsec / 1000 ))e-2)
	separator_idx=$(expr index "${msec_tmp}" .)
	msec=${msec_tmp:0:$((separator_idx-1))}

	echo $(printf "%06d" "${msec}")
}

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_dstip_port_path "${url}" "${ip_proto}" "${proto}" dst_ip dst_port file_path dst_proto
	if [ -z "${dst_ip}" ] || [ -z "${dst_port}" ] || [ -z "${file_path}" ] || [ -z "${dst_proto}" ]; then
		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

	# 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 "duration_end=0" > /proc/tc3162/speed_test
	echo "lro=1" > /proc/tc3162/speed_test
	echo "conns=5" > /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}:${dst_port}" > /proc/tc3162/speed_test
	else
		echo "host=[${dst_ip}]:${dst_port}" > /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=`cat /proc/tc3162/speedtest_state | grep "download state" | awk '{ print $NF }'`

		tmp=$(cat /proc/tc3162/speedtest_result | grep EOMTime:)
		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')
		reset_test_run
		download_error "${err}" "${proto}"
		return
	fi

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

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

	# Read result
	tmp=$(cat "${TEST_RESULT_PATH}" | grep ROMTime:)
	rom_time="${tmp:9:-1}"
	separator_idx=$(expr index "${rom_time}" :)
	rom_frac=${rom_time:$separator_idx}
	rom_msec=$(get_usec_from_nsec_floor "${rom_frac}")
	rom_sec=${rom_time:0:$((separator_idx-1))}

	tmp=$(cat "${TEST_RESULT_PATH}" | grep BOMTime:)
	bom_time="${tmp:9:-1}"
	separator_idx=$(expr index "${bom_time}" :)
	bom_frac=${bom_time:$separator_idx}
	bom_msec=$(get_usec_from_nsec_floor "${bom_frac}")
	bom_sec=${bom_time:0:$((separator_idx-1))}

	tmp=$(cat "${TEST_RESULT_PATH}" | grep EOMTime:)
	eom_time="${tmp:9:-1}"
	separator_idx=$(expr index "${eom_time}" :)
	eom_frac=${eom_time:$separator_idx}
	eom_msec=$(get_usec_from_nsec_ceil "${eom_frac}")
	eom_sec=${eom_time:0:$((separator_idx-1))}

	tmp=$(cat "${TEST_RESULT_PATH}" | grep TCPRequesetTime:)
	tcpreq_time="${tmp:17:-1}"
	separator_idx=$(expr index "${tcpreq_time}" :)
	tcpreq_frac=${tcpreq_time:$separator_idx}
	tcpreq_msec=$(get_usec_from_nsec_floor "${tcpreq_frac}")
	tcpreq_sec=${tcpreq_time:0:$((separator_idx-1))}

	tmp=$(cat "${TEST_RESULT_PATH}" | grep TCPResponseTime:)
	tcpres_time="${tmp:17:-1}"
	separator_idx=$(expr index "${tcpres_time}" :)
	tcpres_frac=${tcpres_time:$separator_idx}
	tcpres_msec=$(get_usec_from_nsec_ceil "${tcpres_frac}")
	tcpres_sec=${tcpres_time:0:$((separator_idx-1))}

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

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

	tmp=$(cat "${TEST_RESULT_PATH}" | grep TotalByteSend\(Byte\):)
	total_send="${tmp:21:-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}")

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

	reset_test_run
	publish_result ${dut_ip} ${ROMTime} ${BOMTime} ${EOMTime} ${test_recv} ${total_recv} \
		${total_send} ${period_time} ${TCPOpenRequestTime} ${TCPOpenResponseTime} ${enable_per_con} ${proto}
}

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" "${nbr_of_rep}" "${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
	res=$(curl ${ip_proto} --fail --silent --max-time ${FTP_DOWNLOAD_TIMEOUT} -w "${format}" "${url}" --output /dev/null)
	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 "bbf_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

	[ "$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
	}
	
	tcp_open_request_time=$(echo "${time_start}" "${time_appconnect}" | awk '{printf "%.6f", $1 + $2}')
	tcp_open_response_time=$(echo "${time_start}" "${time_connect}" | awk '{printf "%.6f", $1 + $2}')
	rom_time=$(echo "${time_start}" "${time_pretransfer}" | awk '{printf "%.6f", $1 + $2}')
	bom_time=$(echo "${time_start}" "${time_starttransfer}" | awk '{printf "%.6f", $1 + $2}')
	eom_time=$(echo "${time_start}" "${time_total}" | awk '{printf "%.6f", $1 + $2}')

	separator_idx=$(expr index "${tcp_open_request_time}" .)
	TCPOpenRequestTime_MicroSec=${tcp_open_request_time:$separator_idx}
	TCPOpenRequestTime_Sec=${tcp_open_request_time:0:$((separator_idx-1))}

	separator_idx=$(expr index "${tcp_open_response_time}" .)
	TCPOpenResponseTime_MicroSec=${tcp_open_response_time:$separator_idx}
	TCPOpenResponseTime_Sec=${tcp_open_response_time:0:$((separator_idx-1))}

	separator_idx=$(expr index "${rom_time}" .)
	ROMTime_MicroSec=${rom_time:$separator_idx}
	ROMTime_Sec=${rom_time:0:$((separator_idx-1))}

	separator_idx=$(expr index "${bom_time}" .)
	BOMTime_MicroSec=${bom_time:$separator_idx}
	BOMTime_Sec=${bom_time:0:$((separator_idx-1))}

	separator_idx=$(expr index "${eom_time}" .)
	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))
	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} ${proto}
}

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

	# 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_ADD_BBF_DMMAP dmmap_diagnostics download
		$UCI_RENAME_BBF_DMMAP dmmap_diagnostics.@download[0]='download'
		$UCI_SET_BBF_DMMAP dmmap_diagnostics.download.Status="running"
		$UCI_COMMIT_BBF_DMMAP
	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"
		$UCI_COMMIT_BBF_DMMAP
	fi

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

	if [ "${url:0:7}" == "http://" ]; then
		# TODO remove when IPv6 issue would be fixed in speed_test module
		get_dstip_port_path "${url}" "${ip_proto}" "${proto}" test_ip test_port test_path test_proto
		if [ -z "${test_ip}" ] || [ -z "${test_port}" ] || [ -z "${test_path}" ] || [ -z "${test_proto}" ]; then
			return
		fi

		if [ "${test_proto}" == "IPv6" ]; then
			execute_legacy_download_test "${url}" "${iface}" "${dscp}" "${eth_prio}" "${ip_proto}" \
				"${num_of_con}" "${enable_per_con}" "${proto}"
		else
			install_speedtest_modules
			execute_http_download_test "${url}" "${iface}" "${dscp}" "${eth_prio}" "${ip_proto}" \
				"${num_of_con}" "${enable_per_con}" "${proto}"
			remove_speedtest_modules
		fi
	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
