From patchwork Sat Feb 6 04:43:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Stotland, Inga" X-Patchwork-Id: 12071913 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE, SPF_PASS,UNWANTED_LANGUAGE_BODY,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EB594C433E0 for ; Sat, 6 Feb 2021 04:47:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AF70564F97 for ; Sat, 6 Feb 2021 04:47:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231432AbhBFErG (ORCPT ); Fri, 5 Feb 2021 23:47:06 -0500 Received: from mga14.intel.com ([192.55.52.115]:19905 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231330AbhBFEou (ORCPT ); Fri, 5 Feb 2021 23:44:50 -0500 IronPort-SDR: +2+/YJYkvYpT2jIJmgqdq/VGW5dc9PTHVq8wyWXOHXWpCXuHJ7RXZ+yXkWw7SWGHF3Mn3A1kCa vPT/gLpgoASQ== X-IronPort-AV: E=McAfee;i="6000,8403,9886"; a="180745501" X-IronPort-AV: E=Sophos;i="5.81,156,1610438400"; d="scan'208";a="180745501" Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga103.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 05 Feb 2021 20:43:58 -0800 IronPort-SDR: b1tt8ciO0Hh/IzwE1guo+yzp1CTMx2fpTOpgY0cOjfvtgVr/D2U7J+KJ5eIMAjgkYsNXc1j1Ce 6LD+o41Wj+ig== X-IronPort-AV: E=Sophos;i="5.81,156,1610438400"; d="scan'208";a="484717251" Received: from yxiong5-mobl2.amr.corp.intel.com (HELO istotlan-desk.intel.com) ([10.212.99.79]) by fmsmga001-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 05 Feb 2021 20:43:58 -0800 From: Inga Stotland To: linux-bluetooth@vger.kernel.org Cc: brian.gix@intel.com, luiz.dentz@gmail.com, Inga Stotland Subject: [PATCH BlueZ v2 1/3] shared/tester: Create ell-based version of tester code Date: Fri, 5 Feb 2021 20:43:38 -0800 Message-Id: <20210206044340.396467-2-inga.stotland@intel.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210206044340.396467-1-inga.stotland@intel.com> References: <20210206044340.396467-1-inga.stotland@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#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; +} From patchwork Sat Feb 6 04:43:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Stotland, Inga" X-Patchwork-Id: 12071915 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-11.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE, SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C9A8FC433E0 for ; Sat, 6 Feb 2021 04:48:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8DA9E64DFF for ; Sat, 6 Feb 2021 04:48:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230184AbhBFErj (ORCPT ); Fri, 5 Feb 2021 23:47:39 -0500 Received: from mga14.intel.com ([192.55.52.115]:19906 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231348AbhBFEov (ORCPT ); Fri, 5 Feb 2021 23:44:51 -0500 IronPort-SDR: 83sC6r8QzRjcBb7NwgpuCeCsVE4AJCfmrrJnOeO9GPRUmYszCpoiDrjheS+LbY1KffZ05rV3Zp BCvdE6dC3YDw== X-IronPort-AV: E=McAfee;i="6000,8403,9886"; a="180745503" X-IronPort-AV: E=Sophos;i="5.81,156,1610438400"; d="scan'208";a="180745503" Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga103.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 05 Feb 2021 20:43:59 -0800 IronPort-SDR: xXcjnZcn02AP7Zy/n47PbfcR+DqVpiEc3NZWuRgusN5YOzODmHRDPuzyW0HWHO9lI+7fWRpctO F3Umhlv4Eufg== X-IronPort-AV: E=Sophos;i="5.81,156,1610438400"; d="scan'208";a="484717255" Received: from yxiong5-mobl2.amr.corp.intel.com (HELO istotlan-desk.intel.com) ([10.212.99.79]) by fmsmga001-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 05 Feb 2021 20:43:59 -0800 From: Inga Stotland To: linux-bluetooth@vger.kernel.org Cc: brian.gix@intel.com, luiz.dentz@gmail.com Subject: [PATCH BlueZ v2 2/3] mesh: Add unit test IO Date: Fri, 5 Feb 2021 20:43:39 -0800 Message-Id: <20210206044340.396467-3-inga.stotland@intel.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210206044340.396467-1-inga.stotland@intel.com> References: <20210206044340.396467-1-inga.stotland@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Brian Gix 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: When the bluetooth-meshd daemon starts with the "unit" IO type, the daemon opens a socket (fd to open is provided after "unit:" in ). 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 | 20 ++ mesh/mesh-io.c | 9 +- mesh/mesh-io.h | 3 +- 6 files changed, 591 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] | generic[:[hci]])\n" + "\t([hci] | generic[:[hci]] | unit:)\n" "\t\tUse generic HCI io on interface hci, 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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..65f02e26a --- /dev/null +++ b/mesh/mesh-io-unit.h @@ -0,0 +1,20 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +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 { From patchwork Sat Feb 6 04:43:40 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Stotland, Inga" X-Patchwork-Id: 12071917 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE, SPF_PASS,UNWANTED_LANGUAGE_BODY,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6B888C433E0 for ; Sat, 6 Feb 2021 04:49:12 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1E18064EEC for ; Sat, 6 Feb 2021 04:49:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231285AbhBFEsl (ORCPT ); Fri, 5 Feb 2021 23:48:41 -0500 Received: from mga14.intel.com ([192.55.52.115]:19902 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231810AbhBFEqZ (ORCPT ); Fri, 5 Feb 2021 23:46:25 -0500 IronPort-SDR: 9UWKlrmgF+wh/OhWmD3VOiMwdQ9qmZ3xUxUa5wECRgUHeBYxMgCv4qGzvv5xQbYMRe4RBrQ/zZ gElxuc3RQ5xg== X-IronPort-AV: E=McAfee;i="6000,8403,9886"; a="180745505" X-IronPort-AV: E=Sophos;i="5.81,156,1610438400"; d="scan'208";a="180745505" Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga103.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 05 Feb 2021 20:44:00 -0800 IronPort-SDR: xafxpHbxDTb05yT2C8oa6UCTdwG0/5HhpxmH5ufPUEaD8TV/xkia7nO+KQxcaU7tiNg736QBzO NYbZQ5bgxJiQ== X-IronPort-AV: E=Sophos;i="5.81,156,1610438400"; d="scan'208";a="484717262" Received: from yxiong5-mobl2.amr.corp.intel.com (HELO istotlan-desk.intel.com) ([10.212.99.79]) by fmsmga001-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 05 Feb 2021 20:44:00 -0800 From: Inga Stotland To: linux-bluetooth@vger.kernel.org Cc: brian.gix@intel.com, luiz.dentz@gmail.com, Inga Stotland Subject: [PATCH BlueZ v2 3/3] tools/mesh-cfgtest: Non-iteractive test for mesh daemon Date: Fri, 5 Feb 2021 20:43:40 -0800 Message-Id: <20210206044340.396467-4-inga.stotland@intel.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210206044340.396467-1-inga.stotland@intel.com> References: <20210206044340.396467-1-inga.stotland@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org 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 #include #include +#include +#include #include #include @@ -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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +}