#!/bin/bash

VERITYSETUP=../src/veritysetup

DEV_NAME=verity3273
DEV_OUT="$DEV_NAME.out"
IMG=verity-data
IMG_HASH=verity-hash

function remove_mapping()
{
	[ -b /dev/mapper/$DEV_NAME ] && dmsetup remove $DEV_NAME >/dev/null 2>&1
	[ ! -z "$LOOPDEV1" ] && losetup -d $LOOPDEV1 >/dev/null 2>&1
	rm -f $IMG $IMG_HASH $DEV_OUT >/dev/null 2>&1
	LOOPDEV1=""
	LOOPDEV2=""
}

function fail()
{
	[ -n "$1" ] && echo "$1"
	echo "FAILED"
	[ -f $DEV_OUT ] && cat $DEV_OUT
	remove_mapping
	exit 2
}

function skip()
{
	[ -n "$1" ] && echo "$1"
	exit 0
}

function prepare() # $1 dev1_siz [$2 dev2_size]
{
	remove_mapping

	dd if=/dev/zero of=$IMG      bs=1k count=$1 >/dev/null 2>&1
	LOOPDEV1=$(losetup -f 2>/dev/null)
	[ -z "$LOOPDEV1" ] && fail "No free loop device"
	losetup $LOOPDEV1 $IMG

	[ -z "$2" ] && return
	LOOPDEV2=$IMG_HASH
}

function wipe()
{
	dd if=/dev/zero of=$LOOPDEV1 bs=256k >/dev/null 2>&1
	rm -f $IMG_HASH $DEV_OUT >/dev/null 2>&1
}

function check_exists()
{
	[ -b /dev/mapper/$DEV_NAME ] || fail
}

function check_version()
{
        VER_STR=$(dmsetup targets | grep verity | cut -f 3 -dv)
        VER_MAJ=$(echo $VER_STR | cut -f 1 -d.)
        VER_MIN=$(echo $VER_STR | cut -f 2 -d.)

        # option supported in 1.3
        test $VER_MAJ -gt 1 && return 0
        test $VER_MIN -ge 3 && return 0
        return 1
}

function compare_out() # $1 what, $2 expected
{
	OPT=$(grep -v "^#" $DEV_OUT | grep -i "$1" | sed -e s/.*\:\ // )
	[ -z "$OPT" ] && fail
	[ $OPT != $2 ] && fail "$1 differs ($2)"
}

function check_root_hash() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, [$6 offset]
{
	if [ -z "$LOOPDEV2" ] ; then
		BLOCKS=$(($6 / $1))
		DEV_PARAMS="$LOOPDEV1 $LOOPDEV1 \
			   --hash-offset $6 \
			   --data-blocks=$BLOCKS --debug"
	else
		DEV_PARAMS="$LOOPDEV1 $LOOPDEV2"
	fi

	for sb in yes no; do
	FORMAT_PARAMS="--format=$4 --data-block-size=$1 --hash-block-size=$1 --hash=$5 --salt=$3"
	if [ $sb == yes ] ; then
		VERIFY_PARAMS=""
	else
		FORMAT_PARAMS="$FORMAT_PARAMS --no-superblock"
		VERIFY_PARAMS=$FORMAT_PARAMS
	fi

	for fail in data hash; do
	wipe
	echo -n "V$4(sb=$sb) $5 block size $1: "
	$VERITYSETUP format $DEV_PARAMS $FORMAT_PARAMS >$DEV_OUT || fail

	echo -n "[root hash]"
	compare_out "root hash" $2
	compare_out "salt" "$3"

	$VERITYSETUP verify $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 || fail
	echo -n "[verify]"

	$VERITYSETUP create $DEV_NAME $DEV_PARAMS $VERIFY_PARAMS $2  >>$DEV_OUT 2>&1 || fail
	check_exists
	echo -n "[activate]"

	dd if=/dev/mapper/$DEV_NAME of=/dev/null bs=$1 2>/dev/null
	dmsetup status $DEV_NAME | grep "verity V" >/dev/null || fail
	echo -n "[in-kernel verify]"

	$VERITYSETUP remove $DEV_NAME >/dev/null 2>&1 || fail

	case $fail in
	data)
		dd if=/dev/urandom of=$LOOPDEV1 bs=1 seek=3456 count=8 conv=notrunc 2>/dev/null
		TXT="data_dev"
		;;
	hash)
		if [ -z "$LOOPDEV2" ] ; then
			dd if=/dev/urandom of=$LOOPDEV1 bs=1 seek=$((8193 + $4)) count=8 conv=notrunc 2>/dev/null
		else
			dd if=/dev/urandom of=$LOOPDEV2 bs=1 seek=8193 count=8 conv=notrunc 2>/dev/null
		fi
                TXT="hash_dev"
		;;
	esac

	$VERITYSETUP verify $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 && \
		fail "userspace check for $TXT corruption"
	$VERITYSETUP create $DEV_NAME $DEV_PARAMS $VERIFY_PARAMS $2 >>$DEV_OUT 2>&1 || \
		fail "activation"
	dd if=/dev/mapper/$DEV_NAME of=/dev/null bs=$1 2>/dev/null
	dmsetup status $DEV_NAME | grep "verity V" >/dev/null && \
		fail "in-kernel check for $TXT corruption"
	$VERITYSETUP remove $DEV_NAME >/dev/null 2>&1 || fail "deactivation"
	echo "[$TXT corruption]"
	done
	done
}

