#!/usr/bin/env bash
#  SPDX-License-Identifier: BSD-3-Clause
#  Copyright (C) 2022 Intel Corporation
#  All rights reserved.
#

testdir=$(readlink -f "$(dirname "$0")")
rootdir=$(readlink -f "$testdir/../..")

source "$rootdir/test/common/autotest_common.sh"
source "$testdir/common.sh"

rpc_py="$rootdir/scripts/rpc.py"
localnqn=nqn.2016-06.io.spdk:cnode0
tgtnqn=nqn.2016-06.io.spdk:tgt0
key0=1234567890abcdef1234567890abcdef
key1=deadbeefcafebabefeedbeefbabecafe
tgtsock=/var/tmp/spdk.sock2
discovery_port=8009

cleanup() {
	killprocess $smapid
	killprocess $hostpid
	killprocess $tgtpid
}

gen_volume_params() {
	local volume_id=$1 cipher=$2 key=$3 key2=$4 config
	local -a params crypto

	config=$(
		cat <<- PARAMS
			"volume_id": "$(uuid2base64 $volume_id)",
			"nvmf": {
			  "hostnqn": "nqn.2016-06.io.spdk:host0",
			  "discovery": {
			    "discovery_endpoints": [
			      {
			        "trtype": "tcp",
			        "traddr": "127.0.0.1",
			        "trsvcid": "$discovery_port"
			      }
			    ]
			  }
			}
		PARAMS
	)
	params+=("$config")

	local IFS=","
	if [[ -n "$cipher" ]]; then
		crypto+=("\"cipher\": $(get_cipher $cipher)")
		crypto+=("\"key\": \"$(format_key $key)\"")
		if [[ -n "$key2" ]]; then
			crypto+=("\"key2\": \"$(format_key $key2)\"")
		fi

		crypto_config=$(
			cat <<- PARAMS
				"crypto": {
				  ${crypto[*]}
				}
			PARAMS
		)

		params+=("$crypto_config")
	fi

	cat <<- PARAMS
		"volume": {
		  ${params[*]}
		}
	PARAMS
}

create_device() {
	"$rootdir/scripts/sma-client.py" <<- CREATE
		{
		  "method": "CreateDevice",
		  "params": {
		    "nvmf_tcp": {
		      "subnqn": "$localnqn",
		      "adrfam": "ipv4",
		      "traddr": "127.0.0.1",
		      "trsvcid": "4420"
		    }
		    ${1:+, $(gen_volume_params "$@")}
		  }
		}
	CREATE
}

delete_device() {
	"$rootdir/scripts/sma-client.py" <<- DELETE
		{
		  "method": "DeleteDevice",
		  "params": {
		    "handle": "$1"
		  }
		}
	DELETE
}

attach_volume() {
	local device=$1
	shift

	"$rootdir/scripts/sma-client.py" <<- ATTACH
		{
		  "method": "AttachVolume",
		  "params": {
		    "device_handle": "$device",
		    $(gen_volume_params "$@")
		  }
		}
	ATTACH
}

detach_volume() {
	"$rootdir/scripts/sma-client.py" <<- DETACH
		{
		  "method": "DetachVolume",
		  "params": {
		    "device_handle": "$1",
		    "volume_id": "$(uuid2base64 $2)"
		  }
		}
	DETACH
}

verify_crypto_volume() {
	local nqn=$1 uuid=$2 ns ns_bdev

	ns=$(rpc_cmd nvmf_get_subsystems $nqn | jq -r '.[0].namespaces[0]')
	ns_bdev=$(jq -r '.name' <<< "$ns")

	# Make sure that the namespace is a crypto bdev and that there's only a single crypto bdev
	[[ $(rpc_cmd bdev_get_bdevs -b "$ns_bdev" | jq -r '.[0].product_name') == crypto ]]
	[[ $(rpc_cmd bdev_get_bdevs | jq -r '[.[] | select(.product_name == "crypto")] | length') -eq 1 ]]
	# Check that the namespace's UUID/NGUID matches the one requested by the user
	[[ $(jq -r '.uuid' <<< "$ns") == "$uuid" ]]
	[[ $(jq -r '.nguid' <<< "$ns") == "$(uuid2nguid $uuid)" ]]
}

trap "cleanup; exit 1" SIGINT SIGTERM EXIT

"$rootdir/build/bin/spdk_tgt" -m 0x1 &
hostpid=$!

"$rootdir/build/bin/spdk_tgt" -r "$tgtsock" -m 0x2 &
tgtpid=$!

$rootdir/scripts/sma.py -c <(
	cat <<- CONFIG
		address: 127.0.0.1
		port: 8080
		devices:
		  - name: 'nvmf_tcp'
		crypto:
		  name: 'bdev_crypto'
		  params:
		    driver: 'crypto_aesni_mb'
	CONFIG
) &
smapid=$!

# Wait until the SMA starts listening
sma_waitforlisten

# Prepare the target
uuid=$(uuidgen)
waitforlisten "$tgtpid" "$tgtsock"
$rpc_py -s "$tgtsock" << CONFIG
	bdev_malloc_create -b malloc0 32 4096 -u $uuid
	nvmf_create_transport -t tcp
	nvmf_create_subsystem $tgtnqn -a
	nvmf_subsystem_add_listener discovery -t tcp -a 127.0.0.1 -s $discovery_port
	nvmf_subsystem_add_listener -t tcp -a 127.0.0.1 -s 4421 -f ipv4 $tgtnqn
	nvmf_subsystem_add_ns $tgtnqn malloc0
