2021-02-06 05:53:38

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ v3 0/3] Framework for non-interactive mesh test

v3: Fixed copyright style in mesh-io-unit.h

**********
v2: Fixed errors reported by bluez-bot.
Regarding the warnings for the "__attribute__((packed))":
keeping this style to be consistent with the rest of BLueZ codebase

**********
This patch set introduces a framework for non-interactive testing of
mesh daemon functionality and consists of the three components:

1. A version of src/shared/tester that uses ELL primitives:
everything mesh-related uses ELL.

2. New type of mesh IO used only for testing.

3. The mesh-cfgtest tool that excercises a number of mesh D-Bus
API calls. Currently, the tool covers limited number of initial
test cases. The extended coverage will be provided after this
patch set is accepted.


Brian Gix (1):
mesh: Add unit test IO

Inga Stotland (2):
shared/tester: Create ell-based version of tester code
tools/mesh-cfgtest: Non-iteractive test for mesh daemon

Makefile.am | 8 +-
Makefile.mesh | 2 +
Makefile.tools | 6 +
mesh/main.c | 51 +-
mesh/mesh-io-unit.c | 533 ++++++++++++++++
mesh/mesh-io-unit.h | 11 +
mesh/mesh-io.c | 9 +-
mesh/mesh-io.h | 3 +-
src/shared/tester-ell.c | 887 ++++++++++++++++++++++++++
tools/mesh-cfgtest.c | 1319 +++++++++++++++++++++++++++++++++++++++
10 files changed, 2808 insertions(+), 21 deletions(-)
create mode 100644 mesh/mesh-io-unit.c
create mode 100644 mesh/mesh-io-unit.h
create mode 100644 src/shared/tester-ell.c
create mode 100644 tools/mesh-cfgtest.c

--
2.26.2


2021-02-06 05:53:38

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ v3 1/3] shared/tester: Create ell-based version of tester code

Create a version of tester that uses ell primitives instead of glib:
tester-ell.c. This source is included to generate lishared-ell library.
The original tester.c is built as part of libshared-glib library.
---
Makefile.am | 8 +-
src/shared/tester-ell.c | 887 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 892 insertions(+), 3 deletions(-)
create mode 100644 src/shared/tester-ell.c

diff --git a/Makefile.am b/Makefile.am
index d0f979586..5fa9706c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -189,7 +189,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
src/shared/crypto.h src/shared/crypto.c \
src/shared/ecc.h src/shared/ecc.c \
src/shared/ringbuf.h src/shared/ringbuf.c \
- src/shared/tester.h src/shared/tester.c \
+ src/shared/tester.h\
src/shared/hci.h src/shared/hci.c \
src/shared/hci-crypto.h src/shared/hci-crypto.c \
src/shared/hfp.h src/shared/hfp.c \
@@ -216,7 +216,8 @@ src_libshared_glib_la_SOURCES = $(shared_sources) \
src/shared/timeout-glib.c \
src/shared/mainloop-glib.c \
src/shared/mainloop-notify.h \
- src/shared/mainloop-notify.c
+ src/shared/mainloop-notify.c \
+ src/shared/tester.c

src_libshared_mainloop_la_SOURCES = $(shared_sources) \
src/shared/io-mainloop.c \
@@ -230,7 +231,8 @@ src_libshared_ell_la_SOURCES = $(shared_sources) \
src/shared/io-ell.c \
src/shared/timeout-ell.c \
src/shared/mainloop.h \
- src/shared/mainloop-ell.c
+ src/shared/mainloop-ell.c \
+ src/shared/tester-ell.c
endif

attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \
diff --git a/src/shared/tester-ell.c b/src/shared/tester-ell.c
new file mode 100644
index 000000000..6fa7e5250
--- /dev/null
+++ b/src/shared/tester-ell.c
@@ -0,0 +1,887 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012-2014, 2021 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <getopt.h>
+#include <stdio.h>
+#include <errno.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <ell/ell.h>
+
+#include "lib/bluetooth.h"
+#include "lib/hci.h"
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include "src/shared/mainloop.h"
+#include "src/shared/util.h"
+#include "src/shared/tester.h"
+#include "src/shared/log.h"
+
+#define COLOR_OFF "\x1B[0m"
+#define COLOR_BLACK "\x1B[0;30m"
+#define COLOR_RED "\x1B[0;31m"
+#define COLOR_GREEN "\x1B[0;32m"
+#define COLOR_YELLOW "\x1B[0;33m"
+#define COLOR_BLUE "\x1B[0;34m"
+#define COLOR_MAGENTA "\x1B[0;35m"
+#define COLOR_CYAN "\x1B[0;36m"
+#define COLOR_WHITE "\x1B[0;37m"
+#define COLOR_HIGHLIGHT "\x1B[1;39m"
+
+#define print_text(color, fmt, args...) \
+ tester_log(color fmt COLOR_OFF, ## args)
+
+#define print_summary(label, color, value, fmt, args...) \
+ tester_log("%-52s " color "%-10s" COLOR_OFF fmt, \
+ label, value, ## args)
+
+#define print_progress(name, color, fmt, args...) \
+ tester_log(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \
+ color fmt COLOR_OFF, name, ## args)
+
+enum test_result {
+ TEST_RESULT_NOT_RUN,
+ TEST_RESULT_PASSED,
+ TEST_RESULT_FAILED,
+ TEST_RESULT_TIMED_OUT,
+};
+
+enum test_stage {
+ TEST_STAGE_INVALID,
+ TEST_STAGE_PRE_SETUP,
+ TEST_STAGE_SETUP,
+ TEST_STAGE_RUN,
+ TEST_STAGE_TEARDOWN,
+ TEST_STAGE_POST_TEARDOWN,
+};
+
+struct test_case {
+ char *name;
+ enum test_result result;
+ enum test_stage stage;
+ const void *test_data;
+ tester_data_func_t pre_setup_func;
+ tester_data_func_t setup_func;
+ tester_data_func_t test_func;
+ tester_data_func_t teardown_func;
+ tester_data_func_t post_teardown_func;
+ double start_time;
+ double end_time;
+ unsigned int timeout;
+ struct l_timeout *run_timer;
+ tester_destroy_func_t destroy;
+ void *user_data;
+ bool teardown;
+};
+
+static char *tester_name;
+
+static struct l_queue *test_list;
+static const struct l_queue_entry *test_entry;
+static struct timeval tester_start;
+
+static bool option_quiet;
+static bool option_debug;
+static bool option_monitor;
+static bool option_list;
+static const char *option_prefix;
+static const char *option_string;
+
+static bool terminated;
+
+struct monitor_hdr {
+ uint16_t opcode;
+ uint16_t index;
+ uint16_t len;
+ uint8_t priority;
+ uint8_t ident_len;
+} __attribute__((packed));
+
+struct monitor_l2cap_hdr {
+ uint16_t cid;
+ uint16_t psm;
+} __attribute__((packed));
+
+static void test_destroy(void *data)
+{
+ struct test_case *test = data;
+
+ l_timeout_remove(test->run_timer);
+
+ if (test->destroy)
+ test->destroy(test->user_data);
+
+ l_free(test->name);
+ l_free(test);
+}
+
+static void tester_vprintf(const char *format, va_list ap)
+{
+ if (tester_use_quiet())
+ return;
+
+ printf(" %s", COLOR_WHITE);
+ vprintf(format, ap);
+ printf("%s\n", COLOR_OFF);
+}
+
+static void tester_log(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ printf("\n");
+ va_end(ap);
+
+ va_start(ap, format);
+ bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap);
+ va_end(ap);
+}
+
+void tester_print(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ tester_vprintf(format, ap);
+ va_end(ap);
+
+ va_start(ap, format);
+ bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap);
+ va_end(ap);
+}
+
+void tester_debug(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ tester_vprintf(format, ap);
+ va_end(ap);
+
+ va_start(ap, format);
+ bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_DEBUG, format, ap);
+ va_end(ap);
+}
+
+void tester_warn(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ tester_vprintf(format, ap);
+ va_end(ap);
+
+ va_start(ap, format);
+ bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_WARNING, format, ap);
+ va_end(ap);
+}
+
+static void monitor_debug(const char *str, void *user_data)
+{
+ const char *label = user_data;
+
+ tester_debug("%s: %s", label, str);
+}
+
+static void monitor_log(char dir, uint16_t cid, uint16_t psm, const void *data,
+ size_t len)
+{
+ struct iovec iov[3];
+ struct monitor_l2cap_hdr hdr;
+ uint8_t term = 0x00;
+ char label[16];
+
+ if (snprintf(label, sizeof(label), "%c %s", dir, tester_name) < 0)
+ return;
+
+ hdr.cid = cpu_to_le16(cid);
+ hdr.psm = cpu_to_le16(psm);
+
+ iov[0].iov_base = &hdr;
+ iov[0].iov_len = sizeof(hdr);
+
+ iov[1].iov_base = (void *) data;
+ iov[1].iov_len = len;
+
+ /* Kernel won't forward if data is no NULL terminated */
+ iov[2].iov_base = &term;
+ iov[2].iov_len = sizeof(term);
+
+ bt_log_sendmsg(HCI_DEV_NONE, label, LOG_INFO, iov, 3);
+}
+
+void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data,
+ size_t len)
+{
+ monitor_log(dir, cid, psm, data, len);
+
+ if (!tester_use_debug())
+ return;
+
+ util_hexdump(dir, data, len, monitor_debug, (void *) tester_name);
+}
+
+static void default_pre_setup(const void *test_data)
+{
+ tester_pre_setup_complete();
+}
+
+static void default_setup(const void *test_data)
+{
+ tester_setup_complete();
+}
+
+static void default_teardown(const void *test_data)
+{
+ tester_teardown_complete();
+}
+
+static void default_post_teardown(const void *test_data)
+{
+ tester_post_teardown_complete();
+}
+
+void tester_add_full(const char *name, const void *test_data,
+ tester_data_func_t pre_setup_func,
+ tester_data_func_t setup_func,
+ tester_data_func_t test_func,
+ tester_data_func_t teardown_func,
+ tester_data_func_t post_teardown_func,
+ unsigned int timeout,
+ void *user_data, tester_destroy_func_t destroy)
+{
+ struct test_case *test;
+
+ if (!test_func)
+ return;
+
+ if (option_prefix && !l_str_has_prefix(name, option_prefix)) {
+ if (destroy)
+ destroy(user_data);
+ return;
+ }
+
+ if (option_string && !strstr(name, option_string)) {
+ if (destroy)
+ destroy(user_data);
+ return;
+ }
+
+ if (option_list) {
+ tester_log("%s", name);
+ if (destroy)
+ destroy(user_data);
+ return;
+ }
+
+ test = l_new(struct test_case, 1);
+ test->name = l_strdup(name);
+ test->result = TEST_RESULT_NOT_RUN;
+ test->stage = TEST_STAGE_INVALID;
+
+ test->test_data = test_data;
+
+ if (pre_setup_func)
+ test->pre_setup_func = pre_setup_func;
+ else
+ test->pre_setup_func = default_pre_setup;
+
+ if (setup_func)
+ test->setup_func = setup_func;
+ else
+ test->setup_func = default_setup;
+
+ test->test_func = test_func;
+
+ if (teardown_func)
+ test->teardown_func = teardown_func;
+ else
+ test->teardown_func = default_teardown;
+
+ if (post_teardown_func)
+ test->post_teardown_func = post_teardown_func;
+ else
+ test->post_teardown_func = default_post_teardown;
+
+ test->timeout = timeout;
+
+ test->destroy = destroy;
+ test->user_data = user_data;
+
+ l_queue_push_tail(test_list, test);
+}
+
+void tester_add(const char *name, const void *test_data,
+ tester_data_func_t setup_func,
+ tester_data_func_t test_func,
+ tester_data_func_t teardown_func)
+{
+ tester_add_full(name, test_data, NULL, setup_func, test_func,
+ teardown_func, NULL, 0, NULL, NULL);
+}
+
+void *tester_get_data(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return NULL;
+
+ test = test_entry->data;
+
+ return test->user_data;
+}
+
+static double get_elapsed_time(struct timeval *base)
+{
+ static struct timeval now, elapsed;
+
+ gettimeofday(&now, NULL);
+ timersub(&now, base, &elapsed);
+
+ return elapsed.tv_sec + ((double) elapsed.tv_usec) / 1000000;
+}
+
+static int tester_summarize(void)
+{
+ unsigned int not_run = 0, passed = 0, failed = 0;
+ double execution_time;
+ const struct l_queue_entry *entry;
+
+ tester_log("");
+ print_text(COLOR_HIGHLIGHT, "");
+ print_text(COLOR_HIGHLIGHT, "Test Summary");
+ print_text(COLOR_HIGHLIGHT, "------------");
+
+ entry = l_queue_get_entries(test_list);
+
+ for (; entry; entry = entry->next) {
+ struct test_case *test = entry->data;
+ double exec_time;
+
+ exec_time = test->end_time - test->start_time;
+
+ switch (test->result) {
+ case TEST_RESULT_NOT_RUN:
+ print_summary(test->name, COLOR_YELLOW, "Not Run", "");
+ not_run++;
+ break;
+ case TEST_RESULT_PASSED:
+ print_summary(test->name, COLOR_GREEN, "Passed",
+ "%8.3f seconds", exec_time);
+ passed++;
+ break;
+ case TEST_RESULT_FAILED:
+ print_summary(test->name, COLOR_RED, "Failed",
+ "%8.3f seconds", exec_time);
+ failed++;
+ break;
+ case TEST_RESULT_TIMED_OUT:
+ print_summary(test->name, COLOR_RED, "Timed out",
+ "%8.3f seconds", exec_time);
+ failed++;
+ break;
+ }
+ }
+
+ tester_log("Total: %d, "
+ COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", "
+ COLOR_RED "Failed: %d" COLOR_OFF ", "
+ COLOR_YELLOW "Not Run: %d" COLOR_OFF,
+ not_run + passed + failed, passed,
+ (not_run + passed + failed) ?
+ (float) passed * 100 / (not_run + passed + failed) : 0,
+ failed, not_run);
+
+ execution_time = get_elapsed_time(&tester_start);
+ tester_log("Overall execution time: %.3g seconds", execution_time);
+
+ return failed;
+}
+
+static void teardown_callback(void *user_data)
+{
+ struct test_case *test = user_data;
+
+ test->stage = TEST_STAGE_TEARDOWN;
+ test->teardown = false;
+
+ print_progress(test->name, COLOR_MAGENTA, "teardown");
+ test->teardown_func(test->test_data);
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_DO_ADDED_LEAK_CHECK;
+#endif
+}
+
+static void test_timeout(struct l_timeout *timer, void *user_data)
+{
+ struct test_case *test = user_data;
+
+ l_timeout_remove(timer);
+ test->run_timer = NULL;
+
+ test->result = TEST_RESULT_TIMED_OUT;
+ print_progress(test->name, COLOR_RED, "test timed out");
+
+ l_idle_oneshot(teardown_callback, test, NULL);
+}
+
+static void next_test_case(void)
+{
+ struct test_case *test;
+
+ if (test_entry)
+ test_entry = test_entry->next;
+ else
+ test_entry = l_queue_get_entries(test_list);
+
+ if (!test_entry) {
+ mainloop_quit();
+ return;
+ }
+
+ test = test_entry->data;
+
+ tester_log("");
+ print_progress(test->name, COLOR_BLACK, "init");
+
+ test->start_time = get_elapsed_time(&tester_start);
+
+ if (test->timeout > 0)
+ test->run_timer = l_timeout_create(test->timeout, test_timeout,
+ test, NULL);
+
+ test->stage = TEST_STAGE_PRE_SETUP;
+
+ test->pre_setup_func(test->test_data);
+}
+
+static void setup_callback(void *user_data)
+{
+ struct test_case *test = user_data;
+
+ test->stage = TEST_STAGE_SETUP;
+
+ print_progress(test->name, COLOR_BLUE, "setup");
+ test->setup_func(test->test_data);
+}
+
+static void run_callback(void *user_data)
+{
+ struct test_case *test = user_data;
+
+ test->stage = TEST_STAGE_RUN;
+
+ print_progress(test->name, COLOR_BLACK, "run");
+ test->test_func(test->test_data);
+}
+
+static void done_callback(void *user_data)
+{
+ struct test_case *test = user_data;
+
+ test->end_time = get_elapsed_time(&tester_start);
+
+ print_progress(test->name, COLOR_BLACK, "done");
+ next_test_case();
+}
+
+void tester_pre_setup_complete(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_PRE_SETUP)
+ return;
+
+ l_idle_oneshot(setup_callback, test, NULL);
+}
+
+void tester_pre_setup_failed(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_PRE_SETUP)
+ return;
+
+ print_progress(test->name, COLOR_RED, "pre setup failed");
+
+ l_idle_oneshot(done_callback, test, NULL);
+}
+
+void tester_setup_complete(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_SETUP)
+ return;
+
+ print_progress(test->name, COLOR_BLUE, "setup complete");
+
+ l_idle_oneshot(run_callback, test, NULL);
+}
+
+void tester_setup_failed(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_SETUP)
+ return;
+
+ test->stage = TEST_STAGE_POST_TEARDOWN;
+
+ l_timeout_remove(test->run_timer);
+ test->run_timer = NULL;
+
+ print_progress(test->name, COLOR_RED, "setup failed");
+ print_progress(test->name, COLOR_MAGENTA, "teardown");
+
+ test->post_teardown_func(test->test_data);
+}
+
+static void test_result(enum test_result result)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_RUN)
+ return;
+
+ l_timeout_remove(test->run_timer);
+ test->run_timer = NULL;
+
+ test->result = result;
+ switch (result) {
+ case TEST_RESULT_PASSED:
+ print_progress(test->name, COLOR_GREEN, "test passed");
+ break;
+ case TEST_RESULT_FAILED:
+ print_progress(test->name, COLOR_RED, "test failed");
+ break;
+ case TEST_RESULT_NOT_RUN:
+ print_progress(test->name, COLOR_YELLOW, "test not run");
+ break;
+ case TEST_RESULT_TIMED_OUT:
+ print_progress(test->name, COLOR_RED, "test timed out");
+ break;
+ }
+
+ if (test->teardown)
+ return;
+
+ test->teardown = true;
+
+ l_idle_oneshot(teardown_callback, test, NULL);
+}
+
+void tester_test_passed(void)
+{
+ test_result(TEST_RESULT_PASSED);
+}
+
+void tester_test_failed(void)
+{
+ test_result(TEST_RESULT_FAILED);
+}
+
+void tester_test_abort(void)
+{
+ test_result(TEST_RESULT_NOT_RUN);
+}
+
+void tester_teardown_complete(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_TEARDOWN)
+ return;
+
+ test->stage = TEST_STAGE_POST_TEARDOWN;
+
+ test->post_teardown_func(test->test_data);
+}
+
+void tester_teardown_failed(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_TEARDOWN)
+ return;
+
+ test->stage = TEST_STAGE_POST_TEARDOWN;
+
+ tester_post_teardown_failed();
+}
+
+void tester_post_teardown_complete(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_POST_TEARDOWN)
+ return;
+
+ print_progress(test->name, COLOR_MAGENTA, "teardown complete");
+
+ l_idle_oneshot(done_callback, test, NULL);
+}
+
+void tester_post_teardown_failed(void)
+{
+ struct test_case *test;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ if (test->stage != TEST_STAGE_POST_TEARDOWN)
+ return;
+
+ print_progress(test->name, COLOR_RED, "teardown failed");
+
+ l_idle_oneshot(done_callback, test, NULL);
+}
+
+static void start_tester(void *user_data)
+{
+ gettimeofday(&tester_start, NULL);
+ next_test_case();
+}
+
+struct wait_data {
+ unsigned int seconds;
+ struct test_case *test;
+ tester_wait_func_t func;
+ void *user_data;
+};
+
+static void wait_callback(struct l_timeout *timer, void *user_data)
+{
+ struct wait_data *wait = user_data;
+ struct test_case *test = wait->test;
+
+ wait->seconds--;
+
+ if (wait->seconds > 0) {
+ print_progress(test->name, COLOR_BLACK, "%u seconds left",
+ wait->seconds);
+ return;
+ }
+
+ print_progress(test->name, COLOR_BLACK, "waiting done");
+
+ wait->func(wait->user_data);
+
+ free(wait);
+
+ l_timeout_remove(timer);
+}
+
+void tester_wait(unsigned int seconds, tester_wait_func_t func,
+ void *user_data)
+{
+ struct test_case *test;
+ struct wait_data *wait;
+
+ if (!func || seconds < 1)
+ return;
+
+ if (!test_entry)
+ return;
+
+ test = test_entry->data;
+
+ wait = new0(struct wait_data, 1);
+ wait->seconds = seconds;
+ wait->test = test;
+ wait->func = func;
+ wait->user_data = user_data;
+
+ l_timeout_create(seconds, wait_callback, wait, NULL);
+
+ print_progress(test->name, COLOR_BLACK, "waiting %u seconds", seconds);
+}
+
+static void signal_callback(int signum, void *user_data)
+{
+ switch (signum) {
+ case SIGINT:
+ case SIGTERM:
+ if (!terminated)
+ mainloop_quit();
+
+ terminated = true;
+ break;
+ }
+}
+
+bool tester_use_quiet(void)
+{
+ return option_quiet;
+}
+
+bool tester_use_debug(void)
+{
+ return option_debug;
+}
+
+static const struct option options[] = {
+ { "version", no_argument, NULL, 'v' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "debug", no_argument, NULL, 'd' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "list", no_argument, NULL, 'l' },
+ { "prefix", required_argument, NULL, 'p' },
+ { "string", required_argument, NULL, 's' },
+ { }
+};
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ "\%s [options]\n", tester_name);
+ fprintf(stderr,
+ "Options:\n"
+ "\t-v, --version Show version information and exit\n"
+ "\t-q, --quiet Run tests without logging\n"
+ "\t-d, --debug Run tests with debug output\n"
+ "\t-m, --monitor Enable monitor output\n"
+ "\t-l, --list Only list the tests to be run\n"
+ "\t-p, --prefix Run tests matching provided prefix\n"
+ "\t-s, --string Run tests matching provided string\n");
+}
+
+void tester_init(int *argc, char ***argv)
+{
+ tester_name = strrchr(*argv[0], '/');
+ if (!tester_name)
+ tester_name = strdup(*argv[0]);
+ else
+ tester_name = strdup(++tester_name);
+
+ for (;;) {
+ int opt;
+
+ opt = getopt_long(*argc, *argv, "ps:vqdml", options, NULL);
+ if (opt < 0)
+ break;
+
+ switch (opt) {
+ case 'v':
+ printf("%s\n", VERSION);
+ exit(EXIT_SUCCESS);
+ case 'q':
+ option_quiet = true;
+ break;
+ case 'd':
+ option_debug = true;
+ break;
+ case 'm':
+ option_monitor = true;
+ break;
+ case 'l':
+ option_list = true;
+ break;
+ case 'p':
+ option_prefix = optarg;
+ break;
+ case 's':
+ option_string = optarg;
+ break;
+ default:
+ usage();
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ mainloop_init();
+
+ test_list = l_queue_new();
+}
+
+int tester_run(void)
+{
+ int ret;
+
+ if (option_list) {
+ mainloop_quit();
+ return EXIT_SUCCESS;
+ }
+
+ l_idle_oneshot(start_tester, NULL, NULL);
+
+ mainloop_run_with_signal(signal_callback, NULL);
+
+ ret = tester_summarize();
+
+ l_queue_destroy(test_list, test_destroy);
+
+ if (option_monitor)
+ bt_log_close();
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
--
2.26.2

2021-02-06 05:55:55

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ v3 2/3] mesh: Add unit test IO

From: Brian Gix <[email protected]>

This adds a new type of mesh IO that is used for non-interactive testing.
The new io option can be specified on command line as:
--io unit:<socket_name>

When the bluetooth-meshd daemon starts with the "unit" IO type,
the daemon opens a socket (fd to open is provided after "unit:"
in <socket_name>). The communication with the daemon is done either
through the loop-back using mesh DBus-based APIs or the specified
named socket.
---
Makefile.mesh | 2 +
mesh/main.c | 41 +++-
mesh/mesh-io-unit.c | 533 ++++++++++++++++++++++++++++++++++++++++++++
mesh/mesh-io-unit.h | 11 +
mesh/mesh-io.c | 9 +-
mesh/mesh-io.h | 3 +-
6 files changed, 582 insertions(+), 17 deletions(-)
create mode 100644 mesh/mesh-io-unit.c
create mode 100644 mesh/mesh-io-unit.h

diff --git a/Makefile.mesh b/Makefile.mesh
index 228dd1b5f..73eaded4a 100644
--- a/Makefile.mesh
+++ b/Makefile.mesh
@@ -17,6 +17,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \
mesh/error.h mesh/mesh-io-api.h \
mesh/mesh-io-generic.h \
mesh/mesh-io-generic.c \
+ mesh/mesh-io-unit.h \
+ mesh/mesh-io-unit.c \
mesh/net.h mesh/net.c \
mesh/crypto.h mesh/crypto.c \
mesh/friend.h mesh/friend.c \
diff --git a/mesh/main.c b/mesh/main.c
index 4356e3f65..1b466598b 100644
--- a/mesh/main.c
+++ b/mesh/main.c
@@ -61,7 +61,7 @@ static void usage(void)
"\t--help Show %s information\n", __func__);
fprintf(stderr,
"io:\n"
- "\t([hci]<index> | generic[:[hci]<index>])\n"
+ "\t([hci]<index> | generic[:[hci]<index>] | unit:<fd_path>)\n"
"\t\tUse generic HCI io on interface hci<index>, or the first\n"
"\t\tavailable one\n");
}
@@ -77,6 +77,7 @@ static void mesh_ready_callback(void *user_data, bool success)
{
struct l_dbus *dbus = user_data;

+ l_info("mesh_ready_callback");
if (!success) {
l_error("Failed to start mesh");
l_main_quit();
@@ -92,10 +93,8 @@ static void mesh_ready_callback(void *user_data, bool success)
static void request_name_callback(struct l_dbus *dbus, bool success,
bool queued, void *user_data)
{
- l_info("Request name %s",
- success ? "success": "failed");
-
- if (!success) {
+ if (!success && io_type != MESH_IO_TYPE_UNIT_TEST) {
+ l_info("Request name failed");
l_main_quit();
return;
}
@@ -159,6 +158,21 @@ static bool parse_io(const char *optarg, enum mesh_io_type *type, void **opts)
return true;

return false;
+
+ } else if (strstr(optarg, "unit") == optarg) {
+ char *test_path;
+
+ *type = MESH_IO_TYPE_UNIT_TEST;
+
+ optarg += strlen("unit");
+ if (*optarg != ':')
+ return false;
+
+ optarg++;
+ test_path = strdup(optarg);
+
+ *opts = test_path;
+ return true;
}

return false;
@@ -187,11 +201,19 @@ int main(int argc, char *argv[])
for (;;) {
int opt;

- opt = getopt_long(argc, argv, "i:s:c:ndbh", main_options, NULL);
+ opt = getopt_long(argc, argv, "u:i:s:c:ndbh", main_options,
+ NULL);
if (opt < 0)
break;

switch (opt) {
+ case 'u':
+ if (sscanf(optarg, "%d", &hci_index) == 1 ||
+ sscanf(optarg, "%d", &hci_index) == 1)
+ io = l_strdup_printf("unit:%d", hci_index);
+ else
+ io = l_strdup(optarg);
+ break;
case 'i':
if (sscanf(optarg, "hci%d", &hci_index) == 1 ||
sscanf(optarg, "%d", &hci_index) == 1)
@@ -261,11 +283,8 @@ int main(int argc, char *argv[])
status = l_main_run_with_signal(signal_handler, NULL);

done:
- if (io)
- l_free(io);
-
- if (io_opts)
- l_free(io_opts);
+ l_free(io);
+ l_free(io_opts);

mesh_cleanup();
l_dbus_destroy(dbus);
diff --git a/mesh/mesh-io-unit.c b/mesh/mesh-io-unit.c
new file mode 100644
index 000000000..c5aae6741
--- /dev/null
+++ b/mesh/mesh-io-unit.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2021 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ell/ell.h>
+
+#include "mesh/mesh-defs.h"
+#include "mesh/dbus.h"
+#include "mesh/mesh-io.h"
+#include "mesh/mesh-io-api.h"
+#include "mesh/mesh-io-generic.h"
+
+struct mesh_io_private {
+ struct l_io *sio;
+ void *user_data;
+ char *unique_name;
+ mesh_io_ready_func_t ready_callback;
+ struct l_timeout *tx_timeout;
+ struct l_queue *rx_regs;
+ struct l_queue *tx_pkts;
+ struct sockaddr_un addr;
+ int fd;
+ uint16_t interval;
+};
+
+struct pvt_rx_reg {
+ mesh_io_recv_func_t cb;
+ void *user_data;
+ uint8_t len;
+ uint8_t filter[0];
+};
+
+struct process_data {
+ struct mesh_io_private *pvt;
+ const uint8_t *data;
+ uint8_t len;
+ struct mesh_io_recv_info info;
+};
+
+struct tx_pkt {
+ struct mesh_io_send_info info;
+ bool delete;
+ uint8_t len;
+ uint8_t pkt[30];
+};
+
+struct tx_pattern {
+ const uint8_t *data;
+ uint8_t len;
+};
+
+static uint32_t get_instant(void)
+{
+ struct timeval tm;
+ uint32_t instant;
+
+ gettimeofday(&tm, NULL);
+ instant = tm.tv_sec * 1000;
+ instant += tm.tv_usec / 1000;
+
+ return instant;
+}
+
+static uint32_t instant_remaining_ms(uint32_t instant)
+{
+ instant -= get_instant();
+ return instant;
+}
+
+static void process_rx_callbacks(void *v_reg, void *v_rx)
+{
+ struct pvt_rx_reg *rx_reg = v_reg;
+ struct process_data *rx = v_rx;
+
+ if (!memcmp(rx->data, rx_reg->filter, rx_reg->len))
+ rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx->len);
+}
+
+static void process_rx(struct mesh_io_private *pvt, int8_t rssi,
+ uint32_t instant, const uint8_t *addr,
+ const uint8_t *data, uint8_t len)
+{
+ struct process_data rx = {
+ .pvt = pvt,
+ .data = data,
+ .len = len,
+ .info.instant = instant,
+ .info.addr = addr,
+ .info.chan = 7,
+ .info.rssi = rssi,
+ };
+
+ l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx);
+}
+
+static bool incoming(struct l_io *sio, void *user_data)
+{
+ struct mesh_io_private *pvt = user_data;
+ uint32_t instant;
+ uint8_t buf[31];
+ size_t size;
+
+ instant = get_instant();
+
+ size = recv(pvt->fd, buf, sizeof(buf), MSG_DONTWAIT);
+
+ if (size > 9 && buf[0]) {
+ process_rx(pvt, -20, instant, NULL, buf + 1, (uint8_t)size);
+ } else if (size == 1 && !buf[0] && pvt->unique_name) {
+
+ /* Return DBUS unique name */
+ size = strlen(pvt->unique_name);
+
+ if (size > sizeof(buf) - 2)
+ return true;
+
+ buf[0] = 0;
+ memcpy(buf + 1, pvt->unique_name, size + 1);
+ send(pvt->fd, buf, size + 2, MSG_DONTWAIT);
+ }
+
+ return true;
+}
+
+static bool find_by_ad_type(const void *a, const void *b)
+{
+ const struct tx_pkt *tx = a;
+ uint8_t ad_type = L_PTR_TO_UINT(b);
+
+ return !ad_type || ad_type == tx->pkt[0];
+}
+
+static bool find_by_pattern(const void *a, const void *b)
+{
+ const struct tx_pkt *tx = a;
+ const struct tx_pattern *pattern = b;
+
+ if (tx->len < pattern->len)
+ return false;
+
+ return (!memcmp(tx->pkt, pattern->data, pattern->len));
+}
+
+static void free_socket(struct mesh_io_private *pvt)
+{
+ l_io_destroy(pvt->sio);
+ close(pvt->fd);
+ unlink(pvt->addr.sun_path);
+}
+
+static void hello_callback(struct l_dbus_message *msg, void *user_data)
+{
+ struct mesh_io_private *pvt = user_data;
+
+ pvt->unique_name = l_strdup(l_dbus_message_get_destination(msg));
+ l_debug("User-Daemon unique name: %s", pvt->unique_name);
+}
+
+static void get_name(struct l_timeout *timeout, void *user_data)
+{
+ struct mesh_io_private *pvt = user_data;
+ struct l_dbus *dbus = dbus_get_bus();
+ struct l_dbus_message *msg;
+
+ l_timeout_remove(timeout);
+ if (!dbus) {
+ l_timeout_create_ms(20, get_name, pvt, NULL);
+ return;
+ }
+
+ /* Retrieve unique name */
+ msg = l_dbus_message_new_method_call(dbus, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetId");
+
+ l_dbus_message_set_arguments(msg, "");
+
+ l_dbus_send_with_reply(dbus, msg, hello_callback, pvt, NULL);
+}
+
+static void unit_up(void *user_data)
+{
+ struct mesh_io_private *pvt = user_data;
+
+ l_debug("Started io-unit");
+
+ if (pvt->ready_callback)
+ pvt->ready_callback(pvt->user_data, true);
+
+ l_timeout_create_ms(1, get_name, pvt, NULL);
+}
+
+static bool unit_init(struct mesh_io *io, void *opt,
+ mesh_io_ready_func_t cb, void *user_data)
+{
+ struct mesh_io_private *pvt;
+ char *sk_path;
+ size_t size;
+
+ l_debug("Starting Unit test IO");
+ if (!io || io->pvt)
+ return false;
+
+ sk_path = (char *) opt;
+
+ pvt = l_new(struct mesh_io_private, 1);
+
+ pvt->addr.sun_family = AF_LOCAL;
+ snprintf(pvt->addr.sun_path, sizeof(pvt->addr.sun_path), "%s",
+ sk_path);
+
+ pvt->fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (pvt->fd < 0)
+ goto fail;
+
+ unlink(pvt->addr.sun_path);
+ size = offsetof(struct sockaddr_un, sun_path) +
+ strlen(pvt->addr.sun_path);
+
+ if (bind(pvt->fd, (struct sockaddr *) &pvt->addr, size) < 0)
+ goto fail;
+
+ /* Setup socket handlers */
+ pvt->sio = l_io_new(pvt->fd);
+ if (!l_io_set_read_handler(pvt->sio, incoming, pvt, NULL))
+ goto fail;
+
+ pvt->rx_regs = l_queue_new();
+ pvt->tx_pkts = l_queue_new();
+
+ pvt->ready_callback = cb;
+ pvt->user_data = user_data;
+
+ io->pvt = pvt;
+
+ l_idle_oneshot(unit_up, pvt, NULL);
+
+ return true;
+
+fail:
+ l_error("Failed to bind Unit Test socket");
+ free_socket(pvt);
+ l_free(pvt);
+
+ return false;
+}
+
+static bool unit_destroy(struct mesh_io *io)
+{
+ struct mesh_io_private *pvt = io->pvt;
+
+ if (!pvt)
+ return true;
+
+ l_free(pvt->unique_name);
+ l_timeout_remove(pvt->tx_timeout);
+ l_queue_destroy(pvt->rx_regs, l_free);
+ l_queue_destroy(pvt->tx_pkts, l_free);
+
+ free_socket(pvt);
+
+ l_free(pvt);
+ io->pvt = NULL;
+
+ return true;
+}
+
+static bool unit_caps(struct mesh_io *io, struct mesh_io_caps *caps)
+{
+ struct mesh_io_private *pvt = io->pvt;
+
+ if (!pvt || !caps)
+ return false;
+
+ caps->max_num_filters = 255;
+ caps->window_accuracy = 50;
+
+ return true;
+}
+
+static bool simple_match(const void *a, const void *b)
+{
+ return a == b;
+}
+
+static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt *tx,
+ uint16_t interval)
+{
+ send(pvt->fd, tx->pkt, tx->len, MSG_DONTWAIT);
+
+ if (tx->delete) {
+ l_queue_remove_if(pvt->tx_pkts, simple_match, tx);
+ l_free(tx);
+ }
+}
+
+static void tx_to(struct l_timeout *timeout, void *user_data)
+{
+ struct mesh_io_private *pvt = user_data;
+ struct tx_pkt *tx;
+ uint16_t ms;
+ uint8_t count;
+
+ if (!pvt)
+ return;
+
+ tx = l_queue_pop_head(pvt->tx_pkts);
+ if (!tx) {
+ l_timeout_remove(timeout);
+ pvt->tx_timeout = NULL;
+ return;
+ }
+
+ if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) {
+ ms = tx->info.u.gen.interval;
+ count = tx->info.u.gen.cnt;
+ if (count != MESH_IO_TX_COUNT_UNLIMITED)
+ tx->info.u.gen.cnt--;
+ } else {
+ ms = 25;
+ count = 1;
+ }
+
+ tx->delete = !!(count == 1);
+
+ send_pkt(pvt, tx, ms);
+
+ if (count == 1) {
+ /* Recalculate wakeup if we are responding to POLL */
+ tx = l_queue_peek_head(pvt->tx_pkts);
+
+ if (tx && tx->info.type == MESH_IO_TIMING_TYPE_POLL_RSP) {
+ ms = instant_remaining_ms(tx->info.u.poll_rsp.instant +
+ tx->info.u.poll_rsp.delay);
+ }
+ } else
+ l_queue_push_tail(pvt->tx_pkts, tx);
+
+ if (timeout) {
+ pvt->tx_timeout = timeout;
+ l_timeout_modify_ms(timeout, ms);
+ } else
+ pvt->tx_timeout = l_timeout_create_ms(ms, tx_to, pvt, NULL);
+}
+
+static void tx_worker(void *user_data)
+{
+ struct mesh_io_private *pvt = user_data;
+ struct tx_pkt *tx;
+ uint32_t delay;
+
+ tx = l_queue_peek_head(pvt->tx_pkts);
+ if (!tx)
+ return;
+
+ switch (tx->info.type) {
+ case MESH_IO_TIMING_TYPE_GENERAL:
+ if (tx->info.u.gen.min_delay == tx->info.u.gen.max_delay)
+ delay = tx->info.u.gen.min_delay;
+ else {
+ l_getrandom(&delay, sizeof(delay));
+ delay %= tx->info.u.gen.max_delay -
+ tx->info.u.gen.min_delay;
+ delay += tx->info.u.gen.min_delay;
+ }
+ break;
+
+ case MESH_IO_TIMING_TYPE_POLL:
+ if (tx->info.u.poll.min_delay == tx->info.u.poll.max_delay)
+ delay = tx->info.u.poll.min_delay;
+ else {
+ l_getrandom(&delay, sizeof(delay));
+ delay %= tx->info.u.poll.max_delay -
+ tx->info.u.poll.min_delay;
+ delay += tx->info.u.poll.min_delay;
+ }
+ break;
+
+ case MESH_IO_TIMING_TYPE_POLL_RSP:
+ /* Delay until Instant + Delay */
+ delay = instant_remaining_ms(tx->info.u.poll_rsp.instant +
+ tx->info.u.poll_rsp.delay);
+ if (delay > 255)
+ delay = 0;
+ break;
+
+ default:
+ return;
+ }
+
+ if (!delay)
+ tx_to(pvt->tx_timeout, pvt);
+ else if (pvt->tx_timeout)
+ l_timeout_modify_ms(pvt->tx_timeout, delay);
+ else
+ pvt->tx_timeout = l_timeout_create_ms(delay, tx_to, pvt, NULL);
+}
+
+static bool send_tx(struct mesh_io *io, struct mesh_io_send_info *info,
+ const uint8_t *data, uint16_t len)
+{
+ struct mesh_io_private *pvt = io->pvt;
+ struct tx_pkt *tx;
+ bool sending = false;
+
+ if (!info || !data || !len || len > sizeof(tx->pkt))
+ return false;
+
+ tx = l_new(struct tx_pkt, 1);
+
+ memcpy(&tx->info, info, sizeof(tx->info));
+ memcpy(&tx->pkt, data, len);
+ tx->len = len;
+
+ if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP)
+ l_queue_push_head(pvt->tx_pkts, tx);
+ else {
+ sending = !l_queue_isempty(pvt->tx_pkts);
+
+ l_queue_push_tail(pvt->tx_pkts, tx);
+ }
+
+ if (!sending) {
+ l_timeout_remove(pvt->tx_timeout);
+ pvt->tx_timeout = NULL;
+ l_idle_oneshot(tx_worker, pvt, NULL);
+ }
+
+ return true;
+}
+
+static bool tx_cancel(struct mesh_io *io, const uint8_t *data, uint8_t len)
+{
+ struct mesh_io_private *pvt = io->pvt;
+ struct tx_pkt *tx;
+
+ if (!data)
+ return false;
+
+ if (len == 1) {
+ do {
+ tx = l_queue_remove_if(pvt->tx_pkts, find_by_ad_type,
+ L_UINT_TO_PTR(data[0]));
+ l_free(tx);
+
+ } while (tx);
+ } else {
+ struct tx_pattern pattern = {
+ .data = data,
+ .len = len
+ };
+
+ do {
+ tx = l_queue_remove_if(pvt->tx_pkts, find_by_pattern,
+ &pattern);
+ l_free(tx);
+
+ } while (tx);
+ }
+
+ if (l_queue_isempty(pvt->tx_pkts)) {
+ l_timeout_remove(pvt->tx_timeout);
+ pvt->tx_timeout = NULL;
+ }
+
+ return true;
+}
+
+static bool find_by_filter(const void *a, const void *b)
+{
+ const struct pvt_rx_reg *rx_reg = a;
+ const uint8_t *filter = b;
+
+ return !memcmp(rx_reg->filter, filter, rx_reg->len);
+}
+
+static bool recv_register(struct mesh_io *io, const uint8_t *filter,
+ uint8_t len, mesh_io_recv_func_t cb, void *user_data)
+{
+ struct mesh_io_private *pvt = io->pvt;
+ struct pvt_rx_reg *rx_reg;
+
+ if (!cb || !filter || !len)
+ return false;
+
+ rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter, filter);
+
+ l_free(rx_reg);
+ rx_reg = l_malloc(sizeof(*rx_reg) + len);
+
+ memcpy(rx_reg->filter, filter, len);
+ rx_reg->len = len;
+ rx_reg->cb = cb;
+ rx_reg->user_data = user_data;
+
+ l_queue_push_head(pvt->rx_regs, rx_reg);
+
+ return true;
+}
+
+static bool recv_deregister(struct mesh_io *io, const uint8_t *filter,
+ uint8_t len)
+{
+ return true;
+}
+
+const struct mesh_io_api mesh_io_unit = {
+ .init = unit_init,
+ .destroy = unit_destroy,
+ .caps = unit_caps,
+ .send = send_tx,
+ .reg = recv_register,
+ .dereg = recv_deregister,
+ .cancel = tx_cancel,
+};
diff --git a/mesh/mesh-io-unit.h b/mesh/mesh-io-unit.h
new file mode 100644
index 000000000..846eea7bc
--- /dev/null
+++ b/mesh/mesh-io-unit.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2021 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+extern const struct mesh_io_api mesh_io_unit;
diff --git a/mesh/mesh-io.c b/mesh/mesh-io.c
index 62fc5d12e..96891313a 100644
--- a/mesh/mesh-io.c
+++ b/mesh/mesh-io.c
@@ -22,10 +22,12 @@

