diff mbox series

[BlueZ,v4,2/2] tools: Add mesh-tester to test Kernel mesh support

Message ID 20220831235932.290032-3-brian.gix@intel.com
State Superseded
Headers show
Series Add mesh testing support | expand

Commit Message

Brian Gix Aug. 31, 2022, 11:59 p.m. UTC
Ever growing set of tests for Mesh kernel support
---
 Makefile.tools      |   12 +-
 tools/mesh-tester.c | 1441 +++++++++++++++++++++++++++++++++++++++++++
 tools/test-runner.c |    2 +
 3 files changed, 1454 insertions(+), 1 deletion(-)
 create mode 100644 tools/mesh-tester.c
diff mbox series

Patch

diff --git a/Makefile.tools b/Makefile.tools
index 9412aed36..f4b951474 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -86,7 +86,8 @@  noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \
 					tools/l2cap-tester tools/sco-tester \
 					tools/smp-tester tools/hci-tester \
 					tools/rfcomm-tester tools/bnep-tester \
-					tools/userchan-tester tools/iso-tester
+					tools/userchan-tester tools/iso-tester \
+					tools/mesh-tester
 
 emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \
 				emulator/serial.h emulator/serial.c \
@@ -127,6 +128,15 @@  tools_mgmt_tester_SOURCES = tools/mgmt-tester.c monitor/bt.h \
 tools_mgmt_tester_LDADD = lib/libbluetooth-internal.la \
 				src/libshared-glib.la $(GLIB_LIBS)
 
+tools_mesh_tester_SOURCES = tools/mesh-tester.c monitor/bt.h \
+				emulator/hciemu.h emulator/hciemu.c \
+				emulator/vhci.h emulator/vhci.c \
+				emulator/btdev.h emulator/btdev.c \
+				emulator/bthost.h emulator/bthost.c \
+				emulator/smp.c
+tools_mesh_tester_LDADD = lib/libbluetooth-internal.la \
+				src/libshared-glib.la $(GLIB_LIBS)
+
 tools_l2cap_tester_SOURCES = tools/l2cap-tester.c monitor/bt.h \
 				emulator/hciemu.h emulator/hciemu.c \
 				emulator/vhci.h emulator/vhci.c \