CONFIG

# Create an empty device first
device=$(create_device | jq -r '.handle')

# First attach a volume without crypto
attach_volume $device $uuid

ns_bdev=$(rpc_cmd nvmf_get_subsystems $localnqn | jq -r '.[0].namespaces[0].name')
[[ $(rpc_cmd bdev_get_bdevs -b "$ns_bdev" | jq -r '.[0].product_name') == 'NVMe disk' ]]
[[ $(rpc_cmd bdev_get_bdevs | jq -r '[.[] | select(.product_name == "crypto")] | length') -eq 0 ]]
[[ $(rpc_cmd nvmf_get_subsystems $localnqn | jq -r '.[0].namespaces[0].uuid') == "$uuid" ]]
[[ $(rpc_cmd nvmf_get_subsystems $localnqn | jq -r '.[0].namespaces[0].nguid') == "$(uuid2nguid $uuid)" ]]

detach_volume $device $uuid

# Now attach a volume with crypto enabled
attach_volume $device $uuid AES_CBC $key0

[[ $(rpc_cmd bdev_nvme_get_discovery_info | jq -r '. | length') -eq 1 ]]
[[ $(rpc_cmd nvmf_get_subsystems $localnqn | jq -r '.[0].namespaces | length') -eq 1 ]]
# Make sure that the namespace exposed in the subsystem is a crypto bdev and is using malloc bdev's UUID
verify_crypto_volume $localnqn $uuid
# Check that it's using correct key
crypto_bdev=$(rpc_cmd bdev_get_bdevs | jq -r '.[] | select(.product_name == "crypto")')
[[ $(jq -r '.driver_specific.crypto.key' <<< "$crypto_bdev") == "$key0" ]]

# Attach the same volume again
attach_volume $device $uuid AES_CBC $key0

# Nothing should change
[[ $(rpc_cmd bdev_nvme_get_discovery_info | jq -r '. | length') -eq 1 ]]
[[ $(rpc_cmd nvmf_get_subsystems $localnqn | jq -r '.[0].namespaces | length') -eq 1 ]]
verify_crypto_volume $localnqn $uuid
crypto_bdev2=$(rpc_cmd bdev_get_bdevs | jq -r '.[] | select(.product_name == "crypto")')
[[ $(jq -r '.name' <<< "$crypto_bdev") == $(jq -r '.name' <<< "$crypto_bdev2") ]]
[[ $(jq -r '.driver_specific.crypto.key' <<< "$crypto_bdev2") == "$key0" ]]

# Try to do attach it again, but this time use a different crypto algorithm
NOT attach_volume $device $uuid AES_XTS $key0
# Check the same, this time changing the key
NOT attach_volume $device $uuid AES_CBC $key1
# Check the same, this time adding second key
NOT attach_volume $device $uuid AES_CBC $key0 $key1
# Check out-of-range cipher value
NOT attach_volume $device $uuid 8 $key0

# Make sure these failures haven't affected anything
verify_crypto_volume $localnqn $uuid

detach_volume $device $uuid

# Check that if there's something wrong with crypto params, the volume won't get attached and
# everything is cleaned up afterwards
NOT attach_volume $device $uuid 8 $key0
[[ $(rpc_cmd nvmf_get_subsystems $localnqn | jq -r '.[0].namespaces | length') -eq 0 ]]
[[ $(rpc_cmd bdev_nvme_get_discovery_info | jq -r '. | length') -eq 0 ]]
[[ $(rpc_cmd bdev_get_bdevs | jq -r length) -eq 0 ]]

delete_device $device

# Check that it's possible to create a device immediately specyfing a volume with crypto
device=$(create_device $uuid AES_CBC $key0 | jq -r '.handle')
verify_crypto_volume $localnqn $uuid

detach_volume $device $uuid
delete_device $device

# Try to create a device with incorrect volume crypto params, check that it fails and everything
# is cleaned up afterwards
NOT create_device $uuid 8 $key0
[[ $(rpc_cmd bdev_nvme_get_discovery_info | jq -r '. | length') -eq 0 ]]
[[ $(rpc_cmd bdev_get_bdevs | jq -r length) -eq 0 ]]
[[ $(rpc_cmd nvmf_get_subsystems | jq -r "[.[] | select(.nqn == \"$localnqn\")] | length") -eq 0 ]]

# Check that if crypto is disabled, it's not possible to attach a volume with crypto
killprocess $smapid
$rootdir/scripts/sma.py -c <(
	cat <<- CONFIG
		address: 127.0.0.1
		port: 8080
		devices:
		  - name: 'nvmf_tcp'
	CONFIG
) &
smapid=$!

sma_waitforlisten
device=$(create_device | jq -r '.handle')

NOT attach_volume $device $uuid AES_CBC $key0
[[ $(rpc_cmd bdev_nvme_get_discovery_info | jq -r '. | length') -eq 0 ]]
[[ $(rpc_cmd bdev_get_bdevs | jq -r length) -eq 0 ]]

cleanup
trap - SIGINT SIGTERM EXIT