/* List of Mesh-IO Type headers */
#include "mesh/mesh-io-generic.h"
+#include "mesh/mesh-io-unit.h"

/* List of Supported Mesh-IO Types */
static const struct mesh_io_table table[] = {
- {MESH_IO_TYPE_GENERIC, &mesh_io_generic}
+ {MESH_IO_TYPE_GENERIC, &mesh_io_generic},
+ {MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit},
};

static struct l_queue *io_list;
@@ -64,12 +66,9 @@ struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts,

io = l_new(struct mesh_io, 1);

- if (!io)
- return NULL;
-
io->type = type;
-
io->api = api;
+
if (!api->init(io, opts, cb, user_data))
goto fail;

diff --git a/mesh/mesh-io.h b/mesh/mesh-io.h
index b11c6c6e1..80ef3fa3e 100644
--- a/mesh/mesh-io.h
+++ b/mesh/mesh-io.h
@@ -14,7 +14,8 @@ struct mesh_io;

enum mesh_io_type {
MESH_IO_TYPE_NONE = 0,
- MESH_IO_TYPE_GENERIC
+ MESH_IO_TYPE_GENERIC,
+ MESH_IO_TYPE_UNIT_TEST
};

enum mesh_io_timing_type {
--
2.26.2

2021-02-06 05:55:55

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ v3 3/3] tools/mesh-cfgtest: Non-iteractive test for mesh daemon

This adds a non-interactive test to excercise different datapaths in
bluetooth-meshd. The test cases utilize D-Bus based mesh APIs, e.g.,
to create a new network, import a node, import NetKey, import a remote
node.

Also, the test incorporates a number of configuration messages and
expected responses to verify the daemon's internal implementation of
configuration server.
---
Makefile.tools | 6 +
mesh/main.c | 10 +-
tools/mesh-cfgtest.c | 1319 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1334 insertions(+), 1 deletion(-)
create mode 100644 tools/mesh-cfgtest.c

diff --git a/Makefile.tools b/Makefile.tools
index d5fdf2d89..3217ca8a6 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -335,6 +335,12 @@ tools_mesh_cfgclient_SOURCES = tools/mesh-cfgclient.c \

tools_mesh_cfgclient_LDADD = lib/libbluetooth-internal.la src/libshared-ell.la \
$(ell_ldadd) -ljson-c -lreadline
+
+bin_PROGRAMS += tools/mesh-cfgtest
+
+tools_mesh_cfgtest_SOURCES = tools/mesh-cfgtest.c
+tools_mesh_cfgtest_LDADD = lib/libbluetooth-internal.la src/libshared-ell.la \
+ $(ell_ldadd)
endif

EXTRA_DIST += tools/mesh-gatt/local_node.json tools/mesh-gatt/prov_db.json
diff --git a/mesh/main.c b/mesh/main.c
index 1b466598b..a13866d7e 100644
--- a/mesh/main.c
+++ b/mesh/main.c
@@ -17,7 +17,9 @@
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
+#include <signal.h>

+#include <sys/prctl.h>
#include <sys/stat.h>
#include <ell/ell.h>

@@ -262,7 +264,13 @@ int main(int argc, char *argv[])
if (!detached)
umask(0077);

- dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS);
+ if (io_type != MESH_IO_TYPE_UNIT_TEST)
+ dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS);
+ else {
+ dbus = l_dbus_new_default(L_DBUS_SESSION_BUS);
+ prctl(PR_SET_PDEATHSIG, SIGSEGV);
+ }
+
if (!dbus) {
l_error("unable to connect to D-Bus");
status = EXIT_FAILURE;
diff --git a/tools/mesh-cfgtest.c b/tools/mesh-cfgtest.c
new file mode 100644
index 000000000..12e2e2b89
--- /dev/null
+++ b/tools/mesh-cfgtest.c
@@ -0,0 +1,1319 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2021 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <ctype.h>
+#include <dbus/dbus.h>
+#include <dirent.h>
+#include <errno.h>
+#include <ftw.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <ell/ell.h>
+
+#include "src/shared/util.h"
+#include "src/shared/tester.h"
+
+#include "mesh/mesh-defs.h"
+#include "mesh/mesh.h"
+
+#define MAX_CRPL_SIZE 0x7fff
+#define CFG_SRV_MODEL 0x0000
+#define CFG_CLI_MODEL 0x0001
+#define DEFAULT_IV_INDEX 0x0000
+
+#define IS_CONFIG_MODEL(x) ((x) == CFG_SRV_MODEL || (x) == CFG_CLI_MODEL)
+
+struct meshcfg_el {
+ const char *path;
+ uint8_t index;
+ uint16_t mods[2];
+ uint32_t vmods[2];
+};
+
+struct meshcfg_app {
+ const char *path;
+ const char *agent_path;
+ struct meshcfg_node *node;
+ uint8_t num_ele;
+ struct meshcfg_el ele[2];
+ uint16_t cid;
+ uint16_t pid;
+ uint16_t vid;
+ uint16_t crpl;
+ uint8_t uuid[16];
+};
+
+struct meshcfg_node {
+ const char *path;
+ struct l_dbus_proxy *proxy;
+ struct l_dbus_proxy *mgmt_proxy;
+ union {
+ uint64_t u64;
+ uint8_t u8[8];
+ } token;
+};
+
+struct msg_data {
+ uint16_t len;
+ uint8_t data[MAX_MSG_LEN];
+};
+
+struct key_data {
+ uint16_t idx;
+ bool update;
+};
+
+typedef void (*startup_func_t)(const void *data);
+struct startup_entry {
+ startup_func_t func;
+ void *data;
+};
+
+struct test_data {
+ const char *ele_path;
+ uint16_t dst;
+ uint16_t subnet;
+ void *req;
+};
+
+static struct l_queue *startup_chain;
+
+static struct l_dbus *dbus;
+struct l_dbus_client *client;
+
+static struct l_queue *node_proxies;
+static struct l_dbus_proxy *net_proxy;
+static char *test_dir;
+
+static uint32_t iv_index = DEFAULT_IV_INDEX;
+
+static enum {
+ NONE,
+ IN_PROGRESS,
+ DONE,
+ FAILED,
+} init_state = NONE;
+
+static const char *dbus_err_args = "org.freedesktop.DBus.Error.InvalidArgs";
+static const char *const cli_app_path = "/mesh/cfgtest/client";
+static const char *const cli_agent_path = "/mesh/cfgtest/client/agent";
+static const char *const cli_ele_path_00 = "/mesh/cfgtest/client/ele0";
+static const char *const srv_app_path = "/mesh/cfgtest/server";
+static const char *const srv_agent_path = "/mesh/cfgtest/server/agent";
+static const char *const srv_ele_path_00 = "/mesh/cfgtest/server/ele0";
+static const char *const srv_ele_path_01 = "/mesh/cfgtest/server/ele1";
+
+static struct meshcfg_app client_app = {
+ .path = cli_app_path,
+ .agent_path = cli_agent_path,
+ .cid = 0x05f1,
+ .pid = 0x0002,
+ .vid = 0x0001,
+ .crpl = MAX_CRPL_SIZE,
+ .num_ele = 1,
+ .ele = {
+ {
+ .path = cli_ele_path_00,
+ .index = PRIMARY_ELE_IDX,
+ .mods = {CFG_SRV_MODEL, CFG_CLI_MODEL},
+ .vmods = {0xffffffff, 0xffffffff}
+ }
+ }
+};
+
+static struct meshcfg_app server_app = {
+ .path = srv_app_path,
+ .agent_path = srv_agent_path,
+ .cid = 0x05f1,
+ .pid = 0x0002,
+ .vid = 0x0001,
+ .crpl = MAX_CRPL_SIZE,
+ .num_ele = 2,
+ .ele = {
+ {
+ .path = srv_ele_path_00,
+ .index = PRIMARY_ELE_IDX,
+ .mods = {CFG_SRV_MODEL, 0xffff},
+ .vmods = {0xffffffff, 0xffffffff}
+ },
+ {
+ .path = srv_ele_path_01,
+ .index = PRIMARY_ELE_IDX + 1,
+ .mods = {0x1000, 0xffff},
+ .vmods = {0x5F10001, 0xffffffff}
+ }
+ }
+};
+
+static uint8_t import_devkey[16];
+static uint8_t import_netkey[16];
+static const uint16_t import_netkey_idx = 0x001;
+static const uint16_t import_node_unicast = 0xbcd;
+
+static void create_network(const void *data);
+static struct startup_entry init_create_client = {
+ .func = create_network,
+ .data = NULL,
+};
+
+static void import_node(const void *data);
+static struct startup_entry init_import_server = {
+ .func = import_node,
+ .data = NULL,
+};
+
+static void attach_node(const void *data);
+static struct startup_entry init_attach_client = {
+ .func = attach_node,
+ .data = NULL,
+};
+
+static void import_subnet(const void *data);
+static struct startup_entry init_import_subnet = {
+ .func = import_subnet,
+ .data = NULL,
+};
+
+static void import_remote(const void *data);
+static struct startup_entry init_import_remote = {
+ .func = import_remote,
+ .data = NULL,
+};
+
+static struct msg_data init_add_netkey_rsp = {
+ .len = 5,
+ .data = {0x80, 0x44, 0x00, 0x01, 0x00}
+};
+
+static struct key_data init_add_netkey_req = {
+ .idx = import_netkey_idx,
+ .update = false
+};
+
+static struct test_data init_add_netkey_data = {
+ .ele_path = cli_ele_path_00,
+ .dst = 0x0001,
+ .subnet = 0x0000,
+ .req = &init_add_netkey_req
+};
+
+static void add_netkey(const void *data);
+static struct startup_entry init_add_netkey = {
+ .func = add_netkey,
+ .data = &init_add_netkey_data
+};
+
+static struct msg_data init_add_appkey_rsp = {
+ .len = 6,
+ .data = {0x80, 0x03, 0x00, 0x01, 0x10, 0x00}
+};
+
+static struct key_data init_add_appkey_req = {
+ .idx = 0x001,
+ .update = false
+};
+
+static struct test_data init_add_appkey_data = {
+ .ele_path = cli_ele_path_00,
+ .dst = import_node_unicast,
+ .subnet = import_netkey_idx,
+ .req = &init_add_appkey_req,
+};
+
+static void create_appkey(const void *data);
+static struct startup_entry init_create_appkey = {
+ .func = create_appkey,
+ .data = &init_add_appkey_data
+};
+
+static void add_appkey(const void *data);
+static struct startup_entry init_add_appkey = {
+ .func = add_appkey,
+ .data = &init_add_appkey_data
+};
+
+static struct msg_data test_add_appkey_rsp = {
+ .len = 6,
+ .data = {0x80, 0x03, 0x00, 0x01, 0x20, 0x00}
+};
+
+static struct key_data test_add_appkey_req = {
+ .idx = 0x002,
+ .update = false
+};
+
+static struct test_data test_add_appkey = {
+ .ele_path = cli_ele_path_00,
+ .dst = import_node_unicast,
+ .subnet = import_netkey_idx,
+ .req = &test_add_appkey_req,
+};
+
+static struct test_data common_route = {
+ .ele_path = cli_ele_path_00,
+ .dst = import_node_unicast,
+ .subnet = import_netkey_idx,
+};
+
+static struct msg_data test_set_ttl_rsp = {
+ .len = 3,
+ .data = {0x80, 0x0E, 0x7}
+};
+
+static struct msg_data test_set_ttl_req = {
+ .len = 3,
+ .data = {0x80, 0x0D, 0x7}
+};
+
+static struct msg_data test_bind_rsp = {
+ .len = 9,
+ .data = {0x80, 0x3E, 0x00, 0xCE, 0x0B, 0x01, 0x00, 0x00, 0x10},
+};
+
+static struct msg_data test_bind_req = {
+ .len = 8,
+ .data = {0x80, 0x3D, 0xCE, 0x0B, 0x01, 0x00, 0x00, 0x10}
+};
+
+
+static struct msg_data test_bind_inv_mod_rsp = {
+ .len = 9,
+ .data = {0x80, 0x3E, 0x02, 0xCE, 0x0B, 0x01, 0x00, 0x00, 0x11},
+};
+
+static struct msg_data test_bind_inv_mod_req = {
+ .len = 8,
+ .data = {0x80, 0x3D, 0xCE, 0x0B, 0x01, 0x00, 0x00, 0x11}
+};
+
+static void append_byte_array(struct l_dbus_message_builder *builder,
+ unsigned char *data, unsigned int len)
+{
+ unsigned int i;
+
+ l_dbus_message_builder_enter_array(builder, "y");
+
+ for (i = 0; i < len; i++)
+ l_dbus_message_builder_append_basic(builder, 'y', &(data[i]));
+
+ l_dbus_message_builder_leave_array(builder);
+}
+
+static void append_dict_entry_basic(struct l_dbus_message_builder *builder,
+ const char *key, const char *signature,
+ const void *data)
+{
+ if (!builder)
+ return;
+
+ l_dbus_message_builder_enter_dict(builder, "sv");
+ l_dbus_message_builder_append_basic(builder, 's', key);
+ l_dbus_message_builder_enter_variant(builder, signature);
+ l_dbus_message_builder_append_basic(builder, signature[0], data);
+ l_dbus_message_builder_leave_variant(builder);
+ l_dbus_message_builder_leave_dict(builder);
+}
+
+static void init_continue(void *data)
+{
+ struct startup_entry *next_step;
+
+ if (l_queue_isempty(startup_chain) &&
+ init_state == IN_PROGRESS) {
+ init_state = DONE;
+ tester_pre_setup_complete();
+ return;
+ }
+
+ next_step = l_queue_pop_head(startup_chain);
+
+ next_step->func(next_step->data);
+}
+
+static void test_success(void *user_data)
+{
+ tester_test_passed();
+}
+
+static void test_fail(void *user_data)
+{
+ tester_test_failed();
+}
+
+static void try_set_node_proxy(void *a, void *b)
+{
+ struct l_dbus_proxy *proxy = a;
+ struct meshcfg_node *node = b;
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ if (strcmp(node->path, path))
+ return;
+
+ if (!strcmp(interface, MESH_MANAGEMENT_INTERFACE))
+ node->mgmt_proxy = proxy;
+ else if (!strcmp(interface, MESH_NODE_INTERFACE))
+ node->proxy = proxy;
+}
+
+static void generic_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ l_error("D-Bus call failed: %s", name);
+ l_idle_oneshot(test_fail, NULL, NULL);
+ }
+}
+
+static void send_cfg_msg_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct msg_data *req = user_data;
+ struct l_dbus_message_builder *builder;
+ bool remote = true;
+
+ builder = l_dbus_message_builder_new(msg);
+
+ l_dbus_message_builder_append_basic(builder, 'o',
+ common_route.ele_path);
+ l_dbus_message_builder_append_basic(builder, 'q', &common_route.dst);
+ l_dbus_message_builder_append_basic(builder, 'b', &remote);
+
+ l_dbus_message_builder_append_basic(builder, 'q', &common_route.subnet);
+
+ /* Options */
+ l_dbus_message_builder_enter_array(builder, "{sv}");
+ l_dbus_message_builder_enter_dict(builder, "sv");
+ l_dbus_message_builder_leave_dict(builder);
+ l_dbus_message_builder_leave_array(builder);
+
+ /* Data */
+ append_byte_array(builder, req->data, req->len);
+ l_dbus_message_builder_finalize(builder);
+ l_dbus_message_builder_destroy(builder);
+}
+
+static void send_cfg_msg(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ l_dbus_proxy_method_call(node->proxy, "DevKeySend",
+ send_cfg_msg_setup, generic_reply,
+ (void *) data, NULL);
+}
+
+static void add_key_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct test_data *tst = user_data;
+ struct key_data *req = tst->req;
+ struct l_dbus_message_builder *builder;
+
+ builder = l_dbus_message_builder_new(msg);
+
+ l_dbus_message_builder_append_basic(builder, 'o', tst->ele_path);
+ l_dbus_message_builder_append_basic(builder, 'q', &tst->dst);
+ l_dbus_message_builder_append_basic(builder, 'q', &req->idx);
+ l_dbus_message_builder_append_basic(builder, 'q', &tst->subnet);
+ l_dbus_message_builder_append_basic(builder, 'b', &req->update);
+ l_dbus_message_builder_finalize(builder);
+ l_dbus_message_builder_destroy(builder);
+}
+
+static void add_appkey(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ l_dbus_proxy_method_call(node->proxy, "AddAppKey", add_key_setup,
+ generic_reply, (void *) data, NULL);
+}
+
+static void add_netkey(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ l_dbus_proxy_method_call(node->proxy, "AddNetKey", add_key_setup,
+ generic_reply, (void *) data, NULL);
+}
+
+static void create_appkey_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ tester_print("Add key failed: %s", name);
+ if (init_state == IN_PROGRESS)
+ tester_pre_setup_failed();
+ else
+ tester_setup_failed();
+ } else {
+ if (init_state == IN_PROGRESS)
+ l_idle_oneshot(init_continue, NULL, NULL);
+ else
+ tester_setup_complete();
+ }
+}
+
+static void create_appkey_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct test_data *tst = user_data;
+ struct key_data *req = tst->req;
+
+ l_dbus_message_set_arguments(msg, "qq", tst->subnet, req->idx);
+}
+
+static void create_appkey(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ if (!node || !node->proxy || !node->mgmt_proxy) {
+ tester_print("Node is not attached\n");
+ tester_setup_failed();
+ return;
+ }
+
+ l_dbus_proxy_method_call(node->mgmt_proxy, "CreateAppKey",
+ create_appkey_setup, create_appkey_reply,
+ (void *) data, NULL);
+}
+
+static void import_remote_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ tester_print("Import remote call failed: %s", name);
+
+ init_state = FAILED;
+ tester_pre_setup_failed();
+ return;
+ }
+
+ if (init_state == IN_PROGRESS)
+ l_idle_oneshot(init_continue, NULL, NULL);
+ else
+ tester_test_abort();
+}
+
+static void import_remote_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct l_dbus_message_builder *builder;
+
+ builder = l_dbus_message_builder_new(msg);
+
+ l_dbus_message_builder_append_basic(builder, 'q', &import_node_unicast);
+ l_dbus_message_builder_append_basic(builder, 'y', &server_app.num_ele);
+ append_byte_array(builder, import_devkey, 16);
+ l_dbus_message_builder_finalize(builder);
+ l_dbus_message_builder_destroy(builder);
+}
+
+static void import_remote(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ if (!node || !node->mgmt_proxy) {
+ tester_test_abort();
+ return;
+ }
+
+ l_dbus_proxy_method_call(node->mgmt_proxy, "ImportRemoteNode",
+ import_remote_setup, import_remote_reply,
+ NULL, NULL);
+}
+
+static void import_subnet_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ tester_print("Import subnet failed: %s", name);
+
+ if (init_state == IN_PROGRESS) {
+ init_state = FAILED;
+ tester_pre_setup_failed();
+ }
+
+ return;
+ }
+
+ if (init_state == IN_PROGRESS)
+ l_idle_oneshot(init_continue, NULL, NULL);
+}
+
+static void import_subnet_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct l_dbus_message_builder *builder;
+
+ builder = l_dbus_message_builder_new(msg);
+
+ l_dbus_message_builder_append_basic(builder, 'q', &import_netkey_idx);
+ append_byte_array(builder, import_netkey, 16);
+ l_dbus_message_builder_finalize(builder);
+ l_dbus_message_builder_destroy(builder);
+}
+
+static void import_subnet(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ if (!node || !node->mgmt_proxy) {
+ tester_test_abort();
+ return;
+ }
+
+ l_dbus_proxy_method_call(node->mgmt_proxy, "ImportSubnet",
+ import_subnet_setup, import_subnet_reply,
+ NULL, NULL);
+}
+
+static void attach_node_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+ struct meshcfg_node *node = app->node;
+ struct l_dbus_message_iter iter_cfg;
+ uint32_t ivi;
+
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ l_error("Failed to attach node: %s", name);
+ goto fail;
+
+ }
+
+ if (!l_dbus_message_get_arguments(msg, "oa(ya(qa{sv}))",
+ &node->path, &iter_cfg))
+ goto fail;
+
+ tester_print("Attached with path %s\n", node->path);
+
+ /* Populate node's proxies */
+ l_queue_foreach(node_proxies, try_set_node_proxy, node);
+
+ /* Remove from orphaned proxies list */
+ if (node->proxy)
+ l_queue_remove(node_proxies, node->proxy);
+
+ if (node->mgmt_proxy)
+ l_queue_remove(node_proxies, node->mgmt_proxy);
+
+ if (l_dbus_proxy_get_property(node->proxy, "IvIndex", "u", &ivi) &&
+ ivi != iv_index)
+ iv_index = ivi;
+
+ if (init_state == IN_PROGRESS)
+ l_idle_oneshot(init_continue, NULL, NULL);
+
+ return;
+
+fail:
+ l_free(node);
+ app->node = NULL;
+
+ if (init_state == IN_PROGRESS) {
+ init_state = FAILED;
+ tester_pre_setup_failed();
+ }
+}
+
+static void attach_node_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+
+ l_dbus_message_set_arguments(msg, "ot", app->path,
+ l_get_be64(app->node->token.u8));
+}
+
+static void attach_node(const void *data)
+{
+ struct meshcfg_node *node = client_app.node;
+
+ if (!node) {
+ tester_test_abort();
+ return;
+ }
+
+ l_dbus_proxy_method_call(net_proxy, "Attach",
+ attach_node_setup, attach_node_reply,
+ &client_app, NULL);
+}
+
+static struct l_dbus_message *join_complete(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+ uint64_t tmp;
+
+ if (!l_dbus_message_get_arguments(message, "t", &tmp)) {
+ if (init_state != DONE) {
+ init_state = FAILED;
+ tester_setup_failed();
+ } else
+ l_idle_oneshot(test_fail, NULL, NULL);
+
+ return l_dbus_message_new_error(message, dbus_err_args, NULL);
+ }
+
+ app->node = l_new(struct meshcfg_node, 1);
+ app->node->token.u64 = l_get_be64(&tmp);
+
+ if (init_state == IN_PROGRESS)
+ l_idle_oneshot(init_continue, NULL, NULL);
+
+ return l_dbus_message_new_method_return(message);
+}
+
+static void create_net_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ l_error("Failed to create network: %s", name);
+ tester_setup_failed();
+ }
+}
+
+static void create_net_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct l_dbus_message_builder *builder;
+
+ /* Generate random UUID */
+ l_getrandom(client_app.uuid, sizeof(client_app.uuid));
+
+ builder = l_dbus_message_builder_new(msg);
+
+ l_dbus_message_builder_append_basic(builder, 'o', client_app.path);
+ append_byte_array(builder, client_app.uuid, 16);
+ l_dbus_message_builder_finalize(builder);
+ l_dbus_message_builder_destroy(builder);
+}
+
+static void create_network(const void *data)
+{
+ l_dbus_proxy_method_call(net_proxy, "CreateNetwork", create_net_setup,
+ create_net_reply, &client_app,
+ NULL);
+}
+
+static void import_node_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *msg, void *user_data)
+{
+ if (l_dbus_message_is_error(msg)) {
+ const char *name;
+
+ l_dbus_message_get_error(msg, &name, NULL);
+ l_error("Failed to import local node: %s", name);
+ l_idle_oneshot(test_fail, NULL, NULL);
+ return;
+ }
+}
+
+static void import_node_setup(struct l_dbus_message *msg, void *user_data)
+{
+ struct l_dbus_message_builder *builder;
+ bool iv_update = false;
+ bool key_refresh = false;
+
+ /* Generate random UUID, DevKey, NetKey */
+ l_getrandom(server_app.uuid, sizeof(server_app.uuid));
+ l_getrandom(import_netkey, sizeof(import_netkey));
+ l_getrandom(import_devkey, sizeof(import_devkey));
+
+ builder = l_dbus_message_builder_new(msg);
+
+ l_dbus_message_builder_append_basic(builder, 'o', server_app.path);
+ append_byte_array(builder, server_app.uuid, 16);
+ append_byte_array(builder, import_devkey, 16);
+ append_byte_array(builder, import_netkey, 16);
+ l_dbus_message_builder_append_basic(builder, 'q', &import_netkey_idx);
+ l_dbus_message_builder_enter_array(builder, "{sv}");
+ append_dict_entry_basic(builder, "IvUpdate", "b", &iv_update);
+ append_dict_entry_basic(builder, "KeyRefresh", "b", &key_refresh);
+ l_dbus_message_builder_leave_array(builder);
+ l_dbus_message_builder_append_basic(builder, 'u', &iv_index);
+ l_dbus_message_builder_append_basic(builder, 'q', &import_node_unicast);
+ l_dbus_message_builder_finalize(builder);
+ l_dbus_message_builder_destroy(builder);
+}
+
+static void import_node(const void *data)
+{
+ l_dbus_proxy_method_call(net_proxy, "Import", import_node_setup,
+ import_node_reply, &server_app,
+ NULL);
+}
+
+static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
+{
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ tester_print("Proxy added: %s (%s)\n", interface, path);
+
+ if (!strcmp(interface, MESH_NETWORK_INTERFACE)) {
+ net_proxy = proxy;
+ return;
+ }
+
+ if (!strcmp(interface, MESH_MANAGEMENT_INTERFACE)) {
+ if (client_app.node && client_app.node->path) {
+ if (!strcmp(client_app.node->path, path)) {
+ client_app.node->mgmt_proxy = proxy;
+ return;
+ }
+ }
+
+ if (server_app.node && server_app.node->path) {
+ if (!strcmp(server_app.node->path, path)) {
+ server_app.node->mgmt_proxy = proxy;
+ return;
+ }
+ }
+
+ l_queue_push_tail(node_proxies, proxy);
+ return;
+ }
+
+ if (!strcmp(interface, MESH_NODE_INTERFACE)) {
+
+ if (client_app.node && client_app.node->path) {
+ if (!strcmp(client_app.node->path, path)) {
+ client_app.node->proxy = proxy;
+ return;
+ }
+ }
+
+ if (server_app.node && server_app.node->path) {
+ if (!strcmp(server_app.node->path, path)) {
+ server_app.node->proxy = proxy;
+ return;
+ }
+ }
+
+ l_queue_push_tail(node_proxies, proxy);
+ }
+}
+
+static void proxy_removed(struct l_dbus_proxy *proxy, void *user_data)
+{
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ tester_print("Proxy removed: %s (%s)\n", interface, path);
+
+ if (!strcmp(interface, MESH_NETWORK_INTERFACE)) {
+ tester_print("Mesh removed, terminating.\n");
+ l_main_quit();
+ return;
+ }
+
+ l_queue_remove(node_proxies, proxy);
+}
+
+static void build_model(struct l_dbus_message_builder *builder, uint16_t mod_id,
+ bool pub_enable, bool sub_enable)
+{
+ l_dbus_message_builder_enter_struct(builder, "qa{sv}");
+ l_dbus_message_builder_append_basic(builder, 'q', &mod_id);
+ l_dbus_message_builder_enter_array(builder, "{sv}");
+ append_dict_entry_basic(builder, "Subscribe", "b", &sub_enable);
+ append_dict_entry_basic(builder, "Publish", "b", &pub_enable);
+ l_dbus_message_builder_leave_array(builder);
+ l_dbus_message_builder_leave_struct(builder);
+}
+
+static bool mod_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_el *ele = user_data;
+ uint32_t i;
+
+ l_dbus_message_builder_enter_array(builder, "(qa{sv})");
+
+ for (i = 0; i < L_ARRAY_SIZE(ele->mods); i++) {
+ bool is_cfg = IS_CONFIG_MODEL(ele->mods[i]);
+
+ if (ele->mods[i] == 0xffff)
+ continue;
+
+ build_model(builder, ele->mods[i], !is_cfg, !is_cfg);
+ }
+
+ l_dbus_message_builder_leave_array(builder);
+
+ return true;
+}
+
+static void build_vmodel(struct l_dbus_message_builder *builder, uint16_t vid,
+ uint16_t mod, bool pub_enable, bool sub_enable)
+{
+ l_dbus_message_builder_enter_struct(builder, "qqa{sv}");
+ l_dbus_message_builder_append_basic(builder, 'q', &vid);
+ l_dbus_message_builder_append_basic(builder, 'q', &mod);
+ l_dbus_message_builder_enter_array(builder, "{sv}");
+ append_dict_entry_basic(builder, "Subscribe", "b", &sub_enable);
+ append_dict_entry_basic(builder, "Publish", "b", &pub_enable);
+ l_dbus_message_builder_leave_array(builder);
+ l_dbus_message_builder_leave_struct(builder);
+}
+
+static bool vmod_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_el *ele = user_data;
+ uint32_t i;
+
+ l_dbus_message_builder_enter_array(builder, "(qqa{sv})");
+
+ for (i = 0; i < L_ARRAY_SIZE(ele->vmods); i++) {
+ if (ele->vmods[i] == 0xffffffff)
+ continue;
+
+ build_vmodel(builder, ele->vmods[i] >> 16,
+ ele->vmods[i] & 0xffff, true, true);
+ }
+
+ l_dbus_message_builder_leave_array(builder);
+
+ return true;
+}
+
+static bool ele_idx_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_el *ele = user_data;
+
+ l_dbus_message_builder_append_basic(builder, 'y', &ele->index);
+
+ return true;
+}
+
+static struct l_dbus_message *dev_msg_recv_call(struct l_dbus *dbus,
+ struct l_dbus_message *msg,
+ void *user_data)
+{
+ struct msg_data *rsp;
+ struct l_dbus_message_iter iter;
+ uint16_t src, idx;
+ uint8_t *data;
+ uint32_t n;
+ bool rmt;
+
+ if (!l_dbus_message_get_arguments(msg, "qbqay", &src, &rmt, &idx,
+ &iter)) {
+ l_error("Cannot parse received message");
+ return l_dbus_message_new_error(msg, dbus_err_args, NULL);
+ }
+
+ if (!l_dbus_message_iter_get_fixed_array(&iter, &data, &n)) {
+ l_error("Cannot parse received message: data");
+ return l_dbus_message_new_error(msg, dbus_err_args, NULL);
+ }
+
+ printf("Received dev key message (len %u):", n);
+ {
+ uint32_t i;
+
+ for (i = 0; i < n; i++)
+ printf("%x ", data[i]);
+ printf("\n");
+ }
+
+ if (init_state == IN_PROGRESS) {
+ if (n == init_add_netkey_rsp.len &&
+ !memcmp(data, init_add_netkey_rsp.data, n))
+ l_idle_oneshot(init_continue, NULL, NULL);
+ else if (n == init_add_appkey_rsp.len &&
+ !memcmp(data, init_add_appkey_rsp.data, n))
+ l_idle_oneshot(init_continue, NULL, NULL);
+ else {
+ init_state = FAILED;
+ tester_pre_setup_failed();
+ }
+ } else {
+ rsp = tester_get_data();
+
+ if (rsp && rsp->len == n && !memcmp(data, rsp->data, n))
+ l_idle_oneshot(test_success, NULL, NULL);
+ else
+ l_idle_oneshot(test_fail, NULL, NULL);
+ }
+
+ return l_dbus_message_new_method_return(msg);
+}
+
+static void setup_ele_iface(struct l_dbus_interface *iface)
+{
+ /* Properties */
+ l_dbus_interface_property(iface, "Index", 0, "y", ele_idx_getter,
+ NULL);
+ l_dbus_interface_property(iface, "VendorModels", 0, "a(qqa{sv})",
+ vmod_getter, NULL);
+ l_dbus_interface_property(iface, "Models", 0, "a(qa{sv})", mod_getter,
+ NULL);
+
+ /* Methods */
+ l_dbus_interface_method(iface, "DevKeyMessageReceived", 0,
+ dev_msg_recv_call, "", "qbqay", "source",
+ "remote", "net_index", "data");
+
+ /* TODO: Other methods? */
+}
+
+static bool cid_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+
+ l_dbus_message_builder_append_basic(builder, 'q', &app->cid);
+
+ return true;
+}
+
+static bool pid_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+
+ l_dbus_message_builder_append_basic(builder, 'q', &app->pid);
+
+ return true;
+}
+
+static bool vid_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+
+ l_dbus_message_builder_append_basic(builder, 'q', &app->vid);
+
+ return true;
+}
+static bool crpl_getter(struct l_dbus *dbus,
+ struct l_dbus_message *message,
+ struct l_dbus_message_builder *builder,
+ void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+
+ l_dbus_message_builder_append_basic(builder, 'q', &app->crpl);
+
+ return true;
+}
+
+static void property_changed(struct l_dbus_proxy *proxy, const char *name,
+ struct l_dbus_message *msg, void *user_data)
+{
+ struct meshcfg_app *app = user_data;
+ struct meshcfg_node *node = app->node;
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ if (strcmp(path, node->path))
+ return;
+
+ printf("Property changed: %s %s %s\n", name, path, interface);
+
+ if (!strcmp(interface, "org.bluez.mesh.Node1")) {
+
+ if (!strcmp(name, "IvIndex")) {
+ uint32_t ivi;
+
+ if (!l_dbus_message_get_arguments(msg, "u", &ivi))
+ return;
+
+ printf("New IV Index: %u\n", ivi);
+ }
+ }
+}
+
+static void setup_app_iface(struct l_dbus_interface *iface)
+{
+ l_dbus_interface_property(iface, "CompanyID", 0, "q", cid_getter,
+ NULL);
+ l_dbus_interface_property(iface, "VersionID", 0, "q", vid_getter,
+ NULL);
+ l_dbus_interface_property(iface, "ProductID", 0, "q", pid_getter,
+ NULL);
+ l_dbus_interface_property(iface, "CRPL", 0, "q", crpl_getter, NULL);
+
+ l_dbus_interface_method(iface, "JoinComplete", 0, join_complete,
+ "", "t", "token");
+
+ /* TODO: Other methods? */
+}
+
+static bool register_app_iface(void)
+{
+ if (!l_dbus_register_interface(dbus, MESH_APPLICATION_INTERFACE,
+ setup_app_iface, NULL, false)) {
+ l_error("Failed to register interface %s",
+ MESH_APPLICATION_INTERFACE);
+ return false;
+ }
+
+ if (!l_dbus_register_interface(dbus, MESH_ELEMENT_INTERFACE,
+ setup_ele_iface, NULL, false)) {
+ l_error("Failed to register interface %s",
+ MESH_ELEMENT_INTERFACE);
+ return false;
+ }
+
+ return true;
+}
+
+static bool register_app(struct meshcfg_app *app)
+{
+ uint32_t i;
+
+ if (!l_dbus_register_object(dbus, app->path, NULL, NULL,
+ MESH_APPLICATION_INTERFACE, app,
+ NULL)) {
+ l_error("Failed to register object %s", app->path);
+ return false;
+ }
+
+ for (i = 0; i < L_ARRAY_SIZE(app->ele) && i < app->num_ele; i++) {
+ if (!l_dbus_register_object(dbus, app->ele[i].path, NULL, NULL,
+ MESH_ELEMENT_INTERFACE, &app->ele[i], NULL)) {
+ l_error("Failed to register obj %s", app->ele[i].path);
+ l_dbus_unregister_interface(dbus,
+ MESH_ELEMENT_INTERFACE);
+ return false;
+ }
+ }
+
+ if (!l_dbus_object_add_interface(dbus, app->path,
+ L_DBUS_INTERFACE_OBJECT_MANAGER, NULL)) {
+ l_error("Failed to add interface %s",
+ L_DBUS_INTERFACE_OBJECT_MANAGER);
+ return false;
+ }
+
+ return true;
+}
+
+static void client_ready(struct l_dbus_client *client, void *user_data)
+{
+ printf("D-Bus client ready\n");
+
+ if (!register_app_iface() || !register_app(&client_app) ||
+ !register_app(&server_app))
+ return;
+
+ if (init_state == IN_PROGRESS)
+ init_continue(NULL);
+}
+
+static void client_connected(struct l_dbus *dbus, void *user_data)
+{
+ printf("D-Bus client connected\n");
+}
+
+static void client_disconnected(struct l_dbus *dbus, void *user_data)
+{
+ printf("D-Bus client disconnected, exit\n");
+ l_main_exit();
+}
+
+static void ready_callback(void *user_data)
+{
+ printf("Connected to D-Bus\n");
+
+ if (l_dbus_object_manager_enable(dbus, "/"))
+ return;
+
+ printf("Failed to register the ObjectManager\n");
+ tester_setup_failed();
+}
+
+static void dbus_setup(const void *data)
+{
+ node_proxies = l_queue_new();
+
+ dbus = l_dbus_new_default(L_DBUS_SESSION_BUS);
+
+ l_dbus_set_ready_handler(dbus, ready_callback, NULL, NULL);
+ client = l_dbus_client_new(dbus, BLUEZ_MESH_NAME, "/org/bluez/mesh");
+
+ l_dbus_client_set_connect_handler(client, client_connected, NULL, NULL);
+ l_dbus_client_set_disconnect_handler(client, client_disconnected, NULL,
+ NULL);
+ l_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
+ property_changed, NULL, NULL);
+ l_dbus_client_set_ready_handler(client, client_ready, NULL, NULL);
+
+}
+
+static void init_setup(const void *data)
+{
+ if (init_state == NONE) {
+ init_state = IN_PROGRESS;
+ return dbus_setup(data);
+ }
+
+ if (init_state != DONE)
+ return tester_test_abort();
+
+ tester_pre_setup_complete();
+}
+
+static void init_startup_chain(void)
+{
+
+ startup_chain = l_queue_new();
+
+ l_queue_push_tail(startup_chain, &init_create_client);
+ l_queue_push_tail(startup_chain, &init_import_server);
+ l_queue_push_tail(startup_chain, &init_attach_client);
+ l_queue_push_tail(startup_chain, &init_import_subnet);
+ l_queue_push_tail(startup_chain, &init_import_remote);
+ l_queue_push_tail(startup_chain, &init_add_netkey);
+ l_queue_push_tail(startup_chain, &init_create_appkey);
+ l_queue_push_tail(startup_chain, &init_add_appkey);
+}
+
+static int del_fobject(const char *fpath, const struct stat *sb, int typeflag,
+ struct FTW *ftwbuf)
+{
+ switch (typeflag) {
+ case FTW_DP:
+ rmdir(fpath);
+ break;
+
+ case FTW_SL:
+ default:
+ remove(fpath);
+ break;
+ }
+
+ return 0;
+}
+
+#define tester_add_with_response(name, test_data, test_func, rsp_data) \
+ tester_add_full(name, test_data, init_setup, NULL, \
+ test_func, NULL, NULL, 2, rsp_data, NULL)
+
+int main(int argc, char *argv[])
+{
+ int status, pid;
+ char buf[PATH_MAX];
+ ssize_t len;
+ char *exe, *io, *bluez_dir;
+
+ len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
+
+ if (len != -1)
+ buf[len] = '\0';
+ else
+ return EXIT_FAILURE;
+
+ l_log_set_stderr();
+
+ tester_init(&argc, &argv);
+
+ test_dir = l_strdup_printf("/tmp/mesh");
+ nftw(test_dir, del_fobject, 5, FTW_DEPTH | FTW_PHYS);
+
+ if (mkdir(test_dir, 0700) != 0) {
+ l_error("Failed to create dir %s", test_dir);
+ l_free(test_dir);
+ return EXIT_FAILURE;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ l_error("daemon not spawned");
+ return EXIT_FAILURE;
+ }
+
+ bluez_dir = dirname(dirname(buf));
+ exe = l_strdup_printf("%s/mesh/bluetooth-meshd", bluez_dir);
+ io = l_strdup_printf("unit:%s/%s", test_dir, "test_sk");
+
+ if (pid == 0) {
+ char *const dargs[] = {
+ exe,
+ "--io",
+ io,
+ "-s",
+ test_dir,
+ NULL
+ };
+
+ printf("spawning %s --io %s -s %s", exe, io, test_dir);
+ execv(exe, dargs);
+ return EXIT_SUCCESS;
+ }
+
+ init_startup_chain();
+
+ tester_add_full("Config AppKey Add: Success", &test_add_appkey,
+ init_setup, create_appkey, add_appkey, NULL,
+ NULL, 2, &test_add_appkey_rsp, NULL);
+
+ tester_add_with_response("Config Default TTL Set: Success",
+ &test_set_ttl_req, send_cfg_msg,
+ &test_set_ttl_rsp);
+
+ tester_add_with_response("Config Bind: Success",
+ &test_bind_req, send_cfg_msg,
+ &test_bind_rsp);
+
+ tester_add_with_response("Config Bind: Fail Invalid Model",
+ &test_bind_inv_mod_req, send_cfg_msg,
+ &test_bind_inv_mod_rsp);
+
+ status = tester_run();
+
+ l_free(client_app.node);
+ l_free(server_app.node);
+ l_dbus_client_destroy(client);
+ l_dbus_destroy(dbus);
+ kill(pid, SIGTERM);
+
+ return status;
+}
--
2.26.2

