#!/usr/bin/env bash
#set -x

# shellcheck disable=SC2090

# For the license, see the LICENSE file in the root directory.

# This test case builds various versions of libtpms + swtpm and generates
# TPM 2 state using swtpm_setup with profiles, if supported. After that
# it starts the built versions of swtpm and tries to use the state that
# was created possibly by another version of libtpms and runs tests with it.
# Currently this only works with the null profile whose state can be created
# and read either with libtpms v0.9 or v0.10. In the future tests can be
# covering the default-v1 profile as well that must be exchageable with
# libtpms >v0.10.

if [ "${SWTPM_TEST_EXPENSIVE:-0}" -eq 0 ]; then
	echo "SWTPM_TEST_EXPENSIVE must be set to run this test."
	exit 77
fi

if [ "${SWTPM_TEST_IBMTSS2:-0}" -eq 0 ]; then
	echo "SWTPM_TEST_IBMTSS2 must be set to run this test."
	exit 77
fi

ROOT=${abs_top_builddir:-$(dirname "$0")/..}
TESTDIR=${abs_top_testdir:-$(dirname "$0")}
SRCDIR=${abs_top_srcdir:-$(dirname "$0")/..}

source "${TESTDIR}/common"

workdir="$(mktemp -d)" || exit 1
export TPM_PATH=${workdir}
SWTPM_SERVER_PORT=65478
SWTPM_CTRL_PORT=65479
SWTPM_SERVER_NAME=127.0.0.1
SWTPM_INTERFACE="socket+socket"

LIBTPMS_URL=https://github.com/stefanberger/libtpms
LIBTPMS_INITIAL_BRANCH=master

SWTPM_URL=https://github.com/stefanberger/swtpm
SWTPM_DEFAULT_BRANCH=master   # during development change to local branch

cat <<_EOF_ > "${workdir}/swtpm-localca.options"
--tpm-manufacturer IBM
--tpm-model swtpm-libtpms
--tpm-version 2
--platform-manufacturer "Fedora XYZ"
--platform-version 2.1
--platform-model "QEMU A.B"
_EOF_

cat <<_EOF_ > "${workdir}/swtpm_setup.conf"
create_certs_tool=\${SWTPM_LOCALCA}
create_certs_tool_config=${workdir}/swtpm-localca.conf
create_certs_tool_options=${workdir}/swtpm-localca.options
_EOF_

# Copy swtpm source tree to workdir
pushd "${SRCDIR}" &>/dev/null || exit 1

if [ ! -d ".git" ]; then
	echo "SKIP: This test must be run from a git checkout"
	exit 77
fi

mkdir -p "${workdir}/swtpm"
cp -rp . "${workdir}/swtpm"
cd "${workdir}/swtpm" || exit 1
chmod -R 0755 .   # when using 'distcheck'

if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
	# store all current modifications in a temp patch
	git config --local user.name test
	git config --local user.email test@test.test
	git add --all . >/dev/null
	git commit -m "temp" >/dev/null
fi

git clean -xdf &>/dev/null
popd &>/dev/null || exit 1

function cleanup()
{
	rm -rf "${workdir}"

	if kill_quiet -0 "${SWTPM_PID}"; then
		kill_quiet -9 "${SWTPM_PID}"
	fi
}

trap "cleanup" SIGTERM EXIT

function nv_storefile()
{
	local nvindex="$1"
	local filename="$2"

	local sz

	if ! sz=$(get_filesize "${filename}"); then
		return 1
	fi

	if ! tssnvdefinespace -ha "${nvindex}" -hi o -pwdn nv -sz "${sz}" 1>/dev/null; then
		return 1
	fi

	if ! tssnvwrite -ha "${nvindex}" -pwdn nv -if "${filename}"; then
		return 1
	fi
	return 0
}

