#!/bin/sh

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

# Status codes:
# Need to skip 1-2, 126-165, and 255 because it's reserved status codes
UPDATE_SUCCESS=0
UPDATE_ERROR=3
INSTANCE_RUNNING=4
EXPORT_END=5
MODEM_NOT_FOUND=6
BAD_ARGUMENTS=7

#Error codes for WebUI localization purposes
EC_NO_MANUF="166"
EC_NOT_QUECTEL="167"
EC_NOT_READY="168"
EC_CONN_ERROR="169"
EC_NO_UPDATE="170"
EC_NOT_ENOUGH_MEM="171"
EC_EDL_FAILED="172"
EC_DL_FAILED="173"
EC_UPD_VERIFY_FAILED="174"
EC_UPD_START_FAILED="175"
EC_UPGRADE_START_ERROR="176"
EC_UPGRADE_VIA_FILE_NOT_SUPPORT="177"
EC_NO_MOBILE_CONN="178"

#prepare ENV
modem_ids=""
forced="false"
update="false"
MODE=""
UPDATE_MODEM=""
firmware_size=0
export_info=0
less_status=0
export_status=0
a_modem_id="" # Modem ID from arguments
status="$UPDATE_SUCCESS"
json_file_updates="/tmp/dfota_updates.json"
json_file_status="/tmp/dfota_status.json"
ca_path=$(uci -q get dfota.config.rootca)
[ -n "$ca_path" ] && ca_path="--cacert $ca_path"
LAST_ERROR=""
LAST_ERROR_CODE=""
UPDATE_FAILED="0"
SKIP_REBOOT=0
# Ignores failed state on RX52X modems and attempts to update anyway
IGNORE_FAILED_STATE=0

TOTAL_LIST_RETRIES=10

legacy_AT_commands=1

old_fw=""

get_from_info() {
	ubus call "$1" info | grep "$2" | cut -d'"' -f 4
}

get_firmware() {
	local modem="$1"
	ubus call "$modem" get_firmware | grep "firmware" | cut -d'"' -f 4
}

get_value_from_json() {
	json_init
	json_set_namespace parse_msg old_cb
	json_load "$1"
	json_get_var ret "$2"
	echo "$ret"
	json_set_namespace "$old_cb"
}

#~ #######################FUNCTIONS##############################
print_help() {
	cat <<EOF
	Tool to update modem firmware using Quectel DFOTA
	By default script check if update exist
	Usage: $0 [option] [<modem_id>]
	Options:
	-h	Print help message
	-u	Check if there is an update available and start the update
	-l	<url>	Update modem by link
	-f	<path>	Update modem by file
	-m	<id>	Modem id to update. (gsm.modem0, gsm.modem1...)
	-e	Export JSON to $json_file_updates about available updates
	-s	Export JSON to $json_file_status about update status
	-w	Wait for default route and modem to appear
EOF
}

# ****************parse options****************
while [ -n "$1" ]; do
	case "$1" in
		-w) export WAIT_FOR_WAN=1;;
		-d) debug=1;;
		-u) update="true";;
		-l)
			shift
			MODE="link"
			MODE_PATH="$1"
			update="true"
		;;
		-f)
			shift
			MODE="file"
			MODE_PATH="$1"
			update="true"
		;;
		-m)
			shift
			UPDATE_MODEM="$1"
		;;
		-e) export_info=1;;
		-k) less_status=1;;
		-s)
			export_status=1
		;;
		-i) IGNORE_FAILED_STATE=1;;
		-*)
			print_help
			exit 1
		;;
		*) break;;
	esac
	shift;
done

a_modem_id="$1"

debug() {
	[ "$debug" = "1" ] || return 0
	echo "$@"
}

print_output() {
	local message="$1"
	echo "$message"
}

print_error() {
	LAST_ERROR="$1"
	LAST_ERROR_CODE="$2"
	[ "$2" = "$EC_NO_UPDATE" ] && echo "$LAST_ERROR" || echo "$LAST_ERROR ErrorCode: $2"
	UPDATE_FAILED="1"
}

strstr() {
	[ "${1#*"$2"*}" = "$1" ] && return 1
	return 0
}

control_services() {
	local command="$1"
	case "$command" in
	"stop")
		debug "Lock modem from being used"
		lock /var/lock/modem_dfota.lock
		chmod 760 /var/lock/modem_dfota.lock
		;;
	"start")
		debug "Unlock modem for usage"
		lock -u /var/lock/modem_dfota.lock
		;;
	*)
		debug "Invalid command: $command"
		return 0
		;;
	esac
	"/etc/init.d/modem_trackd" enabled && "/etc/init.d/modem_trackd" "$command"
}