2021-02-06 06:39:47

by bluez.test.bot

[permalink] [raw]
Subject: RE: Framework for non-interactive mesh test

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=429191

---Test result---

##############################
Test: CheckPatch - FAIL
Output:
shared/tester: Create ell-based version of tester code
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#168: FILE: src/shared/tester-ell.c:120:
+} __attribute__((packed));

WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#173: FILE: src/shared/tester-ell.c:125:
+} __attribute__((packed));

- total: 0 errors, 2 warnings, 913 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] shared/tester: Create ell-based version of tester code" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.

mesh: Add unit test IO
WARNING:SPDX_LICENSE_TAG: Improper SPDX comment style for 'mesh/mesh-io-unit.h', please use '/*' instead
#665: FILE: mesh/mesh-io-unit.h:1:
+// SPDX-License-Identifier: LGPL-2.1-or-later

WARNING:SPDX_LICENSE_TAG: Missing or malformed SPDX-License-Identifier tag in line 1
#665: FILE: mesh/mesh-io-unit.h:1:
+// SPDX-License-Identifier: LGPL-2.1-or-later

- total: 0 errors, 2 warnings, 668 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.

"[PATCH] mesh: Add unit test IO" has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: CheckGitLint - PASS

##############################
Test: CheckBuild - PASS

##############################
Test: MakeCheck - PASS



---
Regards,
Linux Bluetooth

2021-02-09 13:55:51

by Michał Lowas-Rzechonek

[permalink] [raw]
Subject: Re: [PATCH BlueZ v3 2/3] mesh: Add unit test IO

Hi Inga, Brian,

We ended up implementing a TCP server inside bluetooth-meshd that can be
used by external radio adapters.

This is useful not only in tests, but also for remote radios in
situations where the box runing bluetooth-meshd is in e.g. a basement,
so you might want to have a small embedded device that does (non-HCI)
bluetooth and TCP/IP.