function nv_savetofile()
{
	local nvindex="$1"
	local filename="$2"

	local sz

	if ! tssnvread -ha "${nvindex}" -pwdn nv -of "${filename}" >/dev/null; then
		return 1
	fi
	sz=$(get_filesize "${filename}")
	if [ "${sz}" -eq 0 ]; then
		# older versions required the -sz parameter
		sz=$(tssnvreadpublic -ha "${nvindex}" | sed -n 's/.*data size //p')
		if ! tssnvread -ha "${nvindex}" -pwdn nv -sz "${sz}" -of "${filename}" >/dev/null; then
			return 1
		fi
	fi

	return 0
}

function create_tpm_state()
{
	local workdir="$1"

	local inputfile="${workdir}/input.txt"
	local encfile="${workdir}/enc"
	local signature="${workdir}/signature.bin"
	local contextfile="${workdir}/context.bin"
	local aespub="${workdir}/aespub.bin"
	local aespriv="${workdir}/aespriv.bin"
	local cc rsasize ecdsaparam

	echo "input" > "${inputfile}"

	export TPM_COMMAND_PORT=${SWTPM_SERVER_PORT}
	export TPM_PLATFORM_PORT=${SWTPM_CTRL_PORT}
	export TPM_SERVER_NAME=${SWTPM_SERVER_NAME}
	export TPM_INTERFACE_TYPE=socsim
	export TPM_SERVER_TYPE=raw

	if tsscreateprimary --help | grep rsa | grep -q keybits; then
		rsasize="3072"
	fi

	# Test signing with RSA 3072 key
	if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo -si 1>/dev/null; then
		return 1
	fi

	if ! tsssign -hk 80000000 -pwdk ooo -if "${inputfile}" -os "${signature}"; then
		return 1
	fi

	if ! nv_storefile 01000000 "${signature}"; then
		return 1
	fi

	# Save the key as contextfile
	if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null; then
		return 1
	fi

	if ! nv_storefile 01000001 "${contextfile}"; then
		return 1
	fi

	# Flush all keys
	if ! tssflushcontext -ha 80000000; then
		return 1
	fi

	# Test HMAC
	# tsssign -salg was added in a later version only; Ubuntu Jammy does not have it
	if tsssign -h | grep -q "salg"; then
		if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo 1>/dev/null; then
			return 1
		fi

		if ! tsscreate -hp 80000000 -pwdp ooo -kh -kt f -kt p -opu "${aespub}" -opr "${aespriv}" -halg sha256; then
			return 1
		fi

		if ! nv_storefile 01000002 "${aespub}" || ! nv_storefile 01000003 "${aespriv}" ; then
			return 1
		fi

		if ! tssload -hp 80000000 -pwdp ooo -ipu "${aespub}" -ipr "${aespriv}" 1>/dev/null; then
			return 1
		fi

		if ! tsssign -hk 80000001 -if "${inputfile}" -salg hmac -scheme hmac -os "${signature}"; then
			return 1
		fi

		if ! nv_storefile 01000004 "${signature}"; then
			return 1
		fi

		if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null || \
		   ! nv_storefile 01000005 "${contextfile}" || \
		   ! tsscontextsave -ha 80000001 -of "${contextfile}" 1>/dev/null || \
		   ! nv_storefile 01000006 "${contextfile}"; then
			return 1
		fi

		# Flush all keys
		if ! tssflushcontext -ha 80000000 || ! tssflushcontext -ha 80000001; then
			return 1
		fi
	fi

	# Encryption/Decryption
	if ! tsscreateprimary -hi o -des 1>/dev/null; then
		return 1
	fi

	if ! tssencryptdecrypt -hk 80000000 -if "${inputfile}" -of "${encfile}"; then
		return 1
	fi

	if ! nv_storefile 01000007 "${encfile}"; then
		return 1
	fi

	if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null || \
	   ! nv_storefile 01000008 "${contextfile}"; then
		return 1
	fi

	if ! tssflushcontext -ha 80000000; then
		return 1
	fi


	# Signing with a NIST P256 key
	if ! tsscreateprimary -ecc nistp256 -hi o -pwdk ooo -si 1>/dev/null; then
		return 1
	fi

	ecdsaparam="-scheme ecdsa"
	# older tools had: [-ecc (ECDSA scheme)]
	if tsssign --help | grep ecc | grep -q scheme; then
		ecdsaparam="-ecc"
	fi
	if ! tsssign ${ecdsaparam:+${ecdsaparam}} -hk 80000000 -pwdk ooo -if "${inputfile}" -os "${signature}"; then
		return 1
	fi

	if ! nv_storefile 01000009 "${signature}"; then
		return 1
	fi

	# Save the key as contextfile
	if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null; then
		return 1
	fi

	if ! nv_storefile 0100000a "${contextfile}"; then
		return 1
	fi

	# Flush all keys
	if ! tssflushcontext -ha 80000000; then
		return 1
	fi


	# Test setting command audit if available; Ubuntu Jammy does not have it
	if type -P tsssetcommandcodeauditstatus >/dev/null; then
		# Set a couple of commands to be audited
		for cc in 11f 12a 133 13a 140 147 150 15c 16e 17a 187 190 197; do
			if ! tsssetcommandcodeauditstatus -hi o -set $cc; then
				return 1
			fi
		done
	fi

	return 0
}

function check_tpm_state()
{
	local workdir="$1"
	local is_fullresume="$2"

	local inputfile="${workdir}/input.txt"
	local encfile="${workdir}/enc"
	local decfile="${workdir}/dec"
	local signature="${workdir}/signature.bin"
	local contextfile="${workdir}/context.bin"
	local aespub="${workdir}/aespub.bin"
	local aespriv="${workdir}/aespriv.bin"
	local cc rsasize

	echo "input" > "${inputfile}"

	export TPM_COMMAND_PORT=${SWTPM_SERVER_PORT}
	export TPM_PLATFORM_PORT=${SWTPM_CTRL_PORT}
	export TPM_SERVER_NAME=${SWTPM_SERVER_NAME}
	export TPM_INTERFACE_TYPE=socsim
	export TPM_SERVER_TYPE=raw

	if tsscreateprimary --help | grep rsa | grep -q keybits; then
		rsasize="3072"
	fi

	# Test RSA 3072 signing key
	if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo -si 1>/dev/null; then
		return 1
	fi

	if ! nv_savetofile 01000000 "${signature}"; then
		return 1
	fi

	if ! tssverifysignature -hk 80000000 -if "${inputfile}" -is "${signature}"; then
		return 1
	fi
	echo "INFO: Verified signature with RSA key"

	if ! tssflushcontext -ha 80000000; then
		return 1
	fi

	# Test with the key stored in context; this only works with save/restore of all state
	if [ "${is_fullresume}" -ne 0 ]; then
		if ! nv_savetofile 01000001 "${contextfile}"; then
			return 1
		fi

		if ! tsscontextload -if "${contextfile}" 1>/dev/null; then
			return 1
		fi

		if ! tssverifysignature -hk 80000000 -if "${inputfile}" -is "${signature}"; then
			return 1
		fi
		echo "INFO: Verified signature with RSA key and restored key context"

		if ! tssflushcontext -ha 80000000; then
			return 1
		fi
	fi

	# HMAC test
	# tsssign -salg was added in a later version only
	if tsssign -h | grep -q "salg"; then
		if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo 1>/dev/null; then
			return 1
		fi

		if ! nv_savetofile 01000002 "${aespub}" \
		   || ! nv_savetofile 01000003 "${aespriv}" \
		   || ! nv_savetofile 01000004 "${signature}"; then
			return 1
		fi

		if ! tssload -hp 80000000 -pwdp ooo -ipu "${aespub}" -ipr "${aespriv}" 1>/dev/null; then
			return 1
		fi

		if ! tssverifysignature -hk 80000001 -if "${inputfile}" -is "${signature}"; then
			return 1
		fi
		echo "INFO: Verified HMAC"

		if ! tssflushcontext -ha 80000000 || ! tssflushcontext -ha 80000001; then
			return 1
		fi

		# Test with the keys stored in contexts; this only works with save/restore of all state
		if [ "${is_fullresume}" -ne 0 ]; then
			if ! nv_savetofile 01000005 "${contextfile}" || \
			   ! tsscontextload -if "${contextfile}" 1>/dev/null; then
				return 1
			fi

			if ! nv_savetofile 01000006 "${contextfile}" || \
			   ! tsscontextload -if "${contextfile}" 1>/dev/null; then
				return 1
			fi

			if ! tssverifysignature -hk 80000001 -if "${inputfile}" -is "${signature}"; then
				return 1
			fi
			echo "INFO: Verified HMAC with restored key context"

			if ! tssflushcontext -ha 80000000 || ! tssflushcontext -ha 80000001; then
				return 1
			fi
		fi
	fi

	# Encryption/Decryption
	if ! tsscreateprimary -hi o -des 1>/dev/null; then
		return 1
	fi

	if ! nv_savetofile 01000007 "${encfile}"; then
		return 1
	fi

	if ! tssencryptdecrypt -hk 80000000 -d -if "${encfile}" -of "${decfile}"; then
		return 1
	fi

	if [ "$(get_sha1_file "${inputfile}")" != "$(get_sha1_file "${decfile}")" ]; then
		return 1
	fi
	echo "INFO: Verified decryption"

	if ! tssflushcontext -ha 80000000; then
		return 1
	fi

	# Test with the key stored in context; this only works with save/restore of all state
	if [ "${is_fullresume}" -ne 0 ]; then
		if ! nv_savetofile 01000008 "${contextfile}" || \
		   ! tsscontextload -if "${contextfile}" 1>/dev/null; then
			return 1
		fi
		if ! tssencryptdecrypt -hk 80000000 -d -if "${encfile}" -of "${decfile}"; then
			return 1
		fi

		if [ "$(get_sha1_file "${inputfile}")" != "$(get_sha1_file "${decfile}")" ]; then
			return 1
		fi
		echo "INFO: Verified decryption with restored key context"

		if ! tssflushcontext -ha 80000000; then
			return 1
		fi
	fi

	# Test NIST p256 signing key
	if ! tsscreateprimary -ecc nistp256 -hi o -pwdk ooo -si 1>/dev/null; then
		return 1
	fi

	if ! nv_savetofile 01000009 "${signature}"; then
		return 1
	fi

	if ! tssverifysignature -ecc -hk 80000000 -if "${inputfile}" -is "${signature}"; then
		return 1
	fi
	echo "INFO: Verified signature with RSA key"

	if ! tssflushcontext -ha 80000000; then
		return 1
	fi

	# Test with the key stored in context; this only works with save/restore of all state
	if [ "${is_fullresume}" -ne 0 ]; then
		if ! nv_savetofile 0100000a "${contextfile}"; then
			return 1
		fi

		if ! tsscontextload -if "${contextfile}" 1>/dev/null; then
			return 1
		fi

		if ! tssverifysignature -ecc -hk 80000000 -if "${inputfile}" -is "${signature}"; then
			return 1
		fi
		echo "INFO: Verified signature with RSA key and restored key context"

		if ! tssflushcontext -ha 80000000; then
			return 1
		fi
	fi


	# Test the audited commands is command was available to set audited commands
	if [ "${is_fullresume}" -ne 0 ]; then
		if type -P tsssetcommandcodeauditstatus >/dev/null; then
			for cc in 11f 12a 133 13a 140 147 150 15c 16e 17a 187 190 197; do
				if ! tssgetcapability -cap 4 | grep -q 00000${cc}; then
					echo "Error: Audit not set for command 0000${cc}"
					tssgetcapability -cap 4
					return 1
				fi
			done
			echo "INFO: Verified audited commands"
		fi
	fi

	return 0
}

