// SPDX-License-Identifier: LGPL-2.1-or-later

#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include <ccan/array_size/array_size.h>
#include <ccan/endian/endian.h>

#include <libnvme.h>

#include "nvme/loopback.h"
#include "util.h"

#define PDU_SIZE NVME_LOG_PAGE_PDU_SIZE

static struct libnvme_transport_handle *test_hdl;

static void test_no_retries(void)
{
	struct nvme_ana_log log;
	__u32 len = sizeof(log);

	/* max_retries = 0 is nonsensical */
	check(libnvme_get_ana_log_atomic(test_hdl, false, false,
				      &log, &len, 0) == -EINVAL,
	      "get log page succeeded");
}

static void test_len_too_short(void)
{
	struct nvme_ana_log log;
	__u32 len = sizeof(log) - 1;

	/* Provided buffer doesn't have enough space to read the header */
	check(libnvme_get_ana_log_atomic(test_hdl, false, false,
				      &log, &len, 1) == -ENOSPC,
	      "get log page succeeded");
}

static void test_no_groups(void)
{
	struct nvme_ana_log header;
	/* The header reports no ANA groups. No additional commands needed. */
	struct libnvme_loopback_cmd mock_admin_cmd = {
		.opcode = nvme_admin_get_log_page,
		.data_len = sizeof(header),
		.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
		       | NVME_LOG_LID_ANA, /* LID */
		.out_data = &header,
	};
	struct nvme_ana_log log;
	__u32 len = sizeof(log);

	arbitrary(&log, sizeof(log));
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(0);
	libnvme_loopback_set_admin_cmds(test_hdl, &mock_admin_cmd, 1);
	check(!libnvme_get_ana_log_atomic(test_hdl, false, false, &log, &len, 1),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(&log, &header, sizeof(header), "incorrect header");
	check(len == sizeof(header),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(header));
}

static void test_one_group_rgo(void)
{
	struct nvme_ana_log header;
	struct nvme_ana_group_desc group;
	__u8 log_page[sizeof(header) + sizeof(group)];
	__u32 len = 123;
	size_t len_dwords = len / 4;
	/*
	 * Header and group fetched in a single Get Log Page command.
	 * Since only one command was issued, chgcnt doesn't need to be checked.
	 */
	struct libnvme_loopback_cmd mock_admin_cmd = {
		.opcode = nvme_admin_get_log_page,
		.data_len = len_dwords * 4,
		.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
		| NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
		       | NVME_LOG_LID_ANA, /* LID */
		.out_data = log_page,
		.out_data_len = sizeof(log_page),
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(1);
	arbitrary(&group, sizeof(group));
	group.nnsids = cpu_to_le32(0);
	memcpy(log_page, &header, sizeof(header));
	memcpy(log_page + sizeof(header), &group, sizeof(group));
	libnvme_loopback_set_admin_cmds(test_hdl, &mock_admin_cmd, 1);
	check(!libnvme_get_ana_log_atomic(test_hdl, false, true, log, &len, 1),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page, sizeof(log_page), "incorrect log page");
	check(len == sizeof(log_page),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
	free(log);
}

static void test_one_group_nsids(void)
{
	struct nvme_ana_log header;
	struct nvme_ana_group_desc group;
	__le32 nsids[3];
	__u8 log_page[sizeof(header) + sizeof(group) + sizeof(nsids)];
	__u32 len = 124;
	size_t len_dwords = len / 4;
	/*
	 * Header, group, and NSIDs fetched in a single Get Log Page command.
	 * Since only one command was issued, chgcnt doesn't need to be checked.
	 */
	struct libnvme_loopback_cmd mock_admin_cmd = {
		.opcode = nvme_admin_get_log_page,
		.data_len = len_dwords * 4,
		.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
		       | NVME_LOG_LID_ANA, /* LID */
		.out_data = log_page,
		.out_data_len = sizeof(log_page),
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(1);
	arbitrary(&group, sizeof(group));
	group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
	arbitrary(nsids, sizeof(nsids));
	memcpy(log_page, &header, sizeof(header));
	memcpy(log_page + sizeof(header), &group, sizeof(group));
	memcpy(log_page + sizeof(header) + sizeof(group), nsids, sizeof(nsids));
	libnvme_loopback_set_admin_cmds(test_hdl, &mock_admin_cmd, 1);
	check(!libnvme_get_ana_log_atomic(test_hdl, false, false, log, &len, 1),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page, sizeof(log_page), "incorrect log page");
	check(len == sizeof(log_page),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
	free(log);
}

static void test_multiple_groups_rgo(void)
{
	struct nvme_ana_log header;
	struct nvme_ana_group_desc groups[3];
	__u8 log_page[sizeof(header) + sizeof(groups)];
	__u32 len = 125;
	size_t len_dwords = len / 4;
	/*
	 * Header and groups fetched in a single Get Log Page command.
	 * Since only one command was issued, chgcnt doesn't need to be checked.
	 */
	struct libnvme_loopback_cmd mock_admin_cmd = {
		.opcode = nvme_admin_get_log_page,
		.data_len = len_dwords * 4,
		.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
		       | 1 << 15 /* RAE */
		       | NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
		       | NVME_LOG_LID_ANA, /* LID */
		.out_data = log_page,
		.out_data_len = sizeof(log_page),
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(ARRAY_SIZE(groups));
	arbitrary(groups, sizeof(groups));
	for (size_t i = 0; i < ARRAY_SIZE(groups); i++)
		groups[i].nnsids = cpu_to_le32(0);
	memcpy(log_page, &header, sizeof(header));
	memcpy(log_page + sizeof(header), groups, sizeof(groups));
	libnvme_loopback_set_admin_cmds(test_hdl, &mock_admin_cmd, 1);
	check(!libnvme_get_ana_log_atomic(test_hdl, true, true, log, &len, 1),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page, sizeof(log_page), "incorrect log page");
	check(len == sizeof(log_page),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
	free(log);
}

static void test_multiple_groups_nsids(void)
{
	struct nvme_ana_log header;
	struct nvme_ana_group_desc group1;
	__le32 nsids1[3];
	struct nvme_ana_group_desc group2;
	__le32 nsids2[2];
	struct nvme_ana_group_desc group3;
	__le32 nsids3[1];
	__u8 log_page[sizeof(header) +
		      sizeof(group1) + sizeof(nsids1) +
		      sizeof(group2) + sizeof(nsids2) +
		      sizeof(group3) + sizeof(nsids3)];
	__u32 len = 456;
	size_t len_dwords = len / 4;
	/*
	 * Header, group, and NSIDs fetched in a single Get Log Page command.
	 * Since only one command was issued, chgcnt doesn't need to be checked.
	 */
	struct libnvme_loopback_cmd mock_admin_cmd = {
		.opcode = nvme_admin_get_log_page,
		.data_len = len_dwords * 4,
		.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
		       | NVME_LOG_LID_ANA, /* LID */
		.out_data = log_page,
		.out_data_len = sizeof(log_page),
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(3);
	arbitrary(&group1, sizeof(group1));
	group1.nnsids = cpu_to_le32(ARRAY_SIZE(nsids1));
	arbitrary(nsids1, sizeof(nsids1));
	arbitrary(&group2, sizeof(group2));
	group2.nnsids = cpu_to_le32(ARRAY_SIZE(nsids2));
	arbitrary(nsids2, sizeof(nsids2));
	arbitrary(&group3, sizeof(group3));
	group3.nnsids = cpu_to_le32(ARRAY_SIZE(nsids3));
	arbitrary(nsids3, sizeof(nsids3));
	memcpy(log_page, &header, sizeof(header));
	memcpy(log_page + sizeof(header), &group1, sizeof(group1));
	memcpy(log_page + sizeof(header) + sizeof(group1),
	       nsids1, sizeof(nsids1));
	memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1),
	       &group2, sizeof(group2));
	memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1) +
			  sizeof(group2),
	       nsids2, sizeof(nsids2));
	memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1) +
			  sizeof(group2) + sizeof(nsids2),
	       &group3, sizeof(group3));
	memcpy(log_page + sizeof(header) + sizeof(group1) + sizeof(nsids1) +
			  sizeof(group2) + sizeof(nsids2) + sizeof(group3),
	       nsids3, sizeof(nsids3));
	libnvme_loopback_set_admin_cmds(test_hdl, &mock_admin_cmd, 1);
	check(!libnvme_get_ana_log_atomic(test_hdl, false, false, log, &len, 1),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page, sizeof(log_page), "incorrect log page");
	check(len == sizeof(log_page),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
	free(log);
}

static void test_long_log(void)
{
	struct nvme_ana_log header;
	struct nvme_ana_group_desc group;
	__le32 nsids[PDU_SIZE * 2 / sizeof(*group.nsids)];
	__u8 log_page[sizeof(header) + sizeof(group) + sizeof(nsids)];
	__u32 len = PDU_SIZE * 4;
	/*
	 * Get Log Page is issued for 4 KB, returning the header (with 1 group),
	 * the group (with 2048 NSIDs) and the start of its NSIDs.
	 * Another Get Log page command is issued for the next 1024 NSIDs.
	 * Another Get Log page command is issued for the last NSIDs.
	 * Header is fetched again to verify chgcnt hasn't changed.
	 */
	struct libnvme_loopback_cmd mock_admin_cmds[] = {
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.cdw12 = PDU_SIZE, /* LPOL */
			.out_data = log_page + PDU_SIZE,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.cdw12 = PDU_SIZE * 2, /* LPOL */
			.out_data = log_page + PDU_SIZE * 2,
			.out_data_len = sizeof(log_page) - PDU_SIZE * 2,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page,
		},
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(1);
	arbitrary(&group, sizeof(group));
	group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
	arbitrary(nsids, sizeof(nsids));
	memcpy(log_page, &header, sizeof(header));
	memcpy(log_page + sizeof(header), &group, sizeof(group));
	memcpy(log_page + sizeof(header) + sizeof(group), nsids, sizeof(nsids));
	libnvme_loopback_set_admin_cmds(test_hdl, mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
	check(!libnvme_get_ana_log_atomic(test_hdl, true, false, log, &len, 1),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page, sizeof(log_page), "incorrect log page");
	check(len == sizeof(log_page),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page));
	free(log);
}

static void test_chgcnt_change(void)
{
	struct nvme_ana_log header1;
	struct nvme_ana_group_desc groups1[PDU_SIZE / sizeof(*header1.descs)];
	__u8 log_page1[sizeof(header1) + sizeof(groups1)];
	struct nvme_ana_log header2;
	struct nvme_ana_group_desc group2;
	__u8 log_page2[sizeof(header2) + sizeof(group2)];
	__u32 len = PDU_SIZE + 126;
	size_t remainder_len_dwords = (len - PDU_SIZE) / 4;
	/*
	 * Get Log Page is issued for 4 KB,
	 * returning the header (with 128 groups), and the start of the groups.
	 * Get Log Page is issued for the rest of the groups.
	 * Get Log Page is issued for the first 4 KB again to check chgcnt.
	 * chgcnt has changed, but there is only 1 group now,
	 * which was already fetched with the header.
	 */
	struct libnvme_loopback_cmd mock_admin_cmds[] = {
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page1,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = remainder_len_dwords * 4,
			.cdw10 = (remainder_len_dwords - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
			       | NVME_LOG_LID_ANA, /* LID */
			.cdw12 = PDU_SIZE, /* LPOL */
			.out_data = log_page1 + PDU_SIZE,
			.out_data_len = sizeof(log_page1) - PDU_SIZE,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_ANA_LSP_RGO_GROUPS_ONLY << 8 /* LSP */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page2,
			.out_data_len = sizeof(log_page2),
		},
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header1, sizeof(header1));
	header1.ngrps = cpu_to_le16(ARRAY_SIZE(groups1));
	arbitrary(&groups1, sizeof(groups1));
	for (size_t i = 0; i < ARRAY_SIZE(groups1); i++)
		groups1[i].nnsids = cpu_to_le32(0);
	memcpy(log_page1, &header1, sizeof(header1));
	memcpy(log_page1 + sizeof(header1), groups1, sizeof(groups1));
	arbitrary(&header2, sizeof(header2));
	header2.ngrps = cpu_to_le16(1);
	arbitrary(&group2, sizeof(group2));
	group2.nnsids = cpu_to_le32(0);
	memcpy(log_page2, &header2, sizeof(header2));
	memcpy(log_page2 + sizeof(header2), &group2, sizeof(group2));
	libnvme_loopback_set_admin_cmds(test_hdl, mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
	check(!libnvme_get_ana_log_atomic(test_hdl, true, true, log, &len, 2),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page2, sizeof(log_page2), "incorrect log page");
	check(len == sizeof(log_page2),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page2));
	free(log);
}

static void test_buffer_too_short_chgcnt_change(void)
{
	struct nvme_ana_log header1;
	struct nvme_ana_group_desc group1_1;
	__le32 nsids1[PDU_SIZE / sizeof(*group1_1.nsids)];
	struct nvme_ana_group_desc group1_2;
	__u8 log_page1[sizeof(header1) +
		       sizeof(group1_1) + sizeof(nsids1) + sizeof(group1_2)];
	struct nvme_ana_log header2;
	struct nvme_ana_group_desc group2;
	__le32 nsid2;
	uint8_t log_page2[sizeof(header2) + sizeof(group2) + sizeof(nsid2)];
	__u32 len = PDU_SIZE + 123;
	size_t remainder_len_dwords = (len - PDU_SIZE) / 4;
	/*
	 * Get Log Page issued for 4 KB, returning the header (with 2 groups),
	 * the first group (with 1024 NSIDs), and the start of the NSIDs.
	 * Get Log Page is issued for the rest of the NSIDs and the second group.
	 * The second group contains garbage, making the log exceed the buffer.
	 * The first 4 KB is fetched again, returning a header with a new chgcnt
	 * and a group with one NSID.
	 */
	struct libnvme_loopback_cmd mock_admin_cmds[] = {
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page1,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = remainder_len_dwords * 4,
			.cdw10 = (remainder_len_dwords - 1) << 16 /* NUMDL */
			       | NVME_LOG_LID_ANA, /* LID */
			.cdw12 = PDU_SIZE, /* LPOL */
			.out_data = log_page1 + PDU_SIZE,
			.out_data_len = sizeof(log_page1) - PDU_SIZE,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page2,
			.out_data_len = sizeof(log_page2),
		},
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header1, sizeof(header1));
	header1.ngrps = cpu_to_le16(2);
	arbitrary(&group1_1, sizeof(group1_1));
	group1_1.nnsids = cpu_to_le32(ARRAY_SIZE(nsids1));
	arbitrary(nsids1, sizeof(nsids1));
	memset(&group1_2, -1, sizeof(group1_2));
	memcpy(log_page1, &header1, sizeof(header1));
	memcpy(log_page1 + sizeof(header1), &group1_1, sizeof(group1_1));
	memcpy(log_page1 + sizeof(header1) + sizeof(group1_1),
	       nsids1, sizeof(nsids1));
	memcpy(log_page1 + sizeof(header1) + sizeof(group1_1) + sizeof(nsids1),
	       &group1_2, sizeof(group1_2));
	arbitrary(&header2, sizeof(header2));
	header2.ngrps = cpu_to_le16(1);
	arbitrary(&group2, sizeof(group2));
	group2.nnsids = cpu_to_le32(1);
	arbitrary(&nsid2, sizeof(nsid2));
	memcpy(log_page2, &header2, sizeof(header2));
	memcpy(log_page2 + sizeof(header2), &group2, sizeof(group2));
	memcpy(log_page2 + sizeof(header2) + sizeof(group2),
	       &nsid2, sizeof(nsid2));
	libnvme_loopback_set_admin_cmds(test_hdl, mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
	check(!libnvme_get_ana_log_atomic(test_hdl, false, false, log, &len, 2),
	      "get log page failed");
	libnvme_loopback_end(test_hdl);
	cmp(log, log_page2, sizeof(log_page2), "incorrect log page");
	check(len == sizeof(log_page2),
	      "got len %" PRIu32 ", expected %zu", len, sizeof(log_page2));
	free(log);
}

static void test_chgcnt_max_retries(void)
{
	struct nvme_ana_log header1, header2, header3;
	struct nvme_ana_group_desc group;
	__le32 nsids[PDU_SIZE / sizeof(*group.nsids)];
	__u8 log_page1[sizeof(header1) + sizeof(group) + sizeof(nsids)],
	     log_page2[sizeof(header2) + sizeof(group) + sizeof(nsids)];
	__u32 len = PDU_SIZE * 2;
	/*
	 * Get Log Page is issued for 4 KB, returning the header (with 1 group),
	 * the group (with 1024 NSIDs), and the start of the NSIDs.
	 * Get Log Page is issued for the rest of the NSIDs.
	 * Get Log Page is issued for the first 4 KB again to check chgcnt.
	 * chgcnt has changed and there is still 1 group with 1024 NSIDs.
	 * Get Log Page is issued for the rest of the NSIDs.
	 * Get Log Page is issued for the first 4 KB again to check chgcnt.
	 * chgcnt has changed again.
	 * This exceeds max_retries = 2 so nvme_get_ana_log() exits with EAGAIN.
	 */
	struct libnvme_loopback_cmd mock_admin_cmds[] = {
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page1,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.cdw12 = PDU_SIZE, /* LPOL */
			.out_data = log_page1 + PDU_SIZE,
			.out_data_len = sizeof(log_page1) - PDU_SIZE,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = log_page2,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.cdw12 = PDU_SIZE, /* LPOL */
			.out_data = log_page2 + PDU_SIZE,
			.out_data_len = sizeof(log_page2) - PDU_SIZE,
		},
		{
			.opcode = nvme_admin_get_log_page,
			.data_len = PDU_SIZE,
			.cdw10 = (PDU_SIZE / 4 - 1) << 16 /* NUMDL */
			       | 1 << 15 /* RAE */
			       | NVME_LOG_LID_ANA, /* LID */
			.out_data = &header3,
			.out_data_len = sizeof(header3),
		},
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header1, sizeof(header1));
	header1.ngrps = cpu_to_le16(1);
	arbitrary(&header2, sizeof(header2));
	header2.ngrps = cpu_to_le16(1);
	arbitrary(&header3, sizeof(header3));
	header3.ngrps = cpu_to_le16(0);
	arbitrary(&group, sizeof(group));
	group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
	arbitrary(nsids, sizeof(nsids));
	memcpy(log_page1, &header1, sizeof(header1));
	memcpy(log_page1 + sizeof(header1), &group, sizeof(group));
	memcpy(log_page1 + sizeof(header1) + sizeof(group),
	       nsids, sizeof(nsids));
	memcpy(log_page2, &header2, sizeof(header2));
	memcpy(log_page2 + sizeof(header2), &group, sizeof(group));
	memcpy(log_page2 + sizeof(header2) + sizeof(group),
	       nsids, sizeof(nsids));
	libnvme_loopback_set_admin_cmds(test_hdl, mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
	check(libnvme_get_ana_log_atomic(test_hdl, true, false, log, &len, 2) == -EAGAIN,
	      "get log page succeeded");
	libnvme_loopback_end(test_hdl);
	free(log);
}

static void test_buffer_too_short(void)
{
	struct nvme_ana_log header;
	struct nvme_ana_group_desc group;
	__le32 nsids[20];
	__u8 log_page[sizeof(header) + sizeof(group) + sizeof(nsids)];
	__u32 len = 123;
	__u32 len_dwords = len / 4;
	/*
	 * Header, group, and NSIDs fetched in a single Get Log Page command.
	 * This length exceeds the provided buffer.
	 * Only one command was issued, so the log page couldn't have changed.
	 * nvme_get_ana_log() returns ENOSPC because the buffer is too small.
	 */
	struct libnvme_loopback_cmd mock_admin_cmd = {
		.opcode = nvme_admin_get_log_page,
		.data_len = len_dwords * 4,
		.cdw10 = (len_dwords - 1) << 16 /* NUMDL */
		       | 1 << 15 /* RAE */
		       | NVME_LOG_LID_ANA, /* LID */
		.out_data = log_page,
	};
	struct nvme_ana_log *log = malloc(len);

	arbitrary(log, len);
	arbitrary(&header, sizeof(header));
	header.ngrps = cpu_to_le16(1);
	arbitrary(&group, sizeof(group));
	group.nnsids = cpu_to_le32(ARRAY_SIZE(nsids));
	arbitrary(nsids, sizeof(nsids));
	memcpy(log_page, &header, sizeof(header));
	memcpy(log_page + sizeof(header), &group, sizeof(group));
	memcpy(log_page + sizeof(header) + sizeof(group), nsids, sizeof(nsids));
	libnvme_loopback_set_admin_cmds(test_hdl, &mock_admin_cmd, 1);
	check(libnvme_get_ana_log_atomic(test_hdl, true, false, log, &len, 2) == -ENOSPC,
	      "log page succeeded");
	libnvme_loopback_end(test_hdl);
	free(log);
}

static void run_test(const char *test_name, void (*test_fn)(void))
{
	printf("Running test %s...", test_name);
	fflush(stdout);
	test_fn();
	puts(" OK");
}

#define RUN_TEST(name) run_test(#name, test_ ## name)

int main(void)
{
	struct libnvme_global_ctx *ctx = libnvme_create_global_ctx();
	libnvme_set_logging_file(ctx, stdout);

	check(!libnvme_open_loopback(ctx, &test_hdl),
	      "opening test link failed");

	RUN_TEST(no_retries);
	RUN_TEST(len_too_short);
	RUN_TEST(no_groups);
	RUN_TEST(one_group_rgo);
	RUN_TEST(one_group_nsids);
	RUN_TEST(multiple_groups_rgo);
	RUN_TEST(multiple_groups_nsids);
	RUN_TEST(long_log);
	RUN_TEST(chgcnt_change);
	RUN_TEST(buffer_too_short_chgcnt_change);
	RUN_TEST(chgcnt_max_retries);
	RUN_TEST(buffer_too_short);

	libnvme_free_global_ctx(ctx);
}
