summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/ioctl/discovery.c428
-rw-r--r--test/ioctl/features.c1604
-rw-r--r--test/ioctl/identify.c572
-rw-r--r--test/ioctl/meson.build42
-rw-r--r--test/ioctl/mock.c174
-rw-r--r--test/ioctl/mock.h104
-rw-r--r--test/ioctl/util.c65
-rw-r--r--test/ioctl/util.h19
-rw-r--r--test/meson.build18
-rw-r--r--test/mi-mctp.c118
-rw-r--r--test/mi.c17
-rw-r--r--test/mock-ifaddrs.c123
-rw-r--r--test/test-util.c1
-rw-r--r--test/test.c18
-rw-r--r--test/tree.c1115
15 files changed, 4274 insertions, 144 deletions
diff --git a/test/ioctl/discovery.c b/test/ioctl/discovery.c
new file mode 100644
index 0000000..f5f6f51
--- /dev/null
+++ b/test/ioctl/discovery.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include <libnvme.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+#include "../../src/nvme/private.h"
+#include "mock.h"
+#include "util.h"
+
+#define TEST_FD 0xFD
+#define HEADER_LEN 20
+
+static void arbitrary_ascii_string(size_t max_len, char *str, char *log_str)
+{
+ size_t len;
+ size_t i;
+
+ len = arbitrary_range(max_len + 1);
+ for (i = 0; i < len; i++) {
+ /*
+ * ASCII strings shall contain only code values 20h through 7Eh.
+ * Exclude 20h (space) because it ends the string.
+ */
+ str[i] = log_str[i] = arbitrary_range(0x7E - 0x20) + 0x20 + 1;
+ }
+ for (i = len; i < max_len; i++) {
+ str[i] = '\0';
+ log_str[i] = ' ';
+ }
+}
+
+static void arbitrary_entry(struct nvmf_disc_log_entry *entry,
+ struct nvmf_disc_log_entry *log_entry)
+{
+ arbitrary(entry, sizeof(*entry));
+ memcpy(log_entry, entry, sizeof(*entry));
+ arbitrary_ascii_string(
+ sizeof(entry->trsvcid), entry->trsvcid, log_entry->trsvcid);
+ arbitrary_ascii_string(
+ sizeof(entry->traddr), entry->traddr, log_entry->traddr);
+}
+
+static void arbitrary_entries(size_t len,
+ struct nvmf_disc_log_entry *entries,
+ struct nvmf_disc_log_entry *log_entries)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ arbitrary_entry(&entries[i], &log_entries[i]);
+}
+
+static void test_no_entries(nvme_ctrl_t c)
+{
+ struct nvmf_discovery_log header = {};
+ /* No entries to fetch after fetching the header */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header, sizeof(header), "incorrect header");
+ free(log);
+}
+
+static void test_four_entries(nvme_ctrl_t c)
+{
+ size_t num_entries = 4;
+ struct nvmf_disc_log_entry entries[num_entries];
+ struct nvmf_disc_log_entry log_entries[num_entries];
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(num_entries)};
+ /*
+ * All 4 entries should be fetched at once
+ * followed by the header again (to ensure genctr hasn't changed)
+ */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entries),
+ .cdw10 = (sizeof(entries) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .out_data = log_entries,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary_entries(num_entries, entries, log_entries);
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header, sizeof(header), "incorrect header");
+ cmp(log->entries, entries, sizeof(entries), "incorrect entries");
+ free(log);
+}
+
+static void test_five_entries(nvme_ctrl_t c)
+{
+ size_t num_entries = 5;
+ struct nvmf_disc_log_entry entries[num_entries];
+ struct nvmf_disc_log_entry log_entries[num_entries];
+ size_t first_entries = 4;
+ size_t first_data_len = first_entries * sizeof(*entries);
+ size_t second_entries = num_entries - first_entries;
+ size_t second_data_len = second_entries * sizeof(*entries);
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(num_entries)};
+ /*
+ * The first 4 entries (4 KB) are fetched together,
+ * followed by last entry separately.
+ * Finally, the header is fetched again to check genctr.
+ */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = first_data_len,
+ .cdw10 = (first_data_len / 4 - 1) << 16 /* NUMDL */
+ | 1 << 15 /* RAE */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .out_data = log_entries,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = second_data_len,
+ .cdw10 = (second_data_len / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header) + first_data_len, /* LPOL */
+ .out_data = log_entries + first_entries,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary_entries(num_entries, entries, log_entries);
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header, sizeof(header), "incorrect header");
+ cmp(log->entries, entries, sizeof(entries), "incorrect entries");
+ free(log);
+}
+
+static void test_genctr_change(nvme_ctrl_t c)
+{
+ struct nvmf_disc_log_entry entries1[1];
+ struct nvmf_discovery_log header1 = {
+ .numrec = cpu_to_le64(ARRAY_SIZE(entries1)),
+ };
+ size_t num_entries2 = 2;
+ struct nvmf_disc_log_entry entries2[num_entries2];
+ struct nvmf_disc_log_entry log_entries2[num_entries2];
+ struct nvmf_discovery_log header2 = {
+ .genctr = cpu_to_le64(1),
+ .numrec = cpu_to_le64(num_entries2),
+ };
+ /*
+ * genctr changes after the entries are fetched the first time,
+ * so the log page entries are refetched
+ */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header1,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entries1),
+ .cdw10 = (sizeof(entries1) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* NUMDL */
+ .cdw12 = sizeof(header1), /* LPOL */
+ .out_data = entries1,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header2,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entries2),
+ .cdw10 = (sizeof(entries2) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header2), /* LPOL */
+ .out_data = log_entries2,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header2,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary(entries1, sizeof(entries1));
+ arbitrary_entries(num_entries2, entries2, log_entries2);
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 2) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header2, sizeof(header2), "incorrect header");
+ cmp(log->entries, entries2, sizeof(entries2), "incorrect entries");
+ free(log);
+}
+
+static void test_max_retries(nvme_ctrl_t c)
+{
+ struct nvmf_disc_log_entry entry;
+ struct nvmf_discovery_log header1 = {.numrec = cpu_to_le64(1)};
+ struct nvmf_discovery_log header2 = {
+ .genctr = cpu_to_le64(1),
+ .numrec = cpu_to_le64(1),
+ };
+ struct nvmf_discovery_log header3 = {
+ .genctr = cpu_to_le64(2),
+ .numrec = cpu_to_le64(1),
+ };
+ /* genctr changes in both attempts, hitting the max retries (2) */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header1,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entry),
+ .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header1), /* LPOL */
+ .out_data = &entry,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header2,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entry),
+ .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header2), /* LPOL */
+ .out_data = &entry,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header3,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary(&entry, sizeof(entry));
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 2) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(errno == EAGAIN, "discovery failed: %m");
+ check(!log, "unexpected log page returned");
+}
+
+static void test_header_error(nvme_ctrl_t c)
+{
+ /* Stop after an error in fetching the header the first time */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .err = NVME_SC_INVALID_OPCODE,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(!log, "unexpected log page returned");
+}
+
+static void test_entries_error(nvme_ctrl_t c)
+{
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(1)};
+ size_t entry_size = sizeof(struct nvmf_disc_log_entry);
+ /* Stop after an error in fetching the entries */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = entry_size,
+ .cdw10 = (entry_size / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .err = -EIO,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(errno == EIO, "discovery failed: %m");
+ check(!log, "unexpected log page returned");
+}
+
+static void test_genctr_error(nvme_ctrl_t c)
+{
+ struct nvmf_disc_log_entry entry;
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(1)};
+ /* Stop after an error in refetching the header */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entry),
+ .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .out_data = &entry,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .err = NVME_SC_INTERNAL,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary(&entry, sizeof(entry));
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(!log, "unexpected log page returned");
+}
+
+static void run_test(const char *test_name, void (*test_fn)(nvme_ctrl_t))
+{
+ struct nvme_ctrl c = {.fd = TEST_FD};
+
+ printf("Running test %s...", test_name);
+ fflush(stdout);
+ check(asprintf(&c.name, "%s_ctrl", test_name) >= 0, "asprintf() failed");
+ test_fn(&c);
+ free(c.name);
+ puts(" OK");
+}
+
+#define RUN_TEST(name) run_test(#name, test_ ## name)
+
+int main(void)
+{
+ set_mock_fd(TEST_FD);
+ RUN_TEST(no_entries);
+ RUN_TEST(four_entries);
+ RUN_TEST(five_entries);
+ RUN_TEST(genctr_change);
+ RUN_TEST(max_retries);
+ RUN_TEST(header_error);
+ RUN_TEST(entries_error);
+ RUN_TEST(genctr_error);
+}
diff --git a/test/ioctl/features.c b/test/ioctl/features.c
new file mode 100644
index 0000000..7386497
--- /dev/null
+++ b/test/ioctl/features.c
@@ -0,0 +1,1604 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include <libnvme.h>
+
+#include <errno.h>
+#include <inttypes.h>
+
+#include "mock.h"
+#include "util.h"
+
+#define TEST_FD 0xFD
+#define TEST_TIMEOUT 1234
+#define TEST_NSID 0x89ABCDEF
+#define TEST_CDW11 0x11111111
+#define TEST_CDW12 0x12121212
+#define TEST_CDW13 0x13131313
+#define TEST_CDW15 0x15151515
+#define TEST_UUIDX 0b1001110
+#define TEST_FID 0xFE
+#define TEST_RESULT 0x12345678
+#define TEST_SEL NVME_GET_FEATURES_SEL_SAVED
+#define TEST_SC NVME_SC_INVALID_FIELD
+
+static void test_set_features(void)
+{
+ uint32_t result = 0;
+ uint8_t data[256];
+ struct nvme_set_features_args args = {
+ .result = &result,
+ .data = data,
+ .args_size = sizeof(args),
+ .fd = TEST_FD,
+ .timeout = TEST_TIMEOUT,
+ .nsid = TEST_NSID,
+ .cdw11 = TEST_CDW11,
+ .cdw12 = TEST_CDW12,
+ .cdw13 = TEST_CDW13,
+ .cdw15 = TEST_CDW15,
+ .data_len = sizeof(data),
+ .save = true,
+ .uuidx = TEST_UUIDX,
+ .fid = TEST_FID,
+ };
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .in_data = data,
+ .data_len = sizeof(data),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .cdw12 = TEST_CDW12,
+ .cdw13 = TEST_CDW13,
+ .cdw14 = TEST_UUIDX,
+ .cdw15 = TEST_CDW15,
+ .timeout_ms = TEST_TIMEOUT,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features(&args);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_features(void)
+{
+ uint32_t result = 0;
+ uint8_t data[256], get_data[sizeof(data)] = {};
+ struct nvme_get_features_args args = {
+ .result = &result,
+ .data = get_data,
+ .args_size = sizeof(args),
+ .fd = TEST_FD,
+ .timeout = TEST_TIMEOUT,
+ .nsid = TEST_NSID,
+ .sel = TEST_SEL,
+ .cdw11 = TEST_CDW11,
+ .data_len = sizeof(data),
+ .fid = TEST_FID,
+ .uuidx = TEST_UUIDX,
+ };
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(data),
+ .cdw10 = TEST_SEL << 8 | TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .cdw14 = TEST_UUIDX,
+ .timeout_ms = TEST_TIMEOUT,
+ .out_data = data,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features(&args);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(get_data, data, sizeof(data), "incorrect data");
+}
+
+static void test_set_features_data(void)
+{
+ uint8_t data[128];
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .in_data = data,
+ .data_len = sizeof(data),
+ .cdw10 = TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_data(
+ TEST_FD, TEST_FID, TEST_NSID, TEST_CDW11, false,
+ sizeof(data), data, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_features_data(void)
+{
+ uint8_t data[128], get_data[sizeof(data)] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(data),
+ .cdw10 = NVME_GET_FEATURES_SEL_CURRENT << 8 | TEST_FID,
+ .out_data = data,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_data(
+ TEST_FD, TEST_FID, TEST_NSID, sizeof(data), get_data, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(get_data, data, sizeof(data), "incorrect data");
+}
+
+static void test_set_features_simple(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_simple(
+ TEST_FD, TEST_FID, TEST_NSID, TEST_CDW11, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_features_simple(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_GET_FEATURES_SEL_CURRENT << 8 | TEST_FID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_simple(TEST_FD, TEST_FID, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_arbitration(void)
+{
+ uint8_t HPW = 0xAA, MPW = 0xBB, LPW = 0xCC, AB = 0b111;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_ARBITRATION,
+ .cdw11 = (uint32_t)HPW << 24 | MPW << 16 | LPW << 8 | AB,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_arbitration(
+ TEST_FD, AB, LPW, MPW, HPW, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_arbitration(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ARBITRATION,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_arbitration(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_power_mgmt(void)
+{
+ uint8_t PS = 0b10101, WH = 0b101;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_POWER_MGMT,
+ .cdw11 = WH << 5 | PS,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_power_mgmt(TEST_FD, PS, WH, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_power_mgmt(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_POWER_MGMT,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_power_mgmt(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_lba_range(void)
+{
+ uint8_t NUM = 64;
+ struct nvme_lba_range_type range_types;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .in_data = &range_types,
+ .data_len = sizeof(range_types),
+ .cdw10 = NVME_FEAT_FID_LBA_RANGE,
+ .cdw11 = NUM - 1,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&range_types, sizeof(range_types));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_lba_range(
+ TEST_FD, TEST_NSID, NUM, false, &range_types, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_lba_range(void)
+{
+ struct nvme_lba_range_type range_types, get_range_types = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(range_types),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_LBA_RANGE,
+ .out_data = &range_types,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&range_types, sizeof(range_types));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_lba_range2(
+ TEST_FD, TEST_SEL, TEST_NSID, &get_range_types, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_range_types, &range_types, sizeof(range_types),
+ "incorrect LBA range types");
+}
+
+static void test_set_temp_thresh(void)
+{
+ uint16_t TMPTH = 0xFEDC;
+ uint8_t TMPSEL = 0x8;
+ enum nvme_feat_tmpthresh_thsel THSEL =
+ NVME_FEATURE_TEMPTHRESH_THSEL_UNDER;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_TEMP_THRESH,
+ .cdw11 = THSEL << 20 | TMPSEL << 16 | TMPTH,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_temp_thresh(
+ TEST_FD, TMPTH, TMPSEL, THSEL, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_temp_thresh(void)
+{
+ /*
+ * nvme_get_features_temp_thresh() doesn't support
+ * specifying TMPSEL and THSEL
+ */
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_TEMP_THRESH,
+ .cdw11 = NVME_FEATURE_TEMPTHRESH_THSEL_OVER << 20,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_temp_thresh(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_err_recovery(void)
+{
+ uint16_t TLER = 0xCDEF;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_ERR_RECOVERY,
+ .cdw11 = 1 << 16 /* DULBE */
+ | TLER,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_err_recovery(
+ TEST_FD, TEST_NSID, TLER, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_err_recovery(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ERR_RECOVERY,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_err_recovery2(
+ TEST_FD, TEST_SEL, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_volatile_wc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_VOLATILE_WC,
+ .cdw11 = 1 << 0, /* WCE */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_volatile_wc(TEST_FD, true, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_volatile_wc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8
+ | NVME_FEAT_FID_VOLATILE_WC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_volatile_wc(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_num_queues(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NUM_QUEUES,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_num_queues(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_irq_coalesce(void)
+{
+ uint8_t THR = 0xAB, TIME = 0xCD;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_IRQ_COALESCE,
+ .cdw11 = TIME << 8 | THR,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_irq_coalesce(
+ TEST_FD, THR, TIME, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_irq_coalesce(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IRQ_COALESCE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_irq_coalesce(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_irq_config(void)
+{
+ uint16_t IV = 0x1234;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_IRQ_CONFIG,
+ .cdw11 = 1 << 16 /* CD */
+ | IV,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_irq_config(TEST_FD, IV, true, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_irq_config(void)
+{
+ uint16_t IV = 0x5678;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IRQ_CONFIG,
+ .cdw11 = IV,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_irq_config(TEST_FD, TEST_SEL, IV, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_write_atomic(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_WRITE_ATOMIC,
+ .cdw11 = 1 << 0, /* DN */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_write_atomic(TEST_FD, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_write_atomic(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_WRITE_ATOMIC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_write_atomic(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_async_event(void)
+{
+ uint32_t EVENTS = 0x87654321;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_ASYNC_EVENT,
+ .cdw11 = EVENTS,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_async_event(TEST_FD, EVENTS, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_async_event(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ASYNC_EVENT,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_async_event(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_auto_pst(void)
+{
+ struct nvme_feat_auto_pst apst;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &apst,
+ .data_len = sizeof(apst),
+ .cdw10 = NVME_FEAT_FID_AUTO_PST,
+ .cdw11 = 1 << 0, /* APSTE */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&apst, sizeof(apst));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_auto_pst(TEST_FD, true, false, &apst, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_auto_pst(void)
+{
+ struct nvme_feat_auto_pst apst, get_apst = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(apst),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_AUTO_PST,
+ .out_data = &apst,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&apst, sizeof(apst));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_auto_pst(TEST_FD, TEST_SEL, &get_apst, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_apst, &apst, sizeof(apst), "incorrect apst");
+}
+
+static void test_get_host_mem_buf(void)
+{
+ struct nvme_host_mem_buf_attrs attrs, get_attrs = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(attrs),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_MEM_BUF,
+ .out_data = &attrs,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&attrs, sizeof(attrs));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_mem_buf2(
+ TEST_FD, TEST_SEL, &get_attrs, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_attrs, &attrs, sizeof(attrs), "incorrect attrs");
+}
+
+static void test_set_timestamp(void)
+{
+ struct nvme_timestamp ts = {.timestamp = {1, 2, 3, 4, 5, 6}};
+ uint64_t timestamp = ts.timestamp[0]
+ | (uint64_t) ts.timestamp[1] << 8
+ | (uint64_t) ts.timestamp[2] << 16
+ | (uint64_t) ts.timestamp[3] << 24
+ | (uint64_t) ts.timestamp[4] << 32
+ | (uint64_t) ts.timestamp[5] << 40;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &ts,
+ .data_len = sizeof(ts),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_TIMESTAMP,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_timestamp(TEST_FD, true, timestamp);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_timestamp(void)
+{
+ struct nvme_timestamp ts, get_ts = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(ts),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_TIMESTAMP,
+ .out_data = &ts,
+ };
+ int err;
+
+ arbitrary(&ts, sizeof(ts));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_timestamp(TEST_FD, TEST_SEL, &get_ts);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ cmp(&get_ts, &ts, sizeof(ts), "incorrect timestamp");
+}
+
+static void test_get_kato(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_KATO,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_kato(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_hctm(void)
+{
+ uint16_t TMT2 = 0x4321, TMT1 = 0x8765;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_HCTM,
+ .cdw11 = (uint32_t)TMT1 << 16 | TMT2,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_hctm(TEST_FD, TMT2, TMT1, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_hctm(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HCTM,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_hctm(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_nopsc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_NOPSC,
+ .cdw11 = 1 << 0 /* NOPPME */,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_nopsc(TEST_FD, true, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_nopsc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NOPSC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_nopsc(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_rrl(void)
+{
+ uint8_t RRL = 0xA;
+ uint16_t NVMSETID = 0x1234;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_RRL,
+ .cdw11 = NVMSETID,
+ .cdw12 = RRL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_rrl(TEST_FD, RRL, NVMSETID, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_rrl(void)
+{
+ /* nvme_get_features_rrl() doesn't support specifying the NVMSETID */
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RRL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_rrl(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_plm_config(void)
+{
+ uint16_t NVMSETID = 0xFEDC;
+ struct nvme_plm_config config;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &config,
+ .data_len = sizeof(config),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_PLM_CONFIG,
+ .cdw11 = NVMSETID,
+ .cdw12 = 1 << 0 /* Predictable Latency Enable */,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&config, sizeof(config));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_plm_config(
+ TEST_FD, true, NVMSETID, true, &config, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_plm_config(void)
+{
+ uint16_t NVMSETID = 0xABCD;
+ struct nvme_plm_config config, get_config = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(config),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_PLM_CONFIG,
+ .cdw11 = NVMSETID,
+ .out_data = &config,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&config, sizeof(config));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_plm_config(
+ TEST_FD, TEST_SEL, NVMSETID, &get_config, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_config, &config, sizeof(config), "incorrect PLM config");
+}
+
+static void test_set_plm_window(void)
+{
+ enum nvme_feat_plm_window_select SEL = NVME_FEATURE_PLM_NDWIN;
+ uint16_t NVMSETID = 0x4321;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_PLM_WINDOW,
+ .cdw11 = NVMSETID,
+ .cdw12 = SEL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_plm_window(
+ TEST_FD, SEL, NVMSETID, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_plm_window(void)
+{
+ uint16_t NVMSETID = 0x8765;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_PLM_WINDOW,
+ .cdw11 = NVMSETID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_plm_window(
+ TEST_FD, TEST_SEL, NVMSETID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_lba_sts_interval(void)
+{
+ uint16_t LSIRI = 0x1234, LSIPI = 0x5678;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_LBA_STS_INTERVAL,
+ .cdw11 = LSIPI << 16 | LSIRI,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_lba_sts_interval(
+ TEST_FD, LSIRI, LSIPI, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_lba_sts_interval(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_LBA_STS_INTERVAL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_lba_sts_interval(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_host_behavior(void)
+{
+ /* nvme_set_features_host_behavior() ignores SAVE */
+ struct nvme_feat_host_behavior behavior;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &behavior,
+ .data_len = sizeof(behavior),
+ .cdw10 = NVME_FEAT_FID_HOST_BEHAVIOR,
+ };
+ int err;
+
+ arbitrary(&behavior, sizeof(behavior));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_host_behavior(TEST_FD, true, &behavior);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_host_behavior(void)
+{
+ struct nvme_feat_host_behavior behavior, get_behavior = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(behavior),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_BEHAVIOR,
+ .out_data = &behavior,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&behavior, sizeof(behavior));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_behavior(
+ TEST_FD, TEST_SEL, &get_behavior, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_behavior, &behavior, sizeof(behavior), "incorrect behavior");
+}
+
+static void test_set_sanitize(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_SANITIZE,
+ .cdw11 = 1 << 0, /* NODRM */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_sanitize(TEST_FD, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_sanitize(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_SANITIZE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_sanitize(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_endurance_evt_cfg(void)
+{
+ uint16_t ENDGID = 0x9876;
+ uint8_t EGWARN = 0xCD;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_ENDURANCE_EVT_CFG,
+ .cdw11 = EGWARN << 16 | ENDGID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_endurance_evt_cfg(
+ TEST_FD, ENDGID, EGWARN, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_endurance_event_cfg(void)
+{
+ uint16_t ENDGID = 0x6789;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ENDURANCE_EVT_CFG,
+ .cdw11 = ENDGID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_endurance_event_cfg(
+ TEST_FD, TEST_SEL, ENDGID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_iocs_profile(void)
+{
+ uint16_t IOCSI = 0b101100111;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_IOCS_PROFILE,
+ .cdw11 = IOCSI,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_iocs_profile(TEST_FD, IOCSI, false);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_iocs_profile(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IOCS_PROFILE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_iocs_profile(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_sw_progress(void)
+{
+ uint8_t PBSLC = 0xBA;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_SW_PROGRESS,
+ .cdw11 = PBSLC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_sw_progress(TEST_FD, PBSLC, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_sw_progress(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_SW_PROGRESS,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_sw_progress(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_host_id(void)
+{
+ uint8_t hostid[8];
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = hostid,
+ .data_len = sizeof(hostid),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_HOST_ID,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_host_id(TEST_FD, false, true, hostid);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_set_host_id_extended(void)
+{
+ uint8_t hostid[16];
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = hostid,
+ .data_len = sizeof(hostid),
+ .cdw10 = NVME_FEAT_FID_HOST_ID,
+ .cdw11 = 1 << 0, /* EXHID */
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_host_id(TEST_FD, true, false, hostid);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_host_id(void)
+{
+ uint8_t hostid[8], get_hostid[sizeof(hostid)] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(hostid),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_ID,
+ .out_data = hostid,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_id(
+ TEST_FD, TEST_SEL, false, sizeof(hostid), get_hostid);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ cmp(get_hostid, hostid, sizeof(hostid), "incorrect host identifier");
+}
+
+static void test_get_host_id_extended(void)
+{
+ uint8_t hostid[16], get_hostid[sizeof(hostid)] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(hostid),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_ID,
+ .cdw11 = 1 << 0, /* EXHID */
+ .out_data = hostid,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_id(
+ TEST_FD, TEST_SEL, true, sizeof(hostid), get_hostid);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ cmp(get_hostid, hostid, sizeof(hostid), "incorrect host identifier");
+}
+
+static void test_set_resv_mask(void)
+{
+ uint32_t MASK = 0x23456789;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_RESV_MASK,
+ .cdw11 = MASK,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_resv_mask2(
+ TEST_FD, TEST_NSID, MASK, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_resv_mask(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RESV_MASK,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_resv_mask2(
+ TEST_FD, TEST_SEL, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_resv_persist(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_RESV_PERSIST,
+ .cdw11 = 1 << 0, /* PTPL */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_resv_persist2(
+ TEST_FD, TEST_NSID, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_resv_persist(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RESV_PERSIST,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_resv_persist2(
+ TEST_FD, TEST_SEL, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_write_protect(void)
+{
+ /* nvme_set_features_write_protect() ignores SAVE */
+ enum nvme_feat_nswpcfg_state STATE =
+ NVME_FEAT_NS_WRITE_PROTECT_PERMANENT;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_WRITE_PROTECT,
+ .cdw11 = STATE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_write_protect2(
+ TEST_FD, TEST_NSID, STATE, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_write_protect(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_WRITE_PROTECT,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_write_protect(
+ TEST_FD, TEST_NSID, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+/*
+ * All set_features functions tail-call nvme_set_features(),
+ * so testing errors in any of them will do
+ */
+
+static void test_set_status_code_error(void)
+{
+ uint32_t EVENTS = 0x12345678;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_ASYNC_EVENT,
+ .cdw11 = EVENTS,
+ .result = TEST_RESULT,
+ .err = TEST_SC,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_async_event(TEST_FD, EVENTS, false, &result);
+ end_mock_cmds();
+ check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_kernel_error(void)
+{
+ uint32_t MASK = 0x87654321;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_RESV_MASK,
+ .cdw11 = MASK,
+ .result = TEST_RESULT,
+ .err = -EIO,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_resv_mask2(
+ TEST_FD, TEST_NSID, MASK, false, &result);
+ end_mock_cmds();
+ check(err == -1, "got error %d, expected -1", err);
+ check(errno == EIO, "unexpected error %m");
+ check(!result, "result unexpectedly set to %" PRIu32, result);
+}
+
+/*
+ * All get_features functions tail-call nvme_get_features(),
+ * so testing errors in any of them will do
+ */
+
+static void test_get_status_code_error(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_KATO,
+ .result = TEST_RESULT,
+ .err = TEST_SC,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_kato(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_kernel_error(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NUM_QUEUES,
+ .result = TEST_RESULT,
+ .err = -EBUSY,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_num_queues(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == -1, "got error %d, expected -1", err);
+ check(errno == EBUSY, "unexpected error %m");
+ check(!result, "result unexpectedly set to %" PRIu32, result);
+}
+
+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)
+{
+ set_mock_fd(TEST_FD);
+ RUN_TEST(set_features);
+ RUN_TEST(get_features);
+ RUN_TEST(set_features_data);
+ RUN_TEST(get_features_data);
+ RUN_TEST(set_features_simple);
+ RUN_TEST(get_features_simple);
+ RUN_TEST(set_arbitration);
+ RUN_TEST(get_arbitration);
+ RUN_TEST(set_power_mgmt);
+ RUN_TEST(get_power_mgmt);
+ RUN_TEST(set_lba_range);
+ RUN_TEST(get_lba_range);
+ RUN_TEST(set_temp_thresh);
+ RUN_TEST(get_temp_thresh);
+ RUN_TEST(set_err_recovery);
+ RUN_TEST(get_err_recovery);
+ RUN_TEST(set_volatile_wc);
+ RUN_TEST(get_volatile_wc);
+ RUN_TEST(get_num_queues);
+ RUN_TEST(set_irq_coalesce);
+ RUN_TEST(get_irq_coalesce);
+ RUN_TEST(set_irq_config);
+ RUN_TEST(get_irq_config);
+ RUN_TEST(set_write_atomic);
+ RUN_TEST(get_write_atomic);
+ RUN_TEST(set_async_event);
+ RUN_TEST(get_async_event);
+ RUN_TEST(set_auto_pst);
+ RUN_TEST(get_auto_pst);
+ RUN_TEST(get_host_mem_buf);
+ RUN_TEST(set_timestamp);
+ RUN_TEST(get_timestamp);
+ RUN_TEST(get_kato);
+ RUN_TEST(set_hctm);
+ RUN_TEST(get_hctm);
+ RUN_TEST(set_nopsc);
+ RUN_TEST(get_nopsc);
+ RUN_TEST(set_rrl);
+ RUN_TEST(get_rrl);
+ RUN_TEST(set_plm_config);
+ RUN_TEST(get_plm_config);
+ RUN_TEST(set_plm_window);
+ RUN_TEST(get_plm_window);
+ RUN_TEST(set_lba_sts_interval);
+ RUN_TEST(get_lba_sts_interval);
+ RUN_TEST(set_host_behavior);
+ RUN_TEST(get_host_behavior);
+ RUN_TEST(set_sanitize);
+ RUN_TEST(get_sanitize);
+ RUN_TEST(set_endurance_evt_cfg);
+ RUN_TEST(get_endurance_event_cfg);
+ RUN_TEST(set_iocs_profile);
+ RUN_TEST(get_iocs_profile);
+ RUN_TEST(set_sw_progress);
+ RUN_TEST(get_sw_progress);
+ RUN_TEST(set_host_id);
+ RUN_TEST(set_host_id_extended);
+ RUN_TEST(get_host_id);
+ RUN_TEST(get_host_id_extended);
+ RUN_TEST(set_resv_mask);
+ RUN_TEST(get_resv_mask);
+ RUN_TEST(set_resv_persist);
+ RUN_TEST(get_resv_persist);
+ RUN_TEST(set_write_protect);
+ RUN_TEST(get_write_protect);
+ RUN_TEST(set_status_code_error);
+ RUN_TEST(set_kernel_error);
+ RUN_TEST(get_status_code_error);
+ RUN_TEST(get_kernel_error);
+}
diff --git a/test/ioctl/identify.c b/test/ioctl/identify.c
new file mode 100644
index 0000000..ccde02b
--- /dev/null
+++ b/test/ioctl/identify.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include <libnvme.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mock.h"
+#include "util.h"
+
+#define TEST_FD 0xFD
+#define TEST_NSID 0x12345678
+#define TEST_NVMSETID 0xABCD
+#define TEST_UUID 123
+#define TEST_CSI NVME_CSI_KV
+#define TEST_CNTID 0x4321
+#define TEST_DOMID 0xFEDC
+#define TEST_ENDGID 0x0123
+#define TEST_SC NVME_SC_INVALID_FIELD
+
+static void test_ns(void)
+{
+ struct nvme_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ctrl(void)
+{
+ struct nvme_id_ctrl expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CTRL,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ctrl(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_active_ns_list(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS_ACTIVE_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_active_ns_list(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ns_descs(void)
+{
+ uint8_t expected_id[NVME_IDENTIFY_DATA_SIZE];
+ struct nvme_ns_id_desc *id;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS_DESC_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(expected_id, sizeof(expected_id));
+ id = calloc(1, NVME_IDENTIFY_DATA_SIZE);
+ check(id, "memory allocation failed");
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns_descs(TEST_FD, TEST_NSID, id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(id, expected_id, sizeof(expected_id), "incorrect identify data");
+ free(id);
+}
+
+static void test_nvmset_list(void)
+{
+ struct nvme_id_nvmset_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NVMSET_LIST,
+ .cdw11 = TEST_NVMSETID,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_nvmset_list(TEST_FD, TEST_NVMSETID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ns_csi(void)
+{
+ uint8_t expected_id[NVME_IDENTIFY_DATA_SIZE];
+ uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_NS,
+ .cdw11 = TEST_CSI << 24,
+ .cdw14 = TEST_UUID,
+ .out_data = expected_id,
+ };
+ int err;
+
+ arbitrary(expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns_csi(TEST_FD, TEST_NSID, TEST_UUID, TEST_CSI, id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(id, expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_zns_identify_ns(void)
+{
+ struct nvme_zns_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_NS,
+ .cdw11 = NVME_CSI_ZNS << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_zns_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_nvm_identify_ctrl(void)
+{
+ struct nvme_id_ctrl_nvm expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_CTRL,
+ .cdw11 = NVME_CSI_NVM << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_nvm_identify_ctrl(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_zns_identify_ctrl(void)
+{
+ struct nvme_zns_id_ctrl expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_CTRL,
+ .cdw11 = NVME_CSI_ZNS << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_zns_identify_ctrl(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_active_ns_list_csi(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_NS_ACTIVE_LIST,
+ .cdw11 = TEST_CSI << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_active_ns_list_csi(
+ TEST_FD, TEST_NSID, TEST_CSI, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_independent_identify_ns(void)
+{
+ struct nvme_id_independent_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ /* That's a mouthful! */
+ err = nvme_identify_independent_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_allocated_ns_list(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_ALLOCATED_NS_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_allocated_ns_list(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_allocated_ns(void)
+{
+ struct nvme_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_ALLOCATED_NS,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_allocated_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_nsid_ctrl_list(void)
+{
+ struct nvme_ctrl_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_NS_CTRL_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_nsid_ctrl_list(TEST_FD, TEST_NSID, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ctrl_list(void)
+{
+ struct nvme_ctrl_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_CTRL_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ctrl_list(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_primary_ctrl(void)
+{
+ struct nvme_primary_ctrl_cap expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_PRIMARY_CTRL_CAP,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_primary_ctrl(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_secondary_ctrl_list(void)
+{
+ struct nvme_secondary_ctrl_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_secondary_ctrl_list(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ns_granularity(void)
+{
+ struct nvme_id_ns_granularity_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS_GRANULARITY,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns_granularity(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_uuid(void)
+{
+ struct nvme_id_uuid_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_UUID_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_uuid(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_domain_list(void)
+{
+ struct nvme_id_domain_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_DOMAIN_LIST,
+ .cdw11 = TEST_DOMID,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_domain_list(TEST_FD, TEST_DOMID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_endurance_group_list(void)
+{
+ struct nvme_id_endurance_group_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_ENDURANCE_GROUP_ID,
+ .cdw11 = TEST_ENDGID,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_endurance_group_list(TEST_FD, TEST_ENDGID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_allocated_ns_list_csi(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_ALLOCATED_NS_LIST,
+ .cdw11 = TEST_CSI << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_allocated_ns_list_csi(
+ TEST_FD, TEST_NSID, TEST_CSI, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_iocs(void)
+{
+ struct nvme_id_iocs expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_COMMAND_SET_STRUCTURE,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_iocs(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+/*
+ * All identify functions tail-call nvme_identify(),
+ * so testing errors in any of them will do
+ */
+
+static void test_status_code_error(void)
+{
+ struct nvme_id_nvmset_list id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(id),
+ .cdw10 = NVME_IDENTIFY_CNS_NVMSET_LIST,
+ .cdw11 = TEST_NVMSETID,
+ .err = TEST_SC,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_nvmset_list(TEST_FD, TEST_NVMSETID, &id);
+ end_mock_cmds();
+ check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC);
+}
+
+static void test_kernel_error(void)
+{
+ struct nvme_id_ns id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS,
+ .err = -EIO,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == -1, "got error %d, expected -1", err);
+ check(errno == EIO, "unexpected error %m");
+}
+
+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)
+{
+ set_mock_fd(TEST_FD);
+ RUN_TEST(ns);
+ RUN_TEST(ctrl);
+ RUN_TEST(active_ns_list);
+ RUN_TEST(ns_descs);
+ RUN_TEST(nvmset_list);
+ RUN_TEST(ns_csi);
+ RUN_TEST(zns_identify_ns);
+ RUN_TEST(nvm_identify_ctrl);
+ RUN_TEST(zns_identify_ctrl);
+ RUN_TEST(active_ns_list_csi);
+ RUN_TEST(independent_identify_ns);
+ RUN_TEST(allocated_ns_list);
+ RUN_TEST(allocated_ns);
+ RUN_TEST(nsid_ctrl_list);
+ RUN_TEST(ctrl_list);
+ RUN_TEST(primary_ctrl);
+ RUN_TEST(secondary_ctrl_list);
+ RUN_TEST(ns_granularity);
+ RUN_TEST(uuid);
+ RUN_TEST(domain_list);
+ RUN_TEST(endurance_group_list);
+ RUN_TEST(allocated_ns_list_csi);
+ RUN_TEST(iocs);
+ RUN_TEST(status_code_error);
+ RUN_TEST(kernel_error);
+}
diff --git a/test/ioctl/meson.build b/test/ioctl/meson.build
new file mode 100644
index 0000000..b329d27
--- /dev/null
+++ b/test/ioctl/meson.build
@@ -0,0 +1,42 @@
+mock_ioctl = library(
+ 'mock-ioctl',
+ ['mock.c', 'util.c'],
+)
+
+# Add mock-ioctl to the LD_PRELOAD path so it overrides libc.
+# Append to LD_PRELOAD so existing libraries, e.g. libasan, are kept.
+# If libasan isn't specified in the LD_PRELOAD path, ASAN warns about mock-ioctl
+# being loaded first because its memory allocations might not get intercepted.
+# But it appears this isn't a problem; ASAN errors in mock-ioctl are reported.
+# This is likely because the executable still links with libasan before libc.
+mock_ioctl_env = environment()
+mock_ioctl_env.append('LD_PRELOAD', mock_ioctl.full_path())
+mock_ioctl_env.set('ASAN_OPTIONS', 'verify_asan_link_order=0')
+
+discovery = executable(
+ 'test-discovery',
+ 'discovery.c',
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir],
+ link_with: mock_ioctl,
+)
+
+test('discovery', discovery, env: mock_ioctl_env)
+
+features = executable(
+ 'test-features',
+ 'features.c',
+ dependencies: libnvme_dep,
+ link_with: mock_ioctl,
+)
+
+test('features', features, env: mock_ioctl_env)
+
+identify = executable(
+ 'test-identify',
+ 'identify.c',
+ dependencies: libnvme_dep,
+ link_with: mock_ioctl,
+)
+
+test('identify', identify, env: mock_ioctl_env)
diff --git a/test/ioctl/mock.c b/test/ioctl/mock.c
new file mode 100644
index 0000000..a97a357
--- /dev/null
+++ b/test/ioctl/mock.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "mock.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "../../src/nvme/ioctl.h"
+#include "util.h"
+
+struct mock_cmds {
+ const char *name;
+ const struct mock_cmd *cmds;
+ size_t remaining_cmds;
+};
+
+static int mock_fd = -1;
+static struct mock_cmds mock_admin_cmds = {.name = "admin"};
+static struct mock_cmds mock_io_cmds = {.name = "IO"};
+
+static void set_mock_cmds(
+ struct mock_cmds *mock_cmds, const struct mock_cmd *cmds, size_t len)
+{
+ mock_cmds->cmds = cmds;
+ mock_cmds->remaining_cmds = len;
+}
+
+static void mock_cmds_done(const struct mock_cmds *mock_cmds)
+{
+ check(!mock_cmds->remaining_cmds,
+ "%zu %s commands not executed",
+ mock_cmds->remaining_cmds, mock_cmds->name);
+}
+
+void set_mock_fd(int fd)
+{
+ mock_fd = fd;
+}
+
+void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len)
+{
+ set_mock_cmds(&mock_admin_cmds, cmds, len);
+}
+
+void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len)
+{
+ set_mock_cmds(&mock_io_cmds, cmds, len);
+}
+
+void end_mock_cmds(void)
+{
+ mock_cmds_done(&mock_admin_cmds);
+ mock_cmds_done(&mock_io_cmds);
+}
+
+#define execute_ioctl(cmd, mock_cmd) ({ \
+ check((cmd)->opcode == (mock_cmd)->opcode, \
+ "got opcode %" PRIu8 ", expected %" PRIu8, \
+ (cmd)->opcode, (mock_cmd)->opcode); \
+ check((cmd)->flags == (mock_cmd)->flags, \
+ "got flags %" PRIu8 ", expected %" PRIu8, \
+ (cmd)->flags, (mock_cmd)->flags); \
+ check((cmd)->nsid == (mock_cmd)->nsid, \
+ "got nsid %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->nsid, (mock_cmd)->nsid); \
+ check((cmd)->cdw2 == (mock_cmd)->cdw2, \
+ "got cdw2 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw2, (mock_cmd)->cdw2); \
+ check((cmd)->cdw3 == (mock_cmd)->cdw3, \
+ "got cdw3 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw3, (mock_cmd)->cdw3); \
+ check((cmd)->metadata_len == (mock_cmd)->metadata_len, \
+ "got metadata_len %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->metadata_len, (mock_cmd)->metadata_len); \
+ if ((cmd)->metadata_len) { \
+ cmp((void const *)(uintptr_t)(cmd)->metadata, \
+ (mock_cmd)->metadata, \
+ (cmd)->metadata_len, \
+ "incorrect metadata"); \
+ } \
+ __u32 data_len = (cmd)->data_len; \
+ check(data_len == (mock_cmd)->data_len, \
+ "got data_len %" PRIu32 ", expected %" PRIu32, \
+ data_len, (mock_cmd)->data_len); \
+ void *data = (void *)(uintptr_t)(cmd)->addr; \
+ if ((mock_cmd)->in_data) { \
+ cmp(data, (mock_cmd)->in_data, data_len, "incorrect data"); \
+ } \
+ check((cmd)->cdw10 == (mock_cmd)->cdw10, \
+ "got cdw10 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw10, (mock_cmd)->cdw10); \
+ check((cmd)->cdw11 == (mock_cmd)->cdw11, \
+ "got cdw11 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw11, (mock_cmd)->cdw11); \
+ check((cmd)->cdw12 == (mock_cmd)->cdw12, \
+ "got cdw12 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw12, (mock_cmd)->cdw12); \
+ check((cmd)->cdw13 == (mock_cmd)->cdw13, \
+ "got cdw13 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw13, (mock_cmd)->cdw13); \
+ check((cmd)->cdw14 == (mock_cmd)->cdw14, \
+ "got cdw14 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw14, (mock_cmd)->cdw14); \
+ check((cmd)->cdw15 == (mock_cmd)->cdw15, \
+ "got cdw15 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw15, (mock_cmd)->cdw15); \
+ check((cmd)->timeout_ms == (mock_cmd)->timeout_ms, \
+ "got timeout_ms %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->timeout_ms, (mock_cmd)->timeout_ms); \
+ (cmd)->result = (mock_cmd)->result; \
+ if ((mock_cmd)->out_data) { \
+ memcpy(data, (mock_cmd)->out_data, data_len); \
+ } \
+})
+
+#ifdef HAVE_GLIBC_IOCTL
+int ioctl(int fd, unsigned long request, ...)
+#else
+int ioctl(int fd, int request, ...)
+#endif
+{
+ struct mock_cmds *mock_cmds;
+ bool result64;
+ const struct mock_cmd *mock_cmd;
+ va_list args;
+ void *cmd;
+
+ check(fd == mock_fd, "got fd %d, expected %d", fd, mock_fd);
+ switch (request) {
+ case NVME_IOCTL_ADMIN_CMD:
+ mock_cmds = &mock_admin_cmds;
+ result64 = false;
+ break;
+ case NVME_IOCTL_ADMIN64_CMD:
+ mock_cmds = &mock_admin_cmds;
+ result64 = true;
+ break;
+ case NVME_IOCTL_IO_CMD:
+ mock_cmds = &mock_io_cmds;
+ result64 = false;
+ break;
+ case NVME_IOCTL_IO64_CMD:
+ mock_cmds = &mock_io_cmds;
+ result64 = true;
+ break;
+ default:
+ fail("unexpected %s %lu", __func__, (unsigned long) request);
+ }
+ check(mock_cmds->remaining_cmds,
+ "unexpected %s command", mock_cmds->name);
+ mock_cmd = mock_cmds->cmds++;
+ mock_cmds->remaining_cmds--;
+
+ va_start(args, request);
+ cmd = va_arg(args, void *);
+ va_end(args);
+ if (result64) {
+ execute_ioctl((struct nvme_passthru_cmd64 *)cmd, mock_cmd);
+ } else {
+ check((uint32_t)mock_cmd->result == mock_cmd->result,
+ "expected 64-bit %s for result %" PRIu64,
+ __func__, mock_cmd->result);
+ execute_ioctl((struct nvme_passthru_cmd *)cmd, mock_cmd);
+ }
+ if (mock_cmd->err < 0) {
+ errno = -mock_cmd->err;
+ return -1;
+ }
+
+ return mock_cmd->err;
+}
diff --git a/test/ioctl/mock.h b/test/ioctl/mock.h
new file mode 100644
index 0000000..192eba8
--- /dev/null
+++ b/test/ioctl/mock.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _LIBNVME_TEST_IOCTL_MOCK_H
+#define _LIBNVME_TEST_IOCTL_MOCK_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * struct mock_cmd - a mock NVMe passthru ioctl() invocation
+ * @opcode: the expected `opcode` passed to ioctl()
+ * @flags: the expected `flags` passed to ioctl()
+ * @nsid: the expected `nsid` passed to ioctl()
+ * @cdw2: the expected `cdw2` passed to ioctl()
+ * @cdw3: the expected `cdw3` passed to ioctl()
+ * @metadata: the expected `metadata` of length `metadata_len` passed to ioctl()
+ * @in_data: the expected `addr` of length `data_len` passed to ioctl().
+ * Set this to NULL to skip checking the data,
+ * for example if the command is in the read direction.
+ * @metadata_len: the expected `metadata_len` passed to ioctl()
+ * @data_len: the expected `data_len` passed to ioctl()
+ * @cdw10: the expected `cdw10` passed to ioctl()
+ * @cdw11: the expected `cdw11` passed to ioctl()
+ * @cdw12: the expected `cdw12` passed to ioctl()
+ * @cdw13: the expected `cdw13` passed to ioctl()
+ * @cdw14: the expected `cdw14` passed to ioctl()
+ * @cdw15: the expected `cdw15` passed to ioctl()
+ * @timeout_ms: the expected `timeout_ms` passed to ioctl()
+ * @out_data: if not NULL, `data_len` bytes to copy to the caller's `addr`
+ * @result: copied to the caller's `result`.
+ * If `result` doesn't fit in a u32, the ioctl() must be the 64-bit one.
+ * @err: If negative, ioctl() returns -1 and sets `errno` to `-err`.
+ * Otherwise, ioctl() returns `err`, representing a NVMe status code.
+ */
+struct mock_cmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint32_t nsid;
+ uint32_t cdw2;
+ uint32_t cdw3;
+ const void *metadata;
+ const void *in_data;
+ uint32_t metadata_len;
+ uint32_t data_len;
+ uint32_t cdw10;
+ uint32_t cdw11;
+ uint32_t cdw12;
+ uint32_t cdw13;
+ uint32_t cdw14;
+ uint32_t cdw15;
+ uint32_t timeout_ms;
+ const void *out_data;
+ uint64_t result;
+ int err;
+};
+
+/**
+ * set_mock_fd() - sets the expected file descriptor for NVMe passthru ioctls()
+ * @fd: file descriptor expected to be passed to ioctl()
+ */
+void set_mock_fd(int fd);
+
+/**
+ * set_mock_admin_cmds() - mocks NVMe admin passthru ioctl() invocations
+ * @cmds: pointer to start of the mock_cmd slice
+ * @len: length of the mock_cmd slice (number of ioctl() invocations)
+ *
+ * Provides a sequence of mocks for NVMe admin passthru ioctl() invocations.
+ * Each ioctl() consumes the next mock from the sequence.
+ * Its arguments are checked against the mock's expected arguments,
+ * aborting the process if unexpected arguments are passed.
+ * The mock results (return value, NVMe result and data)
+ * are returned from the ioctl().
+ *
+ * Analogous to set_mock_io_cmds(), but for admin commands.
+ * Both admin and IO mocks can be active at the same time.
+ */
+void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len);
+
+/**
+ * set_mock_io_cmds() - mocks NVMe IO passthru ioctl() invocations
+ * @cmds: pointer to start of the mock_cmd slice
+ * @len: length of the mock_cmd slice (number of ioctl() invocations)
+ *
+ * Provides a sequence of mocks for NVMe IO passthru ioctl() invocations.
+ * Each ioctl() consumes the next mock from the sequence.
+ * Its arguments are checked against the mock's expected arguments,
+ * aborting the process if unexpected arguments are passed.
+ * The mock results (return value, NVMe result and data)
+ * are returned from the ioctl().
+ *
+ * Analogous to set_mock_admin_cmds(), but for IO commands.
+ * Both admin and IO mocks can be active at the same time.
+ */
+void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len);
+
+/**
+ * end_mock_cmds() - finishes mocking NVMe passthru ioctl() invocations
+ *
+ * Checks that all mock ioctl() invocations were performed.
+ */
+void end_mock_cmds(void);
+
+#endif /* #ifndef _LIBNVME_TEST_IOCTL_MOCK_H */
diff --git a/test/ioctl/util.c b/test/ioctl/util.c
new file mode 100644
index 0000000..09c6e7f
--- /dev/null
+++ b/test/ioctl/util.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "util.h"
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void hexdump(const uint8_t *buf, size_t len)
+{
+ size_t i = 0;
+
+ if (!len)
+ return;
+
+ for (;;) {
+ fprintf(stderr, "%02X", buf[i++]);
+ if (i >= len)
+ break;
+
+ fputc(i % 16 > 0 ? ' ' : '\n', stderr);
+ }
+ fputc('\n', stderr);
+}
+
+void fail(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fputc('\n', stderr);
+ abort();
+}
+
+void cmp(const void *actual, const void *expected, size_t len, const char *msg)
+{
+ if (memcmp(actual, expected, len) == 0)
+ return;
+
+ fputs(msg, stderr);
+ fputs("\nactual:\n", stderr);
+ hexdump(actual, len);
+ fputs("expected:\n", stderr);
+ hexdump(expected, len);
+ abort();
+}
+
+void arbitrary(void *buf_, size_t len)
+{
+ uint8_t *buf = buf_;
+
+ while (len--)
+ *(buf++) = rand();
+}
+
+size_t arbitrary_range(size_t max)
+{
+ size_t value;
+ arbitrary(&value, sizeof(value));
+ return value % max;
+}
diff --git a/test/ioctl/util.h b/test/ioctl/util.h
new file mode 100644
index 0000000..aa86422
--- /dev/null
+++ b/test/ioctl/util.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _LIBNVME_TEST_IOCTL_UTIL_H
+#define _LIBNVME_TEST_IOCTL_UTIL_H
+
+#include <stddef.h>
+#include <stdnoreturn.h>
+
+noreturn void fail(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+
+#define check(condition, fmt...) ((condition) || (fail(fmt), 0))
+
+void cmp(const void *actual, const void *expected, size_t len, const char *msg);
+
+void arbitrary(void *buf, size_t len);
+
+size_t arbitrary_range(size_t max);
+
+#endif /* #ifndef _LIBNVME_TEST_IOCTL_UTIL_H */
diff --git a/test/meson.build b/test/meson.build
index 49cd1ac..2b4c6d8 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -67,21 +67,33 @@ uuid = executable(
test('uuid', uuid)
if conf.get('HAVE_NETDB')
+ mock_ifaddrs = library(
+ 'mock-ifaddrs',
+ ['mock-ifaddrs.c', ],
+ )
+
+ # See comment in test/ioctl/meson.build explaining how LD_PRELOAD is used
+ mock_ifaddrs_env = environment()
+ mock_ifaddrs_env.append('LD_PRELOAD', mock_ifaddrs.full_path())
+ mock_ifaddrs_env.set('ASAN_OPTIONS', 'verify_asan_link_order=0')
+
tree = executable(
'tree',
['tree.c'],
dependencies: libnvme_dep,
- include_directories: [incdir, internal_incdir]
+ include_directories: [incdir, internal_incdir],
+ link_with: mock_ifaddrs,
)
- test('tree', tree)
+ test('tree', tree, env: mock_ifaddrs_env)
test_util = executable(
'test-util',
['test-util.c'],
include_directories: [incdir, internal_incdir]
)
- test('Test util.c', test_util)
+ test('util', test_util)
endif
+subdir('ioctl')
subdir('nbft')
diff --git a/test/mi-mctp.c b/test/mi-mctp.c
index 6d83d42..5711c03 100644
--- a/test/mi-mctp.c
+++ b/test/mi-mctp.c
@@ -83,12 +83,14 @@ static void test_set_tx_mic(struct test_peer *peer)
{
extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
__u32 crc = 0xffffffff;
+ __le32 crc_le;
- assert(peer->tx_buf_len + sizeof(crc) <= MAX_BUFSIZ);
+ assert(peer->tx_buf_len + sizeof(crc_le) <= MAX_BUFSIZ);
crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len);
- *(uint32_t *)(peer->tx_buf + peer->tx_buf_len) = cpu_to_le32(~crc);
- peer->tx_buf_len += sizeof(crc);
+ crc_le = cpu_to_le32(~crc);
+ memcpy(peer->tx_buf + peer->tx_buf_len, &crc_le, sizeof(crc_le));
+ peer->tx_buf_len += sizeof(crc_le);
}
int __wrap_socket(int family, int type, int protocol)
@@ -293,6 +295,89 @@ static void test_mi_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
assert(rc == 0x2);
}
+static void setup_unaligned_ctrl_list_resp(struct test_peer *peer)
+{
+ /* even number of controllers */
+ peer->tx_buf[8] = 0x02;
+ peer->tx_buf[9] = 0x00;
+
+ /* controller ID 1 */
+ peer->tx_buf[10] = 0x01;
+ peer->tx_buf[11] = 0x00;
+
+ /* controller ID 2 */
+ peer->tx_buf[12] = 0x02;
+ peer->tx_buf[13] = 0x00;
+
+ peer->tx_buf_len = 14;
+}
+
+/* Will call through the xfer/submit API expecting a full-sized list (so
+ * resp->data_len is set to sizeof(list)), but the endpoint will return an
+ * unaligned short list.
+ */
+static void test_mi_resp_unaligned(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_ctrl_list list;
+ int rc;
+
+ setup_unaligned_ctrl_list_resp(peer);
+
+ memset(&list, 0, sizeof(list));
+
+ rc = nvme_mi_mi_read_mi_data_ctrl_list(ep, 0, &list);
+ assert(rc == 0);
+
+ assert(le16_to_cpu(list.num) == 2);
+ assert(le16_to_cpu(list.identifier[0]) == 1);
+ assert(le16_to_cpu(list.identifier[1]) == 2);
+}
+
+/* Will call through the xfer/submit API expecting an unaligned list,
+ * and get a response of exactly that size.
+ */
+static void test_mi_resp_unaligned_expected(nvme_mi_ep_t ep,
+ struct test_peer *peer)
+{
+ /* direct access to the raw submit() API */
+ extern int nvme_mi_submit(nvme_mi_ep_t ep, struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp);
+ struct nvme_mi_mi_resp_hdr resp_hdr;
+ struct nvme_mi_mi_req_hdr req_hdr;
+ struct nvme_ctrl_list list;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ setup_unaligned_ctrl_list_resp(peer);
+
+ memset(&list, 0, sizeof(list));
+
+ memset(&req_hdr, 0, sizeof(req_hdr));
+ req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME;
+ req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3);
+ req_hdr.opcode = nvme_mi_mi_opcode_mi_data_read;
+ req_hdr.cdw0 = cpu_to_le32(nvme_mi_dtyp_ctrl_list << 24);
+
+ memset(&req, 0, sizeof(req));
+ req.hdr = &req_hdr.hdr;
+ req.hdr_len = sizeof(req_hdr);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &resp_hdr.hdr;
+ resp.hdr_len = sizeof(resp_hdr);
+ resp.data = &list;
+ resp.data_len = peer->tx_buf_len;
+
+ rc = nvme_mi_submit(ep, &req, &resp);
+ assert(rc == 0);
+ assert(resp.data_len == 6); /* 2-byte length, 2*2 byte controller IDs */
+
+ assert(le16_to_cpu(list.num) == 2);
+ assert(le16_to_cpu(list.identifier[0]) == 1);
+ assert(le16_to_cpu(list.identifier[1]) == 2);
+}
+
static void test_admin_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
{
struct nvme_id_ctrl id;
@@ -340,30 +425,6 @@ static void test_admin_resp_sizes(nvme_mi_ep_t ep, struct test_peer *peer)
nvme_mi_close_ctrl(ctrl);
}
-/* test: unaligned response sizes - should always report a transport error */
-static void test_admin_resp_sizes_unaligned(nvme_mi_ep_t ep, struct test_peer *peer)
-{
- struct nvme_id_ctrl id;
- nvme_mi_ctrl_t ctrl;
- unsigned int i;
- int rc;
-
- ctrl = nvme_mi_init_ctrl(ep, 1);
- assert(ctrl);
-
- peer->tx_buf[4] = 0x02; /* internal error */
-
- for (i = 8; i <= 4096 + 8; i++) {
- peer->tx_buf_len = i;
- if (!(i & 0x3))
- continue;
- rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
- assert(rc < 0);
- }
-
- nvme_mi_close_ctrl(ctrl);
-}
-
/* test: timeout value passed to poll */
static int poll_fn_timeout_value(struct test_peer *peer, struct pollfd *fds,
nfds_t nfds, int timeout)
@@ -664,9 +725,10 @@ struct test {
DEFINE_TEST(read_mi_data),
DEFINE_TEST(poll_err),
DEFINE_TEST(mi_resp_err),
+ DEFINE_TEST(mi_resp_unaligned),
+ DEFINE_TEST(mi_resp_unaligned_expected),
DEFINE_TEST(admin_resp_err),
DEFINE_TEST(admin_resp_sizes),
- DEFINE_TEST(admin_resp_sizes_unaligned),
DEFINE_TEST(poll_timeout_value),
DEFINE_TEST(poll_timeout),
DEFINE_TEST(mpr_mi),
diff --git a/test/mi.c b/test/mi.c
index 7f8e005..a09c108 100644
--- a/test/mi.c
+++ b/test/mi.c
@@ -44,7 +44,8 @@ static int test_transport_submit(struct nvme_mi_ep *ep,
/* start from a minimal response: zeroed data, nmp to match request */
memset(resp->hdr, 0, resp->hdr_len);
- memset(resp->data, 0, resp->data_len);
+ if (resp->data_len)
+ memset(resp->data, 0, resp->data_len);
resp->hdr->type = NVME_MI_MSGTYPE_NVME;
resp->hdr->nmp = req->hdr->nmp | (NVME_MI_ROR_RSP << 7);
@@ -1245,7 +1246,6 @@ static int test_admin_id_secondary_ctrl_list_cb(struct nvme_mi_ep *ep,
void *data)
{
__u16 cns, ctrlid;
- __u32 nsid;
__u8 *hdr;
hdr = (__u8 *)req->hdr;
@@ -1256,9 +1256,6 @@ static int test_admin_id_secondary_ctrl_list_cb(struct nvme_mi_ep *ep,
cns = hdr[45] << 8 | hdr[44];
assert(cns == NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST);
- nsid = hdr[11] << 24 | hdr[10] << 16 | hdr[9] << 8 | hdr[8];
- assert(nsid == 0x01020304);
-
ctrlid = hdr[47] << 8 | hdr[46];
assert(ctrlid == 5);
@@ -1280,8 +1277,7 @@ static void test_admin_id_secondary_ctrl_list(struct nvme_mi_ep *ep)
ctrl = nvme_mi_init_ctrl(ep, 5);
assert(ctrl);
- rc = nvme_mi_admin_identify_secondary_ctrl_list(ctrl, 0x01020304,
- 5, &list);
+ rc = nvme_mi_admin_identify_secondary_ctrl_list(ctrl, 5, &list);
assert(!rc);
}
@@ -1651,7 +1647,10 @@ static int test_admin_format_nvm_cb(struct nvme_mi_ep *ep,
assert(rq_hdr[4] == nvme_admin_format_nvm);
- nsid = rq_hdr[11] << 24 | rq_hdr[10] << 16 | rq_hdr[9] << 8 | rq_hdr[8];
+ nsid = (__u32)rq_hdr[11] << 24
+ | rq_hdr[10] << 16
+ | rq_hdr[9] << 8
+ | rq_hdr[8];
assert(nsid == args->nsid);
assert(((rq_hdr[44] >> 0) & 0xf) == args->lbaf);
@@ -1726,7 +1725,7 @@ static int test_admin_sanitize_nvm_cb(struct nvme_mi_ep *ep,
assert(((rq_hdr[45] >> 0) & 0x1) == args->oipbp);
assert(((rq_hdr[45] >> 1) & 0x1) == args->nodas);
- ovrpat = rq_hdr[51] << 24 | rq_hdr[50] << 16 |
+ ovrpat = (__u32)rq_hdr[51] << 24 | rq_hdr[50] << 16 |
rq_hdr[49] << 8 | rq_hdr[48];
assert(ovrpat == args->ovrpat);
diff --git a/test/mock-ifaddrs.c b/test/mock-ifaddrs.c
new file mode 100644
index 0000000..87a2e50
--- /dev/null
+++ b/test/mock-ifaddrs.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2023 Martin Belanger, Dell Technologies Inc.
+ */
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+
+struct ifaddrs_storage {
+ struct ifaddrs ifa;
+ union {
+ /* Reserve space for the biggest of the sockaddr types */
+ struct sockaddr_in s4;
+ struct sockaddr_in6 s6;
+ } addr, netmask, broadaddr;
+ char name[IF_NAMESIZE + 1];
+};
+
+static void init_entry(struct ifaddrs_storage *storage,
+ const char *ifname,
+ int family,
+ uint32_t addr1,
+ uint32_t addr2,
+ uint32_t addr3,
+ uint32_t addr4,
+ bool last)
+{
+ struct ifaddrs *p;
+
+ p = &storage->ifa;
+ p->ifa_next = last ? NULL : &storage[1].ifa;
+ p->ifa_name = storage->name;
+ strcpy(p->ifa_name, ifname);
+ p->ifa_flags = 0;
+
+ if (family == AF_INET) {
+ struct sockaddr_in *ipv4;
+
+ ipv4 = &storage->addr.s4;
+ ipv4->sin_family = family;
+ ipv4->sin_port = 0;
+ ipv4->sin_addr.s_addr = htonl(addr1);
+ p->ifa_addr = (struct sockaddr *)ipv4;
+
+ ipv4 = &storage->netmask.s4;
+ ipv4->sin_family = family;
+ ipv4->sin_port = 0;
+ ipv4->sin_addr.s_addr = 0xffffff00;
+ p->ifa_netmask = (struct sockaddr *)ipv4;
+
+ ipv4 = &storage->broadaddr.s4;
+ ipv4->sin_family = family;
+ ipv4->sin_port = 0;
+ ipv4->sin_addr.s_addr = 0;
+ p->ifa_broadaddr = (struct sockaddr *)ipv4;;
+ } else {
+ struct sockaddr_in6 *ipv6;
+
+ ipv6 = &storage->addr.s6;
+ ipv6->sin6_family = family;
+ ipv6->sin6_port = 0;
+ ipv6->sin6_flowinfo = 0;
+ ipv6->sin6_addr.s6_addr32[0] = htonl(addr1);
+ ipv6->sin6_addr.s6_addr32[1] = htonl(addr2);
+ ipv6->sin6_addr.s6_addr32[2] = htonl(addr3);
+ ipv6->sin6_addr.s6_addr32[3] = htonl(addr4);
+ ipv6->sin6_scope_id = 0;
+ p->ifa_addr = (struct sockaddr *)ipv6;
+
+ ipv6 = &storage->netmask.s6;
+ ipv6->sin6_family = family;
+ ipv6->sin6_port = 0;
+ ipv6->sin6_flowinfo = 0;
+ ipv6->sin6_addr.s6_addr32[0] = 0xffffffff;
+ ipv6->sin6_addr.s6_addr32[1] = 0xffffffff;
+ ipv6->sin6_addr.s6_addr32[2] = 0xffffffff;
+ ipv6->sin6_addr.s6_addr32[3] = 0;
+ ipv6->sin6_scope_id = 0;
+ p->ifa_netmask = (struct sockaddr *)ipv6;
+
+ ipv6 = &storage->broadaddr.s6;
+ ipv6->sin6_family = family;
+ ipv6->sin6_port = 0;
+ ipv6->sin6_flowinfo = 0;
+ ipv6->sin6_addr.s6_addr32[0] = 0;
+ ipv6->sin6_addr.s6_addr32[1] = 0;
+ ipv6->sin6_addr.s6_addr32[2] = 0;
+ ipv6->sin6_addr.s6_addr32[3] = 0;
+ ipv6->sin6_scope_id = 0;
+ p->ifa_broadaddr = (struct sockaddr *)ipv6;
+ }
+
+ p->ifa_data = NULL;
+}
+
+int getifaddrs(struct ifaddrs **ifap) {
+ struct ifaddrs_storage *storage;
+
+ /* Allocate memory for 4 interfaces */
+ storage = (struct ifaddrs_storage *)calloc(4, sizeof(struct ifaddrs_storage));
+ *ifap = &storage[0].ifa;
+
+ init_entry(&storage[0], "eth0", AF_INET, 0xc0a80114, 0, 0, 0, false); /* 192.168.1.20 */
+ init_entry(&storage[1], "eth0", AF_INET6, 0xfe800000, 0, 0, 0xdeadbeef, false); /* fe80::dead:beef */
+
+ /* Loopback interface */
+ init_entry(&storage[2], "lo", AF_INET, 0x7f000001, 0, 0, 0, false); /* 127.0.0.1 */
+ init_entry(&storage[3], "lo", AF_INET6, 0, 0, 0, 1, true); /* ::1 */
+
+ return 0;
+}
+
+void freeifaddrs(struct ifaddrs *ifa) {
+ free(ifa);
+}
+
diff --git a/test/test-util.c b/test/test-util.c
index e32f030..88a3f42 100644
--- a/test/test-util.c
+++ b/test/test-util.c
@@ -20,7 +20,6 @@
#include <netdb.h>
#include <string.h>
-#include "nvme/cleanup.c" /* to resolve cleanup_charp() */
#include "nvme/log.c" /* to resolve __nvme_msg() */
#include "nvme/util.c"
diff --git a/test/test.c b/test/test.c
index 2f24e1e..23036bb 100644
--- a/test/test.c
+++ b/test/test.c
@@ -16,6 +16,7 @@
* somewhere.
*/
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
@@ -112,7 +113,7 @@ static int test_ctrl(nvme_ctrl_t c)
printf(" PASSED: Identify Primary\n");
else
printf(" ERROR: Identify Primary:%x\n", ret);
- ret = nvme_identify_secondary_ctrl_list(fd, 1, 0, &sec);
+ ret = nvme_identify_secondary_ctrl_list(fd, 0, &sec);
if (!ret)
printf(" PASSED: Identify Secondary\n");
else
@@ -197,7 +198,7 @@ static int test_ctrl(nvme_ctrl_t c)
printf(" Temperature Threshold:%x\n", result);
else if (ret > 0)
printf(" ERROR: Temperature Threshold:%x\n", ret);
- ret = nvme_get_features_err_recovery(fd, sel, &result);
+ ret = nvme_get_features_err_recovery2(fd, sel, 0, &result);
if (!ret)
printf(" Error Recovery:%x\n", result);
else if (ret > 0)
@@ -257,12 +258,12 @@ static int test_ctrl(nvme_ctrl_t c)
printf(" SW Progress Marker:%x\n", result);
else if (ret > 0)
printf(" ERROR: Sanitize:%x\n", ret);
- ret = nvme_get_features_resv_mask(fd, sel, &result);
+ ret = nvme_get_features_resv_mask2(fd, sel, 0, &result);
if (!ret)
printf(" Reservation Mask:%x\n", result);
else if (ret > 0)
printf(" ERROR: Reservation Mask:%x\n", ret);
- ret = nvme_get_features_resv_persist(fd, sel, &result);
+ ret = nvme_get_features_resv_persist2(fd, sel, 0, &result);
if (!ret)
printf(" Reservation Persistence:%x\n", result);
else if (ret > 0)
@@ -274,7 +275,7 @@ static int test_namespace(nvme_ns_t n)
{
int ret, nsid = nvme_ns_get_nsid(n), fd = nvme_ns_get_fd(n);
struct nvme_id_ns ns = { 0 }, allocated = { 0 };
- struct nvme_ns_id_desc descs = { 0 };
+ struct nvme_ns_id_desc *descs;
__u32 result = 0;
__u8 flbas;
@@ -292,11 +293,16 @@ static int test_namespace(nvme_ns_t n)
printf(" Identify allocated ns\n");
else
printf(" ERROR: Identify allocated ns:%x\n", ret);
- ret = nvme_identify_ns_descs(fd, nsid, &descs);
+ descs = malloc(NVME_IDENTIFY_DATA_SIZE);
+ if (!descs)
+ return -1;
+
+ ret = nvme_identify_ns_descs(fd, nsid, descs);
if (!ret)
printf(" Identify NS Descriptors\n");
else
printf(" ERROR: Identify NS Descriptors:%x\n", ret);
+ free(descs);
ret = nvme_get_features_write_protect(fd, nsid,
NVME_GET_FEATURES_SEL_CURRENT, &result);
if (!ret)
diff --git a/test/tree.c b/test/tree.c
index b3baac8..c9370f9 100644
--- a/test/tree.c
+++ b/test/tree.c
@@ -6,10 +6,14 @@
#include <assert.h>
#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
#include <ccan/array_size/array_size.h>
#include <libnvme.h>
+#include <nvme/private.h>
struct test_data {
/* input data */
@@ -29,6 +33,8 @@ struct test_data {
#define DEFAULT_SUBSYSNAME "subsysname"
#define DEFAULT_SUBSYSNQN "subsysnqn"
+#define SRC_ADDR4 "192.168.56.100"
+#define SRC_ADDR6 "1234:5678:abcd:EF01:1234:5678:abcd:EF01"
struct test_data test_data[] = {
{ DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "tcp", "192.168.1.1", "192.168.1.20", NULL, "4420" },
@@ -65,8 +71,10 @@ static void show_ctrl(nvme_ctrl_t c)
if (d)
printf("ctrl%d ", d->ctrl_id);
+ else
+ printf(" ");
- printf("0x%p: %s %s %s %s %s\n",
+ printf("0x%p: %s %s %s %s %s ",
c,
nvme_ctrl_get_transport(c),
nvme_ctrl_get_traddr(c),
@@ -75,6 +83,41 @@ static void show_ctrl(nvme_ctrl_t c)
nvme_ctrl_get_trsvcid(c));
}
+static bool match_ctrl(struct test_data *d, nvme_ctrl_t c)
+{
+ bool pass = true;
+ const char *trsvid, *host_traddr, *host_iface;
+
+ if (d->c != c)
+ pass = false;
+
+ if (strcmp(d->transport, nvme_ctrl_get_transport(d->c)))
+ pass = false;
+
+ if (strcmp(d->traddr, nvme_ctrl_get_traddr(d->c)))
+ pass = false;
+
+
+ host_traddr = nvme_ctrl_get_host_traddr(c);
+ if (d->host_traddr &&
+ (!host_traddr || strcmp(d->host_traddr, host_traddr)))
+ pass = false;
+
+ host_iface = nvme_ctrl_get_host_iface(c);
+ if (d->host_iface &&
+ (!host_iface || strcmp(d->host_iface, host_iface)))
+ pass = false;
+
+ trsvid = nvme_ctrl_get_trsvcid(c);
+ if (d->trsvcid &&
+ (!trsvid || strcmp(d->trsvcid, trsvid)))
+ pass = false;
+
+ printf("[%s]", pass? "PASS" : "FAILED");
+
+ return pass;
+}
+
static nvme_root_t create_tree()
{
nvme_root_t r;
@@ -97,14 +140,10 @@ static nvme_root_t create_tree()
assert(d->c);
d->ctrl_id = i;
- assert(!strcmp(d->transport, nvme_ctrl_get_transport(d->c)));
- assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(d->c)));
- assert(!d->host_traddr || !strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(d->c)));
- assert(!d->host_iface || !strcmp(d->host_iface, nvme_ctrl_get_host_iface(d->c)));
- assert(!d->trsvcid || !strcmp(d->trsvcid, nvme_ctrl_get_trsvcid(d->c)));
-
printf(" ");
show_ctrl(d->c);
+ match_ctrl(d, d->c);
+ printf("\n");
}
printf("\n");
@@ -118,146 +157,1028 @@ static unsigned int count_entries(nvme_root_t r)
nvme_ctrl_t c;
unsigned int i = 0;
- nvme_for_each_host(r, h) {
- nvme_for_each_subsystem(h, s) {
- nvme_subsystem_for_each_ctrl(s, c) {
+ nvme_for_each_host(r, h)
+ nvme_for_each_subsystem(h, s)
+ nvme_subsystem_for_each_ctrl(s, c)
i++;
- }
- }
- }
return i;
}
-static void ctrl_lookups(nvme_root_t r)
+static bool tcp_ctrl_lookup(nvme_subsystem_t s, struct test_data *d)
{
- nvme_host_t h;
- nvme_subsystem_t s;
nvme_ctrl_t c;
+ bool pass = true;
- h = nvme_first_host(r);
- s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN);
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, NULL,
+ NULL, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, "", "");
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
- printf(" lookup controller:\n");
- for (int i = 0; i < ARRAY_SIZE(test_data); i++) {
- struct test_data *d = &test_data[i];
+ if (d->host_traddr) {
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
+ NULL, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, d->host_traddr, "");
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+ }
- printf("%10s %10s ", "", "");
- show_ctrl(d->c);
+ if (d->host_iface) {
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, NULL,
+ d->host_iface, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, "", d->host_iface);
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+ }
+
+ if (d->host_iface && d->traddr) {
c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
- NULL, NULL, NULL);
- printf("%10s %10s -> ", "-", "-");
+ d->host_iface, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, d->host_traddr, d->host_iface);
show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+ }
- assert(!strcmp(d->transport, nvme_ctrl_get_transport(c)));
- assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c)));
- assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c)));
+ return pass;
+}
- if (d->host_iface) {
- c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
- d->host_iface, NULL, NULL);
- printf("%10s %10s -> ", d->host_iface, "-");
- show_ctrl(c);
+static bool default_ctrl_lookup(nvme_subsystem_t s, struct test_data *d)
+{
+ nvme_ctrl_t c;
+ bool pass = true;
- assert(!strcmp(d->transport, nvme_ctrl_get_transport(c)));
- assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c)));
- assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c)));
- assert(!strcmp(d->host_iface, nvme_ctrl_get_host_iface(c)));
- }
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
+ NULL, NULL, NULL);
+ printf("%10s %12s %10s -> ", "", "", "");
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
- if (d->trsvcid) {
- c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
- NULL, d->trsvcid, NULL);
- printf("%10s %10s -> ", "-", d->trsvcid);
- show_ctrl(c);
+ return pass;
+}
- assert(!strcmp(d->transport, nvme_ctrl_get_transport(c)));
- assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c)));
- assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c)));
- assert(!strcmp(d->trsvcid, nvme_ctrl_get_trsvcid(c)));
- }
+static bool ctrl_lookups(nvme_root_t r)
+{
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ bool pass = true;
- if (d->host_iface && d->trsvcid) {
- c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
- d->host_iface, d->trsvcid, NULL);
- printf("%10s %10s -> ", d->host_iface, d->trsvcid);
- show_ctrl(c);
-
- assert(!strcmp(d->transport, nvme_ctrl_get_transport(c)));
- assert(!strcmp(d->traddr, nvme_ctrl_get_traddr(c)));
- assert(!strcmp(d->host_traddr, nvme_ctrl_get_host_traddr(c)));
- assert(!strcmp(d->trsvcid, nvme_ctrl_get_trsvcid(c)));
- assert(!strcmp(d->host_iface, nvme_ctrl_get_host_iface(c)));
- }
+ h = nvme_first_host(r);
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN);
+
+ printf(" lookup controller:\n");
+ for (int i = 0; i < ARRAY_SIZE(test_data); i++) {
+ struct test_data *d = &test_data[i];
+
+ printf("%10s %12s %10s ", "", "", "");
+ show_ctrl(d->c);
+ printf("\n");
+
+ if (!strcmp("tcp", d->transport))
+ pass &= tcp_ctrl_lookup(s, d);
+ else
+ pass &= default_ctrl_lookup(s, d);
printf("\n");
}
+
+ return pass;
}
-static void test_lookup_1(void)
+static bool test_lookup(void)
{
nvme_root_t r;
+ bool pass;
- printf("test_lookup_1:\n");
+ printf("test_lookup:\n");
r = create_tree();
- assert(count_entries(r) == ARRAY_SIZE(test_data));
- ctrl_lookups(r);
+ pass = count_entries(r) == ARRAY_SIZE(test_data);
+ pass &= ctrl_lookups(r);
nvme_free_tree(r);
+
+ return pass;
}
-static void test_lookup_2(void)
+static bool test_src_addr()
{
+ bool pass = true;
nvme_root_t r;
- nvme_subsystem_t s;
nvme_host_t h;
- nvme_ctrl_t c1, c2, c3, c4;
+ nvme_ctrl_t c;
+ nvme_subsystem_t s;
+ char *src_addr, buffer[100]; /* big enough for IPv6 max length */
- printf("test_lookup_2:\n");
+ printf("\n"
+ "test_src_addr:\n");
r = nvme_create_root(stdout, LOG_DEBUG);
assert(r);
+
h = nvme_default_host(r);
assert(h);
s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN);
assert(s);
- assert(nvme_lookup_ctrl(s, "tcp", "192.168.2.1", "192.168.2.20",
- "eth0", "4420", NULL));
-
- c1 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20",
- NULL, NULL, NULL);
- assert(c1);
- printf("%10s %10s ", "", "");
- show_ctrl(c1);
-
- c2 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20",
- "eth0", NULL, NULL);
- assert(c1 == c2);
- printf("%10s %10s ", "eth0", "-");
- show_ctrl(c2);
-
- c3 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20",
- NULL, "4420", NULL);
- assert(c1 == c3);
- printf("%10s %10s ", "-", "4420");
- show_ctrl(c3);
-
- c4 = nvme_lookup_ctrl(s, "tcp", "192.168.1.1", "192.168.1.20",
- "eth0", "4420", NULL);
- assert(c1 == c4);
- printf("%10s %10s ", "eth0", "4420");
- show_ctrl(c4);
+ c = nvme_lookup_ctrl(s, "tcp", "192.168.56.1", NULL, NULL, "8009", NULL);
+ assert(c);
+
+ c->address = NULL;
+ printf(" - Test c->address = NULL : src_addr = NULL ");
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (src_addr != NULL) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=NULL should return src_addr=NULL\n");
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "";
+ printf(" - Test c->address = \"\" : src_addr = NULL ");
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (src_addr != NULL) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address="" should return src_addr=NULL\n");
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=192.168.56.1,trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = NULL ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (src_addr != NULL) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=NULL\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=192.168.56.1,trsvcid=8009,src_addr=" SRC_ADDR4;
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR4 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR4)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR4 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=192.168.56.1,src_addr=" SRC_ADDR4 ",trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR4 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR4)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR4 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,trsvcid=8009,src_addr=" SRC_ADDR6;
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,src_addr=" SRC_ADDR6 ",trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,trsvcid=8009,src_addr=" SRC_ADDR6 "%scope";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,src_addr=" SRC_ADDR6 "%scope,trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = NULL; /* Needed to avoid freeing non-malloced memory (see above) */
+
+ nvme_free_tree(r);
+
+ return pass;
+}
+
+struct ctrl_args {
+ const char *transport;
+ const char *traddr;
+ const char *trsvcid;
+ const char *host_traddr;
+ const char *host_iface;
+ const char *address;
+ const char *subsysnqn;
+};
+
+static void set_ctrl_args(struct ctrl_args *args,
+ const char *transport,
+ const char *traddr,
+ const char *trsvcid,
+ const char *host_traddr,
+ const char *host_iface,
+ const char *address,
+ const char *subsysnqn)
+{
+ args->transport = transport;
+ args->traddr = traddr;
+ args->trsvcid = trsvcid;
+ args->host_traddr = host_traddr;
+ args->host_iface = host_iface;
+ args->address = address;
+ args->subsysnqn = subsysnqn;
+}
+
+static bool ctrl_match(const char *tag,
+ int reference_id,
+ int candidate_id,
+ struct ctrl_args *reference,
+ struct ctrl_args *candidate,
+ bool should_match)
+{
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t reference_ctrl; /* Existing controller (from sysfs) */
+ nvme_ctrl_t candidate_ctrl;
+ nvme_ctrl_t found_ctrl;
+ nvme_subsystem_t s;
+
+ r = nvme_create_root(stdout, LOG_INFO);
+ assert(r);
+
+ h = nvme_default_host(r);
+ assert(h);
+
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, reference->subsysnqn ? reference->subsysnqn : DEFAULT_SUBSYSNQN);
+ assert(s);
+
+ reference_ctrl = nvme_lookup_ctrl(s, reference->transport, reference->traddr,
+ reference->host_traddr, reference->host_iface,
+ reference->trsvcid, NULL);
+ assert(reference_ctrl);
+ reference_ctrl->name = "nvme1"; /* fake the device name */
+ if (reference->address) {
+ reference_ctrl->address = (char *)reference->address;
+ }
+
+ /* nvme_ctrl_find() MUST BE RUN BEFORE nvme_lookup_ctrl() */
+ found_ctrl = nvme_ctrl_find(s, candidate->transport, candidate->traddr,
+ candidate->trsvcid, candidate->subsysnqn,
+ candidate->host_traddr,
+ candidate->host_iface);
+
+ candidate_ctrl = nvme_lookup_ctrl(s, candidate->transport, candidate->traddr,
+ candidate->host_traddr, candidate->host_iface,
+ candidate->trsvcid, NULL);
+
+ if (should_match) {
+ if (candidate_ctrl != reference_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) failed to match (%s, %s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface,
+ reference->transport, reference->traddr, reference->trsvcid, reference->subsysnqn,
+ reference->host_traddr, reference->host_iface, reference->address);
+ return false;
+ }
+
+ if (!found_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) failed to find controller\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface);
+ return false;
+ }
+ } else {
+ if (candidate_ctrl == reference_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) should not match (%s, %s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface,
+ reference->transport, reference->traddr, reference->trsvcid, reference->subsysnqn,
+ reference->host_traddr, reference->host_iface, reference->address);
+ return false;
+ }
+
+ if (found_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) should not have found controller. found_ctrl=%p reference=%p\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid, candidate->subsysnqn,
+ candidate->host_traddr, candidate->host_iface, found_ctrl, reference_ctrl);
+ return false;
+ }
+ }
+
+ /* Set the faked data back to NULL before freeing the tree */
+ reference_ctrl->name = NULL;
+ reference_ctrl->address = NULL;
nvme_free_tree(r);
+
+ return true;
}
+/**
+ * test_ctrl_match_fc - Test that we can look up FC controllers
+ *
+ * @return true when all tests have passed. false otherwise.
+ */
+static bool test_ctrl_match_fc(void)
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("test_ctrl_match_fc:\n");
+
+ /*******************************************************************/
+ /* Reference ID 1 */
+ set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 1, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 1, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:21", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 5, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("FC", 1, 6, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 2 */
+ set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 2, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 2, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 2, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 2, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 2, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("FC", 2, 5, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 3 */
+ set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 3, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 3, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 3, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 3, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 3, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("FC", 3, 5, &reference, &candidate, false);
+
+ return pass;
+}
+
+/**
+ * test_ctrl_match_rdma - Test that we can look up RDMA controllers
+ *
+ * @return true when all tests have passed. false otherwise.
+ */
+static bool test_ctrl_match_rdma(void)
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("test_ctrl_match_rdma:\n");
+
+ /*******************************************************************/
+ /* Reference ID 1 */
+ set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 5, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 6, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 2 */
+ set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 5, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 3 */
+ set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 5, &reference, &candidate, false);
+
+ return pass;
+}
+
+/**
+ * test_ctrl_match_tcp - Test that we can look up TCP controllers
+ *
+ * @note: The mocked getifaddrs() returns 2 interface entries with the
+ * following addresses. Therefore the tests must use IP addresses
+ * that match these.
+ *
+ * eth0
+ * \_ 192.168.1.20
+ * \_ fe80::dead:beef
+ *
+ * lo
+ * \_ 127.0.0.1
+ * \_ ::1
+ *
+ * @return true when all tests have passed. false otherwise.
+ */
+static bool test_ctrl_match_tcp()
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("\n"
+ "test_ctrl_match_tcp:\n");
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 1 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 5, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 7, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 8, &reference, &candidate, true);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 2 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 3 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 4 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 5 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 6 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, "traddr=123.123.123.123,trsvcid=8009,src_addr=192.168.1.20", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 7 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, "traddr=123.123.123.123,trsvcid=8009,src_addr=127.0.0.1", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 1 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 5, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 7, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 8, &reference, &candidate, true);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 2 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 3 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 4 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 5 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 6 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, "traddr=aaaa::bbbb,trsvcid=8009,src_addr=fe80::dead:beef", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 7 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, "traddr=aaaa::bbbb,trsvcid=8009,src_addr=::1", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 8, &reference, &candidate, false);
+
+ return pass;
+}
+
+static bool ctrl_config_match(const char *tag,
+ int reference_id,
+ int candidate_id,
+ struct ctrl_args *reference,
+ struct ctrl_args *candidate,
+ bool should_match)
+{
+ bool match;
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t reference_ctrl; /* Existing controller (from sysfs) */
+ nvme_subsystem_t s;
+
+ r = nvme_create_root(stdout, LOG_INFO);
+ assert(r);
+
+ h = nvme_default_host(r);
+ assert(h);
+
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, reference->subsysnqn ? reference->subsysnqn : DEFAULT_SUBSYSNQN);
+ assert(s);
+
+ reference_ctrl = nvme_lookup_ctrl(s, reference->transport, reference->traddr,
+ reference->host_traddr, reference->host_iface,
+ reference->trsvcid, NULL);
+ assert(reference_ctrl);
+ reference_ctrl->name = "nvme1"; /* fake the device name */
+ if (reference->address) {
+ reference_ctrl->address = (char *)reference->address;
+ }
+
+ match = nvme_ctrl_config_match(reference_ctrl, candidate->transport, candidate->traddr,
+ candidate->trsvcid, candidate->subsysnqn,
+ candidate->host_traddr, candidate->host_iface);
+
+ if (should_match) {
+ if (!match) {
+ printf("%s-%d-%d: Failed to match config for Candidate (%s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface);
+ return false;
+ }
+ } else {
+ if (match) {
+ printf("%s-%d-%d: Config should not have matched for Candidate (%s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface);
+ return false;
+ }
+ }
+
+ /* Set the faked data back to NULL before freeing the tree */
+ reference_ctrl->name = NULL;
+ reference_ctrl->address = NULL;
+
+ nvme_free_tree(r);
+
+ return true;
+}
+
+static bool test_ctrl_config_match()
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("\n"
+ "test_ctrl_config_match:\n");
+
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 5, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 7, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 8, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, "hello");
+ pass &= ctrl_config_match("IPv4", 1, 9, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, DEFAULT_SUBSYSNQN);
+ pass &= ctrl_config_match("IPv4", 1, 9, &reference, &candidate, true);
+
+ return pass;
+}
+
+
+/**
+ * This test module uses a mocked ifaddrs library (mock-ifaddrs.c)
+ * such that there are 2 fake interfaces (eth0 and lo) with the
+ * following IP addresses:
+ *
+ * - eth0
+ * \_ IPv4: 192.168.1.20
+ * \_ IPv6: fe80::dead:beef
+ *
+ * - lo
+ * \_ IPv4: 127.0.0.1
+ * \_ IPv6: ::1
+ */
int main(int argc, char *argv[])
{
- test_lookup_1();
- test_lookup_2();
+ bool pass = true;
+
+ pass &= test_lookup();
+ pass &= test_src_addr();
+ pass &= test_ctrl_match_fc();
+ pass &= test_ctrl_match_rdma();
+ pass &= test_ctrl_match_tcp();
+ pass &= test_ctrl_config_match();
+
+ fflush(stdout);
- return 0;
+ exit(pass ? EXIT_SUCCESS : EXIT_FAILURE);
}