function build_swtpm()
{
	local srcdir="$1"
	local cflags="$2"
	local destdir="$3"
	local swtpm_git_version="$4"

	echo -e "\nBuilding swtpm ${swtpm_git_version} with CFLAGS=${cflags} ..."

	pushd "${srcdir}" &>/dev/null || exit 1

	git clean -xdf &>/dev/null
	git reset --hard HEAD
	if ! git checkout "${swtpm_git_version}"; then
		# Travis needs this
		git remote add upstream "${SWTPM_URL}"
		git fetch upstream "${swtpm_git_version}"
		git checkout "upstream/${swtpm_git_version}"
	fi
	PKG_CONFIG_PATH="${destdir}" CFLAGS="${cflags}" \
		./autogen.sh --without-cuse --without-selinux --without-seccomp
	make -j "$(nproc)" || return 1

	cp \
		src/swtpm/.libs/swtpm src/swtpm/.libs/libswtpm*.so* \
		src/swtpm_setup/swtpm_setup \
		src/swtpm_ioctl/swtpm_ioctl \
			"${destdir}"

	popd &>/dev/null || exit 1
}

# Copy a compiled libtpms version to its destination directory; the destination
# directory will be created
# @param1: Destination directory's parent
# @param2: The version of libtpms, e.g. '0.9' or 'master'
function copy_libtpms()
{
	local dest="$1"
	local version="$2"

	local destdir="${dest}/libtpms-${version}"

	mkdir -p "${destdir}"
	cp libtpms.pc src/.libs/libtpms.so* "${destdir}"

	return 0
}

