#!/bin/bash -

# Copyright (C) MOXA Inc. All rights reserved.
# This software is distributed under the terms of the MOXA SOFTWARE NOTICE.
# See the file MOXA-SOFTWARE-NOTICE for details.
#
# Name:
#	MOXA Bootloader Upgrade Utility
#
# Description:
#	Upgrade moxa device's bootloader
#
# Copyright (C) Moxa, Inc. All rights reserved.
# Copyright (C) 2019	Wes Huang	<Wes.Huang@moxa.com>
# Copyright (C) 2021	Remus Wu	<remusty.wu@moxa.com>
# Copyright (C) 2022	Henry LC Chen	<HenryLC.Chen@moxa.com>

BASENAME="mx-bootloader-upgrade-tool"
INTERFACE_NAME="mx-bootloader-mgmt upgrade"

IOTHINX_UBOOT_DEV=/dev/mmcblk2boot0
IOTHINX_FLAG=0

if [ -f "/proc/device-tree/model" ]; then
	device_tree_model_name="$(tr -d '\0' < /proc/device-tree/model)"
fi

if echo "${device_tree_model_name}" | grep -q "ioThinx"; then
	DEFAULT_UBOOT_DEV="$IOTHINX_UBOOT_DEV"
	DEFAULT_UBOOT_SIZE=0x100000
	IOTHINX_FLAG=1
else
	DEFAULT_UBOOT_PARTITION_LABEL="u-boot 1"
	DEFAULT_UBOOT_DEV=/dev/$(grep "$DEFAULT_UBOOT_PARTITION_LABEL" < /proc/mtd | awk '{print $1}' | sed 's/[[:punct:]]//g')
	DEFAULT_UBOOT_SIZE=0x$(grep "$DEFAULT_UBOOT_PARTITION_LABEL" < /proc/mtd | awk '{print $2}' | sed -r 's/0*([0-9])/\1/')
fi

INFO_LENGTH=1024
UBOOT_LENGTH=$(($((DEFAULT_UBOOT_SIZE))-INFO_LENGTH))
INFO_START_ADDRESS=0x$(printf "%x\n" $UBOOT_LENGTH)

IOTHINX_INFO_START_ADDRESS=0x1$(printf "%x\n" $UBOOT_LENGTH)

_logger() {
	echo "$1"
	logger -i -t $BASENAME "$1"
}

usage() {
	echo "Usage: $INTERFACE_NAME [OPTION...] [FILE]"
	echo
	echo "Examples:"
	echo "  $INTERFACE_NAME -i                    # Get the information of the current bootloader installed on device"
	echo "  $INTERFACE_NAME -i -f u-boot.bin  # Get bootloader information from binary u-boot.bin"
	echo "  $INTERFACE_NAME -f u-boot.bin     # Upgrade bootloader using 'u-boot.bin'"
	echo
	echo "Options:"
	echo "  -f, --file <file>               Set <file> as the file path of bootloader binary"
	echo "  -i, --info                      Get bootloader information from current device or binary"
	echo "  -y, --yes                       Automatic yes to prompts"
	echo
	echo "  -h, --help                      Display the help menu"
}

_caculate_sha256sum() {
	local source="$1"
	local length="$2"

	if [ ! -e "$source" ]; then
		echo "$source it not exist"
		exit 1
	fi

	if [ "$source" = "$IOTHINX_UBOOT_DEV" ]; then
		local block_size=1024
		local skip_address=1048576
		dd if="$source" bs=$(( 1 * "$block_size" )) count=$(( "$length" / "$block_size" )) skip=$(( "$skip_address" / "$block_size" )) status=none | sha256sum | awk '{print $1}'
	else
		dd if="$source" bs="$length" count=1 status=none | sha256sum | awk '{print $1}'
	fi
}

_caculate_md5sum() {
	local source="$1"
	local length="$2"

	if [ ! -e "$source" ]; then
		echo "$source it not exist"
		exit 1
	fi

	if [ "$source" = "$IOTHINX_UBOOT_DEV" ]; then
		local block_size=1024
		local skip_address=1048576
		dd if="$source" bs=$(( 1 * "$block_size" )) count=$(( "$length" / "$block_size" )) skip=$(( "$skip_address" / "$block_size" )) status=none | md5sum | awk '{print $1}'
	else
		dd if="$source" bs="$length" count=1 status=none | md5sum | awk '{print $1}'
	fi
}

_question() {
	local yes="$1"
	local message="$2"
	local choice

	if [ "$yes" != "y" ]; then
		read -r -p "$message" choice
		if [ "${choice,,}" != "y" ]; then
			exit 1
		fi
	else
		echo "${message}${yes}"
	fi
}

