// SPDX-License-Identifier: LGPL-2.1-or-later
/**
 * This file is part of libnvme.
 * Copyright (c) 2020 Western Digital Corporation or its affiliates.
 *
 * Authors: Keith Busch <keith.busch@wdc.com>
 */

/**
 * Basic libnvme test: uses scan filters, single controllers, and many admin
 * command APIs for identifications, logs, and features. No verification for
 * specific values are performed: the test will only report which commands
 * executed were completed successfully or with an error. User inspection of
 * the output woould be required to know if everything is working when the
 * program exists successfully; an ungraceful exit means a bug exists
 * somewhere.
 */
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <ccan/endian/endian.h>

#include <libnvme.h>

#include "nvme/private.h"

static bool nvme_match_subsysnqn_filter(libnvme_subsystem_t s,
		libnvme_ctrl_t c, libnvme_ns_t ns, void *f_args)
{
	char *nqn_match = f_args;

	if (s)
		return strcmp(libnvme_subsystem_get_subsysnqn(s), nqn_match) == 0;
	return true;
}

static int test_ctrl(libnvme_ctrl_t c)
{
	static __u8 buf[0x1000];

	enum nvme_get_features_sel sel = NVME_GET_FEATURES_SEL_CURRENT;
	struct libnvme_transport_handle *hdl = libnvme_ctrl_get_transport_handle(c);
	struct libnvme_passthru_cmd cmd;
	int ret, temp;
	struct nvme_error_log_page error[64];
	struct nvme_smart_log smart = { 0 };
	struct nvme_firmware_slot fw =  { 0 };
	struct nvme_ns_list ns_list = { 0 };
	struct nvme_cmd_effects_log cfx = { 0 };
	struct nvme_self_test_log st = { 0 };
	struct nvme_telemetry_log *telem = (void *)buf;
	struct nvme_endurance_group_log eglog = { 0 };
	struct nvme_ana_log *analog = (void *)buf;
	struct nvme_resv_notification_log resvnotify = { 0 };
	struct nvme_sanitize_log_page sanlog = { 0 };
	struct nvme_id_uuid_list uuid = { 0 };
	struct nvme_id_ns_granularity_list gran = { 0 };
	struct nvme_secondary_ctrl_list sec = { 0 };
	struct nvme_primary_ctrl_cap prim = { 0 };
	struct nvme_ctrl_list ctrlist = { 0 };
	struct nvme_id_ctrl id = { 0 };

	nvme_init_identify_ctrl(&cmd, &id);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (ret) {
		printf("ERROR: no identify for:%s\n", libnvme_ctrl_get_name(c));
		return ret;
	}
	else {
		printf("PASSED: Identify controller\n");
	}

	nvme_init_get_log_smart(&cmd, NVME_NSID_ALL, &smart);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (ret) {
		printf("ERROR: no smart log for:%s %#x\n", libnvme_ctrl_get_name(c), ret);
		return ret;
	}
	else {
		printf("PASSED: smart log\n");
	}

	temp = ((smart.temperature[1] << 8) | smart.temperature[0]) - 273;
	printf("Controller:%s\n", libnvme_ctrl_get_name(c));
	printf("\nIdentify:\n");
	printf("  vid:%#04x\n", le16_to_cpu(id.vid));
	printf("  ssvid:%#04x\n", le16_to_cpu(id.ssvid));
	printf("  oacs:%#x\n", id.oacs);
	printf("  lpa:%#x\n", id.lpa);
	printf("  sn:%-.20s\n", id.sn);
	printf("  model:%-.40s\n", id.mn);

	nvme_init_identify_allocated_ns_list(&cmd, 0, &ns_list);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Allocated NS List\n");
	else
		printf("  ERROR: Allocated NS List:%x\n", ret);
	nvme_init_identify_active_ns_list(&cmd, 0, &ns_list);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Active NS List\n");
	else
		printf("  ERROR: Active NS List:%x\n", ret);
	nvme_init_identify_ctrl_list(&cmd, 0, &ctrlist);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Ctrl List\n");
	else
		printf("  ERROR: CtrlList:%x\n", ret);
	nvme_init_identify_ctrl_list(&cmd, 1, &ctrlist);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: NSID Ctrl List\n");
	else
		printf("  ERROR: NSID CtrlList:%x\n", ret);
	nvme_init_identify_primary_ctrl_cap(&cmd, 0, &prim);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Identify Primary\n");
	else
		printf("  ERROR: Identify Primary:%x\n", ret);
	nvme_init_identify_secondary_ctrl_list(&cmd, 0, &sec);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Identify Secondary\n");
	else
		printf("  ERROR: Identify Secondary:%x\n", ret);
	nvme_init_identify_ns_granularity(&cmd, &gran);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Identify NS granularity\n");
	else
		printf("  ERROR: Identify NS granularity:%x\n", ret);
	nvme_init_identify_uuid_list(&cmd, &uuid);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  PASSED: Identify UUID List\n");
	else
		printf("  ERROR: Identify UUID List:%x\n", ret);

	printf("\nLogs\n");
	printf("  SMART: Current temperature:%d percent used:%d%%\n", temp,
		smart.percent_used);
	nvme_init_get_log_sanitize(&cmd, &sanlog);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Sanitize Log:\n");
	else
		printf("  ERROR: Sanitize Log:%x\n", ret);
	nvme_init_get_log_reservation(&cmd, &resvnotify);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Reservation Log\n");
	else
		printf("  ERROR: Reservation Log:%x\n", ret);
	nvme_init_get_log_ana_groups(&cmd, analog, sizeof(buf));
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  ANA Groups\n");
	else
		printf("  ERROR: ANA Groups:%x\n", ret);
	nvme_init_get_log_endurance_group(&cmd, 0, &eglog);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Endurance Group\n");
	else
		printf("  ERROR: Endurance Group:%x\n", ret);
	nvme_init_get_log_telemetry_ctrl(&cmd, 0, telem, sizeof(buf));
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Telemetry Controller\n");
	else
		printf("  ERROR: Telemetry Controller:%x\n", ret);
	nvme_init_get_log_device_self_test(&cmd, &st);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Device Self Test\n");
	else
		printf("  ERROR: Device Self Test:%x\n", ret);
	nvme_init_get_log_cmd_effects(&cmd, NVME_CSI_NVM, &cfx);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Command Effects\n");
	else
		printf("  ERROR: Command Effects:%x\n", ret);
	nvme_init_get_log_changed_alloc_ns(&cmd, &ns_list, sizeof(ns_list));
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Change NS List\n");
	else
		printf("  ERROR: Change NS List:%x\n", ret);
	nvme_init_get_log_fw_slot(&cmd, &fw);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  FW Slot\n");
	else
		printf("  ERROR: FW Slot%x\n", ret);
	nvme_init_get_log_error(&cmd, 64, error);
	ret = libnvme_get_log(hdl, &cmd, true, NVME_LOG_PAGE_PDU_SIZE);
	if (!ret)
		printf("  Error Log\n");
	else
		printf("  ERROR: Error Log:%x\n", ret);
	printf("\nFeatures\n");
	nvme_init_get_features_arbitration(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Arbitration:%" PRIu64 "\n", (uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Arbitration:%x\n", ret);
	nvme_init_get_features_power_mgmt(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Power Management:%" PRIu64 "x\n", (uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Power Management:%x\n", ret);

	nvme_init_get_features_temp_thresh(&cmd, sel, 0, 0);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Temperature Threshold:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Temperature Threshold:%x\n", ret);
	nvme_init_get_features_volatile_wc(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Volatile Write Cache:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Volatile Write Cache:%x\n", ret);
	nvme_init_get_features_num_queues(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Number of Queues:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Number of Queues:%x\n", ret);
	nvme_init_get_features_irq_coalesce(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  IRQ Coalescing:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: IRQ Coalescing:%x\n", ret);
	nvme_init_get_features_write_atomic(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Write Atomic:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Write Atomic:%x\n", ret);
	nvme_init_get_features_async_event(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Asycn Event Config:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Asycn Event Config:%x\n", ret);
	nvme_init_get_features_hctm(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  HCTM:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: HCTM:%x\n", ret);
	nvme_init_get_features_nopsc(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  NOP Power State Config:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: NOP Power State Configrbitration:%x\n", ret);
	nvme_init_get_features_rrl(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Read Recover Levels:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Read Recover Levels:%x\n", ret);
	nvme_init_get_features_lba_sts_interval(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  LBA Status Interval:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: LBA Status Interval:%x\n", ret);
	nvme_init_get_features_sanitize(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Sanitize:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: SW Progress Marker:%x\n", ret);
	nvme_init_get_features_sw_progress(&cmd, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  SW Progress Marker:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Sanitize:%x\n", ret);
	nvme_init_get_features_resv_nf_mask(&cmd, 0, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Reservation Notification Mask:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Reservation Notification Mask:%x\n", ret);
	nvme_init_get_features_resv_persist(&cmd, 0, sel);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Reservation Persistence:%" PRIu64 "\n",
			(uint64_t)cmd.result);
	else if (ret > 0)
		printf("  ERROR: Reservation Persistence:%x\n", ret);
	return 0;
}

static int test_namespace(libnvme_ns_t n)
{
	int ret, nsid = libnvme_ns_get_nsid(n);
	struct libnvme_transport_handle *hdl;
	struct libnvme_passthru_cmd cmd;
	struct nvme_id_ns ns = { 0 }, allocated = { 0 };
	struct nvme_ns_id_desc *descs;
	__u64 result = 0;
	__u8 flbas;

	ret = libnvme_ns_get_transport_handle(n, &hdl);
	if (ret)
		return ret;

	ret = libnvme_ns_identify(n, &ns);
	if (ret)
		return ret;

	nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &flbas);
	printf("%s: nsze:%" PRIu64 " lba size:%d\n",
		libnvme_ns_get_name(n), le64_to_cpu(ns.nsze),
		1 << ns.lbaf[flbas].ds);

	nvme_init_identify_allocated_ns(&cmd, nsid, &allocated);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Identify allocated ns\n");
	else
		printf("  ERROR: Identify allocated ns:%x\n", ret);
	descs = malloc(NVME_IDENTIFY_DATA_SIZE);
	if (!descs)
		return -1;

	nvme_init_identify_ns_descs_list(&cmd, nsid, descs);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Identify NS Descriptors\n");
	else
		printf("  ERROR: Identify NS Descriptors:%x\n", ret);
	free(descs);
	nvme_init_get_features_write_protect(&cmd, nsid,
		NVME_GET_FEATURES_SEL_CURRENT);
	ret = libnvme_exec_admin_passthru(hdl, &cmd);
	if (!ret)
		printf("  Write Protect:%" PRIu64 "\n", (uint64_t)result);
	else if (ret > 0)
		printf("  ERROR: Write Protect:%x\n", ret);
	return 0;
}

static void print_hex(const uint8_t *x, int len)
{
	int i;

	for (i = 0; i < len; i++)
		printf("%02x", x[i]);
}

/* Safe display fallbacks for the lazy eui64/nguid/uuid getters below. */
static const uint8_t zero_eui64[8];
static const uint8_t zero_nguid[16];
static const unsigned char zero_uuid[NVME_UUID_LEN];

int main(int argc, char **argv)
{
	struct libnvme_global_ctx *ctx;
	libnvme_host_t h;
	libnvme_subsystem_t s;
	libnvme_ctrl_t c;
	libnvme_path_t p;
	libnvme_ns_t n;
	int err;
	const char *ctrl = "nvme4";
	const char *nqn_match = "testnqn";

	printf("Test filter for common loop back target\n");
	ctx = libnvme_create_global_ctx();
	if (!ctx)
		return 1;
	libnvme_set_logging_file(ctx, stdout);

	err = libnvme_scan_topology(ctx, nvme_match_subsysnqn_filter,
		(void *)nqn_match);
	if (err && !(err == ENOENT || err == EACCES)) {
		libnvme_free_global_ctx(ctx);
		return 1;
	}

	libnvme_for_each_host(ctx, h) {
		libnvme_for_each_subsystem(h, s) {
			printf("%s - NQN=%s\n", libnvme_subsystem_get_name(s),
			       libnvme_subsystem_get_subsysnqn(s));
			libnvme_subsystem_for_each_ctrl(s, c) {
				printf("  %s %s %s %s\n", libnvme_ctrl_get_name(c),
				       libnvme_ctrl_get_transport(c),
				       libnvme_ctrl_get_traddr(c),
				       libnvme_ctrl_get_state(c));
			}
		}
	}
	printf("\n");

	if (argc > 1)
		ctrl = argv[1];

	printf("Test scan specific controller\n");
	if (!libnvme_scan_ctrl(ctx, ctrl, &c)) {
		printf("%s %s %s %s\n", libnvme_ctrl_get_name(c),
			libnvme_ctrl_get_transport(c),
			libnvme_ctrl_get_traddr(c),
			libnvme_ctrl_get_state(c));
		libnvme_free_ctrl(c);
	}
	printf("\n");

	printf("Test walking the topology\n");
	libnvme_for_each_host(ctx, h) {
		libnvme_for_each_subsystem(h, s) {
			printf("%s - NQN=%s\n", libnvme_subsystem_get_name(s),
			       libnvme_subsystem_get_subsysnqn(s));
			libnvme_subsystem_for_each_ctrl(s, c) {
				printf(" `- %s %s %s %s\n",
				       libnvme_ctrl_get_name(c),
				       libnvme_ctrl_get_transport(c),
				       libnvme_ctrl_get_traddr(c),
				       libnvme_ctrl_get_state(c));

				libnvme_ctrl_for_each_ns(c, n) {
					char uuid_str[NVME_UUID_LEN_STRING];
					const unsigned char *uuid;
					const uint8_t *eui64;
					const uint8_t *nguid;
					int lba_size;
					uint64_t lba_count;
					enum nvme_csi csi;

					libnvme_ns_get_lba_size(n,
							&lba_size, 0);
					libnvme_ns_get_lba_count(n,
							&lba_count, 0);
					printf("   `- %s lba size:%d lba max:%" PRIu64 "\n",
					       libnvme_ns_get_name(n),
					       lba_size, lba_count);
					printf("      eui:");
					libnvme_ns_get_eui64(n, &eui64,
							zero_eui64);
					print_hex(eui64, 8);
					printf(" nguid:");
					libnvme_ns_get_nguid(n, &nguid,
							zero_nguid);
					print_hex(nguid, 16);
					libnvme_ns_get_uuid(n, &uuid,
							zero_uuid);
					libnvme_uuid_to_string(
							(unsigned char *)uuid,
							uuid_str);
					libnvme_ns_get_csi(n, &csi,
							NVME_CSI_NVM);
					printf(" uuid:%s csi:%d\n",
					       uuid_str, csi);
				}

				libnvme_ctrl_for_each_path(c, p) {
					const char *ana_state;

					libnvme_path_get_ana_state(p,
							&ana_state, "");
					printf("   `- %s %s\n",
					       libnvme_path_get_name(p),
					       ana_state);
				}
			}

			libnvme_subsystem_for_each_ns(s, n) {
				int lba_size;
				uint64_t lba_count;

				libnvme_ns_get_lba_size(n, &lba_size, 0);
				libnvme_ns_get_lba_count(n, &lba_count, 0);
				printf(" `- %s lba size:%d lba max:%" PRIu64 "\n",
				       libnvme_ns_get_name(n),
				       lba_size, lba_count);
			}
		}
		printf("\n");
	}

	printf("Test identification, logs, and features\n");
	libnvme_for_each_host(ctx, h) {
		libnvme_for_each_subsystem(h, s) {
			libnvme_subsystem_for_each_ctrl(s, c) {
				test_ctrl(c);
				printf("\n");
				libnvme_ctrl_for_each_ns(c, n) {
					test_namespace(n);
					printf("\n");
				}
			}
			libnvme_subsystem_for_each_ns(s, n) {
				test_namespace(n);
				printf("\n");
			}
		}
	}
	libnvme_free_global_ctx(ctx);

	return 0;
}