# Build various versions of libtpms starting with libtpms 0.9 up to 'master'
# Build swtpm for each one of these versions as well.
function build_libtpms_and_swtpm()
{
	local workdir="$1"

	local tmp major maj min
	local libtpmsdir="${workdir}/libtpms"

	pushd "${workdir}" 2>&1 || return 1

	git clone "${LIBTPMS_URL}" libtpms

	pushd libtpms 2>&1 || return 1

	git checkout "${LIBTPMS_INITIAL_BRANCH}"

	echo -e "\nBuilding libtpms ${INITIAL_BRANCH} ..."
	CFLAGS="-g -O2" ./autogen.sh --with-tpm2 --without-tpm1
	make -j "$(nproc)" || return 1

	copy_libtpms "${workdir}" "master"

	# Determine major version of what master built
	tmp=$(ls src/.libs/libtpms.so.*.*)
	major=$(echo "${tmp}" | sed -n 's/.*.so\.\([^\.]\+\)\.\([^\.]\+\)\..*/\1/p')

	if ! build_swtpm \
		"${workdir}/swtpm" \
		"-I${libtpmsdir}/include -L${workdir}/libtpms-master" \
		"${workdir}/libtpms-master" \
		"${SWTPM_DEFAULT_BRANCH}"; then
		echo "Error: Could not build swtpm"
		return 1
	fi

	for ((maj=0; maj<=major; maj++)) {
		min=0
		[ "${maj}" = 0 ] && min=9  # start with v0.9

		for ((;; min++)) {
			git checkout "origin/stable-${maj}.${min}" &>/dev/null || break

			# clean directory containing libtpms.so.*
			rm -rf src/.libs
			echo -e "\nBuilding libtpms ${maj}.${min}..."
			make -j "$(nproc)" || return 1

			copy_libtpms "${workdir}" "${maj}.${min}"

			if ! build_swtpm \
				"${workdir}/swtpm" \
				"-I${libtpmsdir}/include -L${workdir}/libtpms-${maj}.${min}" \
				"${workdir}/libtpms-${maj}.${min}" \
				stable-0.9; then
				echo "Error: Could not build swtpm"
				return 1
			fi
		}
	}

	popd &>/dev/null || return 1

	#ls -l libtpms-*
	#PATH="${workdir}/libtpms-master" LD_LIBRARY_PATH=${PATH} ./libtpms-master/swtpm_setup --help
	#PATH="${workdir}/libtpms-0.9" LD_LIBRARY_PATH=${PATH} ./libtpms-0.9/swtpm_setup socket --help

	popd &>/dev/null || return 1

	return 0
}