function check_option() # $1 size, $2 hash, $3 salt, $4 version, $5 hash, $6 CLI option, $7 status option
{
	DEV_PARAMS="$LOOPDEV1 $LOOPDEV2"
	FORMAT_PARAMS="--format=$4 --data-block-size=$1 --hash-block-size=$1 --hash=$5 --salt=$3"

	echo -n "Option $6 "
	$VERITYSETUP format $DEV_PARAMS $FORMAT_PARAMS >/dev/null 2>&1 || fail
	$VERITYSETUP create $DEV_NAME $DEV_PARAMS $2 $6 >/dev/null 2>&1 || fail
	check_exists
	$VERITYSETUP status $DEV_NAME 2>/dev/null | grep flags | grep -q $7 || fail
	dmsetup table $DEV_NAME 2>/dev/null | grep -q $7 || fail
	$VERITYSETUP remove $DEV_NAME >/dev/null 2>&1 || fail
	echo "[OK]"
}

function valgrind_setup()
{
	which valgrind >/dev/null 2>&1 || fail "Cannot find valgrind."
	[ ! -f $CRYPTSETUP_VALGRIND ] && fail "Unable to get location of cryptsetup executable."
	#export LD_LIBRARY_PATH="$CRYPTSETUP_LIB_VALGRIND:$LD_LIBRARY_PATH"
}

function valgrind_run()
{
	INFOSTRING="$(basename ${BASH_SOURCE[1]})-line-${BASH_LINENO[0]}" ./valg.sh ${VERITYSETUP} "$@"
}

[ $(id -u) != 0 ] && skip "WARNING: You must be root to run this test, test skipped."
[ ! -x "$VERITYSETUP" ] && skip "Cannot find $VERITYSETUP, test skipped."

[ -n "$VALG" ] && valgrind_setup && VERITYSETUP=valgrind_run
modprobe dm-verity >/dev/null 2>&1
dmsetup targets | grep verity >/dev/null 2>&1 || skip "Cannot find dm-verity target, test skipped."

# VERITYSETUP tests

SALT=e48da609055204e89ae53b655ca2216dd983cf3cb829f34f63a297d106d53e2d

echo "Verity tests [separate devices]"
prepare 8192 1024
check_root_hash  512 9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 $SALT 1 sha256
check_root_hash 1024 54d92778750495d1f80832b486ebd007617d746271511bbf0e295e143da2b3df $SALT 1 sha256
check_root_hash 4096 e522df0f97da4febb882ac40f30b37dc0b444bf6df418929463fa25280f09d5c $SALT 1 sha256
# version 0
check_root_hash 4096 cbbf4ebd004ef65e29b935bb635a39cf754d677f3fa10b0126da725bbdf10f7d $SALT 0 sha256
# no salt
check_root_hash 4096 ef29c902d87350f1da4bfa536e16cebc162a909bf89abe448b81ec500d4fb9bf - 1 sha256
# sha1
check_root_hash 1024 d0e9163ca8844aaa2e88fe5265a8c5d9ee494a99 $SALT 1 sha1
check_root_hash 1024 73509e8e868be6b8ac939817a98a3d35121413b2 dadada 1 sha1

echo "Verity tests [one device offset]"
prepare $((8192 + 1024))
check_root_hash  512 9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174 $SALT 1 sha256 8388608
check_root_hash 1024 54d92778750495d1f80832b486ebd007617d746271511bbf0e295e143da2b3df $SALT 1 sha256 8388608
check_root_hash 4096 e522df0f97da4febb882ac40f30b37dc0b444bf6df418929463fa25280f09d5c $SALT 1 sha256 8388608
# version 0
check_root_hash 4096 cbbf4ebd004ef65e29b935bb635a39cf754d677f3fa10b0126da725bbdf10f7d $SALT 0 sha256 8388608
# no salt
check_root_hash 4096 ef29c902d87350f1da4bfa536e16cebc162a909bf89abe448b81ec500d4fb9bf - 1 sha256 8388608
# sha1
check_root_hash 1024 d0e9163ca8844aaa2e88fe5265a8c5d9ee494a99 $SALT 1 sha1 8388608
check_root_hash 1024 73509e8e868be6b8ac939817a98a3d35121413b2 dadada 1 sha1 8388608

if check_version ; then
	echo "Verity data corruption options test."
	SALT=e48da609055204e89ae53b655ca2216dd983cf3cb829f34f63a297d106d53e2d
	HASH=9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174
	prepare 8192 1024
	check_option 512 $HASH $SALT 1 sha256 "--ignore-corruption" "ignore_corruption"
	check_option 512 $HASH $SALT 1 sha256 "--restart-on-corruption" "restart_on_corruption"
	check_option 512 $HASH $SALT 1 sha256 "--ignore-zero-blocks" "ignore_zero_blocks"
	check_option 512 $HASH $SALT 1 sha256 "--ignore-corruption --ignore-zero-blocks" "ignore_corruption"
fi

remove_mapping
exit 0
