[RFC 53/56] NAN: Add test utility for NAN module

Andrei Otcheretianski andrei.otcheretianski at intel.com
Sun Dec 7 03:19:02 PST 2025


From: Ilan Peer <ilan.peer at intel.com>

The main motivation of the utility is to be able to test
NAN NDP/NDL state machine transitions.

Signed-off-by: Ilan Peer <ilan.peer at intel.com>
---
 src/nan/nan_module_test_cases.c    |  398 ++++++++++
 src/nan/nan_module_tests.c         | 1164 ++++++++++++++++++++++++++++
 src/nan/nan_module_tests.h         |  129 +++
 src/utils/module_tests.h           |    1 +
 wpa_supplicant/Makefile            |    4 +
 wpa_supplicant/wpas_module_tests.c |    5 +
 6 files changed, 1701 insertions(+)
 create mode 100644 src/nan/nan_module_test_cases.c
 create mode 100644 src/nan/nan_module_tests.c
 create mode 100644 src/nan/nan_module_tests.h

diff --git a/src/nan/nan_module_test_cases.c b/src/nan/nan_module_test_cases.c
new file mode 100644
index 0000000000..43e08d34b4
--- /dev/null
+++ b/src/nan/nan_module_test_cases.c
@@ -0,0 +1,398 @@
+/*
+ * nan_test - NAN NDP state machine test cases
+ * Copyright (C) 2025 Intel Corporation
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "nan_module_tests.h"
+
+static u8 default_bitmap[] = {0xfe, 0xff, 0xfe, 0xff};
+
+static void nan_test_schedule_cb(struct nan_schedule *sched, bool ndc,
+				 int freq, int center_freq1, int center_freq2,
+				 int bandwidth)
+{
+	os_memset(sched, 0, sizeof(*sched));
+
+	/* Only map ID 0 is used */
+	sched->map_ids_bitmap = 0x1;
+
+	sched->n_chans = 1;
+	sched->chans[0].chan.freq = freq;
+	sched->chans[0].chan.center_freq1 = center_freq1;
+	sched->chans[0].chan.center_freq2 = center_freq2;
+	sched->chans[0].chan.bandwidth = bandwidth;
+
+	sched->chans[0].committed.duration = 0;
+	sched->chans[0].committed.period = 3;
+	sched->chans[0].committed.offset = 0;
+	sched->chans[0].committed.len = sizeof(default_bitmap);
+	os_memcpy(sched->chans[0].committed.bitmap,
+		  default_bitmap,
+		  sizeof(default_bitmap));
+
+	sched->chans[0].map_id = 0;
+
+	if (!ndc)
+		return;
+
+	os_memset(&sched->ndc, 0, sizeof(sched->ndc));
+	sched->ndc.duration = 0;
+	sched->ndc.period = 3;
+	sched->ndc.offset = 0;
+	sched->ndc.len = 1;
+	sched->ndc.bitmap[0] = 0xfe;
+}
+
+
+static int nan_test_schedule_cb_all_ndc(struct nan_schedule *sched)
+{
+	static u8 seq_id = 1;
+
+	nan_test_schedule_cb(sched, true, 5745, 5745, true, 20);
+	sched->sequence_id = ++seq_id;
+	return 0;
+}
+
+static int nan_test_schedule_cb_all_no_ndc(struct nan_schedule *sched)
+{
+	static u8 seq_id = 1;
+
+	nan_test_schedule_cb(sched, false, 5745, 5745, 0, 20);
+	sched->sequence_id = ++seq_id;
+	return 0;
+}
+
+
+static int nan_test_schedule_cb_all_no_ndc_period_4(struct nan_schedule *sched)
+{
+	static u8 seq_id = 1;
+
+	nan_test_schedule_cb(sched, false, 5745, 5745, 0, 20);
+
+	/*
+	 * Modify the map to have a period or 1024 TUs (both halfs are
+	 * identical
+	 */
+	sched->chans[0].committed.period = 4;
+	sched->chans[0].committed.len += sizeof(default_bitmap);
+	os_memcpy(sched->chans[0].committed.bitmap + sizeof(default_bitmap),
+		  default_bitmap,
+		  sizeof(default_bitmap));
+
+	sched->sequence_id = ++seq_id;
+	return 0;
+}
+
+
+static int nan_test_schedule_cb_2ghz_no_ndc(struct nan_schedule *sched)
+{
+	static u8 seq_id = 1;
+
+	nan_test_schedule_cb(sched, false, 2437, 2437, 0, 20);
+	sched->sequence_id = ++seq_id;
+	return 0;
+}
+
+
+static int nan_test_get_chans_default(struct nan_channels *chans)
+{
+	chans->n_chans = 2;
+	chans->chans = os_zalloc(sizeof(struct nan_channel_info) *
+				 chans->n_chans);
+	if (!chans->chans)
+		return -1;
+
+	chans->chans[0].channel = 6;
+	chans->chans[0].op_class = 81;
+	chans->chans[0].pref = 255;
+
+	chans->chans[1].channel = 149;
+	chans->chans[1].op_class = 126;
+	chans->chans[1].pref = 254;
+	return 0;
+}
+
+
+static int nan_test_get_chans_default_reverse(struct nan_channels *chans)
+{
+	chans->n_chans = 2;
+	chans->chans = os_zalloc(sizeof(struct nan_channel_info) *
+				 chans->n_chans);
+	if (!chans->chans)
+		return -1;
+
+	chans->chans[0].channel = 149;
+	chans->chans[0].op_class = 126;
+	chans->chans[0].pref = 255;
+
+	chans->chans[1].channel = 6;
+	chans->chans[1].op_class = 81;
+	chans->chans[1].pref = 254;
+
+	return 0;
+}
+
+
+static int nan_test_get_chans_default_24g(struct nan_channels *chans)
+{
+	chans->n_chans = 1;
+	chans->chans = os_zalloc(sizeof(struct nan_channel_info) *
+				 chans->n_chans);
+	if (!chans->chans)
+		return -1;
+
+	chans->chans[0].channel = 6;
+	chans->chans[0].op_class = 81;
+	chans->chans[0].pref = 255;
+	return 0;
+}
+
+
+static int nan_test_get_chans_default_52g(struct nan_channels *chans)
+{
+	chans->n_chans = 1;
+	chans->chans = os_zalloc(sizeof(struct nan_channel_info) *
+				 chans->n_chans);
+	if (!chans->chans)
+		return -1;
+
+	chans->chans[0].channel = 149;
+	chans->chans[0].op_class = 126;
+	chans->chans[0].pref = 255;
+	return 0;
+}
+
+
+static struct nan_test_case three_way_ndp_two_way_ndl_chan_149 = {
+	.name = "Three way NDP and two way NDL channel 149",
+	.pub_conf = {
+		.schedule_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.term_once_connected = 0,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+		.pot_avail = {
+			0x12, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
+			0xba, 0x02, 0x20, 0x02, 0x04
+		},
+		.pot_avail_len = 13,
+	},
+	.sub_conf  = {
+		.schedule_cb = nan_test_schedule_cb_all_no_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.term_once_connected = 1,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+	}
+};
+
+
+static struct nan_test_case three_way_ndp_two_way_ndl_diff_period = {
+	.name = "Three way NDP and two way NDL channel 149. Subscriber period=4",
+	.pub_conf = {
+		.schedule_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.term_once_connected = 0,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+		.pot_avail = {
+			0x12, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
+			0xba, 0x02, 0x20, 0x02, 0x04
+		},
+		.pot_avail_len = 13,
+	},
+	.sub_conf  = {
+		.schedule_cb = nan_test_schedule_cb_all_no_ndc_period_4,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.term_once_connected = 1,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+	}
+};
+
+static struct nan_test_case three_way_ndp_two_way_ndl_chan_6 = {
+	.name = "Three way NDP and two way NDL channel 6",
+	.pub_conf = {
+		.schedule_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.term_once_connected = 1,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+		.pot_avail = {
+			0x12, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
+			0xba, 0x02, 0x20, 0x02, 0x04
+		},
+		.pot_avail_len = 13,
+	},
+	.sub_conf  = {
+		.schedule_cb = nan_test_schedule_cb_all_no_ndc,
+		.get_chans_cb = nan_test_get_chans_default_24g,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.term_once_connected = 0,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+	}
+};
+
+static struct nan_test_case three_way_ndp_two_way_ndl_chan_mis = {
+	.name = "Three way NDP and two way NDL channel mismatch",
+	.pub_conf = {
+		.schedule_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default_52g,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_DISCONNECTED,
+			},
+		},
+		.pot_avail = {
+			0x12, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
+			0xba, 0x02, 0x20, 0x02, 0x04
+		},
+		.pot_avail_len = 13,
+	},
+	.sub_conf  = {
+		.schedule_cb = nan_test_schedule_cb_2ghz_no_ndc,
+		.get_chans_cb = nan_test_get_chans_default_24g,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 0,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_DISCONNECTED,
+			},
+		},
+	}
+};
+
+static struct nan_test_case three_way_ndp_three_way_ndl = {
+	.name = "Three way NDP and three way NDL",
+	.pub_conf = {
+		.schedule_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+			},
+		},
+		.pot_avail = {
+			0x12, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
+			0xba, 0x02, 0x20, 0x02, 0x04
+		},
+		.pot_avail_len = 13,
+	},
+	.sub_conf  = {
+		.schedule_cb = nan_test_schedule_cb_2ghz_no_ndc,
+		.schedule_conf_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default_reverse,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 1,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_CONNECTED,
+				.term_once_connected = 1,
+			},
+		},
+	}
+};
+
+static struct nan_test_case three_way_ndp_two_way_ndl_reject = {
+	.name = "Three way NDP and two way NDL rejected",
+	.pub_conf = {
+		.schedule_cb = nan_test_schedule_cb_all_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 0,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_DISCONNECTED,
+				.reason = NAN_REASON_NDP_REJECTED,
+			},
+		},
+		.pot_avail = {
+			0x12, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
+			0xba, 0x02, 0x20, 0x02, 0x04
+		},
+		.pot_avail_len = 13,
+	},
+	.sub_conf  = {
+		.schedule_cb = nan_test_schedule_cb_all_no_ndc,
+		.get_chans_cb = nan_test_get_chans_default,
+		.n_ndps = 1,
+		.ndp_confs = {
+			{
+				.accept_request = 0,
+				.expected_result =
+					NAN_TEST_NDP_NOTIFY_DISCONNECTED,
+			},
+		},
+	}
+};
+
+
+static struct nan_test_case *g_nan_test_cases[] = {
+	&three_way_ndp_two_way_ndl_chan_149,
+	&three_way_ndp_two_way_ndl_diff_period,
+	&three_way_ndp_two_way_ndl_chan_6,
+	&three_way_ndp_two_way_ndl_chan_mis,
+	&three_way_ndp_two_way_ndl_reject,
+	&three_way_ndp_three_way_ndl,
+	NULL,
+};
+
+
+struct nan_test_case *nan_test_case_get_next(void)
+{
+	static u32 nan_test_case_idx = 0;
+	struct nan_test_case *curr = g_nan_test_cases[nan_test_case_idx];
+
+	if (curr)
+		nan_test_case_idx++;
+
+	return curr;
+}
diff --git a/src/nan/nan_module_tests.c b/src/nan/nan_module_tests.c
new file mode 100644
index 0000000000..6ebf3c792d
--- /dev/null
+++ b/src/nan/nan_module_tests.c
@@ -0,0 +1,1164 @@
+/*
+ * NAN NDP/NDL state machine testing
+ * Copyright (C) 2025 Intel Corporation
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "nan_module_tests.h"
+
+#define NAN_TEST_MAX_NAF_LEN     1024
+#define NAN_TEST_MAX_PEERS       20
+#define NAN_TEST_MAX_ACTIONS     30
+#define NAN_TEST_MIN_SLOTS       12
+#define NAN_TEST_MAX_LATENCY     3
+#define NAN_TEST_PUBLISH_INST_ID 12
+
+static const u8 pub_nmi[] = {0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
+static const u8 pub_ndi[] = {0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00};
+static const u8 sub_nmi[] = {0x00, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB};
+static const u8 sub_ndi[] = {0x00, 0xBB, 0xBB, 0xBB, 0x00, 0x00};
+
+/*
+ * struct nan_test_action - NAN test action context
+ *
+ * @list: Used for global actions list
+ * @dev: NAN device for which the action is intended
+ * @cb: Callback to be called for the action
+ * @ctx: Parameter for the callback function
+ *
+ * The test utility uses actions to handle asynchronous events between the
+ * devices and internally to the local device. When an event is triggered
+ * outside of the NAN module context, the event is wrapped by an action item and
+ * is processed asynchronously. Examples:
+ *
+ * NAF transmission: when a NAF is transmitted by a device, it is translated to
+ * 2 asynchronous actions: one action to indicate Tx status to the transmitting
+ * device and one to indicate an Rx frame to the peer device.
+ *
+ * NDP event: when an NDP event is fired by the NAN module, it is translated to
+ * an asynchronous action to the local device.
+ */
+struct nan_test_action {
+	struct dl_list list;
+	struct nan_device *dev;
+	int (*cb)(struct nan_device *dev, void *ctx);
+	void *ctx;
+};
+
+/*
+ * nan_test_tx_status_action - NAN test Tx status action context
+ *
+ * @data: Copy of the original NAF
+ * @acked: True iff the NAF was acked
+ * @dst: Destination of the NAF
+ */
+struct nan_test_tx_status_action {
+	const struct wpabuf *data;
+	bool acked;
+	u8 dst[ETH_ALEN];
+};
+
+/*
+ * nan_test_ndp_notify - NAN test NDP notification handling
+ *
+ * @type: Notification type
+ * @ndp_id: NDP identifier
+ * @ssi: Service specific information
+ * @ssi_len: Length of service specific information
+ */
+struct nan_test_ndp_notify {
+	enum nan_test_ndp_notify_type type;
+	struct nan_ndp_id ndp_id;
+	const u8 *ssi;
+	size_t ssi_len;
+};
+
+/*
+ * nan_test_global - Global context for the NAN testing
+ *
+ * @devs: List of devices. See &struct nan_device.
+ * @actions: tracks the NAN actions
+ * @elems: Default HT/VHT/HE capabilities elements
+ */
+struct nan_test_global {
+	struct dl_list devs;
+	struct dl_list actions;
+	struct wpabuf *elems;
+};
+
+#define DEV_NOT_INIT_ERR(_dev)                                        \
+do {                                                                  \
+	if (!(_dev) || !(_dev)->nan)  {                               \
+		wpa_printf(MSG_ERROR,                                 \
+			   "NAN: %s: device not initialized",         \
+			   __func__);                                 \
+		return -1;                                            \
+	}                                                             \
+} while (0)
+
+#define DEV_NOT_INIT_ERR_VOID(_dev)                                   \
+do {                                                                  \
+	if (!(_dev) || !(_dev)->nan)  {                               \
+		wpa_printf(MSG_ERROR,                                 \
+			   "NAN: %s: device not initialized",         \
+			   __func__);                                 \
+		return;                                               \
+	}                                                             \
+} while (0)
+
+/*
+ * nan_test_global_init - Initialize NAN test global data structures
+ *
+ * @global: NAN test global data structure
+ */
+static void nan_test_global_init(struct nan_test_global *global)
+{
+	u8 elems[] = {
+		/* HT capabilities */
+		0x2d, 0x1a, 0x7e, 0x10, 0x1b, 0xff, 0xff, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+
+		/* VHT capabilities */
+		0xbf, 0x0c, 0xfa, 0x04, 0x80, 0x03, 0xaa, 0xaa,
+		0x00, 0x00, 0xaa, 0xaa, 0x00, 0x00,
+
+		/* HE capabilities */
+		0xff, 0x1e, 0x23, 0x01, 0x78, 0xc8, 0x1a, 0x40,
+		0x00, 0x1c, 0xbf, 0xce, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0xfa, 0xff, 0xfa, 0xff,
+		0xfa, 0xff, 0xfa, 0xff, 0xfa, 0xff, 0xfa, 0xff,
+	};
+
+	wpa_printf(MSG_INFO, "%s: Enter", __func__);
+	os_memset(global, 0, sizeof(struct nan_test_global));
+	dl_list_init(&global->devs);
+	dl_list_init(&global->actions);
+
+	global->elems = wpabuf_alloc(sizeof(elems));
+	wpabuf_put_data(global->elems, elems, sizeof(elems));
+
+	wpa_printf(MSG_INFO, "%s: Done\n", __func__);
+}
+
+
+/*
+ * nan_test_dev_deinit - De-initialize NAN device
+ *
+ * @dev: NAN device
+ */
+static void nan_test_dev_deinit(struct nan_device *dev)
+{
+	DEV_NOT_INIT_ERR_VOID(dev);
+
+	os_free(dev->pot_avail);
+	nan_stop(dev->nan);
+	nan_deinit(dev->nan);
+	os_memset(dev, 0, sizeof(struct nan_device));
+}
+
+
+/*
+ * nan_test_global_deinit - De-initialize NAN test global data structures
+ *
+ * @global: NAN test global data structure
+ */
+static void nan_test_global_deinit(struct nan_test_global *global)
+{
+	struct nan_device *dev, *next;
+	struct nan_test_action *action, *action_next;
+
+	wpa_printf(MSG_INFO, "%s: Enter", __func__);
+
+	dl_list_for_each_safe(dev, next, &global->devs, struct nan_device,
+			      list) {
+		dl_list_del(&dev->list);
+		nan_test_dev_deinit(dev);
+		os_free(dev);
+	}
+
+	dl_list_for_each_safe(action, action_next, &global->actions,
+			      struct nan_test_action, list) {
+		dl_list_del(&dev->list);
+		os_free(action->ctx);
+		os_free(action);
+	}
+
+	wpabuf_free(global->elems);
+}
+
+
+/*
+ * nan_test_add_action - Add a NAN test action to the list of actions
+ *
+ * @global: NAN test global data structure
+ * @dev: NAN device for which the action is intended
+ * @cb: Callback to be called for the action
+ * @ctx: Parameter for the callback function
+ * Returns a pointer to the newly added action, or NULL on failure
+ */
+static struct nan_test_action *
+nan_test_add_action(struct nan_test_global *global,
+		    struct nan_device *dev,
+		    int (*cb)(struct nan_device *dev, void *ctx),
+		    void *ctx)
+{
+	struct nan_test_action *action;
+
+	action = os_malloc(sizeof(struct nan_test_action));
+	if (!action) {
+		wpa_printf(MSG_ERROR, "NAN Test: Failed to allocate action");
+		return NULL;
+	}
+
+	action->dev = dev;
+	action->cb = cb;
+	action->ctx = ctx;
+
+	dl_list_add_tail(&global->actions, &action->list);
+	return action;
+}
+
+
+/*
+ * nan_test_run_actions - Iterate over all NAN test actions and execute them
+ * @global: NAN test global data structure
+ * return 0 in success, -1 on error.
+ *
+ * Runs as longs as there are actions to run. Exists where there are no more
+ * action to perform or on error.
+ */
+static int nan_test_run_actions(struct nan_test_global *global)
+{
+	u32 n_actions = 0;
+
+	wpa_printf(MSG_INFO, "%s: Running actions", __func__);
+
+	while (!dl_list_empty(&global->actions)) {
+		struct nan_test_action *action =
+			dl_list_first(&global->actions, struct nan_test_action,
+				      list);
+		int ret;
+
+		dl_list_del(&action->list);
+
+		if (++n_actions > NAN_TEST_MAX_ACTIONS) {
+			wpa_printf(MSG_ERROR,
+				   "NAN Test action: Too many actions executed");
+			return -1;
+		}
+
+		if (!action->cb || !action->dev) {
+			wpa_printf(MSG_ERROR,
+				   "NAN Test action: Invalid action");
+			return -1;
+		}
+
+		wpa_printf(MSG_INFO, "%s: ===> NAN Test action <===",
+			   action->dev->name);
+		ret = action->cb(action->dev, action->ctx);
+
+		/*
+		 * The action context should be freed by the callback function
+		 * that understands the content of the context and can properly
+		 * handle it.
+		 */
+		os_free(action);
+
+		if (ret) {
+			wpa_printf(MSG_ERROR, "NAN Test action: FAILED");
+			return ret;
+		}
+	}
+
+	wpa_printf(MSG_INFO, "%s: Running actions done", __func__);
+	return 0;
+}
+
+static int nan_test_start_cb(void *ctx, struct nan_cluster_config *config)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+
+	DEV_NOT_INIT_ERR(dev);
+
+	return 0;
+}
+
+
+/*
+ * nan_test_stop_cb - Stop the NAN device.
+ *
+ * @ctx: Pointer to the &struct nan_device
+ */
+static void nan_test_stop_cb(void *ctx)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+
+	DEV_NOT_INIT_ERR_VOID(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: Done", __func__, dev->name);
+}
+
+
+static int nan_test_nan_ndp_action(struct nan_device *dev, void *ctx)
+{
+	struct nan_ndp_params *params = (struct nan_ndp_params *)ctx;
+	int ret;
+
+	wpa_printf(MSG_INFO, "%s: %s: type=%u, peer_nmi=" MACSTR,
+		   dev->name, __func__, params->type,
+		   MAC2STR(params->ndp_id.peer_nmi));
+
+	ret = nan_handle_ndp_setup(dev->nan, params);
+
+	os_free(params);
+
+	return ret;
+}
+
+
+/*
+ * nan_ndp_notify_action - Default NAN NDP notification handling
+ *
+ * @dev: NAN device
+ * @ctx: Pointer to &struct nan_test_ndp_notify
+ */
+static int nan_ndp_notify_action(struct nan_device *dev, void *ctx)
+{
+	struct nan_test_ndp_notify *notify = (struct nan_test_ndp_notify *)ctx;
+	struct nan_test_action *action;
+	int ret;
+
+	wpa_printf(MSG_INFO, "%s: %s: type=%u, peer_nmi=" MACSTR,
+		   dev->name, __func__,
+		   notify->type, MAC2STR(notify->ndp_id.peer_nmi));
+
+	ret = -1;
+	if (notify->type == NAN_TEST_NDP_NOTIFY_REQUEST) {
+		struct nan_ndp_params *params;
+		struct nan_device *curd;
+		u8 found = 0;
+
+		dl_list_for_each(curd, &dev->global->devs, struct nan_device,
+				 list) {
+			if (os_memcmp(curd->nmi, notify->ndp_id.peer_nmi,
+				      ETH_ALEN) == 0) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (!found) {
+			wpa_printf(MSG_ERROR, "Peer device not found");
+			ret = -1;
+			goto out;
+		}
+
+		params = os_zalloc(sizeof(struct nan_ndp_params));
+		if (!params) {
+			ret = -1;
+			goto out;
+		}
+
+		params->type = NAN_NDP_ACTION_RESP;
+		os_memcpy(params->ndp_id.peer_nmi, notify->ndp_id.peer_nmi,
+			  ETH_ALEN);
+		os_memcpy(params->ndp_id.init_ndi, notify->ndp_id.init_ndi,
+			  ETH_ALEN);
+		params->ndp_id.id = notify->ndp_id.id;
+		params->qos.min_slots = NAN_TEST_MIN_SLOTS;
+		params->qos.max_latency = NAN_TEST_MAX_LATENCY;
+
+		if (dev->conf->ndp_confs[dev->n_ndps].accept_request) {
+			wpa_printf(MSG_INFO, "%s: Accepting request",
+				   dev->name);
+
+			os_memcpy(params->u.resp.resp_ndi, pub_ndi, ETH_ALEN);
+			params->u.resp.status = NAN_NDP_STATUS_ACCEPTED;
+			dev->conf->schedule_cb(&params->sched);
+			params->sched.elems = dev->global->elems;
+			params->sched_valid = 1;
+		} else {
+			wpa_printf(MSG_INFO, "%s: Rejecting request",
+				   dev->name);
+
+			params->u.resp.status = NAN_NDP_STATUS_REJECTED;
+			params->u.resp.reason_code =
+				dev->conf->ndp_confs[dev->n_ndps].reason;
+		}
+
+		action = nan_test_add_action(dev->global, dev,
+					     nan_test_nan_ndp_action,
+					     params);
+		if (action)
+			ret = 0;
+	} else if (notify->type == NAN_TEST_NDP_NOTIFY_RESPONSE) {
+		struct nan_ndp_params *params;
+
+		params = os_zalloc(sizeof(struct nan_ndp_params));
+		if (!params) {
+			ret = -1;
+			goto out;
+		}
+
+		params->type = NAN_NDP_ACTION_CONF;
+		os_memcpy(params->ndp_id.peer_nmi, notify->ndp_id.peer_nmi,
+			  ETH_ALEN);
+		os_memcpy(params->ndp_id.init_ndi, notify->ndp_id.init_ndi,
+			  ETH_ALEN);
+		params->ndp_id.id = notify->ndp_id.id;
+
+		params->qos.min_slots = NAN_TEST_MIN_SLOTS;
+		params->qos.max_latency = NAN_TEST_MAX_LATENCY;
+
+		if (dev->conf->ndp_confs[dev->n_ndps].accept_request) {
+			wpa_printf(MSG_INFO, "%s: Accepting response",
+				   dev->name);
+
+			os_memcpy(params->u.resp.resp_ndi, sub_ndi, ETH_ALEN);
+			params->u.resp.status = NAN_NDP_STATUS_ACCEPTED;
+
+			if (!dev->conf->schedule_conf_cb) {
+				wpa_printf(MSG_ERROR,
+					   "%s: No schedule conf cb defined",
+					   dev->name);
+				os_free(params);
+				ret = -1;
+				goto out;
+			}
+
+			dev->conf->schedule_conf_cb(&params->sched);
+			params->sched.elems = dev->global->elems;
+			params->sched_valid = 1;
+
+		} else {
+			wpa_printf(MSG_INFO, "%s: Rejecting response",
+				   dev->name);
+
+			params->u.resp.status = NAN_NDP_STATUS_REJECTED;
+			params->u.resp.reason_code =
+				dev->conf->ndp_confs[dev->n_ndps].reason;
+		}
+
+		action = nan_test_add_action(dev->global, dev,
+					     nan_test_nan_ndp_action,
+					     params);
+		if (action)
+			ret = 0;
+	} else if (notify->type ==
+		   dev->conf->ndp_confs[dev->n_ndps].expected_result) {
+		ret = 0;
+		if (notify->type == NAN_TEST_NDP_NOTIFY_CONNECTED) {
+			if (dev->conf->ndp_confs[dev->n_ndps].term_once_connected) {
+				struct nan_ndp_params *params;
+
+				wpa_printf(MSG_INFO,
+					   "%s: Connected successfully. Now term",
+					   dev->name);
+
+				params = os_zalloc(sizeof(*params));
+				if (!params) {
+					ret = -1;
+					goto out;
+				}
+
+				params->type = NAN_NDP_ACTION_TERM;
+				os_memcpy(params->ndp_id.peer_nmi,
+					  notify->ndp_id.peer_nmi,
+					  ETH_ALEN);
+				os_memcpy(params->ndp_id.init_ndi,
+					  notify->ndp_id.init_ndi,
+					  ETH_ALEN);
+				params->ndp_id.id = notify->ndp_id.id;
+
+				action = nan_test_add_action(dev->global, dev,
+							     nan_test_nan_ndp_action,
+							     params);
+				if (action)
+					ret = 0;
+			}
+		}
+	} else if (notify->type == NAN_TEST_NDP_NOTIFY_DISCONNECTED &&
+		   dev->connected_notify_received) {
+		wpa_printf(MSG_INFO,
+			   "%s: Disconnected after connected as expected. Test case done",
+			   dev->name);
+		ret = 0;
+	} else {
+		wpa_printf(MSG_ERROR,
+			   "%s: Unexpected notify: type=%u expected=%u",
+			   dev->name, notify->type,
+			   dev->conf->ndp_confs[dev->n_ndps].expected_result);
+		ret = -1;
+	}
+
+out:
+	os_free((void *)notify->ssi);
+	os_free(notify);
+	return ret;
+}
+
+
+static void nan_test_ndp_action(struct nan_device *dev, enum
+				nan_test_ndp_notify_type type,
+				struct nan_ndp_id *ndp_id,
+				u8 publish_inst_id,
+				const u8 *ssi, size_t ssi_len,
+				enum nan_cipher_suite_id csid,
+				const u8 *pmkid)
+{
+	struct nan_test_ndp_notify *notify;
+
+	DEV_NOT_INIT_ERR_VOID(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: Enter: type=%u",
+		   dev->name, __func__, type);
+
+	notify = os_zalloc(sizeof(struct nan_test_ndp_notify));
+	if (!notify) {
+		wpa_printf(MSG_ERROR,
+			   "Failed allocation: nan_test_ndp_notify");
+		return;
+	}
+
+	notify->type = type;
+	os_memcpy(&notify->ndp_id, ndp_id, sizeof(notify->ndp_id));
+
+	if (ssi && ssi_len) {
+		notify->ssi = os_memdup(ssi, ssi_len);
+		if (!notify->ssi) {
+			wpa_printf(MSG_ERROR, "Failed allocation: ssi");
+			os_free(notify);
+			return;
+		}
+		notify->ssi_len = ssi_len;
+	}
+
+	if (nan_test_add_action(dev->global, dev, nan_ndp_notify_action,
+				notify))
+		return;
+
+	os_free((void *)notify->ssi);
+	os_free(notify);
+}
+
+
+/*
+ * nan_test_ndp_action_notfi_cb - Callback for NDP request
+ *
+ * @ctx: Pointer to &struct nan_device
+ * @params: NDP action notification parameters
+ *
+ * The handling of the event is done asynchronously through the NAN test actions
+ * processing.
+ */
+static void nan_test_ndp_action_notfi_cb(void *ctx, struct
+					 nan_ndp_action_notif_params *params)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+	enum nan_test_ndp_notify_type type;
+
+	DEV_NOT_INIT_ERR_VOID(dev);
+
+	if (params->is_request)
+		type = NAN_TEST_NDP_NOTIFY_REQUEST;
+	else
+		type = NAN_TEST_NDP_NOTIFY_RESPONSE;
+
+	nan_test_ndp_action(dev, type, &params->ndp_id,
+			    params->publish_inst_id, params->ssi,
+			    params->ssi_len,
+			    params->csid, params->pmkid);
+}
+
+
+/*
+ * nan_test_ndp_connected_cb - Callback for NDP connected
+ *
+ * @ctx: Pointer to &struct nan_device
+ * @params: NDP action notification parameters
+ *
+ * The handling of the event is done asynchronously through the NAN test actions
+ * processing.
+ */
+static void nan_test_ndp_connected_cb(void *ctx,
+				      struct nan_ndp_connection_params *params)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+	struct nan_peer_schedule sched;
+	struct nan_peer_potential_avail pot;
+
+	DEV_NOT_INIT_ERR_VOID(dev);
+
+	wpa_printf(MSG_INFO,
+		   "%s: %s: Enter. local_ndi=" MACSTR " peer_ndi=" MACSTR,
+		   dev->name, __func__,
+		   MAC2STR(params->local_ndi), MAC2STR(params->peer_ndi));
+
+	nan_peer_get_schedule_info(dev->nan, params->ndp_id.peer_nmi,
+				   &sched);
+	nan_peer_get_pot_avail(dev->nan, params->ndp_id.peer_nmi,
+			       &pot);
+
+	nan_test_ndp_action(dev, NAN_TEST_NDP_NOTIFY_CONNECTED,
+			    &params->ndp_id, 0,
+			    params->ssi, params->ssi_len,
+			    NAN_CS_NONE, NULL);
+
+	dev->connected_notify_received = true;
+}
+
+
+/*
+ * nan_test_ndp_disconnected_cb - Callback for NDP disconnected
+ *
+ * @ctx: Pointer to &struct nan_device
+ * @ndp_id: NDP identifier
+ * @local_ndi: Local NDI address
+ * @peer_ndi: Peer NDI address
+ * @reason: Reason for disconnection
+ *
+ * The handling of the event is done asynchronously through the NAN test actions
+ * processing.
+ */
+static void nan_test_ndp_disconnected_cb(void *ctx, struct nan_ndp_id *ndp_id,
+					 const u8 *local_ndi,
+					 const u8 *peer_ndi,
+					 enum nan_reason reason)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+
+	DEV_NOT_INIT_ERR_VOID(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: Enter", dev->name, __func__);
+
+	nan_test_ndp_action(dev, NAN_TEST_NDP_NOTIFY_DISCONNECTED,
+			    ndp_id, 0, NULL, 0, NAN_CS_NONE, NULL);
+
+	dev->disconnected_notify_received = true;
+}
+
+
+/*
+ * nan_test_send_naf_cb_action - NAN test action to send a NAN to a device
+ *
+ * @dev: NAN device
+ * @ctx: Pointer to a buffer holding the NAF to be sent
+ */
+static int nan_test_send_naf_cb_action(struct nan_device *dev, void *ctx)
+{
+	struct wpabuf *data = (struct wpabuf *)ctx;
+	int ret;
+
+	DEV_NOT_INIT_ERR(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: Enter", dev->name, __func__);
+	wpa_hexdump(MSG_DEBUG, "NAN Test: NAF:", wpabuf_head(data),
+		    wpabuf_len(data));
+
+	ret = nan_action_rx(dev->nan, wpabuf_head(data), wpabuf_len(data));
+	wpabuf_free(data);
+	return ret;
+}
+
+/*
+ * nan_test_tx_status_action - NAN test action to send Tx status to a device
+ *
+ * @dev: NAN device
+ * @ctx: Pointer to &struct nan_test_tx_status_action
+ */
+static int nan_test_tx_status_action(struct nan_device *dev, void *ctx)
+{
+	struct nan_test_tx_status_action *tx_status =
+		(struct nan_test_tx_status_action *)ctx;
+	int ret;
+
+	DEV_NOT_INIT_ERR(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: enter", dev->name, __func__);
+
+	ret = nan_tx_status(dev->nan, tx_status->dst,
+			    wpabuf_head(tx_status->data),
+			    wpabuf_len(tx_status->data), tx_status->acked);
+
+	wpabuf_free((struct wpabuf *)tx_status->data);
+	os_free(tx_status);
+	return ret;
+}
+
+/*
+ * nan_test_send_naf_cb - NAN send NAF callback function
+ *
+ * @ctx: Pointer to &struct nan_device
+ * @dst: Destination NAN Management Interface address
+ * @src: Source NAN Management Interface address
+ * @cluster_id: NAN Cluster ID
+ *
+ * The callback builds the management frame and creates the following NAN test
+ * actions:
+ * - NAN test tx status action: to be sent to the transmitting device
+ * - NAN test send NAF action: to be sent to the destination device.
+ */
+static int nan_test_send_naf_cb(void *ctx, const u8 *dst, const u8 *src,
+				const u8 *cluster_id, struct wpabuf *buf)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+	struct nan_device *curd;
+	struct ieee80211_hdr *hdr;
+	struct wpabuf *data = NULL;
+	struct nan_test_tx_status_action *tx_status = NULL;
+	struct nan_test_action *dev_action, *cur_action;
+	bool found = false;
+
+	DEV_NOT_INIT_ERR(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: Enter " MACSTR, __func__, dev->name,
+		   MAC2STR(dst));
+
+	dl_list_for_each(curd, &dev->global->devs, struct nan_device, list) {
+		if (os_memcmp(curd->nmi, dst, ETH_ALEN) == 0) {
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		wpa_printf(MSG_ERROR, "%s: Destination device not found",
+			   __func__);
+		return -1;
+	}
+
+	/* Prepare action to send the frame to the peer */
+	data = wpabuf_alloc(sizeof(struct ieee80211_hdr) + wpabuf_len(buf));
+	if (!data) {
+		wpa_printf(MSG_ERROR, "%s: Failed to allocate NAF", __func__);
+		return -1;
+	}
+
+	hdr = wpabuf_put(data, sizeof(struct ieee80211_hdr));
+	hdr->frame_control =
+		IEEE80211_FC(WLAN_FC_TYPE_MGMT, WLAN_FC_STYPE_ACTION);
+
+	if (dst)
+		os_memcpy(hdr->addr1, dst, ETH_ALEN);
+	if (!src)
+		os_memcpy(hdr->addr2, dev->nmi, ETH_ALEN);
+	else
+		os_memcpy(hdr->addr2, src, ETH_ALEN);
+	if (cluster_id)
+		os_memcpy(hdr->addr3, cluster_id, ETH_ALEN);
+
+	wpabuf_put_data(data, wpabuf_head(buf), wpabuf_len(buf));
+
+	/* Prepare action to send Tx status */
+	tx_status = os_malloc(sizeof(struct nan_test_tx_status_action));
+	if (!tx_status) {
+		wpa_printf(MSG_ERROR, "%s: Failed to allocate Tx status",
+			   __func__);
+		goto fail;
+	}
+
+	tx_status->data = wpabuf_dup(data);
+	if (!tx_status->data) {
+		wpa_printf(MSG_ERROR, "%s: Failed to allocate Tx status data",
+			   __func__);
+		goto fail;
+	}
+
+	os_memcpy(tx_status->dst, dst, ETH_ALEN);
+	tx_status->acked = 1;
+
+	/* First send the TX status */
+	dev_action = nan_test_add_action(dev->global, dev,
+					 nan_test_tx_status_action,
+					 tx_status);
+	if (!dev_action)
+		goto fail;
+
+	/* And then deliver the frame */
+	cur_action = nan_test_add_action(curd->global, curd,
+					 nan_test_send_naf_cb_action,
+					 data);
+	if (!cur_action)
+		goto fail;
+
+	return 0;
+fail:
+	wpabuf_free(data);
+	if (tx_status)
+		wpabuf_free((struct wpabuf *)tx_status->data);
+
+	os_free(tx_status);
+
+	return -1;
+}
+
+
+/*
+ * nan_test_get_chans_cb - Get NAN supported channels callback
+ *
+ * @ctx: Pointer to &struct nan_device
+ * @map_id: Channel map identifier
+ * @chans: Pointer to &struct nan_channels to be filled with supported channels
+ */
+static int nan_test_get_chans_cb(void *ctx, u8 map_id,
+				 struct nan_channels *chans)
+{
+	struct nan_device *dev = (struct nan_device *)ctx;
+
+	DEV_NOT_INIT_ERR(dev);
+
+	wpa_printf(MSG_INFO, "%s: %s: Enter", dev->name, __func__);
+
+	return dev->conf->get_chans_cb(chans);
+}
+
+
+/*
+ * nan_test_is_valid_publish_id_cb - Check if the publish instance ID is valid
+ *
+ * @ctx: Pointer to &struct nan_device
+ * @instance_id: Publish instance ID
+ * @service_id: Buffer to be filled with the service ID
+ * Returns true if the instance ID is valid, false otherwise
+ */
+static bool nan_test_is_valid_publish_id_cb(void *ctx, u8 instance_id,
+					    u8 *service_id)
+{
+	if (instance_id != NAN_TEST_PUBLISH_INST_ID)
+		return false;
+
+	os_memset(service_id, 0xaa, NAN_SERVICE_ID_LEN);
+	return true;
+}
+
+
+/*
+ * nan_test_dev_init - Initialize a test device instance
+ *
+ * @dev: the instance of the device to initialize
+ */
+static int nan_test_dev_init(struct nan_device *dev)
+{
+	struct nan_config nan;
+
+	os_memset(&nan, 0, sizeof(nan));
+	nan.cb_ctx = dev;
+
+	nan.start = nan_test_start_cb;
+	nan.stop = nan_test_stop_cb;
+	nan.ndp_action_notif = nan_test_ndp_action_notfi_cb;
+	nan.ndp_connected = nan_test_ndp_connected_cb;
+	nan.ndp_disconnected = nan_test_ndp_disconnected_cb;
+	nan.send_naf = nan_test_send_naf_cb;
+	nan.get_chans = nan_test_get_chans_cb;
+	nan.is_valid_publish_id = nan_test_is_valid_publish_id_cb;
+
+	/* Awake on every DW on 2 GHz and 5 GHz */
+	nan.dev_capa.cdw_info = 0x9;
+	nan.dev_capa.supported_bands = NAN_DEV_CAPA_SBAND_2G |
+		NAN_DEV_CAPA_SBAND_5G |
+		NAN_DEV_CAPA_SBAND_6G;
+
+	nan.dev_capa.op_mode = NAN_DEV_CAPA_OP_MODE_PHY_MODE;
+	nan.dev_capa.n_antennas = 0x22;
+	nan.dev_capa.channel_switch_time = 10;
+	nan.dev_capa.capa = 0;
+
+	nan.dev_capa_ext_reg_info = 0;
+	nan.dev_capa_ext_pairing_npk_caching =
+		NAN_DEV_CAPA_EXT_INFO_1_PAIRING_SETUP |
+		NAN_DEV_CAPA_EXT_INFO_1_NPK_NIK_CACHING;
+
+	dev->nan = nan_init(&nan);
+	if (!dev->nan) {
+		wpa_printf(MSG_DEBUG, "NAN: Failed to init");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/*
+ * nan_test_start_dev - Start a NAN test device
+ *
+ * @global: NAN test global data structure
+ * @name: Name of the device
+ * @nmi: NAN Management interface address
+ * @cconf: NAN cluster configuration
+ * @dconf: Test device configuration
+ */
+static struct nan_device *
+nan_test_start_dev(struct nan_test_global *global,
+		   const char *name, const u8 *nmi,
+		   struct nan_cluster_config *conf,
+		   struct nan_test_dev_conf *dconf)
+{
+	struct nan_device *dev;
+	int ret;
+
+	dev = os_zalloc(sizeof(struct nan_device));
+	if (!dev)
+		return NULL;
+
+	os_memcpy(dev->name, name, os_strlen(name));
+	os_memcpy(dev->nmi, nmi, sizeof(dev->nmi));
+	dl_list_init(&dev->list);
+	dev->global = global;
+
+	if (dconf->pot_avail_len) {
+		dev->pot_avail = os_memdup(dconf->pot_avail,
+					   dconf->pot_avail_len);
+		if (!dev->pot_avail)
+			goto fail;
+		dev->pot_avail_len = dconf->pot_avail_len;
+	}
+
+	ret = nan_test_dev_init(dev);
+	if (ret)
+		goto fail;
+
+	dev->conf = dconf;
+	ret = nan_start(dev->nan, conf);
+	if (ret)
+		goto fail;
+
+	dl_list_add(&global->devs, &dev->list);
+	return dev;
+fail:
+	os_free(dev->pot_avail);
+	nan_test_dev_deinit(dev);
+	os_free(dev);
+	return NULL;
+}
+
+
+/*
+ * nan_test_setup_devices - Setup the test devices
+ *
+ * @global: NAN test global data structure
+ * @pub_conf: Publisher test configuration
+ * @sub_conf: Subscriber test configuration
+ */
+static struct nan_device *
+nan_test_setup_devices(struct nan_test_global *global,
+		       struct nan_test_dev_conf *pub_conf,
+		       struct nan_test_dev_conf *sub_conf)
+{
+	struct nan_cluster_config cconf = {
+		.master_pref = 2,
+		.dual_band = 1,
+	};
+	const u8 pot_avail[] = {
+		0x12, 0x0c, 0x00, 0x01, 0x20, 0x00, 0x07, 0x00,
+		0x1a, 0x00, 0x11, 0x51, 0xff, 0x07, 0x00,
+	};
+
+	struct nan_device *pub, *sub;
+
+	wpa_printf(MSG_INFO, "%s: Enter\n", __func__);
+
+	pub = nan_test_start_dev(global, "publisher", pub_nmi, &cconf,
+				 pub_conf);
+	if (!pub)
+		goto fail;
+
+	sub = nan_test_start_dev(global, "subscriber", sub_nmi, &cconf,
+				 sub_conf);
+	if (!sub)
+		goto fail;
+
+	nan_add_peer(pub->nan, sub_nmi, pot_avail, sizeof(pot_avail));
+	nan_add_peer(sub->nan, pub_nmi, pot_avail, sizeof(pot_avail));
+
+	wpa_printf(MSG_INFO, "\n%s: Done\n", __func__);
+	return sub;
+fail:
+	wpa_printf(MSG_INFO, "\n%s: Fail\n", __func__);
+	return NULL;
+}
+
+
+static int nan_test_ndp_request(struct nan_device *sub)
+{
+	struct nan_ndp_params *params;
+	struct nan_test_action *action;
+
+	DEV_NOT_INIT_ERR(sub);
+
+	params = os_zalloc(sizeof(struct nan_ndp_params));
+	if (!params) {
+		wpa_printf(MSG_ERROR, "Failed allocation: nan_ndp_params");
+		return -1;
+	}
+
+	params->type = NAN_NDP_ACTION_REQ;
+	os_memcpy(params->ndp_id.peer_nmi, pub_nmi, ETH_ALEN);
+	os_memcpy(params->ndp_id.init_ndi, sub_ndi, ETH_ALEN);
+	params->ndp_id.id = ++sub->counter;
+	params->qos.min_slots = NAN_TEST_MIN_SLOTS;
+	params->qos.max_latency = NAN_TEST_MAX_LATENCY;
+
+	/* Use the device specific schedule callback */
+	sub->conf->schedule_cb(&params->sched);
+	params->sched_valid = 1;
+	params->sched.elems = sub->global->elems;
+
+	params->u.req.publish_inst_id = NAN_TEST_PUBLISH_INST_ID;
+	os_memset(params->u.req.service_id, 0xaa, NAN_SERVICE_ID_LEN);
+
+	action = nan_test_add_action(sub->global, sub, nan_test_nan_ndp_action,
+				     params);
+	if (action)
+		return 0;
+
+	wpa_printf(MSG_ERROR, "Failed adding NDP request action");
+	os_free(params);
+
+	return -1;
+}
+
+
+/*
+ * nan_test_ndp_setup - test NDP setup
+ *
+ * @global: NAN test global data structure
+ * @pub_conf: Publisher test configuration
+ * @sub_conf: Subscriber test configuration
+ *
+ * Create the test devices, perform the basic publish/subscribe/match to allow
+ * NDP establishment and trigger an NDP request flow based on the actions
+ * mechanism.
+ */
+static struct nan_device *nan_test_ndp_setup(struct nan_test_global *global,
+					     struct nan_test_dev_conf *pub_conf,
+					     struct nan_test_dev_conf *sub_conf)
+{
+	if (pub_conf->n_ndps != sub_conf->n_ndps || !pub_conf->n_ndps ||
+	    pub_conf->n_ndps >= NAN_MAX_NUM_NDPS) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN Test: Publisher and Subscriber n_ndps mismatch or invalid");
+		return NULL;
+	}
+
+	return nan_test_setup_devices(global, pub_conf, sub_conf);
+}
+
+
+static int nan_test_verify_expected_result(struct nan_device *dev)
+{
+	DEV_NOT_INIT_ERR(dev);
+
+	if (dev->conf->ndp_confs[dev->n_ndps].expected_result ==
+	    NAN_TEST_NDP_NOTIFY_CONNECTED &&
+	    !dev->connected_notify_received) {
+		wpa_printf(MSG_ERROR,
+			   "%s: Expected connected notify not received",
+			   dev->name);
+		return -1;
+	} else if (dev->conf->ndp_confs[dev->n_ndps].expected_result ==
+		   NAN_TEST_NDP_NOTIFY_DISCONNECTED &&
+		   !dev->disconnected_notify_received) {
+		wpa_printf(MSG_ERROR,
+			   "%s: Expected disconnected notify not received",
+			   dev->name);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int nan_test_iteration_done(struct nan_test_global *global)
+{
+	struct nan_device *dev;
+	int ret;
+
+	dl_list_for_each(dev, &global->devs, struct nan_device, list) {
+		ret = nan_test_verify_expected_result(dev);
+
+		if (ret)
+			return ret;
+
+		dev->connected_notify_received = false;
+		dev->disconnected_notify_received = false;
+		dev->n_ndps++;
+	}
+
+	return 0;
+}
+
+
+/*
+ * nan_test_run - Run the NAN tests.
+ *
+ * Iterates over all tests cases and for each test case initializes the global
+ * context, creates the NAN devices and perform the test case.
+ */
+static int nan_test_run(void)
+{
+	struct nan_test_global global;
+	struct nan_test_case *curr_tc;
+	bool all_failed = false;
+
+	while ((curr_tc = nan_test_case_get_next()) != NULL) {
+		struct nan_device *sub;
+		bool failed = false;
+		size_t i;
+
+		wpa_printf(MSG_INFO,
+			   "\n======> NAN TEST CASE: %s <======\n",
+			   curr_tc->name);
+
+		nan_test_global_init(&global);
+		sub = nan_test_ndp_setup(&global,
+					 &curr_tc->pub_conf,
+					 &curr_tc->sub_conf);
+		if (!sub) {
+			wpa_printf(MSG_ERROR,
+				   "NAN Test: Failed to setup devices");
+			nan_test_global_deinit(&global);
+			failed = true;
+			continue;
+		}
+
+		for (i = 0; i < sub->conf->n_ndps; i++) {
+			int ret = nan_test_ndp_request(sub);
+
+			if (!ret)
+				ret = nan_test_run_actions(&global);
+
+			if (!ret)
+				ret = nan_test_iteration_done(&global);
+
+			wpa_printf(MSG_INFO,
+				   "\n======> NAN TEST CASE: %s: iter=%zu: result=%s <======\n",
+				   curr_tc->name, i, ret ? "FAILED" : "SUCCESS");
+
+			if (ret) {
+				failed = true;
+				break;
+			}
+		}
+
+		wpa_printf(MSG_INFO,
+			   "\n======> NAN TEST CASE: %s: Done. Result=%s <======\n",
+			   curr_tc->name, failed ? "FAILED" : "SUCCESS");
+
+		all_failed |= failed;
+
+		nan_test_global_deinit(&global);
+	}
+
+	return all_failed ? -1 : 0;
+}
+
+
+int nan_module_tests(void)
+{
+	return nan_test_run();
+}
diff --git a/src/nan/nan_module_tests.h b/src/nan/nan_module_tests.h
new file mode 100644
index 0000000000..f50eb76953
--- /dev/null
+++ b/src/nan/nan_module_tests.h
@@ -0,0 +1,129 @@
+/*
+ * NAN NDP/NDL state machine testing definitions
+ * Copyright (C) 2025 Intel Corporation
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/list.h"
+#include "utils/os.h"
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "common/ieee802_11_defs.h"
+#include "drivers/driver.h"
+#include "nan_i.h"
+
+#define NAN_TEST_NAME_MAX     32
+
+/*
+ * enum nan_test_ndp_notify_type - NDP notification
+ *
+ * NAN_TEST_NDP_NOTIFY_INVALID: Invalid.
+ * NAN_TEST_NDP_NOTIFY_REQUEST: NDP request
+ * NAN_TEST_NDP_NOTIFY_RESPONSE: NDP response
+ * NAN_TEST_NDP_NOTIFY_CONNECTED: NDP connected
+ * NAN_TEST_NDP_NOTIFY_DISCONNECTED: NDP disconnected.
+ */
+enum nan_test_ndp_notify_type {
+	NAN_TEST_NDP_NOTIFY_INVALID = 0,
+	NAN_TEST_NDP_NOTIFY_REQUEST,
+	NAN_TEST_NDP_NOTIFY_RESPONSE,
+	NAN_TEST_NDP_NOTIFY_CONNECTED,
+	NAN_TEST_NDP_NOTIFY_DISCONNECTED,
+};
+
+#define NAN_TEST_MAX_POT_AVAIL 256
+#define NAN_MAX_NUM_NDPS       8
+
+/*
+ * nan_test_dev_conf - NAN test device configuration
+ *
+ * @schedule_cb: Callback to configure different schedule handling policies
+ *     for a device for initial request and response.
+ * @schedule_conf_cb: Callback to configure different schedule handling
+ *     policies for a device for confirmation.
+ * @get_chans_cb: Callback to configure different channels for potential
+ *     availability.
+ * @pot_avail: Device potential availability
+ * @pot_avail_len: Length of the device potential availability
+ * @n_ndps: Number of NDP configurations (to support scenarios that involve
+ *      establishing multiple NDPs in a single test case).
+ * @ndp_confs: Array of NDP configurations.
+ * @accept_request: For publisher device, indicates whether to accept an NDP
+ *     request or reject it. For subscriber device, indicates whether to accept
+ *     an NDP response or reject it.
+ * @term_once_connected: Terminate once connected.
+ * @expected_result: Expected NDP establishment result
+ * @reason: For publisher device, indicates the reject reason
+ */
+struct nan_test_dev_conf {
+	int (*schedule_cb)(struct nan_schedule *sched);
+	int (*schedule_conf_cb)(struct nan_schedule *sched);
+	int (*get_chans_cb)(struct nan_channels *chans);
+
+	u8 pot_avail[NAN_TEST_MAX_POT_AVAIL];
+	size_t pot_avail_len;
+
+	size_t n_ndps;
+	struct ndp_conf {
+		bool accept_request;
+		bool term_once_connected;
+		enum nan_test_ndp_notify_type expected_result;
+		u8 reason;
+	} ndp_confs[NAN_MAX_NUM_NDPS];
+};
+
+/*
+ * nan_device - Represents a NAN test device
+ *
+ * @list: Used for global devices list
+ * @global: Pointer to NAN test global data structure
+ * @name: Test device name
+ * @nmi: Test device NMI
+ * @counter: Device counter for NDP various purposes
+ * @pot_avail: Device potential availability
+ * @pot_avail_len: Length of the device potential availability
+ * @nan: Pointer to NAN data structure
+ * @conf: NAN test device configuration
+ * @n_ndps: Number of NDPs established so far minus one
+ * @connected_notify_received: Indicates whether a connected notification
+ *     was received
+ * @disconnected_notify_received: Indicates whether a disconnected notification
+ *     was received
+ */
+struct nan_device {
+	struct dl_list list;
+	struct nan_test_global *global;
+
+	u8 name[NAN_TEST_NAME_MAX];
+	u8 nmi[ETH_ALEN];
+	u32 counter;
+
+	u8 *pot_avail;
+	size_t pot_avail_len;
+
+	struct nan_data *nan;
+	struct nan_test_dev_conf *conf;
+
+	size_t n_ndps;
+
+	bool connected_notify_received;
+	bool disconnected_notify_received;
+};
+
+/*
+ * nan_test_case - Single NAN test configuration
+ *
+ * @name: Test name
+ * @pub_conf: Publisher test configuration
+ * @sub_conf: Subscriber test configuration
+ */
+struct nan_test_case {
+	const char *name;
+	struct nan_test_dev_conf pub_conf;
+	struct nan_test_dev_conf sub_conf;
+};
+
+struct nan_test_case *nan_test_case_get_next(void);
diff --git a/src/utils/module_tests.h b/src/utils/module_tests.h
index 3bfe4ad026..14f26a1398 100644
--- a/src/utils/module_tests.h
+++ b/src/utils/module_tests.h
@@ -16,5 +16,6 @@ int utils_module_tests(void);
 int wps_module_tests(void);
 int common_module_tests(void);
 int crypto_module_tests(void);
+int nan_module_tests(void);
 
 #endif /* MODULE_TESTS_H */
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index 89d8bf8f78..a1f66d4540 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -1942,6 +1942,10 @@ OBJS += ../src/crypto/crypto_module_tests.o
 ifdef CONFIG_WPS
 OBJS += ../src/wps/wps_module_tests.o
 endif
+ifdef CONFIG_NAN
+OBJS += ../src/nan/nan_module_tests.o
+OBJS += ../src/nan/nan_module_test_cases.o
+endif
 endif
 
 OBJS += ../src/drivers/driver_common.o
diff --git a/wpa_supplicant/wpas_module_tests.c b/wpa_supplicant/wpas_module_tests.c
index 9e7a57c4f1..b9140fba87 100644
--- a/wpa_supplicant/wpas_module_tests.c
+++ b/wpa_supplicant/wpas_module_tests.c
@@ -113,5 +113,10 @@ int wpas_module_tests(void)
 	if (crypto_module_tests() < 0)
 		ret = -1;
 
+#ifdef CONFIG_NAN
+	if (nan_module_tests() < 0)
+		ret = -1;
+#endif /* CONFIG_NAN */
+
 	return ret;
 }
-- 
2.49.0




More information about the Hostap mailing list