# Run swtpm_setup on all versions >= v0.10 of libtpms. The state is written
# into directories with the name patterns of state--${branch}--${profile},
# which leads to names like state--0.10--null for null profile and libtpms v0.10
function swtpm_setup_create_profile_state()
{
	local workdir="$1"

	local branch profiles libtpmsdir statedir err=0 has_option

	echo -e "INFO: Creating state with various profiles\n"

	pushd "${workdir}" &>/dev/null || return 1

	# Setup swtpm-0.10 and later with null profile and check that swtpm-0.9 works with it
	for libtpmsdir in libtpms-master libtpms-*.*; do
		branch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p')

		export LD_LIBRARY_PATH="${workdir}/${libtpmsdir}"
		export SWTPM_IOCTL=./${libtpmsdir}/swtpm_ioctl

		echo
		has_option=$(PATH="${workdir}/${libtpmsdir}" \
			       swtpm socket --help | grep -E "\-profile")

		if [ -n "${has_option}" ]; then
			# get a space-separated list of profile names
			profiles=$(PATH="${workdir}/${libtpmsdir}" \
				     swtpm socket --tpm2 --print-capabilities \
					| sed -n \
						-e "s/.* \"profiles\": { \"names\": \[ \(.*\) \].*/\1/" \
						-e 's/[ "]//g' -e 's/,/ /gp')
		else
			profiles="<none>"
		fi

		for profile in ${profiles}; do
			local profileopt=""

			if [ "${profile}" = "<none>" ]; then
				profile=null
			else
				# shellcheck disable=SC2089
				profileopt="--profile {\"Name\":\"${profile}\"}"
			fi

			statedir="state--${branch}--${profile}"
			mkdir "${statedir}"
			echo
			PATH="${workdir}/${libtpmsdir}" \
				swtpm -v
			echo "state dir:$statedir, libtpms dir:$libtpmsdir, profile: $profile"

			if ! PATH="${workdir}/${libtpmsdir}" \
			     "./${libtpmsdir}/swtpm_setup" \
				--tpm2 \
				--tpmstate "${statedir}" \
				--config "${workdir}/swtpm_setup.conf" \
				--log "${statedir}/swtpm_setup.log" \
				--tpm "${workdir}/${libtpmsdir}/swtpm socket" \
				${profileopt:+${profileopt}}; then
				echo "Error: Could not run swtpm_setup with libtpms from branch ${branch} with profile ${profile}"
				err=1
				break
			fi

			SWTPM_PID=""

			if ! SWTPM_SERVER_NO_DISCONNECT=1 \
				SWTPM_EXE=./${libtpmsdir}/swtpm \
					run_swtpm "${SWTPM_INTERFACE}" \
					--tpm2 \
					--tpmstate "dir=${workdir}/${statedir}" \
					--flags not-need-init,startup-clear; then
				echo "Error: Could not start swtpm with null profile state and libtpms version '${libtpmsbranch}'."
				err=1
				break
			fi

			if ! create_tpm_state "${workdir}"; then
				echo "Error: Creating TPM-internal state failed"
				err=1
				break
			fi
			echo "INFO: Successfully created TPM-internal state"

			if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --save permanent "${workdir}/${statedir}/permanent" \
			   || ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --save volatile  "${workdir}/${statedir}/volatile"; then
				echo "Error: Could not save permanent or volatile state"
				err=1
				break
			fi
			echo "INFO: Successfully saved permanent and volatile state to ${workdir}/${statedir}/"

			if ! check_tpm_state "${workdir}" 1; then
				echo "Error: Checking TPM-internal state failed"
				err=1
				break
			fi
			echo "INFO: Successfully checked proper functioning of TPM-internal state"

			# Use kill here rather than a shutdown to be able to compare the
			# size of the state later on. A shutdown would generate a bigger
			# tpm2-00.permall file that cannot be compare with while the TPM 2
			# is running.
			kill_quiet -9 "${SWTPM_PID}"
			SWTPM_PID=""

			echo "INFO: Sizes of state in ${workdir}/${statedir}"
			ls -l "${workdir}/${statedir}"
		done

		if [ -n "${SWTPM_PID}" ]; then
			kill_quiet -9 "${SWTPM_PID}"
		fi
		if [ "${err}" -ne 0 ]; then
			break
		fi
	done

	unset LD_LIBRARY_PATH

	popd &>/dev/null || return 1

	echo -e "INFO: Done creating state with various profiles\n\n"

	return "${err}"
}