_query_info() {
	local source="$1"
	local item="$2"
	local result

	if [ ! -e "$source" ]; then
		_logger "$source it not exist"
		exit 1
	fi

	if [ -z "$item" ]; then
		_logger "Please assign query item"
		exit 1
	fi

	# 1024 means 1024 bytes show in 1 line.
	if [ -z "$bootloader_info" ]; then
		if [ "$source" = "$IOTHINX_UBOOT_DEV" ]; then
			bootloader_info=$(hexdump -v -e "$INFO_LENGTH \"%_p\" \"\n\"" "$source" -s "$IOTHINX_INFO_START_ADDRESS" -n "$INFO_LENGTH")
		else
			bootloader_info=$(hexdump -v -e "$INFO_LENGTH \"%_p\" \"\n\"" "$source" -s "$INFO_START_ADDRESS" -n "$INFO_LENGTH")
		fi
	fi

	result=$(grep -o -E "${item}=\S{1,}" <<< "$bootloader_info" | cut -d'=' -f 2)
	if [ -z "$result" ]; then
		_logger "Can't query $item information"
		exit 1
	fi

	echo "$result"
}

verify_checksum() {
	local source=$1

	if [ "$(_caculate_sha256sum "$source" $UBOOT_LENGTH)" != "$(_query_info "$source" sha256sum)" ]; then
		_logger "The $source sha256sum is incorrect!"
		return 1
	elif [ "$(_caculate_md5sum "$source" $UBOOT_LENGTH)" != "$(_query_info "$source" md5sum)" ]; then
		_logger "The $source md5sum is incorrect!"
		return 1
	fi

	return 0
}

upgrade() {
	local source_binary=$1
	local target_device=$DEFAULT_UBOOT_DEV
	local source_binary_biosver
	local target_device_biosver
	local systemfailback

	if ! verify_checksum "$source_binary"; then
		exit 1
	fi

	source_binary_biosver=$(_query_info "$source_binary" biosver)
	target_device_biosver=$(_query_info "$target_device" biosver)

	_logger "The version of bootloader being updated: $source_binary_biosver"
	_logger "The version of current bootloader: $target_device_biosver"

	if [ "$source_binary_biosver" = "$target_device_biosver" ]; then
		_logger "Your bootloader version is the same as the version of bootloader being updated."
	fi
	_question "$assume_yes" "Do you want to continue? (y/N)"

	systemfailback=$(mx-system-mgmt -S state -V)
	if [ "$systemfailback" -eq "0" ]; then
		_logger "Bootloader update failure could result in device malfunction."
		_logger "It is recommended to enable system failback before the update."
		_question "$assume_yes" "Would you still like to continue without failback enabled? (y/N)"
	fi

	_logger "Start to upgrade bootloader..."
	if [ "$IOTHINX_FLAG" -eq "1" ]; then
		echo 0 > /sys/block/mmcblk2boot0/force_ro
		if ! dd if="$source_binary" of="$target_device" bs=1048576 seek=1 count=1; then
			_logger "Upgrade failed!"
			echo 1 > /sys/block/mmcblk2boot0/force_ro
			exit 1
		fi
		echo 1 > /sys/block/mmcblk2boot0/force_ro
	else
		if ! flashcp "$source_binary" "$target_device"; then
			_logger "Upgrade failed!"
			exit 1
		fi
	fi

	if ! verify_checksum "$target_device"; then
		_logger "Upgrade failed!"
		exit 1
	fi

	if ! systemctl start mx-bootloader-info-init; then
		_logger "Regen bootloader info failed!"
	fi

	_logger "Upgrade $target_device bootloader to version $source_binary_biosver successfully"
}

info() {
	local target=$1

	_logger "compatible model: $(_query_info "$target" modelname)"
	_logger "bootloader version: $(_query_info "$target" biosver)"
	_logger "sha256sum: $(_query_info "$target" sha256sum)"
	_logger "md5sum: $(_query_info "$target" md5sum)"
}

parsing_options() {
	if ! OPTS=$(getopt -o if:yh --long info,file:,yes,help,version -n "$INTERFACE_NAME" -- "$@"); then
		echo "Failed parsing options." >&2
		exit 1
	fi
	eval set -- "$OPTS"

	while [ -n "$1" ]; do
		case "$1" in
		-i | --info)
			action=info
			shift
			;;
		-f | --file)
			file="$2"
			shift
			shift
			;;
		-y | --yes)
			assume_yes=y
			shift
			;;
		-h | --help)
			usage
			exit 0
			;;
		--)
			shift
			break
			;;
		*)
			shift
			break
			;;
		esac
	done

	if [ "$action" = "info" ]; then
		if [ -z "$file" ]; then
			target="$DEFAULT_UBOOT_DEV"
			_logger "Current bootloader information:"
		else
			if [ ! -f "$file" ]; then
				echo "$file it not exist"
				return 1
			fi
			target="$file"
			_logger "Bootloader file '$target' information:"
		fi
	fi

	if [ -z "$action" ]; then
		action=upgrade
		if [ -z "$file" ]; then
			return 1
		elif [ ! -f "$file" ]; then
			echo "$file it not exist"
			return 1
		fi
		target="$file"
	fi

	return 0
}

main() {
	if ! parsing_options "$@"; then
		usage
		exit 1
	fi

	case "$action" in
	info)
		info "$target"
		;;
	upgrade)
		upgrade "$target"
		;;
	*)
		;;
	esac

	return 0
}

main "$@"
