From patchwork Fri Mar 19 20:38:24 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Stotland, Inga" X-Patchwork-Id: 12151879 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.8 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 2FE4EC433DB for ; Fri, 19 Mar 2021 20:39:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 01BD76195A for ; Fri, 19 Mar 2021 20:39:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230218AbhCSUix (ORCPT ); Fri, 19 Mar 2021 16:38:53 -0400 Received: from mga05.intel.com ([192.55.52.43]:53937 "EHLO mga05.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229974AbhCSUis (ORCPT ); Fri, 19 Mar 2021 16:38:48 -0400 IronPort-SDR: vY3F1GSvOiFdCxziLQyMJfXQCltLeFZBDqbwLGtWzndRUQPDSxg5N3PDo5gH0gbfmxq9dQhNby IQDU9sYWkF/w== X-IronPort-AV: E=McAfee;i="6000,8403,9928"; a="275035465" X-IronPort-AV: E=Sophos;i="5.81,262,1610438400"; d="scan'208";a="275035465" Received: from fmsmga008.fm.intel.com ([10.253.24.58]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 19 Mar 2021 13:38:48 -0700 IronPort-SDR: MJSJlcnzMw8oBvXACwnwE43n1xlwErvaROlh5DIJ9+Oyf1LaMwrQgOSUqJKY1+0Vfoa4ETGgfi 1ANkVhitZdYg== X-IronPort-AV: E=Sophos;i="5.81,262,1610438400"; d="scan'208";a="406934909" Received: from rkuhnis-mobl.amr.corp.intel.com (HELO istotlan-desk.intel.com) ([10.212.65.199]) by fmsmga008-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 19 Mar 2021 13:38:47 -0700 From: Inga Stotland To: brian.gix@intel.com, linux-bluetooth@vger.kernel.org Subject: [PATCH BlueZ v4 1/2] mesh: Add unit test IO Date: Fri, 19 Mar 2021 13:38:24 -0700 Message-Id: <20210319203825.459070-2-inga.stotland@intel.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210319203825.459070-1-inga.stotland@intel.com> References: <20210319203825.459070-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 | 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] | 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..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 { From patchwork Fri Mar 19 20:38:25 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Stotland, Inga" X-Patchwork-Id: 12151881 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=-9.0 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE, SPF_PASS,UNWANTED_LANGUAGE_BODY,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 51F17C433C1 for ; Fri, 19 Mar 2021 20:39:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1EBB16197B for ; Fri, 19 Mar 2021 20:39:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230206AbhCSUix (ORCPT ); Fri, 19 Mar 2021 16:38:53 -0400 Received: from mga05.intel.com ([192.55.52.43]:53937 "EHLO mga05.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230092AbhCSUit (ORCPT ); Fri, 19 Mar 2021 16:38:49 -0400 IronPort-SDR: MdpNEJHnnwVmcC/lJroAVLi5ssjfmfFShXEV4BwGyQ7L0sq3VM223wWcnc/m12zgeinmK9BRED K7hPCNCk1MCQ== X-IronPort-AV: E=McAfee;i="6000,8403,9928"; a="275035467" X-IronPort-AV: E=Sophos;i="5.81,262,1610438400"; d="scan'208";a="275035467" Received: from fmsmga008.fm.intel.com ([10.253.24.58]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 19 Mar 2021 13:38:49 -0700 IronPort-SDR: E5OO6itO8QZ43E+ALOZkidkevNcXkp5IggB/oxndlKlxaKY+xAqBQrFfI6pGY5/8Gv+cl+0Sfc 1gyKsI3oqOzw== X-IronPort-AV: E=Sophos;i="5.81,262,1610438400"; d="scan'208";a="406934915" Received: from rkuhnis-mobl.amr.corp.intel.com (HELO istotlan-desk.intel.com) ([10.212.65.199]) by fmsmga008-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 19 Mar 2021 13:38:48 -0700 From: Inga Stotland To: brian.gix@intel.com, linux-bluetooth@vger.kernel.org Cc: Inga Stotland Subject: [PATCH BlueZ v4 2/2] tools/mesh-cfgtest: Non-iteractive test for mesh Date: Fri, 19 Mar 2021 13:38:25 -0700 Message-Id: <20210319203825.459070-3-inga.stotland@intel.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210319203825.459070-1-inga.stotland@intel.com> References: <20210319203825.459070-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. --- Makefile.am | 14 +- Makefile.tools | 6 + mesh/main.c | 10 +- tools/mesh-cfgtest.c | 1458 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1483 insertions(+), 5 deletions(-) create mode 100644 tools/mesh-cfgtest.c diff --git a/Makefile.am b/Makefile.am index 7fce2e7c0..e70c61a81 100644 --- a/Makefile.am +++ b/Makefile.am @@ -115,6 +115,8 @@ ell_headers = ell/util.h \ ell/hashmap.h \ ell/random.h \ ell/signal.h \ + ell/time.h \ + ell/time-private.h \ ell/timeout.h \ ell/cipher.h \ ell/checksum.h \ @@ -137,7 +139,8 @@ ell_headers = ell/util.h \ ell/pem-private.h \ ell/uuid.h \ ell/useful.h \ - ell/main-private.h + ell/main-private.h \ + ell/tester.h ell_sources = ell/private.h ell/missing.h \ ell/util.c \ @@ -146,6 +149,7 @@ ell_sources = ell/private.h ell/missing.h \ ell/hashmap.c \ ell/random.c \ ell/signal.c \ + ell/time.c \ ell/timeout.c \ ell/io.c \ ell/idle.c \ @@ -173,7 +177,8 @@ ell_sources = ell/private.h ell/missing.h \ ell/gvariant-util.c \ ell/siphash-private.h \ ell/siphash.c \ - ell/uuid.c + ell/uuid.c \ + ell/tester.c ell_libell_internal_la_SOURCES = $(ell_headers) $(ell_sources) endif @@ -193,7 +198,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 \ @@ -220,7 +225,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 \ 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..fa0474faa --- /dev/null +++ b/tools/mesh-cfgtest.c @@ -0,0 +1,1458 @@ +// 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 + +#include "src/shared/util.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_tester *tester; + +static bool option_list; +static const char *option_prefix; +static const char *option_string; +static char *tester_name; + +static bool init_done; +static bool init_failed; +static bool terminated; + +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 char *io; +static char *exe; + +static uint32_t iv_index = DEFAULT_IV_INDEX; + +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 struct msg_data test_dev_comp_rsp = { + .len = 28, + .data = { 0x02, 0x00, 0xf1, 0x05, 0x02, 0x00, 0x01, 0x00, 0xff, 0x7f, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x01, 0x00, 0x10, 0xf1, 0x05, 0x01, 0x00} +}; + +static struct msg_data test_dev_comp_req = { + .len = 3, + .data = {0x80, 0x08, 0x00} +}; + +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; + enum l_tester_stage stage = l_tester_get_stage(tester); + + if (l_queue_isempty(startup_chain) && + stage == L_TESTER_STAGE_PRE_SETUP) { + l_tester_pre_setup_complete(tester); + init_done = true; + return; + } + + next_step = l_queue_pop_head(startup_chain); + + next_step->func(next_step->data); +} + +static void test_success(void *user_data) +{ + l_tester_test_passed(tester); +} + +static void test_fail(void *user_data) +{ + l_tester_test_failed(tester); +} + +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) +{ + enum l_tester_stage stage = l_tester_get_stage(tester); + + if (l_dbus_message_is_error(msg)) { + const char *name; + + l_dbus_message_get_error(msg, &name, NULL); + l_info("Add key failed: %s", name); + + if (stage == L_TESTER_STAGE_PRE_SETUP) { + init_failed = true; + l_tester_pre_setup_failed(tester); + } else + l_tester_setup_failed(tester); + } else { + if (stage == L_TESTER_STAGE_PRE_SETUP) + l_idle_oneshot(init_continue, NULL, NULL); + else + l_tester_setup_complete(tester); + } +} + +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) { + l_info("Node is not attached\n"); + l_tester_setup_failed(tester); + 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) +{ + enum l_tester_stage stage = l_tester_get_stage(tester); + + if (l_dbus_message_is_error(msg)) { + const char *name; + + l_dbus_message_get_error(msg, &name, NULL); + l_info("Import remote call failed: %s", name); + + if (stage == L_TESTER_STAGE_PRE_SETUP) { + init_failed = true; + l_tester_pre_setup_failed(tester); + } + + return; + } + + if (stage == L_TESTER_STAGE_PRE_SETUP) + l_idle_oneshot(init_continue, NULL, NULL); + else + l_tester_test_abort(tester); +} + +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) { + l_tester_test_abort(tester); + 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) +{ + enum l_tester_stage stage = l_tester_get_stage(tester); + + if (stage != L_TESTER_STAGE_PRE_SETUP) + return; + + if (l_dbus_message_is_error(msg)) { + const char *name; + + l_dbus_message_get_error(msg, &name, NULL); + l_info("Import subnet failed: %s", name); + + init_failed = true; + return l_tester_pre_setup_failed(tester); + } + + 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) { + l_tester_test_abort(tester); + 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; + enum l_tester_stage stage = l_tester_get_stage(tester); + + 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; + + l_info("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 (stage == L_TESTER_STAGE_PRE_SETUP) + l_idle_oneshot(init_continue, NULL, NULL); + + return; + +fail: + l_free(node); + app->node = NULL; + + if (stage == L_TESTER_STAGE_PRE_SETUP) { + init_failed = true; + l_tester_pre_setup_failed(tester); + } +} + +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) { + l_tester_test_abort(tester); + 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; + enum l_tester_stage stage = l_tester_get_stage(tester); + + if (!l_dbus_message_get_arguments(message, "t", &tmp)) { + if (stage == L_TESTER_STAGE_PRE_SETUP) { + init_failed = true; + l_tester_pre_setup_failed(tester); + } 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 (stage == L_TESTER_STAGE_PRE_SETUP) + 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); + l_tester_setup_failed(tester); + } +} + +static void create_net_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + + /* Generate random UUID */ + l_uuid_v4(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_uuid_v4(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); + + l_info("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); + + l_info("Proxy removed: %s (%s)\n", interface, path); + + if (!strcmp(interface, MESH_NETWORK_INTERFACE)) { + l_info("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; + enum l_tester_stage stage = l_tester_get_stage(tester); + + 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 (stage == L_TESTER_STAGE_PRE_SETUP) { + 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_failed = true; + l_tester_pre_setup_failed(tester); + } + } else { + rsp = l_tester_get_data(tester); + + 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) +{ + enum l_tester_stage stage = l_tester_get_stage(tester); + + printf("D-Bus client ready\n"); + + if (!register_app_iface() || !register_app(&client_app) || + !register_app(&server_app)) + return; + + if (stage == L_TESTER_STAGE_PRE_SETUP) + 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"); + init_failed = true; + l_tester_pre_setup_failed(tester); +} + +static void init_test(const void *data) +{ + if (init_failed) { + l_tester_pre_setup_failed(tester); + return; + } + + if (init_done) { + l_tester_pre_setup_complete(tester); + return; + } + + 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_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) \ + l_tester_add_full(tester, name, test_data, init_test, NULL, \ + test_func, NULL, NULL, 2, rsp_data, NULL) + +static void signal_callback(unsigned int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) + l_main_quit(); + + terminated = true; + break; + } +} + +static void done_callback(struct l_tester *tester) +{ + if (terminated) + return; + + l_main_quit(); + terminated = true; +} + +static const struct option options[] = { + { "version", no_argument, NULL, 'v' }, + { "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-l, --list Only list the tests to be run\n" + "\t-p, --prefix Run tests matching the provided prefix\n" + "\t-s, --string Run tests matching the provided string\n"); +} + +static void parse_options(int *argc, char ***argv) +{ + tester_name = strrchr(*argv[0], '/'); + + for (;;) { + int opt; + + opt = getopt_long(*argc, *argv, "s:p:vl", options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'v': + printf("%s\n", VERSION); + exit(EXIT_SUCCESS); + case 'l': + option_list = true; + break; + case 'p': + option_prefix = optarg; + break; + case 's': + option_string = optarg; + break; + default: + usage(); + exit(0); + } + } +} + +static bool setup_test_dir(void) +{ + char *bluez_dir; + char buf[PATH_MAX]; + ssize_t len; + + len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); + + if (len == -1) + return false; + + buf[len] = '\0'; + + test_dir = l_strdup_printf("/tmp/mesh"); + nftw(test_dir, del_fobject, 5, FTW_DEPTH | FTW_PHYS); + + if (mkdir(test_dir, 0700) != 0) { + printf("Failed to create dir %s\n", test_dir); + l_free(test_dir); + return false; + } + + 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"); + + return true; +} + +int main(int argc, char *argv[]) +{ + int status = EXIT_SUCCESS, pid = -1; + + if (!setup_test_dir()) + return EXIT_FAILURE; + + l_log_set_stderr(); + l_main_init(); + + parse_options(&argc, &argv); + + if (!option_list) { + + pid = fork(); + if (pid < 0) + return EXIT_FAILURE; + + 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; + } + } + + tester = l_tester_new(option_prefix, option_string, option_list); + + init_startup_chain(); + + l_tester_add_full(tester, "Config AppKey Add: Success", + &test_add_appkey, init_test, 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 Get Device Composition: Success", + &test_dev_comp_req, send_cfg_msg, + &test_dev_comp_rsp); + + tester_add_with_response("Config Bind: Success", + &test_bind_req, send_cfg_msg, + &test_bind_rsp); + + tester_add_with_response("Config Bind: Error Invalid Model", + &test_bind_inv_mod_req, send_cfg_msg, + &test_bind_inv_mod_rsp); + + l_tester_start(tester, done_callback); + + if (!option_list && !terminated) { + l_main_run_with_signal(signal_callback, NULL); + + if (!l_tester_summarize(tester)) + status = EXIT_FAILURE; + } + + l_queue_destroy(startup_chain, NULL); + l_queue_destroy(node_proxies, NULL); + + l_free(client_app.node); + l_free(server_app.node); + l_dbus_client_destroy(client); + l_dbus_destroy(dbus); + + l_tester_destroy(tester); + + if (pid > 0) + kill(pid, SIGTERM); + + l_free(test_dir); + l_free(io); + l_free(exe); + + return status; +}