# This function tests that the NULL profile state, that was created by a certain
# version of libtpms, can be used by swtpm with libtpms v0.9 and others.
function swtpm_run_with_null_profile_state()
{
	local workdir="$1"

	local statedir branch libtpmsbranch resp exp fsize err=0

	echo "INFO: Running with NULL profile states using different version of libtpms"

	pushd "${workdir}" &>/dev/null || return 1

	for statedir in state--*--null; do
		branch=$(echo "${statedir}" | sed -n 's/.*--\(.*\)--.*/\1/p')
		for libtpmsdir in libtpms-master libtpms-*.*; do
			libtpmsbranch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p')
			fsize=$(get_filesize "${workdir}/${statedir}/tpm2-00.permall")

			export LD_LIBRARY_PATH="${workdir}/${libtpmsdir}"
			export SWTPM_IOCTL="./${libtpmsdir}/swtpm_ioctl"

			echo -e "\nINFO: Using swtpm in ${libtpmsdir} with TPM state in ${workdir}/${statedir}"

			if ! SWTPM_SERVER_NO_DISCONNECT=1 \
				SWTPM_EXE=./${libtpmsdir}/swtpm \
					run_swtpm "${SWTPM_INTERFACE}" \
					--tpm2 \
					--tpmstate "dir=${workdir}/${statedir}" \
					--flags not-need-init,startup-clear; then
				echo "Error: Could not start swtpm with null profile state and libtpms version '${libtpmsbranch}'."
				err=1
				break
			fi
			# Read PCR 10
			exp=' 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 '
			resp=$(tsspcrread -ha 10 -halg sha256 | grep " 00" | tr -d "\n" | tr -s " ")
			if [ "${resp}" != "${exp}" ]; then
				echo "Error: Did not get expected result from TPM2_PCRRead(10)"
				echo "expected: ${exp}"
				echo "received: ${resp}"
				err=1
				break
			fi
			echo " PCR 10 is good:  profile: null   libtpms: ${libtpmsbranch}   state written by: ${branch}"

			if [ "$(get_filesize "${workdir}/${statedir}/tpm2-00.permall")" != "${fsize}" ]; then
				echo "Error: Size of tpm2 state file has changed"
				echo "expected: ${fsize}"
				echo "actual  : $(get_filesize "${workdir}/${statedir}/tpm2-00.permall")"
				ls -l "${workdir}/${statedir}/"
				err=1
				break
			fi

			if ! check_tpm_state "${workdir}" 0; then
				echo "Error: Checking TPM-internal state failed: profile: null   libtpms: ${libtpmsbranch}  state written by: ${branch}"
				err=1
				break
			fi
			echo "INFO: Successfully checked proper functioning of TPM-internal state"

			# restore permanent and volatile state and run tests again
			if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --stop \
			   || ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --load permanent "${workdir}/${statedir}/permanent" \
			   || ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --load volatile  "${workdir}/${statedir}/volatile" \
			   || ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -i; then
				echo "Error: Could not load permanent or volatile state"
				err=1
				break
			fi
			echo "INFO: Successfully restored permanent and volatile state from ${workdir}/${statedir}/"

			if ! check_tpm_state "${workdir}" 1; then
				echo "Error: Checking TPM-internal state failed (after resume): profile: null   libtpms: ${libtpmsbranch}  state written by: ${branch}"
				err=1
				break
			fi
			echo "INFO: Successfully checked proper functioning of TPM-internal state after state resume"

			kill_quiet -9 "${SWTPM_PID}"
			echo " INFO: Passed with libtpms ${libtpmsbranch}"

		done
		if [ ${err} -ne 0 ]; then
			kill_quiet -9 "${SWTPM_PID}"
			break
		fi
	done

	unset LD_LIBRARY_PATH

	popd &>/dev/null || return 1

	echo -e "INFO: Done running with NULL profile states using different version of libtpms\n\n"

	return "${err}"
}