remove_reboot_prevention() {
	local modem_id=$(echo "$a_modem_id" | cut -d'.' -f3)

	# remove all files in /tmp/mobile that have $modem_id in name
	rm /tmp/mobile/*"$modem_id"* || not=" not"
	echo "reboot loop prevention files${not} removed"
	[ -z "$not" ]
}

end() {
	local option="$1"
	local message="$2"
	local notify_params="$3"
	case "$option" in
		"$UPDATE_SUCCESS" | "$UPDATE_ERROR")
			[ "$option" -eq "$UPDATE_SUCCESS" ] && remove_reboot_prevention

			[ "$less_status" -eq 1 ] || {
				[ "$option" = "$UPDATE_SUCCESS" ] && notify_webui "stop" || notify_webui "error" "$notify_params"
			}
			control_services "start"
			;;
		"$INSTANCE_RUNNING" | "$MODEM_NOT_FOUND" | "$EXPORT_END" | "$BAD_ARGUMENTS")
			;;
	esac
	[ -n "$message" ] && print_output "$message"

	exit "$option"
}

wait_for_wan() {
	local wan_iface
	print_output "Searching for WAN..."

	while true; do
		network_flush_cache
		network_find_wan wan_iface
		debug "WAN iface: $wan_iface"
		[ -n "$wan_iface" ] && {
			print_output "WAN found on interface: $wan_iface"
			break
		}
		sleep 10
	done
	return 0
}

notify_webui() {
	local action="$1"
	local data="$2"
	case "$action" in
		upgrade)
			ubus send vuci.notify "{\"event\": \"dfota_upgrade\", \"data\": ${data:-\"\"}}"
			rm -rf /tmp/dfota_update
			;;
		start)
			ubus send vuci.notify "{\"event\": \"dfota_update\", \"data\": ${data:-\"\"}}"
			touch /tmp/dfota_update
			;;
		stop)
			ubus send vuci.notify "{\"event\": \"dfota_finish\", \"data\": ${data:-\"\"}}"
			rm -rf /tmp/dfota_update
			;;
		error)
			ubus send vuci.notify "{\"event\": \"dfota_error\", \"data\": ${data:-\"\"}}"
			rm -rf /tmp/dfota_update
			;;
	esac
}

add_json_object() {
	local modem_id="$1"

	json_add_object
	json_add_string id "$modem_id"
	modem_usb_id=$(get_from_info "$modem_id" "usb_id")
	json_add_string usb_id "$modem_usb_id"
	if check_manufacturer "$modem_id"; then
		json_add_boolean manufacturer 1
	else
		json_add_boolean manufacturer 0
		json_close_object
		return;
	fi
	if check_update "$modem_id"; then
		json_add_boolean update_exists 1
		[ "$forced" = "true" ] && json_add_boolean forced 1 || json_add_boolean forced 0
	else
		json_add_boolean update_exists 0
		json_close_object
		return;
	fi
	json_close_object
}

print_json_object() {
	local json="$1"
	echo "$json" > "$2"

	# In case dfota ran as root, refresh the perms for WebUI status access
	[[ "$2" = "$json_file_updates" || "$2" = "$json_file_status" ]] && perm "$2" >/dev/null
}

insert_json_on_failure() {
	json_init
	json_add_boolean response 0
	print_json_object "$(json_dump -i)" "$json_file_updates"
}

########################MODEM#######################################
search_for_modems() {
	modem_ids=$(ubus list gsm.modem* | tr "\n" " ")
	if [ "$modem_ids" = "" ]; then
		json_update_dfota_status "failed" 0
		end "$MODEM_NOT_FOUND" "Modem doesn't exist!"
	fi
	debug "Found modem ids: $modem_ids"
}

check_if_modem_exists() {
	local modem_id
	for modem_id in $modem_ids; do
		[ "$modem_id" = "$a_modem_id" ] && return 0
	done
	return 1
}

export_modem_info() {
	local modem_id

	json_init
	json_add_boolean response 1
	json_add_array modems
	if [ -z "$a_modem_id" ]; then
		for modem_id in $modem_ids; do
			add_json_object "$modem_id"
		done
	else
		add_json_object "$a_modem_id"
	fi
	json_close_array
}

check_manufacturer() {
	#Check if modem is from Quectel
	local modem_id="$1"
	local counter=0

	debug "Checking manufacturer of the $modem_id modem"

	manufacturer=$(get_from_info "$modem_id" "manuf")
	model=$(get_from_info "$modem_id" "model")
	debug "Modem $modem_id manufacturer: $manufacturer"
	while [ "$counter" -lt 5 ]; do
		[ "$manufacturer" = "Quectel" ] && return 0

		[ "$manufacturer" = "Telit" ] && [ "$model" = "LE910C4-WWX" ] && return 0

		[ "$manufacturer" != "" ] && {
			print_error "DFOTA is unavailable for this modem!" "$EC_NOT_QUECTEL"
			return 1
		}

		counter=$((counter+1))
		sleep 1
		manufacturer=$(get_from_info "$modem_id" "manuf")
	done;

	print_error "Unable to get manufacturer of modem $modem_id" "$EC_NO_MANUF"
	return 1
}

check_modem_status() {
	#Check if modem is available and respond to requests
	#If wait for WAN is set, wait for modem too
	local modem_id="$1"
	local ret
	ret=$(get_firmware "$modem_id")
	old_fw="$ret"
	if [ "$WAIT_FOR_WAN" = 1 ]; then
		for i in $(seq 18); do
			[ "$ret" != "" ] && [[ ! "$ret" =~ "Command failed: Not found" ]] && return 0
			sleep 10
		done
		print_error "modem $modem_id not ready." "$EC_NOT_READY"
		return 1
	else
		if [ "$ret" = "" ] || [[ "$ret" =~ "Command failed: Not found" ]]; then
			print_error "modem $modem_id not ready." "$EC_NOT_READY"
			return 1
		fi
	fi
	return 0
}

wait_for_abfota_sys_ready()
{
	local modem_id="$1"

	state_str=""
	retry_count=0
	print_output "Waiting for modem to be ready"
	while [ "$state_str" != "succeed" ]; do
		[ "$retry_count" -ge 10 ] && {
			print_error "Modem is not ready FOTA update(state: $state_str)" "$EC_NOT_READY"
			return 1
		}
		retry_count=$((retry_count+1))

		ret=$(ubus call "$modem_id" get_qabfota_state)
		state_str=$(get_value_from_json "$ret" "state_str")
		[ "$state_str" = "succeed" ] && break

		[ "$state_str" = "failed" ] && {
			[ $IGNORE_FAILED_STATE = 1 ] && break
			print_error "Modem is in failed dfota state and backup partition is not available.
To ignore this and attempt to update anyway add \"-i\" option." "$EC_NOT_READY"
			return 1
		}
		debug "Waiting for modem to be ready (current state: $state_str)"
		sleep 30
	done
	print_output "Modem is ready for update"
	return 0
}

check_update() {
	[ -n "$MODE" ] && print_output "Skipping update checking because direct file or link provided" && return

	#Check if there is available update
	local modem_id="$1"
	forced="false"

	print_output "Searching for updates..."

	ubus call rut_fota fw_info > /dev/null 2>&1 || {
		print_error "Connection error to FOTA server!" "$EC_CONN_ERROR"
	}
	modem_usb_id=$(get_from_info "$modem_id" "usb_id")
	json_set_namespace parse_fw_info old_cb
	json_load "$(ubus call rut_fota get_info)"
	json_select "modems" > /dev/null 2>&1
	json_select "$modem_usb_id" > /dev/null 2>&1
	json_get_var download_link modem > /dev/null 2>&1
	json_get_var firmware_size modem_size > /dev/null 2>&1
	json_set_namespace "$old_cb"

	if [ -z "$download_link" ] || [ "$download_link" = "N/A" ] || [ "$download_link" = "Modem_newest" ] ||\
	[ "$firmware_size" = "-1" ]; then
		print_error "No update found!" "$EC_NO_UPDATE"
		return 1
	fi

	strstr "$download_link" "forced" && {
		forced="true"
		# Show more status when forced
		less_status=0
	}

	print_output "Update found! Update size: $firmware_size"
	return 0
}

do_update() {
	#Do modem firmware update

	local free_size
	local dl_file_size
	local cmd_port
	local modem_id="$1"
	local ret

	# Sets size for modem to expect
	[ "$MODE" == "link" ] && firmware_size=$(curl -sI "$MODE_PATH" ${ca_path} | awk -e '$0 ~ /^Content-Length: ([0-9]+)/ { print $2 }')
	[ "$MODE" == "file" ] && firmware_size=$(du -b "$MODE_PATH" | awk '{print $1}')
	[ -n "$MODE" ] && [ -z "$firmware_size" ] && {
		print_error "Failed to get firmware_size from $MODE_PATH"
		return 1
	}

	print_output "Preparing system for update!"

	ubus call "$modem_id" delete_file "{\"path\":\"UFS:dfota.zip\"}" >/dev/null 2>&1
	ubus call "$modem_id" delete_file "{\"path\":\"update:update.zip\"}" >/dev/null 2>&1
	sleep 1

	ret="$(ubus call "$modem_id" get_storage_space)"
	free_size=$(get_value_from_json "$ret" "free")
	if [ "$firmware_size" -lt "$free_size" ]; then
		debug "modem memory check pass"
	else
		print_error "Not enough memory in modem filesystem!" "$EC_NOT_ENOUGH_MEM"
		return 1
	fi
	cmd_port=$(get_from_info "$modem_id" "tty_port")
	echo "Found cmd port: $cmd_port"
	modem_model=$(get_from_info "$modem_id" "firmware" | head -c 6)

	[ "$modem_model" = "RG520N" ] && [ "$legacy_AT_commands" -eq 0 ] &&  {
		wait_for_abfota_sys_ready "$modem_id" || {
			# don't need to reboot because modem is busy, reboot will reset the backup progress
			SKIP_REBOOT=1
			print_error "Modem is not ready for FOTA update" "$EC_NOT_READY"
			return 1
		}
	}

	print_output "Starting download!"
	json_update_modem_status "$modem_id" "downloading"
	notify_webui "start" "{\"modem_id\": \"$(get_from_info "$m_id" "usb_id")\"}"
	# Add notification to rut_fota about start of downloading
	[ -z "$MODE" ] && ubus call rut_fota notify_server "{\"modem_id\": \"$(get_from_info "$m_id" "usb_id")\"}"
	if [ "$MODE" != "file" ]; then
		if [ "$MODE" = "link" ]; then
			download_link="$MODE_PATH"
		fi

		if [ "$modem_model" = "RG500U" ]; then
			dfota_path="/tmp/modem_fw"
			curl -s --ssl-reqd --connect-timeout 30 --max-time 900 -L "$download_link" -o "$dfota_path" ${ca_path}
			ret="$?"

			[ "$ret" = "0" ] || {
				print_error "Firmware update download was unsuccessful. Error: $ret" "$EC_DL_FAILED"
				return 1
			}
		fi

	fi

	ret=$(ubus call "$modem_id" upload_file "{\"size\":${firmware_size},\"timeout\":180}")
	echo "$ret" | grep -q "OK" || {
		print_error "Failed to enter download mode!" "$EC_EDL_FAILED"
		return 1
	}
	sleep 2

	if [ "$MODE" = "file" ]; then
		cat "$MODE_PATH" >"$cmd_port"
	else
		if [ "$modem_model" = "RG500U" ]; then
			cat "$dfota_path" >"$cmd_port"
		else
			curl -s --ssl-reqd --connect-timeout 30 --max-time 900 -L "$download_link" -o "$cmd_port" ${ca_path}
		fi
	fi

	sleep 5

	for i in $(seq $TOTAL_LIST_RETRIES); do
		sleep 2
		get_file_list_result=$(ubus call "$modem_id" get_file_list)
		[ -z $(echo "$get_file_list_result" | grep "error") ] && break
		debug "($i/$TOTAL_LIST_RETRIES) Retrying to get file list"
	done

	dl_file_size=$(echo "$get_file_list_result" | grep "size" | cut -d'"' -f 3 | cut -c 3-)
	[ -n "$dl_file_size" ] &&
		[ -n "$firmware_size" ] &&
		[ "$dl_file_size" -gt 0 ] &&
		[ "$dl_file_size" -eq "$firmware_size" ] && {
		print_output "Firmware update was successfully downloaded!"
		print_output "Sending update command!"

		if [ "$modem_model" = "RG520N" ] && [ "$legacy_AT_commands" -eq 0 ]; then
			# Set package path
			ret=$(ubus call "$modem_id" set_qabfota_package "{\"path\":\"dfota.zip\"}")
			ret=$(get_value_from_json "$ret" "status")
			[ "$ret" != "OK" ] && {
				print_error "Failed to set AB FOTA package path" "$EC_UPD_VERIFY_FAILED"
				return 1
			}
			sleep 5
			# Start the update
			print_output "Starting FOTA update"
			ret=$(ubus call "$modem_id" qabfota_update)
			ret=$(get_value_from_json "$ret" "status")
			[ "$ret" != "OK" ] && {
				print_error "Failed to start FOTA update" "$EC_UPD_START_FAILED"
				return 1
			}
			return 0
		fi

		ret=$(ubus call "$modem_id" dfota_upgrade "{\"path\":\"/data/ufs/dfota.zip\"}")
		ret=$(get_value_from_json "$ret" "status")
		[ "$ret" == "OK" ] && return 0

		ret=$(ubus call "$modem_id" dfota_upgrade "{\"path\":\"/usrdata/ufs/dfota.zip\"}")
		ret=$(get_value_from_json "$ret" "status")
		[ "$ret" == "OK" ] && return 0

		ret=$(ubus call "$modem_id" dfota_upgrade "{\"path\":\"/cache/ufs/dfota.zip\"}")
		ret=$(get_value_from_json "$ret" "status")
		[ "$ret" == "OK" ] && return 0

		ret=$(ubus call "$modem_id" dfota_upgrade "{\"path\":\"/mnt/data/update/update.zip\"}")
		ret=$(get_value_from_json "$ret" "status")
		[ "$ret" == "OK" ] && return 0

		print_error "Unable to start firmware update process!" "$EC_UPD_START_FAILED"
		return 1

	}

	debug "Download values dlsize: $dl_file_size fw: $firmware_size"

	print_error "Firmware update verify was unsuccessful..." "$EC_UPD_VERIFY_FAILED"
	return 1
}

do_update_EG915Q() {
	local free_size
	local dl_file_size
	local cmd_port
	local modem_id="$1"
	local ret

	# Sets size for modem to expect
	[ "$MODE" == "link" ] && firmware_size=$(curl -sI "$MODE_PATH" ${ca_path} | awk -e '$0 ~ /^Content-Length: ([0-9]+)/ { print $2 }')
	[ "$MODE" == "file" ] && firmware_size=$(du -b "$MODE_PATH" | awk '{print $1}')
	[ -n "$MODE" ] && [ -z "$firmware_size" ] && {
		print_error "Failed to get firmware_size from $MODE_PATH"
		return 1
	}

	print_output "Preparing system for update!"

	ret="$(ubus call "$modem_id" get_storage_space)"
	free_size=$(get_value_from_json "$ret" "free")
	if [ "$firmware_size" -lt "$free_size" ]; then
		debug "modem memory check pass"
	else
		print_error "Not enough memory in modem filesystem!" "$EC_NOT_ENOUGH_MEM"
		return 1
	fi
	cmd_port=$(get_from_info "$modem_id" "tty_port")
	echo "Found cmd port: $cmd_port"

	notify_webui "start"

	print_output "Starting download!"
	json_update_modem_status "$modem_id" "downloading"
	notify_webui "start"
	# Add notification to rut_fota about start of downloading
	[ -z "$MODE" ] && ubus call rut_fota notify_server "{\"modem_id\": \"$(get_from_info "$modem_id" "usb_id")\"}"
	if [ "$MODE" != "file" ]; then
		if [ "$MODE" = "link" ]; then
			download_link="$MODE_PATH"
		fi
		modem_model=$(get_from_info "$modem_id" "firmware" | head -c 6)
	fi

	ret=$(ubus call "$modem_id" upload_file "{\"size\":${firmware_size}}")
	echo "$ret" | grep -q "OK" || {
		print_error "Failed to enter download mode!" "$EC_EDL_FAILED"
		return 1
	}
	sleep 2 # Give some time to enter download mode
	print_output "Sending update command!"
	if [ "$MODE" = "file" ]; then
		cat "$MODE_PATH" >"$cmd_port"
	else
		curl -s --ssl-reqd --connect-timeout 30 --max-time 900 -L "$download_link" -o "$cmd_port" ${ca_path}
	fi

	return 0
}

do_update_telit_le() {
	local free_size
	local dl_file_size
	local cmd_port
	local modem_id="$1"
	local ret

	# Sets size for modem to expect
	[ "$MODE" == "link" ] && firmware_size=$(curl -sI "$MODE_PATH" ${ca_path} | awk -e '$0 ~ /^Content-Length: ([0-9]+)/ { print $2 }')
	[ "$MODE" == "file" ] && firmware_size=$(du -b "$MODE_PATH" | awk '{print $1}')
	[ -n "$MODE" ] && [ -z "$firmware_size" ] && {
		print_error "Failed to get firmware_size from $MODE_PATH"
		return 1
	}

	print_output "Preparing Telit system for update!"

	cmd_port=$(get_from_info "$modem_id" "tty_port")
	echo "Found cmd port: $cmd_port"

	notify_webui "start"

	print_output "Starting download!"
	json_update_modem_status "$modem_id" "downloading"
	notify_webui "start"
	# Add notification to rut_fota about start of downloading
	[ -z "$MODE" ] && ubus call rut_fota notify_server "{\"modem_id\": \"$(get_from_info "$modem_id" "usb_id")\"}"
	if [ "$MODE" != "file" ]; then
		if [ "$MODE" = "link" ]; then
			download_link="$MODE_PATH"
		fi
		modem_model=$(get_from_info "$modem_id" "firmware" | head -c 6)
	fi

	ret=$(ubus call "$modem_id" upload_file "{\"size\":${firmware_size}}")
	echo "$ret" | grep -q "OK" || {
		print_error "Failed to enter download mode!" "$EC_EDL_FAILED"
		return 1
	}

	print_output "Uploading file!"
	if [ "$MODE" = "file" ]; then
		echo "upload file: $MODE_PATH"
		cat "$MODE_PATH" >"$cmd_port"
	else
		curl -s --ssl-reqd --connect-timeout 30 --max-time 900 -L "$download_link" -o "$cmd_port" ${ca_path}
	fi

	# exit from uplaod mode
    sleep 1
	if ! ubus call "$modem_id" finish_dfota_upload; then
		print_error "Failed to exit upload mode!" "$EC_EDL_FAILED"
		return 1
	fi

	# send update start command
	ret=$(ubus -t 180 call "$modem_id" dfota_upgrade "{\"path\":\"\"}")
	echo "$ret" | grep -q "OK" || {
		print_error "Failed to start upgrade!" "$EC_UPD_START_FAILED"
		return 1
	}

	return 0
}

do_update_mobile_only() {
	#Do modem firmware update using mobile connection only
	local free_size
	local dl_file_size
	local cmd_port
	local modem_id="$1"
	local ret
	local ip_addr
	local state_id
	local state
	local link_length=256

	[ "$MODE" = "file" ] &&  {
		print_error "Update using file is not supported on this modem" $EC_UPGRADE_VIA_FILE_NOT_SUPPORT
		return 1
	}

	ret="$(ubus call "$modem_id" get_attached_pdp_ctx_list)"
	modem_usb_id=$(get_from_info "$modem_id" "usb_id")
	ip_addr=$(get_value_from_json "$ret" "ip_addr")
	state_id=$(get_value_from_json "$ret" "state_id")
	state=$(gsmctl -j -O $modem_usb_id)

	if [ "$state" = "Disconnected" ] && { [ -z "$ip_addr" ] || [ "$ip_addr" = "0.0.0.0" ] || \
	[ "$ip_addr" = "0.0.0.0,0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0" ]; }; then
		print_error "Modem firmware update is only available using mobile connection" $EC_NO_MOBILE_CONN
		return 1
	fi

	[ -n "$MODE" ] && download_link="$MODE_PATH"

	link_length="$(echo "$download_link" | wc -c)"
	[ "$link_length" -gt 255 ] && {
		print_error "Link length exceeds 255 bytes"
		return 1
	}

	[ -n "$MODE" ] && {
		# Get modem firmware size
		firmware_size=$(curl -sI "$download_link" ${ca_path} | awk -e '$0 ~ /^Content-Length: ([0-9]+)/ { print $2 }')
	}

	[ -z "$firmware_size" ] && {
		print_error "Failed to get firmware_size"
		return 1
	}

	print_output "Preparing system for update!"

	ret="$(ubus call "$modem_id" get_storage_space "{\"storage\":\"ufs\"}")"
	free_size=$(get_value_from_json "$ret" "free")
	if [ "$firmware_size" -lt "$free_size" ]; then
		debug "modem memory check pass"
	else
		print_error "Not enough memory in modem filesystem!" "$EC_NOT_ENOUGH_MEM"
		return 1
	fi

	print_output "Starting download!"

	# If EG12 ifdown all interfaces
	model=$(get_from_info "$modem_id" "firmware" | head -c 6)
	if [ "$model" = "EG12EA" ]; then
		control_mobile_interfaces "$modem_usb_id" "ifdown"
	fi

	json_update_modem_status "$modem_id" "downloading"
	notify_webui "start" "{\"modem_id\": \"$modem_usb_id\"}"

	# Add notification to rut_fota about start of downloading
	[ -z "$MODE" ] && ubus call rut_fota notify_server "{\"modem_id\": \"$modem_usb_id\"}"

	ret=$(ubus call "$modem_id" dfota_upgrade "{\"path\":\"$download_link\"}")
	ret=$(get_value_from_json "$ret" "status")
	[ "$ret" == "OK" ] && return 0

	print_error "Unable to start firmware update process!" "$EC_UPD_START_FAILED"
	[ "$model" = "EG12EA" ] && control_mobile_interfaces "$modem_usb_id" "ifup"
	return 1
}

log_fw_update() {
	local modem_usb_id="$1"
	local new_fw="$2"
	ubus call log write_ext "{
		\"event\": \"Updated $(get_modem_type "$modem_usb_id") modem firmware from $old_fw to $new_fw\",
		\"sender\": \"Firmware\",
		\"table\": 1,
		\"priority\": 6,
		\"write_db\": 1
	}"
}

track_update_status() {
	local modem_id="$1"
	local ret
	local cnt=0
	print_output "Waiting for the update to start..."
	json_update_modem_status "$modem_id" "waiting"
	modem_usb_id=$(get_from_info "$modem_id" "usb_id")

	local modem_json="{\"modem_id\": \"$modem_usb_id\"}"

	# Counter and max wait time
	counter=0

	while [ -e "/sys/bus/usb/devices/$modem_usb_id" ]; do
		counter=$((counter+1))
		if [ $counter -ge $max_wait ]; then
			print_error "Unable to start update process" $EC_UPGRADE_START_ERROR
			json_update_modem_error "$modem_id" "$LAST_ERROR" "$LAST_ERROR_CODE"
			json_update_dfota_status "finished" "$UPDATE_FAILED"
			end "$UPDATE_ERROR" "Update script done!" "$modem_json"
		fi

		sleep 1
	done

	print_output "Update has been started! Modem will perform few restarts and will be available after few minutes."
	print_output "*** DO NOT POWER OFF THE DEVICE! ***"

	json_update_modem_status "$modem_id" "updating"
	while true; do
		cnt=$((cnt+1))
		ret=$(get_firmware "$modem_id") > /dev/null 2>&1

		[ -z "$ret" ] || [[ "$ret" =~ "Command failed: Not found" ]] || break
		debug "Update status result: $ret"
		if [ $((cnt % 5)) -eq 0 ]; then
			print_output "Update is in progress..."
		fi
		sleep 2
	done;

	log_fw_update "$modem_usb_id" "$ret"

	ubus call rut_fota reset_info # Reset rut_fota cache
	print_output "Update finished! Firmware version - \"$ret\""
	json_update_modem_status "$modem_id" "finished"
	notify_webui "upgrade" "$modem_json"
	return 0
}

track_update_status_EG915Q() {
	local modem_id="$1"
	local ret
	local cnt=0
	print_output "Waiting for the update to start..."
	json_update_modem_status "$modem_id" "waiting"
	dfota_state=$(get_from_info "$modem_id" "\"state\": \"UPDATING\"") > /dev/null 2>&1

	# Counter and max wait time
	counter=0

	while [ -z "$dfota_state" ]; do
		counter=$((counter+1))
		dfota_state=$(get_from_info "$modem_id" "\"state\": \"UPDATING\"") > /dev/null 2>&1
		if [ $counter -ge $max_wait ]; then
			print_error "Unable to start update process" $EC_UPGRADE_START_ERROR
			json_update_modem_error "$modem_id" "$LAST_ERROR" "$LAST_ERROR_CODE"
			json_update_dfota_status "finished" "$UPDATE_FAILED"
			end "$UPDATE_ERROR" "Update script done!"
		fi

		sleep 1
	done

	print_output "Update has been started! Modem will perform few restarts and will be available after few minutes."
	print_output "*** DO NOT POWER OFF THE DEVICE! ***"

	json_update_modem_status "$modem_id" "updating"
	while true; do
		cnt=$((cnt+1))
		ret=$(get_firmware "$modem_id") > /dev/null 2>&1

		[ -z "$ret" ] || [[ "$ret" =~ "Command failed: Not found" ]] || break
		debug "Update status result: $ret"
		if [ $((cnt % 5)) -eq 0 ]; then
			print_output "Update is in progress..."
		fi
		sleep 2
	done;

	modem_usb_id=$(get_from_info "$modem_id" "usb_id")
	log_fw_update "$modem_usb_id" "$ret"

	print_output "Update finished! Firmware version - \"$ret\""
	json_update_modem_status "$modem_id" "finished"

	return 0
}

reset_on_fail() {
	#Hack if modem fails to leave data state :/
	local st="$1"
	[ "$st" = "$UPDATE_ERROR" ] || return
	[ $SKIP_REBOOT = 1 ] && return
	print_output "Reseting modems due to error!"
	mctl -r -m "modem" > /dev/null 2>&1
	mctl -r -m "modem2" > /dev/null 2>&1
}

json_find_modem(){
	#creates modems array if it doesn't exist.
	#creates <modem_id> object if it doesnt exist.
	if json_is_a modems array
	then
		json_select modems
  		idx=1
  		while json_is_a ${idx} object
  		do
		  	json_select $idx
			json_get_var id id
			if [ "$id" = "$modem_id" ]; then
				#found
				return 0
			fi
			idx=$(( idx + 1 ))
			json_close_object
  		done
	else
		json_add_array modems
		#create modems object
	fi

	#modem object doesnt so exist we need to create it.
	json_add_object
	json_add_string id "$modem_id"
	[ -z "$modem_usb_id" ] && modem_usb_id=$(get_from_info "$modem_id" "usb_id")
	json_add_string usb_id "$modem_usb_id"
}

json_find_modem_close(){
	json_close_object
	json_close_array
}
_json_update_modem_status(){
	local modem_id="$1"
	local status="$2"

	json_init
	[ -f "$json_file_status" ] && json_load_file "$json_file_status"

	json_find_modem "$modem_id"
	#Inside <modem_id> object
	json_add_string status "$status"
	[ "$forced" = "true" ] && json_add_boolean forced 1 || json_add_boolean forced 0
	json_find_modem_close

	print_json_object "$(json_dump -i)" "$json_file_status"

}

json_update_modem_status(){
	[ "$export_status" -eq 1 ] && {
		_json_update_modem_status "$1" "$2"
	}
}

_json_update_dfota_status(){
	#if fail flag is 1 status will be set to failed
	local status="$1"
	local fail_flag="$2"

	[ "$fail_flag" -eq 1 ] && status="failed"

	json_init
	[ -f "$json_file_status" ] && json_load_file "$json_file_status"
	json_add_string status "$status"

	json_close_object
	print_json_object "$(json_dump -i)" "$json_file_status"

}

json_update_dfota_status(){
	[ "$export_status" -eq 1 ] && _json_update_dfota_status "$1" "$2"
}

_json_update_modem_error(){
	local modem_id="$1"
	local error="$2"
	local error_code="$3"

	json_init
	[ -f "$json_file_status" ] && json_load_file "$json_file_status"

	json_find_modem "$modem_id"
	#Inside <modem_id> object
	json_add_string status "failed"
	json_add_string error "$error"
	json_add_string error_code "$error_code"
	json_find_modem_close

	json_close_object
	print_json_object "$(json_dump -i)" "$json_file_status"

}

json_update_modem_error(){
	[ "$export_status" -eq 1 ] && {
		_json_update_modem_error "$1" "$2" "$3"
	}
}

update_loop(){
	#Iterate modem ids
	local modem_found=""
	local modem_model=""
	local updates=0
	for m_id in $modem_ids; do
		[ -n "$UPDATE_MODEM" ] && [ "$m_id" != "$UPDATE_MODEM" ] && continue
		print_output "Doing basic checks for $m_id..."
		modem_found="true"

		check_modem_status "$m_id" || {
			json_update_modem_error "$m_id" "$LAST_ERROR" "$LAST_ERROR_CODE"
			continue
		}

		check_manufacturer "$m_id" || {
			json_update_modem_error "$m_id" "$LAST_ERROR" "$LAST_ERROR_CODE"
			continue
		}

		print_output "Basic checks: OK!"

		check_update "$m_id" || {
			json_update_modem_error "$m_id" "$LAST_ERROR" "$LAST_ERROR_CODE"
			continue
		}

		max_wait=30
		if [ "$forced" = "true" ] || [ "$update" = "true" ]; then
			debug "Update flag found!"
			control_services "stop"
			modem_fw_prefix=$(get_from_info "$m_id" "firmware" | head -c 6)
			modem_model=$(get_from_info "$m_id" "model")

			if [ "$modem_fw_prefix" = "RG520N" ]; then
				ret=$(ubus call "$m_id" get_qabfota_state)
				state_str=$(get_value_from_json "$ret" "state_str")
				[ -n "$state_str" ] && legacy_AT_commands=0
			fi
			mobile_only_flag=$(ubus call "$m_id" info | grep "mobile_dfota")
			if [ -n "$mobile_only_flag" ]; then
				max_wait=250
				func=do_update_mobile_only
			elif [ "$modem_fw_prefix" = "EG915Q" ]; then
				func=do_update_EG915Q
			elif [ "$modem_model" = "LE910C4-WWX" ]; then
				func=do_update_telit_le
			elif [ "$modem_fw_prefix" = "RG520N" ] && [ "$legacy_AT_commands" -eq 0 ]; then
				max_wait=240
				func=do_update
			else
				func=do_update
			fi

			$func "$m_id" && updates=$(($updates + 1)) || {
				status="$UPDATE_ERROR"
				json_update_modem_error "$m_id" "$LAST_ERROR" "$LAST_ERROR_CODE"
				break
			}

			if [ "$modem_fw_prefix" = "EG915Q" ]; then
				track_update_status_EG915Q "$m_id"
			else
				track_update_status "$m_id"
			fi
		fi
	done

	[ -n "$UPDATE_MODEM" ] && [ -z "$modem_found" ] && {
		print_error "Failed to select $UPDATE_MODEM modem." "$MODEM_NOT_FOUND"
		exit 1
	}

	reset_on_fail "$status"
	json_update_dfota_status "finished" "$UPDATE_FAILED"
	[ "$updates" -eq 0 ] && status="$UPDATE_ERROR" # In case script finished and did nothing
	end "$status" "Update script done!"
}

#********************************
# Main
#*********************************

if [ "$(pgrep -f "$0")" != "$$" ]; then
	end "$INSTANCE_RUNNING" "Unable to start because another instance is running!"
fi

[ "$export_status" -eq 1 ] && rm -f "$json_file_status"

[ "$export_info" -eq 1 ] && [ "$update" = "true" ] && end "$BAD_ARGUMENTS" "Please define only -e or -u in arguments!"

[ "$export_status" -eq 1 ] && [ "$less_status" -eq 0 ] &&  json_update_dfota_status "started" 0

#Wait for full system boot
while true; do
	[ -e "/var/run/init-done" ] && break
	sleep 1
done

#Wait for default route to check connection
[ "$WAIT_FOR_WAN" = "1" ] && wait_for_wan

[ "$export_info" -eq 1 ] && insert_json_on_failure  # If no modem will be found

#Find if router have upgradable modems
search_for_modems

[ -n "$a_modem_id" ] && {
	debug "Checking if defined modem exists in the system"
	check_if_modem_exists "$a_modem_id" || end "$MODEM_NOT_FOUND" "Defined modem does not exist!"
	modem_ids="$a_modem_id"	# Only one modem should be updated if -u passed
}

[ "$export_info" -eq 1 ] && {
	export_modem_info
	print_json_object "$(json_dump -i)" "$json_file_updates"
	end "$EXPORT_END" "Information export is done!"
}

json_update_dfota_status "started" 0
#if we are running a standalone script we don't need a separate instance if we are running for WEBUI we start a thread and exit.
if [ "$export_status" -eq 1 ]; then
	#start a thread in the bg
	update_loop &
else
	update_loop
fi
exit 0