diff --git a/tools/mesh-tester.c b/tools/mesh-tester.c
new file mode 100644
index 000000000..6781bd565
--- /dev/null
+++ b/tools/mesh-tester.c
@@ -0,0 +1,1441 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2012  Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/hci.h"
+#include "lib/hci_lib.h"
+#include "lib/mgmt.h"
+#include "lib/l2cap.h"
+
+#include "monitor/bt.h"
+#include "emulator/vhci.h"
+#include "emulator/bthost.h"
+#include "emulator/hciemu.h"
+
+#include "src/shared/util.h"
+#include "src/shared/tester.h"
+#include "src/shared/mgmt.h"
+#include "src/shared/queue.h"
+
+struct test_data {
+	tester_data_func_t test_setup;
+	const void *test_data;
+	uint8_t expected_version;
+	uint16_t expected_manufacturer;
+	uint32_t expected_supported_settings;
+	uint32_t initial_settings;
+	struct mgmt *mgmt;
+	struct mgmt *mgmt_alt;
+	unsigned int mgmt_settings_id;
+	unsigned int mgmt_alt_settings_id;
+	unsigned int mgmt_alt_ev_id;
+	unsigned int mgmt_discov_ev_id;
+	uint8_t mgmt_version;
+	uint16_t mgmt_revision;
+	uint16_t mgmt_index;
+	struct hciemu *hciemu;
+	enum hciemu_type hciemu_type;
+	bool expect_hci_command_done;
+	struct queue *expect_hci_q;
+	int unmet_conditions;
+	int unmet_setup_conditions;
+	int sk;
+};
+
+static void print_debug(const char *str, void *user_data)
+{
+	const char *prefix = user_data;
+
+	tester_print("%s%s", prefix, str);
+}
+
+static void test_post_teardown(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+
+	if (data->sk >= 0)
+		close(data->sk);
+
+	queue_destroy(data->expect_hci_q, NULL);
+
+	hciemu_unref(data->hciemu);
+	data->hciemu = NULL;
+}
+
+static void test_pre_setup_failed(void)
+{
+	test_post_teardown(NULL);
+	tester_pre_setup_failed();
+}
+
+static void read_version_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct mgmt_rp_read_version *rp = param;
+
+	tester_print("Read Version callback");
+	tester_print("  Status: %s (0x%02x)", mgmt_errstr(status), status);
+
+	if (status || !param) {
+		test_pre_setup_failed();
+		return;
+	}
+
+	data->mgmt_version = rp->version;
+	data->mgmt_revision = btohs(rp->revision);
+
+	tester_print("  Version %u.%u",
+			data->mgmt_version, data->mgmt_revision);
+}
+
+static void read_commands_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	tester_print("Read Commands callback");
+	tester_print("  Status: %s (0x%02x)", mgmt_errstr(status), status);
+
+	if (status || !param) {
+		test_pre_setup_failed();
+		return;
+	}
+}
+
+static bool check_settings(uint32_t supported, uint32_t expected)
+{
+	int i;
+
+	if (supported == expected)
+		return true;
+
+	for (i = 0; i < 17; i++) {
+		if (supported & BIT(i))
+			continue;
+
+		if (expected & BIT(i)) {
+			tester_warn("Expected bit %u not supported", i);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static void read_info_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct mgmt_rp_read_info *rp = param;
+	char addr[18];
+	uint16_t manufacturer;
+	uint32_t supported_settings, current_settings;
+	struct bthost *bthost;
+
+	tester_print("Read Info callback");
+	tester_print("  Status: %s (0x%02x)", mgmt_errstr(status), status);
+
+	if (status || !param) {
+		test_pre_setup_failed();
+		return;
+	}
+
+	ba2str(&rp->bdaddr, addr);
+	manufacturer = btohs(rp->manufacturer);
+	supported_settings = btohl(rp->supported_settings);
+	current_settings = btohl(rp->current_settings);
+
+	tester_print("  Address: %s", addr);
+	tester_print("  Version: 0x%02x", rp->version);
+	tester_print("  Manufacturer: 0x%04x", manufacturer);
+	tester_print("  Supported settings: 0x%08x", supported_settings);
+	tester_print("  Current settings: 0x%08x", current_settings);
+	tester_print("  Class: 0x%02x%02x%02x",
+			rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]);
+	tester_print("  Name: %s", rp->name);
+	tester_print("  Short name: %s", rp->short_name);
+
+	if (strcmp(hciemu_get_address(data->hciemu), addr)) {
+		test_pre_setup_failed();
+		return;
+	}
+
+	if (rp->version != data->expected_version) {
+		tester_warn("Expected version: 0x%02x != 0x%02x",
+				rp->version, data->expected_version);
+		test_pre_setup_failed();
+		return;
+	}
+
+	if (manufacturer != data->expected_manufacturer) {
+		tester_warn("Expected manufacturer: 0x%04x != 0x%04x",
+				manufacturer, data->expected_manufacturer);
+		test_pre_setup_failed();
+		return;
+	}
+
+	if (!check_settings(supported_settings,
+				data->expected_supported_settings)) {
+		tester_warn("Expected supported settings: 0x%08x != 0x%08x",
+				supported_settings,
+				data->expected_supported_settings);
+		test_pre_setup_failed();
+		return;
+	}
+
+	if (!check_settings(current_settings, data->initial_settings)) {
+		tester_warn("Initial settings: 0x%08x != 0x%08x",
+				current_settings, data->initial_settings);
+		test_pre_setup_failed();
+		return;
+	}
+
+	if (rp->dev_class[0] != 0x00 || rp->dev_class[1] != 0x00 ||
+			rp->dev_class[2] != 0x00) {
+		test_pre_setup_failed();
+		return;
+	}
+
+	bthost = hciemu_client_get_host(data->hciemu);
+	bthost_notify_ready(bthost, tester_pre_setup_complete);
+}
+
+static const uint8_t set_exp_feat_param_mesh[] = {
+	0x76, 0x6e, 0xf3, 0xe8, 0x24, 0x5f, 0x05, 0xbf, /* UUID - Mesh */
+	0x8d, 0x4d, 0x03, 0x7a, 0xd7, 0x63, 0xe4, 0x2c,
+	0x01,						/* Action - enable */
+};
+
+static const uint8_t set_exp_feat_rsp_param_mesh[] = {
+	0x76, 0x6e, 0xf3, 0xe8, 0x24, 0x5f, 0x05, 0xbf, /* UUID - Mesh */
+	0x8d, 0x4d, 0x03, 0x7a, 0xd7, 0x63, 0xe4, 0x2c,
+	0x01, 0x00, 0x00, 0x00,			/* Action - enable */
+};
+
+static void mesh_exp_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	if (status != MGMT_STATUS_SUCCESS) {
+		tester_print("Mesh feature could not be enabled");
+		return;
+	}
+
+	tester_print("Mesh feature is enabled");
+}
+
+static void mesh_exp_feature(struct test_data *data, uint16_t index)
+{
+	tester_print("Enabling Mesh feature");
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_EXP_FEATURE, index,
+			sizeof(set_exp_feat_param_mesh),
+			set_exp_feat_param_mesh,
+			mesh_exp_callback, NULL, NULL);
+}
+
+static void index_added_callback(uint16_t index, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("Index Added callback");
+	tester_print("  Index: 0x%04x", index);
+
+	data->mgmt_index = index;
+
+	mgmt_send(data->mgmt, MGMT_OP_READ_INFO, data->mgmt_index, 0, NULL,
+			read_info_callback, NULL, NULL);
+
+	tester_warn("Enable management Mesh interface");
+	mesh_exp_feature(data, data->mgmt_index);
+
+}
+
+static void index_removed_callback(uint16_t index, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("Index Removed callback");
+	tester_print("  Index: 0x%04x", index);
+
+	if (index != data->mgmt_index)
+		return;
+
+	mgmt_unregister_index(data->mgmt, data->mgmt_index);
+	mgmt_unregister_index(data->mgmt_alt, data->mgmt_index);
+
+	mgmt_unref(data->mgmt);
+	data->mgmt = NULL;
+
+	mgmt_unref(data->mgmt_alt);
+	data->mgmt_alt = NULL;
+
+	tester_post_teardown_complete();
+}
+
+struct hci_cmd_data {
+	uint16_t opcode;
+	uint8_t len;
+	const void *param;
+};
+
+struct hci_entry {
+	const struct hci_cmd_data *cmd_data;
+};
+
+struct generic_data {
+	bool setup_le_states;
+	const uint8_t *le_states;
+	const uint16_t *setup_settings;
+	bool setup_nobredr;
+	bool setup_limited_discov;
+	const void *setup_exp_feat_param;
+	uint16_t setup_expect_hci_command;
+	const void *setup_expect_hci_param;
+	uint8_t setup_expect_hci_len;
+	uint16_t setup_send_opcode;
+	const void *setup_send_param;
+	uint16_t setup_send_len;
+	const struct setup_mgmt_cmd *setup_mgmt_cmd_arr;
+	size_t setup_mgmt_cmd_arr_size;
+	bool send_index_none;
+	const void *setup_discovery_param;
+	uint16_t send_opcode;
+	const void *send_param;
+	uint16_t send_len;
+	const void * (*send_func)(uint16_t *len);
+	uint8_t expect_status;
+	bool expect_ignore_param;
+	const void *expect_param;
+	uint16_t expect_len;
+	const void * (*expect_func)(uint16_t *len);
+	uint32_t expect_settings_set;
+	uint32_t expect_settings_unset;
+	uint16_t expect_alt_ev;
+	const void *expect_alt_ev_param;
+	bool (*verify_alt_ev_func)(const void *param, uint16_t length);
+	uint16_t expect_alt_ev_len;
+	uint16_t expect_hci_command;
+	const void *expect_hci_param;
+	int (*expect_hci_param_check_func)(const void *param, uint16_t length);
+	uint8_t expect_hci_len;
+	const void * (*expect_hci_func)(uint8_t *len);
+	const struct hci_cmd_data *expect_hci_list;
+	bool expect_pin;
+	uint8_t pin_len;
+	const void *pin;
+	uint8_t client_pin_len;
+	const void *client_pin;
+	bool client_enable_ssp;
+	uint8_t io_cap;
+	uint8_t client_io_cap;
+	uint8_t client_auth_req;
+	bool reject_confirm;
+	bool client_reject_confirm;
+	bool just_works;
+	bool client_enable_le;
+	bool client_enable_sc;
+	bool client_enable_adv;
+	bool expect_sc_key;
+	bool force_power_off;
+	bool addr_type_avail;
+	bool fail_tolerant;
+	uint8_t addr_type;
+	bool set_adv;
+	const uint8_t *adv_data;
+	uint8_t adv_data_len;
+};
+
+static const uint8_t set_exp_feat_param_debug[] = {
+	0x1c, 0xda, 0x47, 0x1c, 0x48, 0x6c, 0x01, 0xab, /* UUID - Debug */
+	0x9f, 0x46, 0xec, 0xb9, 0x30, 0x25, 0x99, 0xd4,
+	0x01,						/* Action - enable */
+};
+
+static void debug_exp_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	if (status != MGMT_STATUS_SUCCESS) {
+		tester_print("Debug feature could not be enabled");
+		return;
+	}
+
+	tester_print("Debug feature is enabled");
+}
+
+static void debug_exp_feature(struct test_data *data)
+{
+	tester_print("Enabling Debug feature");
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_EXP_FEATURE, MGMT_INDEX_NONE,
+			sizeof(set_exp_feat_param_debug),
+			set_exp_feat_param_debug,
+			debug_exp_callback, NULL, NULL);
+}
+
+static void read_index_list_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+
+	tester_print("Read Index List callback");
+	tester_print("  Status: %s (0x%02x)", mgmt_errstr(status), status);
+
+	if (status || !param) {
+		test_pre_setup_failed();
+		return;
+	}
+
+	mgmt_register(data->mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE,
+			index_added_callback, NULL, NULL);
+
+	mgmt_register(data->mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE,
+			index_removed_callback, NULL, NULL);
+
+	data->hciemu = hciemu_new(data->hciemu_type);
+	if (!data->hciemu) {
+		tester_warn("Failed to setup HCI emulation");
+		test_pre_setup_failed();
+	}
+
+	if (tester_use_debug())
+		hciemu_set_debug(data->hciemu, print_debug, "hciemu: ", NULL);
+
+	if (test && test->setup_le_states)
+		hciemu_set_central_le_states(data->hciemu, test->le_states);
+
+}
+
+static void test_pre_setup(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+
+	data->mgmt = mgmt_new_default();
+	if (!data->mgmt) {
+		tester_warn("Failed to setup management interface");
+		test_pre_setup_failed();
+		return;
+	}
+
+	data->mgmt_alt = mgmt_new_default();
+	if (!data->mgmt_alt) {
+		tester_warn("Failed to setup alternate management interface");
+		test_pre_setup_failed();
+
+		mgmt_unref(data->mgmt);
+		data->mgmt = NULL;
+		return;
+	}
+
+	if (tester_use_debug()) {
+		mgmt_set_debug(data->mgmt, print_debug, "mgmt: ", NULL);
+		mgmt_set_debug(data->mgmt_alt, print_debug, "mgmt-alt: ", NULL);
+		debug_exp_feature(data);
+	}
+
+	mgmt_send(data->mgmt, MGMT_OP_READ_VERSION, MGMT_INDEX_NONE, 0, NULL,
+			read_version_callback, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_READ_COMMANDS, MGMT_INDEX_NONE, 0, NULL,
+			read_commands_callback, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL,
+			read_index_list_callback, NULL, NULL);
+
+	data->sk = -1;
+}
+
+#define test_full(name, data, setup, func, timeout, type, version, \
+		expected_settings, settings) \
+		do { \
+			struct test_data *user; \
+			user = new0(struct test_data, 1); \
+			user->hciemu_type = type; \
+			user->test_setup = setup; \
+			user->test_data = data; \
+			user->expected_version = version; \
+			user->expected_manufacturer = 0x05f1; \
+			user->expected_supported_settings = expected_settings; \
+			user->initial_settings = settings; \
+			tester_add_full(name, data, \
+					test_pre_setup, test_setup, func, \
+					NULL, test_post_teardown, timeout, \
+					user, free); \
+		} while (0)
+
+#define test_bredrle_full(name, data, setup, func, timeout) \
+	test_full(name, data, setup, func, timeout, HCIEMU_TYPE_BREDRLE, \
+			0x09, 0x0001beff, 0x00000080)
+
+#define test_bredrle(name, data, setup, func) \
+	test_bredrle_full(name, data, setup, func, 2)
+
+#define test_bredr20(name, data, setup, func) \
+	test_full(name, data, setup, func, 2, HCIEMU_TYPE_LEGACY, \
+			0x03, 0x000110bf, 0x00000080)
+
+#define test_bredr(name, data, setup, func) \
+	test_full(name, data, setup, func, 2, HCIEMU_TYPE_BREDR, \
+			0x05, 0x000110ff, 0x00000080)
+
+#define test_le_full(name, data, setup, func, timeout) \
+	test_full(name, data, setup, func, timeout, HCIEMU_TYPE_LE, \
+			0x09, 0x0001be1b, 0x00000200)
+
+#define test_le(name, data, setup, func) \
+	test_le_full(name, data, setup, func, 2)
+
+#define test_bredrle50_full(name, data, setup, func, timeout) \
+	test_full(name, data, setup, func, timeout, HCIEMU_TYPE_BREDRLE50, \
+			0x09, 0x0001beff, 0x00000080)
+
+#define test_bredrle50(name, data, setup, func) \
+	test_bredrle50_full(name, data, setup, func, 2)
+
+#define test_hs_full(name, data, setup, func, timeout) \
+	test_full(name, data, setup, func, timeout, HCIEMU_TYPE_BREDRLE, \
+			0x09, 0x0001bfff, 0x00000080)
+
+#define test_hs(name, data, setup, func) \
+	test_hs_full(name, data, setup, func, 2)
+
+static void controller_setup(const void *test_data)
+{
+	tester_test_passed();
+}
+
+struct setup_mgmt_cmd {
+	uint8_t send_opcode;
+	const void *send_param;
+	uint16_t send_len;
+};
+
+static bool power_off(uint16_t index)
+{
+	int sk, err;
+
+	sk = hci_open_dev(index);
+	if (sk < 0)
+		return false;
+
+	err = ioctl(sk, HCIDEVDOWN, index);
+
+	hci_close_dev(sk);
+
+	if (err < 0)
+		return false;
+
+	return true;
+}
+
+static void test_condition_complete(struct test_data *data)
+{
+	data->unmet_conditions--;
+
+	tester_print("Test condition complete, %d left",
+			data->unmet_conditions);
+
+	if (data->unmet_conditions > 0)
+		return;
+
+	tester_test_passed();
+}
+
+static void command_generic_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	const void *expect_param = test->expect_param;
+	uint16_t expect_len = test->expect_len;
+
+	tester_print("%s (0x%04x): %s (0x%02x)", mgmt_opstr(test->send_opcode),
+			test->send_opcode, mgmt_errstr(status), status);
+
+	if (status != test->expect_status) {
+		if (!test->fail_tolerant || !!status != !!test->expect_status) {
+			tester_test_abort();
+			return;
+		}
+
+		tester_warn("Unexpected status got %d expected %d",
+				status, test->expect_status);
+	}
+
+	if (!test->expect_ignore_param) {
+		if (test->expect_func)
+			expect_param = test->expect_func(&expect_len);
+
+		if (length != expect_len) {
+			tester_warn("Invalid cmd response parameter size %d %d",
+					length, expect_len);
+			tester_test_failed();
+			return;
+		}
+
+		if (expect_param && expect_len > 0 &&
+				memcmp(param, expect_param, length)) {
+			tester_warn("Unexpected cmd response parameter value");
+			util_hexdump('>', param, length, print_debug, "");
+			util_hexdump('!', expect_param, length, print_debug,
+					"");
+			tester_test_failed();
+			return;
+		}
+	}
+
+	test_condition_complete(data);
+}
+
+static void test_add_condition(struct test_data *data)
+{
+	data->unmet_conditions++;
+
+	tester_print("Test condition added, total %d", data->unmet_conditions);
+}
+
+/* Read HCI commands in the expect_hci_list and add it to the queue
+ */
+static void add_expect_hci_list(struct test_data *data)
+{
+	const struct generic_data *test = data->test_data;
+	const struct hci_cmd_data *hci_cmd_data;
+
+	/* Initialize the queue */
+	data->expect_hci_q = queue_new();
+
+	hci_cmd_data = test->expect_hci_list;
+	for (; hci_cmd_data->opcode; hci_cmd_data++) {
+		struct hci_entry *entry;
+
+		entry = new0(struct hci_entry, 1);
+		entry->cmd_data = hci_cmd_data;
+		queue_push_tail(data->expect_hci_q, entry);
+
+		test_add_condition(data);
+	}
+}
+
+static bool match_hci_cmd_opcode(const void *data, const void *match_data)
+{
+	const struct hci_entry *entry = data;
+	uint16_t opcode = PTR_TO_UINT(match_data);
+
+	return entry->cmd_data->opcode == opcode;
+}
+
+static void command_hci_list_callback(uint16_t opcode, const void *param,
+		uint8_t length, void *user_data)
+{
+	struct test_data *data = user_data;
+	const struct hci_cmd_data *hci_cmd_data;
+	struct hci_entry *entry;
+	int ret;
+
+	tester_print("HCI Command 0x%04x length %u", opcode, length);
+
+	entry = queue_find(data->expect_hci_q, match_hci_cmd_opcode,
+			UINT_TO_PTR(opcode));
+	if (!entry)
+		return;
+
+	/* Save the hci cmd data before removing the queue entry */
+	hci_cmd_data = entry->cmd_data;
+
+	/* Remove the entry from the queue and free the entry */
+	queue_remove(data->expect_hci_q, entry);
+	free(entry);
+
+	if (length != hci_cmd_data->len) {
+		tester_warn("Invalid parameter size for HCI command");
+		tester_test_failed();
+		return;
+	}
+
+	ret = memcmp(param, hci_cmd_data->param, length);
+	if (ret != 0) {
+		tester_warn("Unexpected HCI command parameter value:");
+		util_hexdump('>', param, length, print_debug, "");
+		util_hexdump('!', hci_cmd_data->param, length, print_debug, "");
+		tester_test_failed();
+		return;
+	}
+
+	test_condition_complete(data);
+}
+
+static void command_hci_callback(uint16_t opcode, const void *param,
+		uint8_t length, void *user_data)
+{
+	struct test_data *data = user_data;
+	const struct generic_data *test = data->test_data;
+	const void *expect_hci_param = test->expect_hci_param;
+	uint8_t expect_hci_len = test->expect_hci_len;
+	int ret;
+
+	tester_print("HCI Command 0x%04x length %u", opcode, length);
+
+	if (opcode != test->expect_hci_command || data->expect_hci_command_done)
+		return;
+
+	data->expect_hci_command_done = true;
+
+	if (test->expect_hci_func)
+		expect_hci_param = test->expect_hci_func(&expect_hci_len);
+
+	if (length != expect_hci_len) {
+		tester_warn("Invalid parameter size for HCI command");
+		tester_test_failed();
+		return;
+	}
+
+	if (test->expect_hci_param_check_func)
+		ret = test->expect_hci_param_check_func(param, length);
+	else
+		ret = memcmp(param, expect_hci_param, length);
+	if (ret != 0) {
+		tester_warn("Unexpected HCI command parameter value:");
+		util_hexdump('>', param, length, print_debug, "");
+		util_hexdump('!', expect_hci_param, length, print_debug, "");
+		tester_test_failed();
+		return;
+	}
+
+	test_condition_complete(data);
+}
+
+static bool verify_alt_ev(const void *param, uint16_t length)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+
+	if (length != test->expect_alt_ev_len) {
+		tester_warn("Invalid length %u != %u", length,
+				test->expect_alt_ev_len);
+		return false;
+	}
+
+	if (test->expect_alt_ev_param &&
+			memcmp(test->expect_alt_ev_param, param, length)) {
+		tester_warn("Event parameters do not match");
+		util_hexdump('>', param, length, print_debug, "");
+		util_hexdump('!', test->expect_alt_ev_param, length,
+				print_debug, "");
+		return false;
+	}
+
+	return true;
+}
+
+static void command_generic_event_alt(uint16_t index, uint16_t length,
+		const void *param,
+		void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	bool (*verify)(const void *param, uint16_t length);
+
+	tester_print("New %s event received", mgmt_evstr(test->expect_alt_ev));
+
+	mgmt_unregister(data->mgmt_alt, data->mgmt_alt_ev_id);
+
+	if (test->verify_alt_ev_func)
+		verify = test->verify_alt_ev_func;
+	else
+		verify = verify_alt_ev;
+
+	if (!verify(param, length)) {
+		tester_warn("Incorrect %s event parameters",
+				mgmt_evstr(test->expect_alt_ev));
+		tester_test_failed();
+		return;
+	}
+
+	test_condition_complete(data);
+}
+
+static void command_generic_new_settings(uint16_t index, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	tester_print("New settings event received");
+
+	mgmt_unregister(data->mgmt, data->mgmt_settings_id);
+
+	tester_test_failed();
+}
+
+static void command_generic_new_settings_alt(uint16_t index, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	uint32_t settings;
+
+	if (length != 4) {
+		tester_warn("Invalid parameter size for new settings event");
+		tester_test_failed();
+		return;
+	}
+
+	settings = get_le32(param);
+
+	tester_print("New settings 0x%08x received", settings);
+
+	if (test->expect_settings_unset) {
+		if ((settings & test->expect_settings_unset) != 0)
+			return;
+		goto done;
+	}
+
+	if (!test->expect_settings_set)
+		return;
+
+	if ((settings & test->expect_settings_set) != test->expect_settings_set)
+		return;
+
+done:
+	tester_print("Unregistering new settings notification");
+
+	mgmt_unregister(data->mgmt_alt, data->mgmt_alt_settings_id);
+
+	test_condition_complete(data);
+}
+
+static void test_command_generic(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	const void *send_param = test->send_param;
+	uint16_t send_len = test->send_len;
+	unsigned int id;
+	uint16_t index;
+
+	index = test->send_index_none ? MGMT_INDEX_NONE : data->mgmt_index;
+
+	if (test->expect_settings_set || test->expect_settings_unset) {
+		tester_print("Registering new settings notification");
+
+		id = mgmt_register(data->mgmt, MGMT_EV_NEW_SETTINGS, index,
+				command_generic_new_settings, NULL, NULL);
+		data->mgmt_settings_id = id;
+
+		id = mgmt_register(data->mgmt_alt, MGMT_EV_NEW_SETTINGS, index,
+				command_generic_new_settings_alt, NULL, NULL);
+		data->mgmt_alt_settings_id = id;
+		test_add_condition(data);
+	}
+
+	if (test->expect_alt_ev) {
+		tester_print("Registering %s notification",
+				mgmt_evstr(test->expect_alt_ev));
+		id = mgmt_register(data->mgmt_alt, test->expect_alt_ev, index,
+				command_generic_event_alt, NULL, NULL);
+		data->mgmt_alt_ev_id = id;
+		test_add_condition(data);
+	}
+
+	if (test->expect_hci_command) {
+		tester_print("Registering HCI command callback");
+		hciemu_add_central_post_command_hook(data->hciemu,
+				command_hci_callback, data);
+		test_add_condition(data);
+	} else if (test->expect_hci_list) {
+		/* Use this when it needs to check more than 1 hci command.
+		 * However, it cannot be used with expect_hci_command.
+		 */
+		tester_print("Registering HCI command list callback");
+		hciemu_add_central_post_command_hook(data->hciemu,
+				command_hci_list_callback, data);
+		add_expect_hci_list(data);
+	}
+
+	if (test->send_opcode == 0x0000) {
+		tester_print("Executing no-op test");
+		return;
+	}
+
+	tester_print("Sending %s (0x%04x)", mgmt_opstr(test->send_opcode),
+			test->send_opcode);
+
+	if (test->send_func)
+		send_param = test->send_func(&send_len);
+
+	if (test->force_power_off) {
+		mgmt_send_nowait(data->mgmt, test->send_opcode, index,
+				send_len, send_param,
+				command_generic_callback, NULL, NULL);
+		power_off(data->mgmt_index);
+	} else {
+		mgmt_send(data->mgmt, test->send_opcode, index, send_len,
+				send_param, command_generic_callback,
+				NULL, NULL);
+	}
+
+	test_add_condition(data);
+}
+
+static void test_add_setup_condition(struct test_data *data)
+{
+	data->unmet_setup_conditions++;
+
+	tester_print("Test setup condition added, total %d",
+			data->unmet_setup_conditions);
+}
+
+static void test_setup_condition_complete(struct test_data *data)
+{
+	data->unmet_setup_conditions--;
+
+	tester_print("Test setup condition complete, %d left",
+			data->unmet_setup_conditions);
+
+	if (data->unmet_setup_conditions > 0)
+		return;
+
+	tester_setup_complete();
+}
+
+static void client_cmd_complete(uint16_t opcode, uint8_t status,
+		const void *param, uint8_t len,
+		void *user_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	struct bthost *bthost;
+
+	bthost = hciemu_client_get_host(data->hciemu);
+
+	switch (opcode) {
+	case BT_HCI_CMD_WRITE_SCAN_ENABLE:
+	case BT_HCI_CMD_LE_SET_ADV_ENABLE:
+	case BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE:
+		tester_print("Client set connectable: %s (0x%02x)",
+				mgmt_errstr(status), status);
+		if (!status && test->client_enable_ssp) {
+			bthost_write_ssp_mode(bthost, 0x01);
+			return;
+		}
+		break;
+	case BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE:
+		tester_print("Client enable SSP: %s (0x%02x)",
+				mgmt_errstr(status), status);
+		break;
+	default:
+		return;
+	}
+
+	if (status)
+		tester_setup_failed();
+	else
+		test_setup_condition_complete(data);
+}
+
+static void setup_bthost(void)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	struct bthost *bthost;
+
+	bthost = hciemu_client_get_host(data->hciemu);
+	bthost_set_cmd_complete_cb(bthost, client_cmd_complete, data);
+	test_add_setup_condition(data);
+
+	if (data->hciemu_type == HCIEMU_TYPE_LE ||
+			test->client_enable_adv) {
+		if (data->hciemu_type >= HCIEMU_TYPE_BREDRLE50) {
+			bthost_set_ext_adv_params(bthost);
+			bthost_set_ext_adv_enable(bthost, 0x01);
+		} else
+			bthost_set_adv_enable(bthost, 0x01);
+	} else
+		bthost_write_scan_enable(bthost, 0x03);
+}
+
+static void setup_complete(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	struct test_data *data = tester_get_data();
+
+	if (status != MGMT_STATUS_SUCCESS) {
+		tester_setup_failed();
+		return;
+	}
+
+	tester_print("Initial settings completed");
+
+	if (data->test_setup)
+		data->test_setup(data);
+	else
+		setup_bthost();
+}
+
+static void pin_code_request_callback(uint16_t index, uint16_t length,
+		const void *param, void *user_data)
+{
+	const struct mgmt_ev_pin_code_request *ev = param;
+	struct test_data *data = user_data;
+	const struct generic_data *test = data->test_data;
+	struct mgmt_cp_pin_code_reply cp;
+
+	test_condition_complete(data);
+
+	memset(&cp, 0, sizeof(cp));
+	memcpy(&cp.addr, &ev->addr, sizeof(cp.addr));
+
+	if (!test->pin) {
+		mgmt_reply(data->mgmt, MGMT_OP_PIN_CODE_NEG_REPLY,
+				data->mgmt_index, sizeof(cp.addr), &cp.addr,
+				NULL, NULL, NULL);
+		return;
+	}
+
+	cp.pin_len = test->pin_len;
+	memcpy(cp.pin_code, test->pin, test->pin_len);
+
+	mgmt_reply(data->mgmt, MGMT_OP_PIN_CODE_REPLY, data->mgmt_index,
+			sizeof(cp), &cp, NULL, NULL, NULL);
+}
+
+static void user_confirm_request_callback(uint16_t index, uint16_t length,
+		const void *param,
+		void *user_data)
+{
+	const struct mgmt_ev_user_confirm_request *ev = param;
+	struct test_data *data = user_data;
+	const struct generic_data *test = data->test_data;
+	struct mgmt_cp_user_confirm_reply cp;
+	uint16_t opcode;
+
+	memset(&cp, 0, sizeof(cp));
+	memcpy(&cp.addr, &ev->addr, sizeof(cp.addr));
+
+	if (test->reject_confirm)
+		opcode = MGMT_OP_USER_CONFIRM_NEG_REPLY;
+	else
+		opcode = MGMT_OP_USER_CONFIRM_REPLY;
+
+	mgmt_reply(data->mgmt, opcode, data->mgmt_index, sizeof(cp), &cp,
+			NULL, NULL, NULL);
+}
+
+static void user_passkey_request_callback(uint16_t index, uint16_t length,
+		const void *param,
+		void *user_data)
+{
+	const struct mgmt_ev_user_passkey_request *ev = param;
+	struct test_data *data = user_data;
+	const struct generic_data *test = data->test_data;
+	struct mgmt_cp_user_passkey_reply cp;
+
+	if (test->just_works) {
+		tester_warn("User Passkey Request for just-works case");
+		tester_test_failed();
+		return;
+	}
+
+	memset(&cp, 0, sizeof(cp));
+	memcpy(&cp.addr, &ev->addr, sizeof(cp.addr));
+
+	if (test->reject_confirm) {
+		mgmt_reply(data->mgmt, MGMT_OP_USER_PASSKEY_NEG_REPLY,
+				data->mgmt_index, sizeof(cp.addr), &cp.addr,
+				NULL, NULL, NULL);
+		return;
+	}
+
+	mgmt_reply(data->mgmt, MGMT_OP_USER_PASSKEY_REPLY, data->mgmt_index,
+			sizeof(cp), &cp, NULL, NULL, NULL);
+}
+
+static void test_setup(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	const struct generic_data *test = data->test_data;
+	struct bthost *bthost = hciemu_client_get_host(data->hciemu);
+	const uint16_t *cmd;
+
+	if (!test)
+		goto proceed;
+
+	if (test->pin || test->expect_pin) {
+		mgmt_register(data->mgmt, MGMT_EV_PIN_CODE_REQUEST,
+				data->mgmt_index, pin_code_request_callback,
+				data, NULL);
+		test_add_condition(data);
+	}
+
+	mgmt_register(data->mgmt, MGMT_EV_USER_CONFIRM_REQUEST,
+			data->mgmt_index, user_confirm_request_callback,
+			data, NULL);
+
+	mgmt_register(data->mgmt, MGMT_EV_USER_PASSKEY_REQUEST,
+			data->mgmt_index, user_passkey_request_callback,
+			data, NULL);
+
+	if (test->client_pin)
+		bthost_set_pin_code(bthost, test->client_pin,
+				test->client_pin_len);
+
+	if (test->client_io_cap)
+		bthost_set_io_capability(bthost, test->client_io_cap);
+
+	if (test->client_auth_req)
+		bthost_set_auth_req(bthost, test->client_auth_req);
+	else if (!test->just_works)
+		bthost_set_auth_req(bthost, 0x01);
+
+	if (test->client_reject_confirm)
+		bthost_set_reject_user_confirm(bthost, true);
+
+	if (test->client_enable_le)
+		bthost_write_le_host_supported(bthost, 0x01);
+
+	if (test->client_enable_sc)
+		bthost_set_sc_support(bthost, 0x01);
+
+proceed:
+	if (!test || !test->setup_settings) {
+		if (data->test_setup)
+			data->test_setup(data);
+		else
+			tester_setup_complete();
+		return;
+	}
+
+	for (cmd = test->setup_settings; *cmd; cmd++) {
+		unsigned char simple_param[] = { 0x01 };
+		unsigned char discov_param[] = { 0x01, 0x00, 0x00 };
+		unsigned char privacy_param[] = { 0x01,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+			0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+		unsigned char set_exp_feat_param[17] = { 0x00 };
+		unsigned char *param = simple_param;
+		size_t param_size = sizeof(simple_param);
+		mgmt_request_func_t func = NULL;
+
+		/* If this is the last command (next one is 0) request
+		 * for a callback.
+		 */
+		if (!cmd[1])
+			func = setup_complete;
+
+		if (*cmd == MGMT_OP_SET_DISCOVERABLE) {
+			if (test->setup_limited_discov) {
+				discov_param[0] = 0x02;
+				discov_param[1] = 0x01;
+			}
+			param_size = sizeof(discov_param);
+			param = discov_param;
+		}
+
+		if (*cmd == MGMT_OP_SET_PRIVACY) {
+			param_size = sizeof(privacy_param);
+			param = privacy_param;
+		}
+
+		if (*cmd == MGMT_OP_START_DISCOVERY) {
+			if (test->setup_discovery_param)
+				memcpy(param, test->setup_discovery_param, 1);
+		}
+
+		if (*cmd == MGMT_OP_SET_EXP_FEATURE) {
+			if (test->setup_exp_feat_param) {
+				memcpy(set_exp_feat_param,
+						test->setup_exp_feat_param, 17);
+				param_size = sizeof(set_exp_feat_param);
+				param = set_exp_feat_param;
+			}
+		}
+
+		if (*cmd == MGMT_OP_SET_LE && test->setup_nobredr) {
+			unsigned char off[] = { 0x00 };
+
+			tester_print("Setup sending %s (0x%04x)",
+					mgmt_opstr(*cmd), *cmd);
+			mgmt_send(data->mgmt, *cmd, data->mgmt_index,
+					param_size, param, NULL, NULL, NULL);
+			tester_print("Setup sending %s (0x%04x)",
+					mgmt_opstr(MGMT_OP_SET_BREDR),
+					MGMT_OP_SET_BREDR);
+			mgmt_send(data->mgmt, MGMT_OP_SET_BREDR,
+					data->mgmt_index, sizeof(off), off,
+					func, data, NULL);
+		} else {
+			tester_print("Setup sending %s (0x%04x)",
+					mgmt_opstr(*cmd), *cmd);
+			mgmt_send(data->mgmt, *cmd, data->mgmt_index,
+					param_size, param, func, data, NULL);
+		}
+	}
+}
+
+static const struct generic_data enable_mesh_1 = {
+	.send_opcode = MGMT_OP_SET_EXP_FEATURE,
+	.send_param = set_exp_feat_param_mesh,
+	.send_len = sizeof(set_exp_feat_param_mesh),
+	.expect_param = set_exp_feat_rsp_param_mesh,
+	.expect_len = sizeof(set_exp_feat_rsp_param_mesh),
+	.expect_status = MGMT_STATUS_SUCCESS,
+};
+
+static const uint8_t set_mesh_receiver_1[] = {
+	0x01,
+	0x6e, 0x01,
+	0xe8, 0x01,
+	0x03,
+	0x2a, 0x2b, 0x29
+};
+
+static const struct generic_data enable_mesh_2 = {
+	.send_opcode = MGMT_OP_SET_MESH_RECEIVER,
+	.send_param = set_mesh_receiver_1,
+	.send_len = sizeof(set_mesh_receiver_1),
+	.expect_status = MGMT_STATUS_SUCCESS,
+};
+
+static const uint8_t read_mesh_feat_rsp_param_mesh[] = {
+	0x00, 0x00,
+	0x03,
+	0x00
+};
+
+static const uint8_t read_mesh_feat_rsp_param_mesh_disabled[] = {
+	0x00, 0x00,
+	0x00,
+	0x00
+};
+
+static const struct generic_data read_mesh_features = {
+	.send_opcode = MGMT_OP_MESH_READ_FEATURES,
+	.expect_param = read_mesh_feat_rsp_param_mesh,
+	.expect_len = sizeof(read_mesh_feat_rsp_param_mesh),
+	.expect_status = MGMT_STATUS_SUCCESS,
+};
+
+static const struct generic_data read_mesh_features_disabled = {
+	.send_opcode = MGMT_OP_MESH_READ_FEATURES,
+	.expect_param = read_mesh_feat_rsp_param_mesh_disabled,
+	.expect_len = sizeof(read_mesh_feat_rsp_param_mesh_disabled),
+	.expect_status = MGMT_STATUS_SUCCESS,
+};
+
+static const uint8_t send_mesh_1[] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	BDADDR_LE_RANDOM,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x03,
+	0x18,
+	0x17, 0x2b, 0x01, 0x00, 0x2d, 0xda, 0x0c, 0x24,
+	0x91, 0x53, 0x7a, 0xe2, 0x00, 0x00, 0x00, 0x00,
+	0x9d, 0xe2, 0x12, 0x0a, 0x72, 0x50, 0x38, 0xb2
+};
+
+static const uint8_t send_mesh_too_long[] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	BDADDR_LE_RANDOM,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x03,
+	0x28,
+	0x17, 0x2b, 0x01, 0x00, 0x2d, 0xda, 0x0c, 0x24,
+	0x91, 0x53, 0x7a, 0xe2, 0x00, 0x00, 0x00, 0x00,
+	0x91, 0x53, 0x7a, 0xe2, 0x00, 0x00, 0x00, 0x00,
+	0x91, 0x53, 0x7a, 0xe2, 0x00, 0x00, 0x00, 0x00,
+	0x9d, 0xe2, 0x12, 0x0a, 0x72, 0x50, 0x38, 0xb2
+};
+
+static const uint8_t mesh_send_rsp_param_mesh[] = {
+	0x01
+};
+
+static const struct generic_data mesh_send_mesh_1 = {
+	.send_opcode = MGMT_OP_MESH_SEND,
+	.send_param = send_mesh_1,
+	.send_len = sizeof(send_mesh_1),
+	.expect_param = mesh_send_rsp_param_mesh,
+	.expect_len = sizeof(mesh_send_rsp_param_mesh),
+	.expect_status = MGMT_STATUS_SUCCESS,
+	.expect_alt_ev = MGMT_EV_MESH_PACKET_CMPLT,
+	.expect_alt_ev_param = mesh_send_rsp_param_mesh,
+	.expect_alt_ev_len = sizeof(mesh_send_rsp_param_mesh),
+	.expect_hci_command = BT_HCI_CMD_LE_SET_ADV_ENABLE,
+	.expect_hci_param = mesh_send_rsp_param_mesh,
+	.expect_hci_len = sizeof(mesh_send_rsp_param_mesh),
+};
+
+static const struct generic_data mesh_send_mesh_too_short = {
+	.send_opcode = MGMT_OP_MESH_SEND,
+	.send_param = send_mesh_1,
+	.send_len = sizeof(send_mesh_1) - 30,
+	.expect_status = MGMT_STATUS_INVALID_PARAMS
+};
+
+static const struct generic_data mesh_send_mesh_too_long = {
+	.send_opcode = MGMT_OP_MESH_SEND,
+	.send_param = send_mesh_too_long,
+	.send_len = sizeof(send_mesh_too_long),
+	.expect_status = MGMT_STATUS_REJECTED
+};
+
+static void setup_powered_callback(uint8_t status, uint16_t length,
+		const void *param, void *user_data)
+{
+	if (status != MGMT_STATUS_SUCCESS) {
+		tester_setup_failed();
+		return;
+	}
+
+	tester_print("Controller powered on");
+
+	setup_bthost();
+}
+
+static const char set_le_on_param[] = { 0x01 };
+
+static void setup_enable_mesh(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+	unsigned char param[] = { 0x01 };
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index,
+			sizeof(param), param,
+			setup_powered_callback, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_LE, data->mgmt_index,
+			sizeof(set_le_on_param), set_le_on_param,
+			NULL, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_SET_EXP_FEATURE, data->mgmt_index,
+			sizeof(set_exp_feat_param_mesh),
+			set_exp_feat_param_mesh,
+			mesh_exp_callback, NULL, NULL);
+}
+
+static const uint8_t send_mesh_cancel_1[] = {
+	0x01
+};
+
+static const uint8_t send_mesh_cancel_2[] = {
+	0x02
+};
+
+static const struct generic_data mesh_send_mesh_cancel_1 = {
+	.send_opcode = MGMT_OP_MESH_SEND_CANCEL,
+	.send_param = send_mesh_cancel_1,
+	.send_len = sizeof(send_mesh_cancel_1),
+	.expect_status = MGMT_STATUS_SUCCESS,
+	.expect_alt_ev = MGMT_EV_MESH_PACKET_CMPLT,
+	.expect_alt_ev_param = send_mesh_cancel_1,
+	.expect_alt_ev_len = sizeof(send_mesh_cancel_1),
+};
+
+static const struct generic_data mesh_send_mesh_cancel_2 = {
+	.send_opcode = MGMT_OP_MESH_SEND_CANCEL,
+	.send_param = send_mesh_cancel_2,
+	.send_len = sizeof(send_mesh_cancel_2),
+	.expect_status = MGMT_STATUS_SUCCESS,
+	.expect_alt_ev = MGMT_EV_MESH_PACKET_CMPLT,
+	.expect_alt_ev_param = send_mesh_cancel_2,
+	.expect_alt_ev_len = sizeof(send_mesh_cancel_2),
+};
+
+static void setup_multi_mesh_send(const void *test_data)
+{
+	struct test_data *data = tester_get_data();
+
+	setup_enable_mesh(test_data);
+
+	mgmt_send(data->mgmt, MGMT_OP_MESH_SEND, data->mgmt_index,
+			sizeof(send_mesh_1), send_mesh_1,
+			NULL, NULL, NULL);
+
+	mgmt_send(data->mgmt, MGMT_OP_MESH_SEND, data->mgmt_index,
+			sizeof(send_mesh_1), send_mesh_1,
+			NULL, NULL, NULL);
+}
+
+int main(int argc, char *argv[])
+{
+	tester_init(&argc, &argv);
+
+	test_bredrle("Controller setup",
+			NULL, NULL, controller_setup);
+
+	/* LL Mesh Enable
+	 * Setup: None
+	 * Run: Send Enable Experimental Feature (mesh)
+	 * Expect: Mesh feature enable success
+	 */
+	test_bredrle("Mesh - Enable 1",
+			&enable_mesh_1,
+			NULL,
+			test_command_generic);
+
+	test_bredrle("Mesh - Enable 2",
+			&enable_mesh_2,
+			setup_enable_mesh,
+			test_command_generic);
+
+	test_bredrle("Mesh - Read Mesh Features",
+			&read_mesh_features,
+			setup_enable_mesh,
+			test_command_generic);
+
+	test_bredrle("Mesh - Read Mesh Features - Disabled",
+			&read_mesh_features_disabled,
+			NULL,
+			test_command_generic);
+
+	test_bredrle("Mesh - Send",
+			&mesh_send_mesh_1,
+			setup_enable_mesh,
+			test_command_generic);
+
+	test_bredrle("Mesh - Send - too short",
+			&mesh_send_mesh_too_short,
+			setup_enable_mesh,
+			test_command_generic);
+
+	test_bredrle("Mesh - Send - too long",
+			&mesh_send_mesh_too_long,
+			setup_enable_mesh,
+			test_command_generic);
+
+	test_bredrle("Mesh - Send cancel - 1",
+			&mesh_send_mesh_cancel_1,
+			setup_multi_mesh_send,
+			test_command_generic);
+
+	test_bredrle("Mesh - Send cancel - 2",
+			&mesh_send_mesh_cancel_2,
+			setup_multi_mesh_send,
+			test_command_generic);
+
+	return tester_run();
+}
diff --git a/tools/test-runner.c b/tools/test-runner.c
index 71d50e015..e1794e8c1 100644
--- a/tools/test-runner.c
+++ b/tools/test-runner.c
@@ -614,6 +614,7 @@  static const char *test_table[] = {
 	"rfcomm-tester",
 	"sco-tester",
 	"iso-tester",
+	"mesh-tester",
 	"bnep-tester",
 	"check-selftest",
 	"tools/mgmt-tester",
@@ -622,6 +623,7 @@  static const char *test_table[] = {
 	"tools/rfcomm-tester",
 	"tools/sco-tester",
 	"tools/iso-tester",
+	"tools/mesh-tester",
 	"tools/bnep-tester",
 	"tools/check-selftest",
 	NULL