# This function tests that a default profile state with 'stateFormatLevel = 2' can be used
# and remains at this level.
function swtpm_run_with_default_profile_state()
{
	local workdir="$1"
	local statefile="$2"

	local libtpmsdir libtpmsbranch output err=0 exp

	mkdir "${workdir}/tmp"
	cp "${statefile}" "${workdir}/tmp/"

	echo -e "\n\nINFO: Testing with previously created default profile state"

	pushd "${workdir}" &>/dev/null || return 1

	for libtpmsdir in libtpms-master libtpms-*.*; do
		libtpmsbranch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p')
		[ "${libtpmsbranch}" = "0.9" ] && continue

		export LD_LIBRARY_PATH="${workdir}/${libtpmsdir}"
		export SWTPM_IOCTL="./${libtpmsdir}/swtpm_ioctl"

		if ! SWTPM_EXE="./${libtpmsdir}/swtpm" \
		     run_swtpm "${SWTPM_INTERFACE}" \
		     --tpm2 \
		     --tpmstate "dir=${workdir}/tmp" \
		     --flags not-need-init,startup-clear; then
			echo "Error: Could not start swtpm with 'default' profile state and libtpms version '${libtpmsbranch}'."
			err=1
			break
		fi
		if ! output=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x20); then
			echo "Error: Could not run swtpm_ioctl: ${output}"
			err=1
			break
		fi
		exp=',"StateFormatLevel":2,'
		if ! echo "${output}" | grep -q "${exp}"; then
			echo "Error: Could not find '${exp}' in swtpm_ioctl output."
			echo "  output: ${output}"
			err=1
			break
		fi

		echo " INFO: Found proper default state in libtpms ${libtpmsbranch}"

		run_swtpm_ioctl "${SWTPM_INTERFACE}" -s
	done

	if [ ${err} -ne 0 ]; then
		run_swtpm_ioctl "${SWTPM_INTERFACE}" -s
	fi

	unset LD_LIBRARY_PATH

	popd &>/dev/null || return 1

	echo -e "INFO: Done testing with previously created default profile state\n\n"

	rm -rf "${workdir}/tmp/"

	return "${err}"
}

if ! build_libtpms_and_swtpm "${workdir}"; then
	echo "Error: Building libtpms and/or swtpm failed"
	exit 1
fi

if ! swtpm_setup_create_profile_state "${workdir}"; then
	exit 1
fi

if ! swtpm_run_with_null_profile_state "${workdir}"; then
	exit 1
fi

if ! swtpm_run_with_default_profile_state "${workdir}" "${TESTDIR}/data/tpm2state7/tpm2-00.permall"; then
	exit 1
fi

exit 0