For various reasons we're authenticating and encrypting the link using
TLS-PSK, so our setup is a bit complex, but I guess it could be trimmed
down.

The underlying protocol is also "ours", but again I'm open to adjusting
it if this means we could include it in the official release.

Would you be interested in something like this? We use it for great
effect to test the daemon using https://docs.pytest.org/en/stable/

https://github.com/homersoft/bluez/blob/master/mesh/silvair-io.c
https://github.com/homersoft/bluez/blob/master/mesh/mesh-io-uart.c
https://github.com/homersoft/bluez/blob/master/mesh/mesh-io-tcpserver.c

On 02/05, Inga Stotland wrote:
> From: Brian Gix <[email protected]>
>
> This adds a new type of mesh IO that is used for non-interactive testing.
> The new io option can be specified on command line as:
> --io unit:<socket_name>
>
> When the bluetooth-meshd daemon starts with the "unit" IO type,
> the daemon opens a socket (fd to open is provided after "unit:"
> in <socket_name>). The communication with the daemon is done either
> through the loop-back using mesh DBus-based APIs or the specified
> named socket.
> ---
> Makefile.mesh | 2 +
> mesh/main.c | 41 +++-
> mesh/mesh-io-unit.c | 533 ++++++++++++++++++++++++++++++++++++++++++++
> mesh/mesh-io-unit.h | 11 +
> mesh/mesh-io.c | 9 +-
> mesh/mesh-io.h | 3 +-
> 6 files changed, 582 insertions(+), 17 deletions(-)
> create mode 100644 mesh/mesh-io-unit.c
> create mode 100644 mesh/mesh-io-unit.h
>
> diff --git a/Makefile.mesh b/Makefile.mesh
> index 228dd1b5f..73eaded4a 100644
> --- a/Makefile.mesh
> +++ b/Makefile.mesh
> @@ -17,6 +17,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \
> mesh/error.h mesh/mesh-io-api.h \
> mesh/mesh-io-generic.h \
> mesh/mesh-io-generic.c \
> + mesh/mesh-io-unit.h \
> + mesh/mesh-io-unit.c \
> mesh/net.h mesh/net.c \
> mesh/crypto.h mesh/crypto.c \
> mesh/friend.h mesh/friend.c \
> diff --git a/mesh/main.c b/mesh/main.c
> index 4356e3f65..1b466598b 100644
> --- a/mesh/main.c
> +++ b/mesh/main.c
> @@ -61,7 +61,7 @@ static void usage(void)
> "\t--help Show %s information\n", __func__);
> fprintf(stderr,
> "io:\n"
> - "\t([hci]<index> | generic[:[hci]<index>])\n"
> + "\t([hci]<index> | generic[:[hci]<index>] | unit:<fd_path>)\n"
> "\t\tUse generic HCI io on interface hci<index>, or the first\n"
> "\t\tavailable one\n");
> }
> @@ -77,6 +77,7 @@ static void mesh_ready_callback(void *user_data, bool success)
> {
> struct l_dbus *dbus = user_data;
>
> + l_info("mesh_ready_callback");
> if (!success) {
> l_error("Failed to start mesh");
> l_main_quit();
> @@ -92,10 +93,8 @@ static void mesh_ready_callback(void *user_data, bool success)
> static void request_name_callback(struct l_dbus *dbus, bool success,
> bool queued, void *user_data)
> {
> - l_info("Request name %s",
> - success ? "success": "failed");
> -
> - if (!success) {
> + if (!success && io_type != MESH_IO_TYPE_UNIT_TEST) {
> + l_info("Request name failed");
> l_main_quit();
> return;
> }
> @@ -159,6 +158,21 @@ static bool parse_io(const char *optarg, enum mesh_io_type *type, void **opts)
> return true;
>
> return false;
> +
> + } else if (strstr(optarg, "unit") == optarg) {
> + char *test_path;
> +
> + *type = MESH_IO_TYPE_UNIT_TEST;
> +
> + optarg += strlen("unit");
> + if (*optarg != ':')
> + return false;
> +
> + optarg++;
> + test_path = strdup(optarg);
> +
> + *opts = test_path;
> + return true;
> }
>
> return false;
> @@ -187,11 +201,19 @@ int main(int argc, char *argv[])
> for (;;) {
> int opt;
>
> - opt = getopt_long(argc, argv, "i:s:c:ndbh", main_options, NULL);
> + opt = getopt_long(argc, argv, "u:i:s:c:ndbh", main_options,
> + NULL);
> if (opt < 0)
> break;
>
> switch (opt) {
> + case 'u':
> + if (sscanf(optarg, "%d", &hci_index) == 1 ||
> + sscanf(optarg, "%d", &hci_index) == 1)
> + io = l_strdup_printf("unit:%d", hci_index);
> + else
> + io = l_strdup(optarg);
> + break;
> case 'i':
> if (sscanf(optarg, "hci%d", &hci_index) == 1 ||
> sscanf(optarg, "%d", &hci_index) == 1)
> @@ -261,11 +283,8 @@ int main(int argc, char *argv[])
> status = l_main_run_with_signal(signal_handler, NULL);
>
> done:
> - if (io)
> - l_free(io);
> -
> - if (io_opts)
> - l_free(io_opts);
> + l_free(io);
> + l_free(io_opts);
>
> mesh_cleanup();
> l_dbus_destroy(dbus);
> diff --git a/mesh/mesh-io-unit.c b/mesh/mesh-io-unit.c
> new file mode 100644
> index 000000000..c5aae6741
> --- /dev/null
> +++ b/mesh/mesh-io-unit.c
> @@ -0,0 +1,533 @@
> +// SPDX-License-Identifier: LGPL-2.1-or-later
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2021 Intel Corporation. All rights reserved.
> + *
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <errno.h>
> +#include <string.h>
> +#include <sys/time.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <ell/ell.h>
> +
> +#include "mesh/mesh-defs.h"
> +#include "mesh/dbus.h"
> +#include "mesh/mesh-io.h"
> +#include "mesh/mesh-io-api.h"
> +#include "mesh/mesh-io-generic.h"
> +
> +struct mesh_io_private {
> + struct l_io *sio;
> + void *user_data;
> + char *unique_name;
> + mesh_io_ready_func_t ready_callback;
> + struct l_timeout *tx_timeout;
> + struct l_queue *rx_regs;
> + struct l_queue *tx_pkts;
> + struct sockaddr_un addr;
> + int fd;
> + uint16_t interval;
> +};
> +
> +struct pvt_rx_reg {
> + mesh_io_recv_func_t cb;
> + void *user_data;
> + uint8_t len;
> + uint8_t filter[0];
> +};
> +
> +struct process_data {
> + struct mesh_io_private *pvt;
> + const uint8_t *data;
> + uint8_t len;
> + struct mesh_io_recv_info info;
> +};
> +
> +struct tx_pkt {
> + struct mesh_io_send_info info;
> + bool delete;
> + uint8_t len;
> + uint8_t pkt[30];
> +};
> +
> +struct tx_pattern {
> + const uint8_t *data;
> + uint8_t len;
> +};
> +
> +static uint32_t get_instant(void)
> +{
> + struct timeval tm;
> + uint32_t instant;
> +
> + gettimeofday(&tm, NULL);
> + instant = tm.tv_sec * 1000;
> + instant += tm.tv_usec / 1000;
> +
> + return instant;
> +}
> +
> +static uint32_t instant_remaining_ms(uint32_t instant)
> +{
> + instant -= get_instant();
> + return instant;
> +}
> +
> +static void process_rx_callbacks(void *v_reg, void *v_rx)
> +{
> + struct pvt_rx_reg *rx_reg = v_reg;
> + struct process_data *rx = v_rx;
> +
> + if (!memcmp(rx->data, rx_reg->filter, rx_reg->len))
> + rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx->len);
> +}
> +
> +static void process_rx(struct mesh_io_private *pvt, int8_t rssi,
> + uint32_t instant, const uint8_t *addr,
> + const uint8_t *data, uint8_t len)
> +{
> + struct process_data rx = {
> + .pvt = pvt,
> + .data = data,
> + .len = len,
> + .info.instant = instant,
> + .info.addr = addr,
> + .info.chan = 7,
> + .info.rssi = rssi,
> + };
> +
> + l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx);
> +}
> +
> +static bool incoming(struct l_io *sio, void *user_data)
> +{
> + struct mesh_io_private *pvt = user_data;
> + uint32_t instant;
> + uint8_t buf[31];
> + size_t size;
> +
> + instant = get_instant();
> +
> + size = recv(pvt->fd, buf, sizeof(buf), MSG_DONTWAIT);
> +
> + if (size > 9 && buf[0]) {
> + process_rx(pvt, -20, instant, NULL, buf + 1, (uint8_t)size);
> + } else if (size == 1 && !buf[0] && pvt->unique_name) {
> +
> + /* Return DBUS unique name */
> + size = strlen(pvt->unique_name);
> +
> + if (size > sizeof(buf) - 2)
> + return true;
> +
> + buf[0] = 0;
> + memcpy(buf + 1, pvt->unique_name, size + 1);
> + send(pvt->fd, buf, size + 2, MSG_DONTWAIT);
> + }
> +
> + return true;
> +}
> +
> +static bool find_by_ad_type(const void *a, const void *b)
> +{
> + const struct tx_pkt *tx = a;
> + uint8_t ad_type = L_PTR_TO_UINT(b);
> +
> + return !ad_type || ad_type == tx->pkt[0];
> +}
> +
> +static bool find_by_pattern(const void *a, const void *b)
> +{
> + const struct tx_pkt *tx = a;
> + const struct tx_pattern *pattern = b;
> +
> + if (tx->len < pattern->len)
> + return false;
> +
> + return (!memcmp(tx->pkt, pattern->data, pattern->len));
> +}
> +
> +static void free_socket(struct mesh_io_private *pvt)
> +{
> + l_io_destroy(pvt->sio);
> + close(pvt->fd);
> + unlink(pvt->addr.sun_path);
> +}
> +
> +static void hello_callback(struct l_dbus_message *msg, void *user_data)
> +{
> + struct mesh_io_private *pvt = user_data;
> +
> + pvt->unique_name = l_strdup(l_dbus_message_get_destination(msg));
> + l_debug("User-Daemon unique name: %s", pvt->unique_name);
> +}
> +
> +static void get_name(struct l_timeout *timeout, void *user_data)
> +{
> + struct mesh_io_private *pvt = user_data;
> + struct l_dbus *dbus = dbus_get_bus();
> + struct l_dbus_message *msg;
> +
> + l_timeout_remove(timeout);
> + if (!dbus) {
> + l_timeout_create_ms(20, get_name, pvt, NULL);
> + return;
> + }
> +
> + /* Retrieve unique name */
> + msg = l_dbus_message_new_method_call(dbus, "org.freedesktop.DBus",
> + "/org/freedesktop/DBus",
> + "org.freedesktop.DBus",
> + "GetId");
> +
> + l_dbus_message_set_arguments(msg, "");
> +
> + l_dbus_send_with_reply(dbus, msg, hello_callback, pvt, NULL);
> +}
> +
> +static void unit_up(void *user_data)
> +{
> + struct mesh_io_private *pvt = user_data;
> +
> + l_debug("Started io-unit");
> +
> + if (pvt->ready_callback)
> + pvt->ready_callback(pvt->user_data, true);
> +
> + l_timeout_create_ms(1, get_name, pvt, NULL);
> +}
> +
> +static bool unit_init(struct mesh_io *io, void *opt,
> + mesh_io_ready_func_t cb, void *user_data)
> +{
> + struct mesh_io_private *pvt;
> + char *sk_path;
> + size_t size;
> +
> + l_debug("Starting Unit test IO");
> + if (!io || io->pvt)
> + return false;
> +
> + sk_path = (char *) opt;
> +
> + pvt = l_new(struct mesh_io_private, 1);
> +
> + pvt->addr.sun_family = AF_LOCAL;
> + snprintf(pvt->addr.sun_path, sizeof(pvt->addr.sun_path), "%s",
> + sk_path);
> +
> + pvt->fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0);
> + if (pvt->fd < 0)
> + goto fail;
> +
> + unlink(pvt->addr.sun_path);
> + size = offsetof(struct sockaddr_un, sun_path) +
> + strlen(pvt->addr.sun_path);
> +
> + if (bind(pvt->fd, (struct sockaddr *) &pvt->addr, size) < 0)
> + goto fail;
> +
> + /* Setup socket handlers */
> + pvt->sio = l_io_new(pvt->fd);
> + if (!l_io_set_read_handler(pvt->sio, incoming, pvt, NULL))
> + goto fail;
> +
> + pvt->rx_regs = l_queue_new();
> + pvt->tx_pkts = l_queue_new();
> +
> + pvt->ready_callback = cb;
> + pvt->user_data = user_data;
> +
> + io->pvt = pvt;
> +
> + l_idle_oneshot(unit_up, pvt, NULL);
> +
> + return true;
> +
> +fail:
> + l_error("Failed to bind Unit Test socket");
> + free_socket(pvt);
> + l_free(pvt);
> +
> + return false;
> +}
> +
> +static bool unit_destroy(struct mesh_io *io)
> +{
> + struct mesh_io_private *pvt = io->pvt;
> +
> + if (!pvt)
> + return true;
> +
> + l_free(pvt->unique_name);
> + l_timeout_remove(pvt->tx_timeout);
> + l_queue_destroy(pvt->rx_regs, l_free);
> + l_queue_destroy(pvt->tx_pkts, l_free);
> +
> + free_socket(pvt);
> +
> + l_free(pvt);
> + io->pvt = NULL;
> +
> + return true;
> +}
> +
> +static bool unit_caps(struct mesh_io *io, struct mesh_io_caps *caps)
> +{
> + struct mesh_io_private *pvt = io->pvt;
> +
> + if (!pvt || !caps)
> + return false;
> +
> + caps->max_num_filters = 255;
> + caps->window_accuracy = 50;
> +
> + return true;
> +}
> +
> +static bool simple_match(const void *a, const void *b)
> +{
> + return a == b;
> +}
> +
> +static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt *tx,
> + uint16_t interval)
> +{
> + send(pvt->fd, tx->pkt, tx->len, MSG_DONTWAIT);
> +
> + if (tx->delete) {
> + l_queue_remove_if(pvt->tx_pkts, simple_match, tx);
> + l_free(tx);
> + }
> +}
> +
> +static void tx_to(struct l_timeout *timeout, void *user_data)
> +{
> + struct mesh_io_private *pvt = user_data;
> + struct tx_pkt *tx;
> + uint16_t ms;
> + uint8_t count;
> +
> + if (!pvt)
> + return;
> +
> + tx = l_queue_pop_head(pvt->tx_pkts);
> + if (!tx) {
> + l_timeout_remove(timeout);
> + pvt->tx_timeout = NULL;
> + return;
> + }
> +
> + if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) {
> + ms = tx->info.u.gen.interval;
> + count = tx->info.u.gen.cnt;
> + if (count != MESH_IO_TX_COUNT_UNLIMITED)
> + tx->info.u.gen.cnt--;
> + } else {
> + ms = 25;
> + count = 1;
> + }
> +
> + tx->delete = !!(count == 1);
> +
> + send_pkt(pvt, tx, ms);
> +
> + if (count == 1) {
> + /* Recalculate wakeup if we are responding to POLL */
> + tx = l_queue_peek_head(pvt->tx_pkts);
> +
> + if (tx && tx->info.type == MESH_IO_TIMING_TYPE_POLL_RSP) {
> + ms = instant_remaining_ms(tx->info.u.poll_rsp.instant +
> + tx->info.u.poll_rsp.delay);
> + }
> + } else
> + l_queue_push_tail(pvt->tx_pkts, tx);
> +
> + if (timeout) {
> + pvt->tx_timeout = timeout;
> + l_timeout_modify_ms(timeout, ms);
> + } else
> + pvt->tx_timeout = l_timeout_create_ms(ms, tx_to, pvt, NULL);
> +}
> +
> +static void tx_worker(void *user_data)
> +{
> + struct mesh_io_private *pvt = user_data;
> + struct tx_pkt *tx;
> + uint32_t delay;
> +
> + tx = l_queue_peek_head(pvt->tx_pkts);
> + if (!tx)
> + return;
> +
> + switch (tx->info.type) {
> + case MESH_IO_TIMING_TYPE_GENERAL:
> + if (tx->info.u.gen.min_delay == tx->info.u.gen.max_delay)
> + delay = tx->info.u.gen.min_delay;
> + else {
> + l_getrandom(&delay, sizeof(delay));
> + delay %= tx->info.u.gen.max_delay -
> + tx->info.u.gen.min_delay;
> + delay += tx->info.u.gen.min_delay;
> + }
> + break;
> +
> + case MESH_IO_TIMING_TYPE_POLL:
> + if (tx->info.u.poll.min_delay == tx->info.u.poll.max_delay)
> + delay = tx->info.u.poll.min_delay;
> + else {
> + l_getrandom(&delay, sizeof(delay));
> + delay %= tx->info.u.poll.max_delay -
> + tx->info.u.poll.min_delay;
> + delay += tx->info.u.poll.min_delay;
> + }
> + break;
> +
> + case MESH_IO_TIMING_TYPE_POLL_RSP:
> + /* Delay until Instant + Delay */
> + delay = instant_remaining_ms(tx->info.u.poll_rsp.instant +
> + tx->info.u.poll_rsp.delay);
> + if (delay > 255)
> + delay = 0;
> + break;
> +
> + default:
> + return;
> + }
> +
> + if (!delay)
> + tx_to(pvt->tx_timeout, pvt);
> + else if (pvt->tx_timeout)
> + l_timeout_modify_ms(pvt->tx_timeout, delay);
> + else
> + pvt->tx_timeout = l_timeout_create_ms(delay, tx_to, pvt, NULL);
> +}
> +
> +static bool send_tx(struct mesh_io *io, struct mesh_io_send_info *info,
> + const uint8_t *data, uint16_t len)
> +{
> + struct mesh_io_private *pvt = io->pvt;
> + struct tx_pkt *tx;
> + bool sending = false;
> +
> + if (!info || !data || !len || len > sizeof(tx->pkt))
> + return false;
> +
> + tx = l_new(struct tx_pkt, 1);
> +
> + memcpy(&tx->info, info, sizeof(tx->info));
> + memcpy(&tx->pkt, data, len);
> + tx->len = len;
> +
> + if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP)
> + l_queue_push_head(pvt->tx_pkts, tx);
> + else {
> + sending = !l_queue_isempty(pvt->tx_pkts);
> +
> + l_queue_push_tail(pvt->tx_pkts, tx);
> + }
> +
> + if (!sending) {
> + l_timeout_remove(pvt->tx_timeout);
> + pvt->tx_timeout = NULL;
> + l_idle_oneshot(tx_worker, pvt, NULL);
> + }
> +
> + return true;
> +}
> +
> +static bool tx_cancel(struct mesh_io *io, const uint8_t *data, uint8_t len)
> +{
> + struct mesh_io_private *pvt = io->pvt;
> + struct tx_pkt *tx;
> +
> + if (!data)
> + return false;
> +
> + if (len == 1) {
> + do {
> + tx = l_queue_remove_if(pvt->tx_pkts, find_by_ad_type,
> + L_UINT_TO_PTR(data[0]));
> + l_free(tx);
> +
> + } while (tx);
> + } else {
> + struct tx_pattern pattern = {
> + .data = data,
> + .len = len
> + };
> +
> + do {
> + tx = l_queue_remove_if(pvt->tx_pkts, find_by_pattern,
> + &pattern);
> + l_free(tx);
> +
> + } while (tx);
> + }
> +
> + if (l_queue_isempty(pvt->tx_pkts)) {
> + l_timeout_remove(pvt->tx_timeout);
> + pvt->tx_timeout = NULL;
> + }
> +
> + return true;
> +}
> +
> +static bool find_by_filter(const void *a, const void *b)
> +{
> + const struct pvt_rx_reg *rx_reg = a;
> + const uint8_t *filter = b;
> +
> + return !memcmp(rx_reg->filter, filter, rx_reg->len);
> +}
> +
> +static bool recv_register(struct mesh_io *io, const uint8_t *filter,
> + uint8_t len, mesh_io_recv_func_t cb, void *user_data)
> +{
> + struct mesh_io_private *pvt = io->pvt;
> + struct pvt_rx_reg *rx_reg;
> +
> + if (!cb || !filter || !len)
> + return false;
> +
> + rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter, filter);
> +
> + l_free(rx_reg);
> + rx_reg = l_malloc(sizeof(*rx_reg) + len);
> +
> + memcpy(rx_reg->filter, filter, len);
> + rx_reg->len = len;
> + rx_reg->cb = cb;
> + rx_reg->user_data = user_data;
> +
> + l_queue_push_head(pvt->rx_regs, rx_reg);
> +
> + return true;
> +}
> +
> +static bool recv_deregister(struct mesh_io *io, const uint8_t *filter,
> + uint8_t len)
> +{
> + return true;
> +}
> +
> +const struct mesh_io_api mesh_io_unit = {
> + .init = unit_init,
> + .destroy = unit_destroy,
> + .caps = unit_caps,
> + .send = send_tx,
> + .reg = recv_register,
> + .dereg = recv_deregister,
> + .cancel = tx_cancel,
> +};
> diff --git a/mesh/mesh-io-unit.h b/mesh/mesh-io-unit.h
> new file mode 100644
> index 000000000..846eea7bc
> --- /dev/null
> +++ b/mesh/mesh-io-unit.h
> @@ -0,0 +1,11 @@
> +// SPDX-License-Identifier: LGPL-2.1-or-later
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2021 Intel Corporation. All rights reserved.
> + *
> + *
> + */
> +
> +extern const struct mesh_io_api mesh_io_unit;
> diff --git a/mesh/mesh-io.c b/mesh/mesh-io.c
> index 62fc5d12e..96891313a 100644
> --- a/mesh/mesh-io.c
> +++ b/mesh/mesh-io.c
> @@ -22,10 +22,12 @@
>
> /* List of Mesh-IO Type headers */
> #include "mesh/mesh-io-generic.h"
> +#include "mesh/mesh-io-unit.h"
>
> /* List of Supported Mesh-IO Types */
> static const struct mesh_io_table table[] = {
> - {MESH_IO_TYPE_GENERIC, &mesh_io_generic}
> + {MESH_IO_TYPE_GENERIC, &mesh_io_generic},
> + {MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit},
> };
>
> static struct l_queue *io_list;
> @@ -64,12 +66,9 @@ struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts,
>
> io = l_new(struct mesh_io, 1);
>
> - if (!io)
> - return NULL;
> -
> io->type = type;
> -
> io->api = api;
> +
> if (!api->init(io, opts, cb, user_data))
> goto fail;
>
> diff --git a/mesh/mesh-io.h b/mesh/mesh-io.h
> index b11c6c6e1..80ef3fa3e 100644
> --- a/mesh/mesh-io.h
> +++ b/mesh/mesh-io.h
> @@ -14,7 +14,8 @@ struct mesh_io;
>
> enum mesh_io_type {
> MESH_IO_TYPE_NONE = 0,
> - MESH_IO_TYPE_GENERIC
> + MESH_IO_TYPE_GENERIC,
> + MESH_IO_TYPE_UNIT_TEST
> };
>
> enum mesh_io_timing_type {
> --
> 2.26.2
>

--
Michał Lowas-Rzechonek <[email protected]>
Silvair http://silvair.com
Jasnogórska 44, 31-358 Krakow, POLAND

2021-02-10 08:40:24

by Stotland, Inga

[permalink] [raw]
Subject: Re: [PATCH BlueZ v3 2/3] mesh: Add unit test IO

On Tue, 2021-02-09 at 22:48 -0800, Stotland, Inga wrote:
> Hi Michal,
>
> On Tue, 2021-02-09 at 14:49 +0100, Michał Lowas-Rzechonek wrote:
> > Hi Inga, Brian,
> >
> > We ended up implementing a TCP server inside bluetooth-meshd that
> > can be
> > used by external radio adapters.
> >
> > This is useful not only in tests, but also for remote radios in
> > situations where the box runing bluetooth-meshd is in e.g. a
> > basement,
> > so you might want to have a small embedded device that does (non-
> > HCI)
> > bluetooth and TCP/IP.
> >
> > For various reasons we're authenticating and encrypting the link
> > using
> > TLS-PSK, so our setup is a bit complex, but I guess it could be
> > trimmed
> > down.
> >
> > The underlying protocol is also "ours", but again I'm open to
> > adjusting
> > it if this means we could include it in the official release.
> >
> > Would you be interested in something like this? We use it for great
> > effect to test the daemon using
> > https://docs.pytest.org/en/stable/
> >
> >
> > https://github.com/homersoft/bluez/blob/master/mesh/silvair-io.c
> >
> > https://github.com/homersoft/bluez/blob/master/mesh/mesh-io-uart.c
> >
> > https://github.com/homersoft/bluez/blob/master/mesh/mesh-io-tcpserver.c
> >
>
> Thank you. I will take a look. If we could have a version of io that
> is adaptable for various testing scenarious that would be great.
> One thing to keep in mind is that for BlueZ framework, the test
> should be able to run in non-sudo mode.

oh yes, one more thing: the test should be runnable even if no
controllers are available. The "unit" IO is essentially a loopback in
our case.

>
> Best regards,
> Inga
>
> > On 02/05, Inga Stotland wrote:
> > > From: Brian Gix <
> > > [email protected]
> > > >
> > >
> > > This adds a new type of mesh IO that is used for non-interactive
> > > testing.
> > > The new io option can be specified on command line as:
> > > --io unit:<socket_name>
> > >
> > > When the bluetooth-meshd daemon starts with the "unit" IO type,
> > > the daemon opens a socket (fd to open is provided after "unit:"
> > > in <socket_name>). The communication with the daemon is done
> > > either
> > > through the loop-back using mesh DBus-based APIs or the specified
> > > named socket.
> > > ---
> > > Makefile.mesh | 2 +
> > > mesh/main.c | 41 +++-
> > > mesh/mesh-io-unit.c | 533
> > > ++++++++++++++++++++++++++++++++++++++++++++
> > > mesh/mesh-io-unit.h | 11 +
> > > mesh/mesh-io.c | 9 +-
> > > mesh/mesh-io.h | 3 +-
> > > 6 files changed, 582 insertions(+), 17 deletions(-)
> > > create mode 100644 mesh/mesh-io-unit.c
> > > create mode 100644 mesh/mesh-io-unit.h
> > >
> > > diff --git a/Makefile.mesh b/Makefile.mesh
> > > index 228dd1b5f..73eaded4a 100644
> > > --- a/Makefile.mesh
> > > +++ b/Makefile.mesh
> > > @@ -17,6 +17,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \
> > > mesh/error.h mesh/mesh-io-api.h \
> > > mesh/mesh-io-generic.h \
> > > mesh/mesh-io-generic.c \
> > > + mesh/mesh-io-unit.h \
> > > + mesh/mesh-io-unit.c \
> > > mesh/net.h mesh/net.c \
> > > mesh/crypto.h mesh/crypto.c \
> > > mesh/friend.h mesh/friend.c \
> > > diff --git a/mesh/main.c b/mesh/main.c
> > > index 4356e3f65..1b466598b 100644
> > > --- a/mesh/main.c
> > > +++ b/mesh/main.c
> > > @@ -61,7 +61,7 @@ static void usage(void)
> > > "\t--help Show %s information\n", __func__);
> > > fprintf(stderr,
> > > "io:\n"
> > > - "\t([hci]<index> | generic[:[hci]<index>])\n"
> > > + "\t([hci]<index> | generic[:[hci]<index>] |
> > > unit:<fd_path>)\n"
> > > "\t\tUse generic HCI io on interface hci<index>, or the
> > > first\n"
> > > "\t\tavailable one\n");
> > > }
> > > @@ -77,6 +77,7 @@ static void mesh_ready_callback(void
> > > *user_data, bool success)
> > > {
> > > struct l_dbus *dbus = user_data;
> > >
> > > + l_info("mesh_ready_callback");
> > > if (!success) {
> > > l_error("Failed to start mesh");
> > > l_main_quit();
> > > @@ -92,10 +93,8 @@ static void mesh_ready_callback(void
> > > *user_data, bool success)
> > > static void request_name_callback(struct l_dbus *dbus, bool
> > > success,
> > > bool queued, void *user_data)
> > > {
> > > - l_info("Request name %s",
> > > - success ? "success": "failed");
> > > -
> > > - if (!success) {
> > > + if (!success && io_type != MESH_IO_TYPE_UNIT_TEST) {
> > > + l_info("Request name failed");
> > > l_main_quit();
> > > return;
> > > }
> > > @@ -159,6 +158,21 @@ static bool parse_io(const char *optarg,
> > > enum mesh_io_type *type, void **opts)
> > > return true;
> > >
> > > return false;
> > > +
> > > + } else if (strstr(optarg, "unit") == optarg) {
> > > + char *test_path;
> > > +
> > > + *type = MESH_IO_TYPE_UNIT_TEST;
> > > +
> > > + optarg += strlen("unit");
> > > + if (*optarg != ':')
> > > + return false;
> > > +
> > > + optarg++;
> > > + test_path = strdup(optarg);
> > > +
> > > + *opts = test_path;
> > > + return true;
> > > }
> > >
> > > return false;
> > > @@ -187,11 +201,19 @@ int main(int argc, char *argv[])
> > > for (;;) {
> > > int opt;
> > >
> > > - opt = getopt_long(argc, argv, "i:s:c:ndbh",
> > > main_options, NULL);
> > > + opt = getopt_long(argc, argv, "u:i:s:c:ndbh",
> > > main_options,
> > > +
> > > NULL);
> > > if (opt < 0)
> > > break;
> > >
> > > switch (opt) {
> > > + case 'u':
> > > + if (sscanf(optarg, "%d", &hci_index) == 1 ||
> > > + sscanf(optarg, "%d",
> > > &hci_index) == 1)
> > > + io = l_strdup_printf("unit:%d",
> > > hci_index);
> > > + else
> > > + io = l_strdup(optarg);
> > > + break;
> > > case 'i':
> > > if (sscanf(optarg, "hci%d", &hci_index) == 1 ||
> > > sscanf(optarg, "%d",
> > > &hci_index) == 1)
> > > @@ -261,11 +283,8 @@ int main(int argc, char *argv[])
> > > status = l_main_run_with_signal(signal_handler, NULL);
> > >
> > > done:
> > > - if (io)
> > > - l_free(io);
> > > -
> > > - if (io_opts)
> > > - l_free(io_opts);
> > > + l_free(io);
> > > + l_free(io_opts);
> > >
> > > mesh_cleanup();
> > > l_dbus_destroy(dbus);
> > > diff --git a/mesh/mesh-io-unit.c b/mesh/mesh-io-unit.c
> > > new file mode 100644
> > > index 000000000..c5aae6741
> > > --- /dev/null
> > > +++ b/mesh/mesh-io-unit.c
> > > @@ -0,0 +1,533 @@
> > > +// SPDX-License-Identifier: LGPL-2.1-or-later
> > > +/*
> > > + *
> > > + * BlueZ - Bluetooth protocol stack for Linux
> > > + *
> > > + * Copyright (C) 2021 Intel Corporation. All rights reserved.
> > > + *
> > > + *
> > > + */
> > > +
> > > +#ifdef HAVE_CONFIG_H
> > > +#include <config.h>
> > > +#endif
> > > +
> > > +#include <errno.h>
> > > +#include <string.h>
> > > +#include <sys/time.h>
> > > +#include <sys/socket.h>
> > > +#include <sys/un.h>
> > > +#include <unistd.h>
> > > +#include <stdio.h>
> > > +#include <ell/ell.h>
> > > +
> > > +#include "mesh/mesh-defs.h"
> > > +#include "mesh/dbus.h"
> > > +#include "mesh/mesh-io.h"
> > > +#include "mesh/mesh-io-api.h"
> > > +#include "mesh/mesh-io-generic.h"
> > > +
> > > +struct mesh_io_private {
> > > + struct l_io *sio;
> > > + void *user_data;
> > > + char *unique_name;
> > > + mesh_io_ready_func_t ready_callback;
> > > + struct l_timeout *tx_timeout;
> > > + struct l_queue *rx_regs;
> > > + struct l_queue *tx_pkts;
> > > + struct sockaddr_un addr;
> > > + int fd;
> > > + uint16_t interval;
> > > +};
> > > +
> > > +struct pvt_rx_reg {
> > > + mesh_io_recv_func_t cb;
> > > + void *user_data;
> > > + uint8_t len;
> > > + uint8_t filter[0];
> > > +};
> > > +
> > > +struct process_data {
> > > + struct mesh_io_private *pvt;
> > > + const uint8_t *data;
> > > + uint8_t len;
> > > + struct mesh_io_recv_info info;
> > > +};
> > > +
> > > +struct tx_pkt {
> > > + struct mesh_io_send_info info;
> > > + bool delete;
> > > + uint8_t len;
> > > + uint8_t pkt[30];
> > > +};
> > > +
> > > +struct tx_pattern {
> > > + const uint8_t *data;
> > > + uint8_t len;
> > > +};
> > > +
> > > +static uint32_t get_instant(void)
> > > +{
> > > + struct timeval tm;
> > > + uint32_t instant;
> > > +
> > > + gettimeofday(&tm, NULL);
> > > + instant = tm.tv_sec * 1000;
> > > + instant += tm.tv_usec / 1000;
> > > +
> > > + return instant;
> > > +}
> > > +
> > > +static uint32_t instant_remaining_ms(uint32_t instant)
> > > +{
> > > + instant -= get_instant();
> > > + return instant;
> > > +}
> > > +
> > > +static void process_rx_callbacks(void *v_reg, void *v_rx)
> > > +{
> > > + struct pvt_rx_reg *rx_reg = v_reg;
> > > + struct process_data *rx = v_rx;
> > > +
> > > + if (!memcmp(rx->data, rx_reg->filter, rx_reg->len))
> > > + rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx-
> > > >len);
> > > +}
> > > +
> > > +static void process_rx(struct mesh_io_private *pvt, int8_t rssi,
> > > + uint32_t instant, const uint8_t
> > > *addr,
> > > + const uint8_t *data, uint8_t
> > > len)
> > > +{
> > > + struct process_data rx = {
> > > + .pvt = pvt,
> > > + .data = data,
> > > + .len = len,
> > > + .info.instant = instant,
> > > + .info.addr = addr,
> > > + .info.chan = 7,
> > > + .info.rssi = rssi,
> > > + };
> > > +
> > > + l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx);
> > > +}
> > > +
> > > +static bool incoming(struct l_io *sio, void *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = user_data;
> > > + uint32_t instant;
> > > + uint8_t buf[31];
> > > + size_t size;
> > > +
> > > + instant = get_instant();
> > > +
> > > + size = recv(pvt->fd, buf, sizeof(buf), MSG_DONTWAIT);
> > > +
> > > + if (size > 9 && buf[0]) {
> > > + process_rx(pvt, -20, instant, NULL, buf + 1,
> > > (uint8_t)size);
> > > + } else if (size == 1 && !buf[0] && pvt->unique_name) {
> > > +
> > > + /* Return DBUS unique name */
> > > + size = strlen(pvt->unique_name);
> > > +
> > > + if (size > sizeof(buf) - 2)
> > > + return true;
> > > +
> > > + buf[0] = 0;
> > > + memcpy(buf + 1, pvt->unique_name, size + 1);
> > > + send(pvt->fd, buf, size + 2, MSG_DONTWAIT);
> > > + }
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static bool find_by_ad_type(const void *a, const void *b)
> > > +{
> > > + const struct tx_pkt *tx = a;
> > > + uint8_t ad_type = L_PTR_TO_UINT(b);
> > > +
> > > + return !ad_type || ad_type == tx->pkt[0];
> > > +}
> > > +
> > > +static bool find_by_pattern(const void *a, const void *b)
> > > +{
> > > + const struct tx_pkt *tx = a;
> > > + const struct tx_pattern *pattern = b;
> > > +
> > > + if (tx->len < pattern->len)
> > > + return false;
> > > +
> > > + return (!memcmp(tx->pkt, pattern->data, pattern->len));
> > > +}
> > > +
> > > +static void free_socket(struct mesh_io_private *pvt)
> > > +{
> > > + l_io_destroy(pvt->sio);
> > > + close(pvt->fd);
> > > + unlink(pvt->addr.sun_path);
> > > +}
> > > +
> > > +static void hello_callback(struct l_dbus_message *msg, void
> > > *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = user_data;
> > > +
> > > + pvt->unique_name =
> > > l_strdup(l_dbus_message_get_destination(msg));
> > > + l_debug("User-Daemon unique name: %s", pvt->unique_name);
> > > +}
> > > +
> > > +static void get_name(struct l_timeout *timeout, void *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = user_data;
> > > + struct l_dbus *dbus = dbus_get_bus();
> > > + struct l_dbus_message *msg;
> > > +
> > > + l_timeout_remove(timeout);
> > > + if (!dbus) {
> > > + l_timeout_create_ms(20, get_name, pvt, NULL);
> > > + return;
> > > + }
> > > +
> > > + /* Retrieve unique name */
> > > + msg = l_dbus_message_new_method_call(dbus,
> > > "org.freedesktop.DBus",
> > > + "/org/freedeskt
> > > op/DBus",
> > > + "org.freedeskto
> > > p.DBus",
> > > + "GetId");
> > > +
> > > + l_dbus_message_set_arguments(msg, "");
> > > +
> > > + l_dbus_send_with_reply(dbus, msg, hello_callback, pvt, NULL);
> > > +}
> > > +
> > > +static void unit_up(void *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = user_data;
> > > +
> > > + l_debug("Started io-unit");
> > > +
> > > + if (pvt->ready_callback)
> > > + pvt->ready_callback(pvt->user_data, true);
> > > +
> > > + l_timeout_create_ms(1, get_name, pvt, NULL);
> > > +}
> > > +
> > > +static bool unit_init(struct mesh_io *io, void *opt,
> > > + mesh_io_ready_func_t cb, void
> > > *user_data)
> > > +{
> > > + struct mesh_io_private *pvt;
> > > + char *sk_path;
> > > + size_t size;
> > > +
> > > + l_debug("Starting Unit test IO");
> > > + if (!io || io->pvt)
> > > + return false;
> > > +
> > > + sk_path = (char *) opt;
> > > +
> > > + pvt = l_new(struct mesh_io_private, 1);
> > > +
> > > + pvt->addr.sun_family = AF_LOCAL;
> > > + snprintf(pvt->addr.sun_path, sizeof(pvt->addr.sun_path), "%s",
> > > + sk_path
> > > );
> > > +
> > > + pvt->fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0);
> > > + if (pvt->fd < 0)
> > > + goto fail;
> > > +
> > > + unlink(pvt->addr.sun_path);
> > > + size = offsetof(struct sockaddr_un, sun_path) +
> > > + strlen(pvt-
> > > >addr.sun_path);
> > > +
> > > + if (bind(pvt->fd, (struct sockaddr *) &pvt->addr, size) < 0)
> > > + goto fail;
> > > +
> > > + /* Setup socket handlers */
> > > + pvt->sio = l_io_new(pvt->fd);
> > > + if (!l_io_set_read_handler(pvt->sio, incoming, pvt, NULL))
> > > + goto fail;
> > > +
> > > + pvt->rx_regs = l_queue_new();
> > > + pvt->tx_pkts = l_queue_new();
> > > +
> > > + pvt->ready_callback = cb;
> > > + pvt->user_data = user_data;
> > > +
> > > + io->pvt = pvt;
> > > +
> > > + l_idle_oneshot(unit_up, pvt, NULL);
> > > +
> > > + return true;
> > > +
> > > +fail:
> > > + l_error("Failed to bind Unit Test socket");
> > > + free_socket(pvt);
> > > + l_free(pvt);
> > > +
> > > + return false;
> > > +}
> > > +
> > > +static bool unit_destroy(struct mesh_io *io)
> > > +{
> > > + struct mesh_io_private *pvt = io->pvt;
> > > +
> > > + if (!pvt)
> > > + return true;
> > > +
> > > + l_free(pvt->unique_name);
> > > + l_timeout_remove(pvt->tx_timeout);
> > > + l_queue_destroy(pvt->rx_regs, l_free);
> > > + l_queue_destroy(pvt->tx_pkts, l_free);
> > > +
> > > + free_socket(pvt);
> > > +
> > > + l_free(pvt);
> > > + io->pvt = NULL;
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static bool unit_caps(struct mesh_io *io, struct mesh_io_caps
> > > *caps)
> > > +{
> > > + struct mesh_io_private *pvt = io->pvt;
> > > +
> > > + if (!pvt || !caps)
> > > + return false;
> > > +
> > > + caps->max_num_filters = 255;
> > > + caps->window_accuracy = 50;
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static bool simple_match(const void *a, const void *b)
> > > +{
> > > + return a == b;
> > > +}
> > > +
> > > +static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt
> > > *tx,
> > > + uint16_t
> > > interval)
> > > +{
> > > + send(pvt->fd, tx->pkt, tx->len, MSG_DONTWAIT);
> > > +
> > > + if (tx->delete) {
> > > + l_queue_remove_if(pvt->tx_pkts, simple_match, tx);
> > > + l_free(tx);
> > > + }
> > > +}
> > > +
> > > +static void tx_to(struct l_timeout *timeout, void *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = user_data;
> > > + struct tx_pkt *tx;
> > > + uint16_t ms;
> > > + uint8_t count;
> > > +
> > > + if (!pvt)
> > > + return;
> > > +
> > > + tx = l_queue_pop_head(pvt->tx_pkts);
> > > + if (!tx) {
> > > + l_timeout_remove(timeout);
> > > + pvt->tx_timeout = NULL;
> > > + return;
> > > + }
> > > +
> > > + if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) {
> > > + ms = tx->info.u.gen.interval;
> > > + count = tx->info.u.gen.cnt;
> > > + if (count != MESH_IO_TX_COUNT_UNLIMITED)
> > > + tx->info.u.gen.cnt--;
> > > + } else {
> > > + ms = 25;
> > > + count = 1;
> > > + }
> > > +
> > > + tx->delete = !!(count == 1);
> > > +
> > > + send_pkt(pvt, tx, ms);
> > > +
> > > + if (count == 1) {
> > > + /* Recalculate wakeup if we are responding to POLL */
> > > + tx = l_queue_peek_head(pvt->tx_pkts);
> > > +
> > > + if (tx && tx->info.type ==
> > > MESH_IO_TIMING_TYPE_POLL_RSP) {
> > > + ms = instant_remaining_ms(tx-
> > > >info.u.poll_rsp.instant +
> > > + tx-
> > > >info.u.poll_rsp.delay);
> > > + }
> > > + } else
> > > + l_queue_push_tail(pvt->tx_pkts, tx);
> > > +
> > > + if (timeout) {
> > > + pvt->tx_timeout = timeout;
> > > + l_timeout_modify_ms(timeout, ms);
> > > + } else
> > > + pvt->tx_timeout = l_timeout_create_ms(ms, tx_to, pvt,
> > > NULL);
> > > +}
> > > +
> > > +static void tx_worker(void *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = user_data;
> > > + struct tx_pkt *tx;
> > > + uint32_t delay;
> > > +
> > > + tx = l_queue_peek_head(pvt->tx_pkts);
> > > + if (!tx)
> > > + return;
> > > +
> > > + switch (tx->info.type) {
> > > + case MESH_IO_TIMING_TYPE_GENERAL:
> > > + if (tx->info.u.gen.min_delay == tx-
> > > >info.u.gen.max_delay)
> > > + delay = tx->info.u.gen.min_delay;
> > > + else {
> > > + l_getrandom(&delay, sizeof(delay));
> > > + delay %= tx->info.u.gen.max_delay -
> > > + tx-
> > > >info.u.gen.min_delay;
> > > + delay += tx->info.u.gen.min_delay;
> > > + }
> > > + break;
> > > +
> > > + case MESH_IO_TIMING_TYPE_POLL:
> > > + if (tx->info.u.poll.min_delay == tx-
> > > >info.u.poll.max_delay)
> > > + delay = tx->info.u.poll.min_delay;
> > > + else {
> > > + l_getrandom(&delay, sizeof(delay));
> > > + delay %= tx->info.u.poll.max_delay -
> > > + tx-
> > > >info.u.poll.min_delay;
> > > + delay += tx->info.u.poll.min_delay;
> > > + }
> > > + break;
> > > +
> > > + case MESH_IO_TIMING_TYPE_POLL_RSP:
> > > + /* Delay until Instant + Delay */
> > > + delay = instant_remaining_ms(tx-
> > > >info.u.poll_rsp.instant +
> > > + tx-
> > > >info.u.poll_rsp.delay);
> > > + if (delay > 255)
> > > + delay = 0;
> > > + break;
> > > +
> > > + default:
> > > + return;
> > > + }
> > > +
> > > + if (!delay)
> > > + tx_to(pvt->tx_timeout, pvt);
> > > + else if (pvt->tx_timeout)
> > > + l_timeout_modify_ms(pvt->tx_timeout, delay);
> > > + else
> > > + pvt->tx_timeout = l_timeout_create_ms(delay, tx_to,
> > > pvt, NULL);
> > > +}
> > > +
> > > +static bool send_tx(struct mesh_io *io, struct mesh_io_send_info
> > > *info,
> > > + const uint8_t *data, uint16_t
> > > len)
> > > +{
> > > + struct mesh_io_private *pvt = io->pvt;
> > > + struct tx_pkt *tx;
> > > + bool sending = false;
> > > +
> > > + if (!info || !data || !len || len > sizeof(tx->pkt))
> > > + return false;
> > > +
> > > + tx = l_new(struct tx_pkt, 1);
> > > +
> > > + memcpy(&tx->info, info, sizeof(tx->info));
> > > + memcpy(&tx->pkt, data, len);
> > > + tx->len = len;
> > > +
> > > + if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP)
> > > + l_queue_push_head(pvt->tx_pkts, tx);
> > > + else {
> > > + sending = !l_queue_isempty(pvt->tx_pkts);
> > > +
> > > + l_queue_push_tail(pvt->tx_pkts, tx);
> > > + }
> > > +
> > > + if (!sending) {
> > > + l_timeout_remove(pvt->tx_timeout);
> > > + pvt->tx_timeout = NULL;
> > > + l_idle_oneshot(tx_worker, pvt, NULL);
> > > + }
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static bool tx_cancel(struct mesh_io *io, const uint8_t *data,
> > > uint8_t len)
> > > +{
> > > + struct mesh_io_private *pvt = io->pvt;
> > > + struct tx_pkt *tx;
> > > +
> > > + if (!data)
> > > + return false;
> > > +
> > > + if (len == 1) {
> > > + do {
> > > + tx = l_queue_remove_if(pvt->tx_pkts,
> > > find_by_ad_type,
> > > + L_UINT_TO_PTR(d
> > > ata[0]));
> > > + l_free(tx);
> > > +
> > > + } while (tx);
> > > + } else {
> > > + struct tx_pattern pattern = {
> > > + .data = data,
> > > + .len = len
> > > + };
> > > +
> > > + do {
> > > + tx = l_queue_remove_if(pvt->tx_pkts,
> > > find_by_pattern,
> > > + &patter
> > > n);
> > > + l_free(tx);
> > > +
> > > + } while (tx);
> > > + }
> > > +
> > > + if (l_queue_isempty(pvt->tx_pkts)) {
> > > + l_timeout_remove(pvt->tx_timeout);
> > > + pvt->tx_timeout = NULL;
> > > + }
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static bool find_by_filter(const void *a, const void *b)
> > > +{
> > > + const struct pvt_rx_reg *rx_reg = a;
> > > + const uint8_t *filter = b;
> > > +
> > > + return !memcmp(rx_reg->filter, filter, rx_reg->len);
> > > +}
> > > +
> > > +static bool recv_register(struct mesh_io *io, const uint8_t
> > > *filter,
> > > + uint8_t len, mesh_io_recv_func_t cb, void
> > > *user_data)
> > > +{
> > > + struct mesh_io_private *pvt = io->pvt;
> > > + struct pvt_rx_reg *rx_reg;
> > > +
> > > + if (!cb || !filter || !len)
> > > + return false;
> > > +
> > > + rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter,
> > > filter);
> > > +
> > > + l_free(rx_reg);
> > > + rx_reg = l_malloc(sizeof(*rx_reg) + len);
> > > +
> > > + memcpy(rx_reg->filter, filter, len);
> > > + rx_reg->len = len;
> > > + rx_reg->cb = cb;
> > > + rx_reg->user_data = user_data;
> > > +
> > > + l_queue_push_head(pvt->rx_regs, rx_reg);
> > > +
> > > + return true;
> > > +}
> > > +
> > > +static bool recv_deregister(struct mesh_io *io, const uint8_t
> > > *filter,
> > > + uint8_t
> > > len)
> > > +{
> > > + return true;
> > > +}
> > > +
> > > +const struct mesh_io_api mesh_io_unit = {
> > > + .init = unit_init,
> > > + .destroy = unit_destroy,
> > > + .caps = unit_caps,
> > > + .send = send_tx,
> > > + .reg = recv_register,
> > > + .dereg = recv_deregister,
> > > + .cancel = tx_cancel,
> > > +};
> > > diff --git a/mesh/mesh-io-unit.h b/mesh/mesh-io-unit.h
> > > new file mode 100644
> > > index 000000000..846eea7bc
> > > --- /dev/null
> > > +++ b/mesh/mesh-io-unit.h
> > > @@ -0,0 +1,11 @@
> > > +// SPDX-License-Identifier: LGPL-2.1-or-later
> > > +/*
> > > + *
> > > + * BlueZ - Bluetooth protocol stack for Linux
> > > + *
> > > + * Copyright (C) 2021 Intel Corporation. All rights reserved.
> > > + *
> > > + *
> > > + */
> > > +
> > > +extern const struct mesh_io_api mesh_io_unit;
> > > diff --git a/mesh/mesh-io.c b/mesh/mesh-io.c
> > > index 62fc5d12e..96891313a 100644
> > > --- a/mesh/mesh-io.c
> > > +++ b/mesh/mesh-io.c
> > > @@ -22,10 +22,12 @@
> > >
> > > /* List of Mesh-IO Type headers */
> > > #include "mesh/mesh-io-generic.h"
> > > +#include "mesh/mesh-io-unit.h"
> > >
> > > /* List of Supported Mesh-IO Types */
> > > static const struct mesh_io_table table[] = {
> > > - {MESH_IO_TYPE_GENERIC, &mesh_io_generic}
> > > + {MESH_IO_TYPE_GENERIC, &mesh_io_generic},
> > > + {MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit},
> > > };
> > >
> > > static struct l_queue *io_list;
> > > @@ -64,12 +66,9 @@ struct mesh_io *mesh_io_new(enum mesh_io_type
> > > type, void *opts,
> > >
> > > io = l_new(struct mesh_io, 1);
> > >
> > > - if (!io)
> > > - return NULL;
> > > -
> > > io->type = type;
> > > -
> > > io->api = api;
> > > +
> > > if (!api->init(io, opts, cb, user_data))
> > > goto fail;
> > >
> > > diff --git a/mesh/mesh-io.h b/mesh/mesh-io.h
> > > index b11c6c6e1..80ef3fa3e 100644
> > > --- a/mesh/mesh-io.h
> > > +++ b/mesh/mesh-io.h
> > > @@ -14,7 +14,8 @@ struct mesh_io;
> > >
> > > enum mesh_io_type {
> > > MESH_IO_TYPE_NONE = 0,
> > > - MESH_IO_TYPE_GENERIC
> > > + MESH_IO_TYPE_GENERIC,
> > > + MESH_IO_TYPE_UNIT_TEST
> > > };
> > >
> > > enum mesh_io_timing_type {
> > > --
> > > 2.26.2
> > >